shixiong 4 سال پیش
والد
کامیت
bcde93b470

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 134 - 0
README.md

@@ -0,0 +1,134 @@
+# ServerStatus中文版:   
+
+* ServerStatus中文版是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。
+* 在线演示:https://tz.cloudcpp.com    
+
+# 目录介绍:
+
+* autodeploy    自动部署.
+* clients       客户端文件
+* server        服务端文件
+* web           网站文件  
+
+# 更新说明:
+
+* 20200407, 网速计算不严谨,fixed    
+* 20190129, 降低CPU占用            
+* 20181221, 增加实时到三网的延迟, 鼠标移到丢包率列,tips显示        
+* 20181126, add tupd(tcp, udp, process ,thread) count for view ddcc attack    
+* 20180829, 网络情况:主机到三网(CU,CT,CM)每小时丢包率的检测
+* 20180726, 一切皆容器额,查看自动部署或autodeploy/readme
+* 20180314, 调整前端,置默认密码为,设置ip和user即可上线      
+* 20180312, 加入失联(被照顾)检测【正常:MH361, 屏蔽:MH370】,校准虚拟化(container)流量统计异常      
+* 20170807, 更新平均1,5,15负载, 去掉无用的IPV6信息,增加服务器总流量监控                           
+
+# 自动部署:
+
+【服务端】:
+```bash
+wget https://raw.githubusercontent.com/cppla/ServerStatus/master/autodeploy/config.json
+docker run -d --restart=always --name=serverstatus -v {$path}/config.json:/ServerStatus/server/config.json -p {$port}:80 -p {$port}:35601 cppla/serverstatus
+
+eg:
+docker run -d --restart=always --name=serverstatus -v ~/config.json:/ServerStatus/server/config.json -p 80:80 -p 35601:35601 cppla/serverstatus
+```
+
+【客户端】:
+```bash
+wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 &
+
+eg:
+wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER=45.79.67.132 USER=s04  >/dev/null 2>&1 &
+```
+
+# 手动安装教程:     
+   
+【克隆代码】:
+```
+git clone https://github.com/cppla/ServerStatus.git
+```
+
+【服务端配置】(服务端程序在ServerStatus/web下):  
+          
+一、生成服务端程序              
+```
+cd ServerStatus/server
+make
+./sergate
+```
+如果没错误提示,OK,ctrl+c关闭;如果有错误提示,检查35601端口是否被占用    
+
+二、修改配置文件         
+修改config.json文件,注意username, password的值需要和客户端对应一致                 
+```
+{"servers":
+	[
+		{
+			"username": "s01",
+			"name": "Mainserver 1",
+			"type": "Dedicated Server",
+			"host": "GenericServerHost123",
+			"location": "Austria",
+			"password": "some-hard-to-guess-copy-paste-password"
+		},
+	]
+}       
+```
+
+三、拷贝ServerStatus/status到你的网站目录        
+例如:
+```
+sudo cp -r ServerStatus/web/* /home/wwwroot/default
+```
+
+四、运行服务端:             
+web-dir参数为上一步设置的网站根目录,务必修改成自己网站的路径   
+```
+./sergate --config=config.json --web-dir=/home/wwwroot/default   
+```
+
+【客户端配置】(客户端程序在ServerStatus/clients下):          
+客户端有两个版本,client-linux为普通linux,client-psutil为跨平台版,普通版不成功,换成跨平台版即可。        
+
+一、client-linux版配置:       
+1、vim client-linux.py, 修改SERVER地址,username帐号, password密码        
+2、python client-linux.py 运行即可。      
+
+二、client-psutil版配置:                
+1、安装psutil跨平台依赖库      
+2、vim client-psutil.py, 修改SERVER地址,username帐号, password密码       
+3、python client-psutil.py 运行即可。           
+```
+### for Centos:
+sudo yum -y install epel-release
+sudo yum -y install python-pip
+sudo yum clean all
+sudo yum -y install gcc
+sudo yum -y install python-devel
+sudo pip install psutil
+### for Ubuntu/Debian:
+sudo root
+apt-get -y install python-setuptools python-dev build-essential
+apt-get -y install python-pip
+pip install psutil
+### for Windows:
+打开网址:https://pypi.python.org/pypi?:action=display&name=psutil#downloads
+下载psutil for windows程序包
+安装即可
+```
+
+打开云探针页面,就可以正常的监控。接下来把服务器和客户端脚本自行加入开机启动,或者进程守护,或以后台方式运行即可!例如: nohup python client-linux.py &      
+
+# 为什么会有ServerStatus中文版:
+
+* 有些功能确实没用
+* 原版本部署,英文说明复杂
+* 不符合中文版的习惯
+* 没有一次又一次的轮子,哪来如此优秀的云探针
+
+# 相关开源项目,感谢: 
+
+* ServerStatus:https://github.com/BotoX/ServerStatus
+* mojeda: https://github.com/mojeda 
+* mojeda's ServerStatus: https://github.com/mojeda/ServerStatus
+* BlueVM's project: http://www.lowendtalk.com/discussion/comment/169690#Comment_169690

+ 24 - 0
autodeploy/Dockerfile

@@ -0,0 +1,24 @@
+FROM debian:latest as builder
+
+MAINTAINER cppla https://cpp.la
+
+RUN apt-get update
+RUN apt-get -y install gcc g++ make git
+RUN git clone https://github.com/cppla/ServerStatus
+
+WORKDIR /ServerStatus/server
+
+RUN make
+RUN pwd && ls -a
+
+# glibc env run 
+FROM nginx:latest
+
+RUN mkdir -p /ServerStatus/server/
+
+COPY --from=builder /ServerStatus/server /ServerStatus/server/
+COPY --from=builder /ServerStatus/web /usr/share/nginx/html/
+
+EXPOSE 80 35601
+
+CMD nohup sh -c '/etc/init.d/nginx start && /ServerStatus/server/sergate --config=/ServerStatus/server/config.json --web-dir=/usr/share/nginx/html'

+ 37 - 0
autodeploy/config.json

@@ -0,0 +1,37 @@
+{"servers":
+	[
+		{
+			"username": "s01",
+			"name": "node1",
+			"type": "xen",
+			"host": "host1",
+			"location": "cn",
+			"password": "USER_DEFAULT_PASSWORD"
+		},
+		{
+			"username": "s02",
+			"name": "node2",
+			"type": "vmware",
+			"host": "host2",
+			"location": "jp",
+			"password": "USER_DEFAULT_PASSWORD"
+		},
+		{
+			"disabled": true,
+			"username": "s03",
+			"name": "node3",
+			"type": "Nothing",
+			"host": "host3",
+			"location": "fr",
+			"password": "USER_DEFAULT_PASSWORD"
+		},
+		{
+			"username": "s04",
+			"name": "ssss",
+			"type": "ssss",
+			"host": "ssss",
+			"location": "ssss",
+			"password": "USER_DEFAULT_PASSWORD"
+		}
+	]
+}

+ 15 - 0
autodeploy/readme

@@ -0,0 +1,15 @@
+服务端:
+
+docker build -f Dockerfile -t sss .
+docker run -d --restart=always --name=sss -v {$path}/config.json:/ServerStatus/server/config.json -p {$port}:80 -p {$port}:35601 sss
+
+客户端:
+
+wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 &
+
+
+
+
+
+附: docker安装
+curl -sSL https://get.docker.com/ | sh

+ 361 - 0
clients/client-linux.py

@@ -0,0 +1,361 @@
+#!/usr/bin/env python
+# coding: utf-8
+# Update by : https://github.com/cppla/ServerStatus
+# 支持Python版本:2.7 to 3.7
+# 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
+# 时间: 20200407
+# 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。
+
+SERVER = "127.0.0.1"
+USER = "s01"
+
+
+
+PORT = 35601
+PASSWORD = "USER_DEFAULT_PASSWORD"
+INTERVAL = 1
+PORBEPORT = 80
+CU = "cu.tz.cloudcpp.com"
+CT = "ct.tz.cloudcpp.com"
+CM = "cm.tz.cloudcpp.com"
+
+import socket
+import time
+import timeit
+import re
+import os
+import sys
+import json
+import subprocess
+import threading
+
+def get_uptime():
+    with open('/proc/uptime', 'r') as f:
+        uptime = f.readline().split('.', 2)
+        return int(uptime[0])
+
+def get_memory():
+    re_parser = re.compile(r'^(?P<key>\S*):\s*(?P<value>\d*)\s*kB')
+    result = dict()
+    for line in open('/proc/meminfo'):
+        match = re_parser.match(line)
+        if not match:
+            continue
+        key, value = match.groups(['key', 'value'])
+        result[key] = int(value)
+    MemTotal = float(result['MemTotal'])
+    MemUsed = MemTotal-float(result['MemFree'])-float(result['Buffers'])-float(result['Cached'])-float(result['SReclaimable'])
+    SwapTotal = float(result['SwapTotal'])
+    SwapFree = float(result['SwapFree'])
+    return int(MemTotal), int(MemUsed), int(SwapTotal), int(SwapFree)
+
+def get_hdd():
+    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")
+    total = p.splitlines()[-1]
+    used = total.split()[3]
+    size = total.split()[2]
+    return int(size), int(used)
+
+def get_time():
+    with open("/proc/stat", "r") as f:
+        time_list = f.readline().split(' ')[2:6]
+        for i in range(len(time_list))  :
+            time_list[i] = int(time_list[i])
+        return time_list
+
+def delta_time():
+    x = get_time()
+    time.sleep(INTERVAL)
+    y = get_time()
+    for i in range(len(x)):
+        y[i]-=x[i]
+    return y
+
+def get_cpu():
+    t = delta_time()
+    st = sum(t)
+    if st == 0:
+        st = 1
+    result = 100-(t[len(t)-1]*100.00/st)
+    return round(result, 1)
+
+def liuliang():
+    NET_IN = 0
+    NET_OUT = 0
+    with open('/proc/net/dev') as f:
+        for line in f.readlines():
+            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)
+            if netinfo:
+                if netinfo[0][0] == 'lo' or 'tun' in netinfo[0][0] \
+                        or 'docker' in netinfo[0][0] or 'veth' in netinfo[0][0] \
+                        or 'br-' in netinfo[0][0] or 'vmbr' in netinfo[0][0] \
+                        or 'vnet' in netinfo[0][0] or 'kube' in netinfo[0][0] \
+                        or netinfo[0][1]=='0' or netinfo[0][9]=='0':
+                    continue
+                else:
+                    NET_IN += int(netinfo[0][1])
+                    NET_OUT += int(netinfo[0][9])
+    return NET_IN, NET_OUT
+
+def tupd():
+    '''
+    tcp, udp, process, thread count: for view ddcc attack , then send warning
+    :return:
+    '''
+    s = subprocess.check_output("ss -t|wc -l", shell=True)
+    t = int(s[:-1])-1
+    s = subprocess.check_output("ss -u|wc -l", shell=True)
+    u = int(s[:-1])-1
+    s = subprocess.check_output("ps -ef|wc -l", shell=True)
+    p = int(s[:-1])-2
+    s = subprocess.check_output("ps -eLf|wc -l", shell=True)
+    d = int(s[:-1])-2
+    return t,u,p,d
+
+def ip_status():
+    ip_check = 0
+    for i in [CU, CT, CM]:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.settimeout(1)
+        try:
+            s.connect((i, PORBEPORT))
+        except:
+            ip_check += 1
+        s.close()
+        del s
+    if ip_check >= 2:
+        return False
+    else:
+        return True
+
+def get_network(ip_version):
+    if(ip_version == 4):
+        HOST = "ipv4.google.com"
+    elif(ip_version == 6):
+        HOST = "ipv6.google.com"
+    try:
+        s = socket.create_connection((HOST, 80), 2)
+        s.close()
+        return True
+    except:
+        return False
+
+lostRate = {
+    '10010': 0.0,
+    '189': 0.0,
+    '10086': 0.0
+}
+pingTime = {
+    '10010': 0,
+    '189': 0,
+    '10086': 0
+}
+netSpeed = {
+    'netrx': 0.0,
+    'nettx': 0.0,
+    'clock': 0.0,
+    'diff': 0.0,
+    'avgrx': 0,
+    'avgtx': 0
+}
+
+def _ping_thread(host, mark, port):
+    lostPacket = 0
+    allPacket = 0
+    startTime = time.time()
+
+    while True:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.settimeout(1)
+        try:
+            b = timeit.default_timer()
+            s.connect((host, port))
+            pingTime[mark] = int((timeit.default_timer()-b)*1000)
+        except:
+            lostPacket += 1
+        finally:
+            allPacket += 1
+        s.close()
+
+        if allPacket > 100:
+            lostRate[mark] = float(lostPacket) / allPacket
+
+        endTime = time.time()
+        if endTime - startTime > 3600:
+            lostPacket = 0
+            allPacket = 0
+            startTime = endTime
+
+        time.sleep(INTERVAL)
+
+def _net_speed():
+    while True:
+        with open("/proc/net/dev", "r") as f:
+            net_dev = f.readlines()
+            avgrx = 0
+            avgtx = 0
+            for dev in net_dev[2:]:
+                dev = dev.split(':')
+                if "lo" in dev[0] or "tun" in dev[0] \
+                        or "docker" in dev[0] or "veth" in dev[0] \
+                        or "br-" in dev[0] or "vmbr" in dev[0] \
+                        or "vnet" in dev[0] or "kube" in dev[0]:
+                    continue
+                dev = dev[1].split()
+                avgrx += int(dev[0])
+                avgtx += int(dev[8])
+            now_clock = time.time()
+            netSpeed["diff"] = now_clock - netSpeed["clock"]
+            netSpeed["clock"] = now_clock
+            netSpeed["netrx"] = int((avgrx - netSpeed["avgrx"]) / netSpeed["diff"])
+            netSpeed["nettx"] = int((avgtx - netSpeed["avgtx"]) / netSpeed["diff"])
+            netSpeed["avgrx"] = avgrx
+            netSpeed["avgtx"] = avgtx
+        time.sleep(INTERVAL)
+
+def get_realtime_date():
+    t1 = threading.Thread(
+        target=_ping_thread,
+        kwargs={
+            'host': CU,
+            'mark': '10010',
+            'port': PORBEPORT
+        }
+    )
+    t2 = threading.Thread(
+        target=_ping_thread,
+        kwargs={
+            'host': CT,
+            'mark': '189',
+            'port': PORBEPORT
+        }
+    )
+    t3 = threading.Thread(
+        target=_ping_thread,
+        kwargs={
+            'host': CM,
+            'mark': '10086',
+            'port': PORBEPORT
+        }
+    )
+    t4 = threading.Thread(
+        target=_net_speed,
+    )
+    t1.setDaemon(True)
+    t2.setDaemon(True)
+    t3.setDaemon(True)
+    t4.setDaemon(True)
+    t1.start()
+    t2.start()
+    t3.start()
+    t4.start()
+
+def byte_str(object):
+    '''
+    bytes to str, str to bytes
+    :param object:
+    :return:
+    '''
+    if isinstance(object, str):
+        return object.encode(encoding="utf-8")
+    elif isinstance(object, bytes):
+        return bytes.decode(object)
+    else:
+        print(type(object))
+
+if __name__ == '__main__':
+    for argc in sys.argv:
+        if 'SERVER' in argc:
+            SERVER = argc.split('SERVER=')[-1]
+        elif 'PORT' in argc:
+            PORT = int(argc.split('PORT=')[-1])
+        elif 'USER' in argc:
+            USER = argc.split('USER=')[-1]
+        elif 'PASSWORD' in argc:
+            PASSWORD = argc.split('PASSWORD=')[-1]
+        elif 'INTERVAL' in argc:
+            INTERVAL = int(argc.split('INTERVAL=')[-1])
+    socket.setdefaulttimeout(30)
+    get_realtime_date()
+    while True:
+        try:
+            print("Connecting...")
+            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            s.connect((SERVER, PORT))
+            data = byte_str(s.recv(1024))
+            if data.find("Authentication required") > -1:
+                s.send(byte_str(USER + ':' + PASSWORD + '\n'))
+                data = byte_str(s.recv(1024))
+                if data.find("Authentication successful") < 0:
+                    print(data)
+                    raise socket.error
+            else:
+                print(data)
+                raise socket.error
+
+            print(data)
+            data = byte_str(s.recv(1024))
+            print(data)
+
+            timer = 0
+            check_ip = 0
+            if data.find("IPv4") > -1:
+                check_ip = 6
+            elif data.find("IPv6") > -1:
+                check_ip = 4
+            else:
+                print(data)
+                raise socket.error
+
+            while True:
+                CPU = get_cpu()
+                NET_IN, NET_OUT = liuliang()
+                Uptime = get_uptime()
+                Load_1, Load_5, Load_15 = os.getloadavg()
+                MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory()
+                HDDTotal, HDDUsed = get_hdd()
+                IP_STATUS = ip_status()
+
+                array = {}
+                if not timer:
+                    array['online' + str(check_ip)] = get_network(check_ip)
+                    timer = 10
+                else:
+                    timer -= 1*INTERVAL
+
+                array['uptime'] = Uptime
+                array['load_1'] = Load_1
+                array['load_5'] = Load_5
+                array['load_15'] = Load_15
+                array['memory_total'] = MemoryTotal
+                array['memory_used'] = MemoryUsed
+                array['swap_total'] = SwapTotal
+                array['swap_used'] = SwapTotal - SwapFree
+                array['hdd_total'] = HDDTotal
+                array['hdd_used'] = HDDUsed
+                array['cpu'] = CPU
+                array['network_rx'] = netSpeed.get("netrx")
+                array['network_tx'] = netSpeed.get("nettx")
+                array['network_in'] = NET_IN
+                array['network_out'] = NET_OUT
+                array['ip_status'] = IP_STATUS
+                array['ping_10010'] = lostRate.get('10010') * 100
+                array['ping_189'] = lostRate.get('189') * 100
+                array['ping_10086'] = lostRate.get('10086') * 100
+                array['time_10010'] = pingTime.get('10010')
+                array['time_189'] = pingTime.get('189')
+                array['time_10086'] = pingTime.get('10086')
+                array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
+
+                s.send(byte_str("update " + json.dumps(array) + "\n"))
+        except KeyboardInterrupt:
+            raise
+        except socket.error:
+            print("Disconnected...")
+            # keep on trying after a disconnect
+            s.close()
+            time.sleep(3)
+        except Exception as e:
+            print("Caught Exception:", e)
+            s.close()
+            time.sleep(3)

+ 337 - 0
clients/client-psutil.py

@@ -0,0 +1,337 @@
+#!/usr/bin/env python
+# coding: utf-8
+# Update by : https://github.com/cppla/ServerStatus
+# 依赖于psutil跨平台库
+# 支持Python版本:2.7 to 3.7
+# 支持操作系统: Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
+# 时间: 20200407
+# 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。
+
+SERVER = "127.0.0.1"
+USER = "s01"
+
+
+
+PORT = 35601
+PASSWORD = "USER_DEFAULT_PASSWORD"
+INTERVAL = 1
+PORBEPORT = 80
+CU = "cu.tz.cloudcpp.com"
+CT = "ct.tz.cloudcpp.com"
+CM = "cm.tz.cloudcpp.com"
+
+import socket
+import time
+import timeit
+import os
+import json
+import psutil
+import sys
+import threading
+
+def get_uptime():
+    return int(time.time() - psutil.boot_time())
+
+def get_memory():
+    Mem = psutil.virtual_memory()
+    return int(Mem.total / 1024.0), int(Mem.used / 1024.0)
+
+def get_swap():
+    Mem = psutil.swap_memory()
+    return int(Mem.total/1024.0), int(Mem.used/1024.0)
+
+def get_hdd():
+    valid_fs = [ "ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs" ]
+    disks = dict()
+    size = 0
+    used = 0
+    for disk in psutil.disk_partitions():
+        if not disk.device in disks and disk.fstype.lower() in valid_fs:
+            disks[disk.device] = disk.mountpoint
+    for disk in disks.values():
+        usage = psutil.disk_usage(disk)
+        size += usage.total
+        used += usage.used
+    return int(size/1024.0/1024.0), int(used/1024.0/1024.0)
+
+def get_cpu():
+    return psutil.cpu_percent(interval=INTERVAL)
+
+def liuliang():
+    NET_IN = 0
+    NET_OUT = 0
+    net = psutil.net_io_counters(pernic=True)
+    for k, v in net.items():
+        if 'lo' in k or 'tun' in k \
+                or 'docker' in k or 'veth' in k \
+                or 'br-' in k or 'vmbr' in k \
+                or 'vnet' in k or 'kube' in k:
+            continue
+        else:
+            NET_IN += v[1]
+            NET_OUT += v[0]
+    return NET_IN, NET_OUT
+
+def tupd():
+    '''
+    tcp, udp, process, thread count: for view ddcc attack , then send warning
+    :return:
+    '''
+    if 'linux' in sys.platform:
+        t = int(os.popen('ss -t|wc -l').read()[:-1])-1
+        u = int(os.popen('ss -u|wc -l').read()[:-1])-1
+        p = int(os.popen('ps -ef|wc -l').read()[:-1])-2
+        d = int(os.popen('ps -eLf|wc -l').read()[:-1])-2
+    else:
+        t = int(os.popen('netstat -an|find "TCP" /c').read()[:-1])-1
+        u = int(os.popen('netstat -an|find "UDP" /c').read()[:-1])-1
+        p = len(psutil.pids())
+        d = 0
+        # cpu is high, default: 0
+        # d = sum([psutil.Process(k).num_threads() for k in [x for x in psutil.pids()]])
+    return t,u,p,d
+
+def ip_status():
+    ip_check = 0
+    for i in [CU, CT, CM]:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.settimeout(1)
+        try:
+            s.connect((i, PORBEPORT))
+        except:
+            ip_check += 1
+        s.close()
+        del s
+    if ip_check >= 2:
+        return False
+    else:
+        return True
+
+def get_network(ip_version):
+    if(ip_version == 4):
+        HOST = "ipv4.google.com"
+    elif(ip_version == 6):
+        HOST = "ipv6.google.com"
+    try:
+        s = socket.create_connection((HOST, 80), 2)
+        s.close()
+        return True
+    except:
+        return False
+
+lostRate = {
+    '10010': 0.0,
+    '189': 0.0,
+    '10086': 0.0
+}
+pingTime = {
+    '10010': 0,
+    '189': 0,
+    '10086': 0
+}
+netSpeed = {
+    'netrx': 0.0,
+    'nettx': 0.0,
+    'clock': 0.0,
+    'diff': 0.0,
+    'avgrx': 0,
+    'avgtx': 0
+}
+
+def _ping_thread(host, mark, port):
+    lostPacket = 0
+    allPacket = 0
+    startTime = time.time()
+
+    while True:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.settimeout(1)
+        try:
+            b = timeit.default_timer()
+            s.connect((host, port))
+            pingTime[mark] = int((timeit.default_timer() - b) * 1000)
+        except:
+            lostPacket += 1
+        finally:
+            allPacket += 1
+        s.close()
+
+        if allPacket > 100:
+            lostRate[mark] = float(lostPacket) / allPacket
+
+        endTime = time.time()
+        if endTime - startTime > 3600:
+            lostPacket = 0
+            allPacket = 0
+            startTime = endTime
+
+        time.sleep(INTERVAL)
+
+def _net_speed():
+    while True:
+        avgrx = 0
+        avgtx = 0
+        for name, stats in psutil.net_io_counters(pernic=True).items():
+            if "lo" in name or "tun" in name \
+                    or "docker" in name or "veth" in name \
+                    or "br-" in name or "vmbr" in name \
+                    or "vnet" in name or "kube" in name:
+                continue
+            avgrx += stats.bytes_recv
+            avgtx += stats.bytes_sent
+        now_clock = time.time()
+        netSpeed["diff"] = now_clock - netSpeed["clock"]
+        netSpeed["clock"] = now_clock
+        netSpeed["netrx"] = int((avgrx - netSpeed["avgrx"]) / netSpeed["diff"])
+        netSpeed["nettx"] = int((avgtx - netSpeed["avgtx"]) / netSpeed["diff"])
+        netSpeed["avgrx"] = avgrx
+        netSpeed["avgtx"] = avgtx
+        time.sleep(INTERVAL)
+
+def get_realtime_date():
+    t1 = threading.Thread(
+        target=_ping_thread,
+        kwargs={
+            'host': CU,
+            'mark': '10010',
+            'port': PORBEPORT
+        }
+    )
+    t2 = threading.Thread(
+        target=_ping_thread,
+        kwargs={
+            'host': CT,
+            'mark': '189',
+            'port': PORBEPORT
+        }
+    )
+    t3 = threading.Thread(
+        target=_ping_thread,
+        kwargs={
+            'host': CM,
+            'mark': '10086',
+            'port': PORBEPORT
+        }
+    )
+    t4 = threading.Thread(
+        target=_net_speed,
+    )
+    t1.setDaemon(True)
+    t2.setDaemon(True)
+    t3.setDaemon(True)
+    t4.setDaemon(True)
+    t1.start()
+    t2.start()
+    t3.start()
+    t4.start()
+
+def byte_str(object):
+    '''
+    bytes to str, str to bytes
+    :param object:
+    :return:
+    '''
+    if isinstance(object, str):
+        return object.encode(encoding="utf-8")
+    elif isinstance(object, bytes):
+        return bytes.decode(object)
+    else:
+        print(type(object))
+
+if __name__ == '__main__':
+    for argc in sys.argv:
+        if 'SERVER' in argc:
+            SERVER = argc.split('SERVER=')[-1]
+        elif 'PORT' in argc:
+            PORT = int(argc.split('PORT=')[-1])
+        elif 'USER' in argc:
+            USER = argc.split('USER=')[-1]
+        elif 'PASSWORD' in argc:
+            PASSWORD = argc.split('PASSWORD=')[-1]
+        elif 'INTERVAL' in argc:
+            INTERVAL = int(argc.split('INTERVAL=')[-1])
+    socket.setdefaulttimeout(30)
+    get_realtime_date()
+    while 1:
+        try:
+            print("Connecting...")
+            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            s.connect((SERVER, PORT))
+            data = byte_str(s.recv(1024))
+            if data.find("Authentication required") > -1:
+                s.send(byte_str(USER + ':' + PASSWORD + '\n'))
+                data = byte_str(s.recv(1024))
+                if data.find("Authentication successful") < 0:
+                    print(data)
+                    raise socket.error
+            else:
+                print(data)
+                raise socket.error
+
+            print(data)
+            data = byte_str(s.recv(1024))
+            print(data)
+
+            timer = 0
+            check_ip = 0
+            if data.find("IPv4") > -1:
+                check_ip = 6
+            elif data.find("IPv6") > -1:
+                check_ip = 4
+            else:
+                print(data)
+                raise socket.error
+
+            while 1:
+                CPU = get_cpu()
+                NET_IN, NET_OUT = liuliang()
+                Uptime = get_uptime()
+                Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform else (0.0, 0.0, 0.0)
+                MemoryTotal, MemoryUsed = get_memory()
+                SwapTotal, SwapUsed = get_swap()
+                HDDTotal, HDDUsed = get_hdd()
+                IP_STATUS = ip_status()
+
+                array = {}
+                if not timer:
+                    array['online' + str(check_ip)] = get_network(check_ip)
+                    timer = 10
+                else:
+                    timer -= 1*INTERVAL
+
+                array['uptime'] = Uptime
+                array['load_1'] = Load_1
+                array['load_5'] = Load_5
+                array['load_15'] = Load_15
+                array['memory_total'] = MemoryTotal
+                array['memory_used'] = MemoryUsed
+                array['swap_total'] = SwapTotal
+                array['swap_used'] = SwapUsed
+                array['hdd_total'] = HDDTotal
+                array['hdd_used'] = HDDUsed
+                array['cpu'] = CPU
+                array['network_rx'] = netSpeed.get("netrx")
+                array['network_tx'] = netSpeed.get("nettx")
+                array['network_in'] = NET_IN
+                array['network_out'] = NET_OUT
+                array['ip_status'] = IP_STATUS
+                array['ping_10010'] = lostRate.get('10010') * 100
+                array['ping_189'] = lostRate.get('189') * 100
+                array['ping_10086'] = lostRate.get('10086') * 100
+                array['time_10010'] = pingTime.get('10010')
+                array['time_189'] = pingTime.get('189')
+                array['time_10086'] = pingTime.get('10086')
+                array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
+
+                s.send(byte_str("update " + json.dumps(array) + "\n"))
+        except KeyboardInterrupt:
+            raise
+        except socket.error:
+            print("Disconnected...")
+            # keep on trying after a disconnect
+            s.close()
+            time.sleep(3)
+        except Exception as e:
+            print("Caught Exception:", e)
+            s.close()
+            time.sleep(3)

+ 2 - 0
server/.gitignore

@@ -0,0 +1,2 @@
+sergate
+.tags*

+ 34 - 0
server/Makefile

@@ -0,0 +1,34 @@
+OUT = sergate
+
+#CC = clang
+CC = gcc
+CFLAGS = -Wall -O2
+
+#CXX = clang++
+CXX = g++
+CXXFLAGS = -Wall -O2
+
+ODIR = obj
+SDIR = src
+LIBS = -pthread -lm
+INC = -Iinclude
+
+C_SRCS := $(wildcard $(SDIR)/*.c)
+CXX_SRCS := $(wildcard $(SDIR)/*.cpp)
+C_OBJS := $(patsubst $(SDIR)/%.c,$(ODIR)/%.o,$(C_SRCS))
+CXX_OBJS := $(patsubst $(SDIR)/%.cpp,$(ODIR)/%.o,$(CXX_SRCS))
+OBJS := $(C_OBJS) $(CXX_OBJS)
+
+$(ODIR)/%.o: $(SDIR)/%.c
+	$(CC) -c $(INC) $(CFLAGS) $< -o $@
+
+$(ODIR)/%.o: $(SDIR)/%.cpp
+	$(CXX) -c $(INC) $(CXXFLAGS) $< -o $@
+
+$(OUT): $(OBJS)
+	$(CXX) $(LIBS) $^ -o $(OUT)
+
+.PHONY: clean
+
+clean:
+	rm -f $(ODIR)/*.o $(OUT)

+ 37 - 0
server/config.json

@@ -0,0 +1,37 @@
+{"servers":
+	[
+		{
+			"username": "s01",
+			"name": "node1",
+			"type": "xen",
+			"host": "host1",
+			"location": "cn",
+			"password": "USER_DEFAULT_PASSWORD"
+		},
+		{
+			"username": "s02",
+			"name": "node2",
+			"type": "vmware",
+			"host": "host2",
+			"location": "jp",
+			"password": "USER_DEFAULT_PASSWORD"
+		},
+		{
+			"disabled": true,
+			"username": "s03",
+			"name": "node3",
+			"type": "Nothing",
+			"host": "host3",
+			"location": "fr",
+			"password": "USER_DEFAULT_PASSWORD"
+		},
+		{
+			"username": "s04",
+			"name": "node4",
+			"type": "kvm",
+			"host": "host4",
+			"location": "kr",
+			"password": "USER_DEFAULT_PASSWORD"
+		}
+	]
+}

+ 139 - 0
server/include/argparse.h

@@ -0,0 +1,139 @@
+#ifndef ARGPARSE_H
+#define ARGPARSE_H
+
+/**
+ * Command-line arguments parsing library.
+ *
+ * This module is inspired by parse-options.c (git) and python's argparse
+ * module.
+ *
+ * Arguments parsing is common task in cli program, but traditional `getopt`
+ * libraries are not easy to use. This library provides high-level arguments
+ * parsing solutions.
+ *
+ * The program defines what arguments it requires, and `argparse` will figure
+ * out how to parse those out of `argc` and `argv`, it also automatically
+ * generates help and usage messages and issues errors when users give the
+ * program invalid arguments.
+ *
+ * Reserved namespaces:
+ *  argparse
+ *  OPT
+ * Author: Yecheng Fu <cofyc.jackson@gmail.com>
+ */
+
+#include <assert.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct argparse;
+struct argparse_option;
+
+typedef int argparse_callback(struct argparse *this_,
+                              const struct argparse_option *option);
+
+enum argparse_flag {
+    ARGPARSE_STOP_AT_NON_OPTION = 1,
+};
+
+enum argparse_option_type {
+    /* special */
+    ARGPARSE_OPT_END,
+    /* options with no arguments */
+    ARGPARSE_OPT_BOOLEAN,
+    ARGPARSE_OPT_BIT,
+    /* options with arguments (optional or required) */
+    ARGPARSE_OPT_INTEGER,
+    ARGPARSE_OPT_STRING,
+};
+
+enum argparse_option_flags {
+    OPT_NONEG = 1,              /* Negation disabled. */
+};
+
+/*
+ *  Argparse option struct.
+ *
+ *  `type`:
+ *    holds the type of the option, you must have an ARGPARSE_OPT_END last in your
+ *    array.
+ *
+ *  `short_name`:
+ *    the character to use as a short option name, '\0' if none.
+ *
+ *  `long_name`:
+ *    the long option name, without the leading dash, NULL if none.
+ *
+ *  `value`:
+ *    stores pointer to the value to be filled.
+ *
+ *  `help`:
+ *    the short help message associated to what the option does.
+ *    Must never be NULL (except for ARGPARSE_OPT_END).
+ *
+ *  `callback`:
+ *    function is called when corresponding argument is parsed.
+ *
+ *  `data`:
+ *    associated data. Callbacks can use it like they want.
+ *
+ *  `flags`:
+ *    option flags.
+ *
+ */
+struct argparse_option {
+    enum argparse_option_type type;
+    const char short_name;
+    const char *long_name;
+    void *value;
+    const char *help;
+    argparse_callback *callback;
+    intptr_t data;
+    int flags;
+};
+
+/*
+ * argpparse
+ */
+struct argparse {
+    // user supplied
+    const struct argparse_option *options;
+    const char *usage;
+    int flags;
+    // internal context
+    int argc;
+    const char **argv;
+    const char **out;
+    int cpidx;
+    const char *optvalue;       // current option value
+};
+
+// builtin callbacks
+int argparse_help_cb(struct argparse *this_,
+                     const struct argparse_option *option);
+
+// builtin option macros
+#define OPT_END()          { ARGPARSE_OPT_END, 0 }
+#define OPT_BOOLEAN(...)   { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ }
+#define OPT_BIT(...)       { ARGPARSE_OPT_BIT, __VA_ARGS__ }
+#define OPT_INTEGER(...)   { ARGPARSE_OPT_INTEGER, __VA_ARGS__ }
+#define OPT_STRING(...)    { ARGPARSE_OPT_STRING, __VA_ARGS__ }
+#define OPT_HELP()         OPT_BOOLEAN('h', "help", 0, "Show this help message and exit", argparse_help_cb)
+
+int argparse_init(struct argparse *this_, struct argparse_option *options,
+                  const char *usage, int flags);
+int argparse_parse(struct argparse *this_, int argc, const char **argv);
+void argparse_usage(struct argparse *this_);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 149 - 0
server/include/detect.h

@@ -0,0 +1,149 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#ifndef BASE_DETECT_H
+#define BASE_DETECT_H
+
+/*
+	this file detected the family, platform and architecture
+	to compile for.
+*/
+
+/* platforms */
+
+/* windows Family */
+#if defined(WIN64) || defined(_WIN64)
+	/* Hmm, is this IA64 or x86-64? */
+	#define CONF_FAMILY_WINDOWS 1
+	#define CONF_FAMILY_STRING "windows"
+	#define CONF_PLATFORM_WIN64 1
+	#define CONF_PLATFORM_STRING "win64"
+#elif defined(WIN32) || defined(_WIN32) || defined(__CYGWIN32__) || defined(__MINGW32__)
+	#define CONF_FAMILY_WINDOWS 1
+	#define CONF_FAMILY_STRING "windows"
+	#define CONF_PLATFORM_WIN32 1
+	#define CONF_PLATFORM_STRING "win32"
+#endif
+
+/* unix family */
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+	#define CONF_FAMILY_UNIX 1
+	#define CONF_FAMILY_STRING "unix"
+	#define CONF_PLATFORM_FREEBSD 1
+	#define CONF_PLATFORM_STRING "freebsd"
+#endif
+
+#if defined(__OpenBSD__)
+	#define CONF_FAMILY_UNIX 1
+	#define CONF_FAMILY_STRING "unix"
+	#define CONF_PLATFORM_OPENBSD 1
+	#define CONF_PLATFORM_STRING "openbsd"
+#endif
+
+#if defined(__LINUX__) || defined(__linux__)
+	#define CONF_FAMILY_UNIX 1
+	#define CONF_FAMILY_STRING "unix"
+	#define CONF_PLATFORM_LINUX 1
+	#define CONF_PLATFORM_STRING "linux"
+#endif
+
+#if defined(__GNU__) || defined(__gnu__)
+	#define CONF_FAMILY_UNIX 1
+	#define CONF_FAMILY_STRING "unix"
+	#define CONF_PLATFORM_HURD 1
+	#define CONF_PLATFORM_STRING "gnu"
+#endif
+
+#if defined(MACOSX) || defined(__APPLE__) || defined(__DARWIN__)
+	#define CONF_FAMILY_UNIX 1
+	#define CONF_FAMILY_STRING "unix"
+	#define CONF_PLATFORM_MACOSX 1
+	#define CONF_PLATFORM_STRING "macosx"
+#endif
+
+#if defined(__sun)
+	#define CONF_FAMILY_UNIX 1
+	#define CONF_FAMILY_STRING "unix"
+	#define CONF_PLATFORM_SOLARIS 1
+	#define CONF_PLATFORM_STRING "solaris"
+#endif
+
+/* beos family */
+#if defined(__BeOS) || defined(__BEOS__)
+	#define CONF_FAMILY_BEOS 1
+	#define CONF_FAMILY_STRING "beos"
+	#define CONF_PLATFORM_BEOS 1
+	#define CONF_PLATFORM_STRING "beos"
+#endif
+
+
+/* use gcc endianness definitions when available */
+#if defined(__GNUC__) && !defined(__APPLE__) && !defined(__MINGW32__) && !defined(__sun)
+	#if defined(__FreeBSD__) || defined(__OpenBSD__)
+		#include <sys/endian.h>
+	#else
+		#include <endian.h>
+	#endif
+
+	#if __BYTE_ORDER == __LITTLE_ENDIAN
+		#define CONF_ARCH_ENDIAN_LITTLE 1
+	#elif __BYTE_ORDER == __BIG_ENDIAN
+		#define CONF_ARCH_ENDIAN_BIG 1
+	#endif
+#endif
+
+
+/* architectures */
+#if defined(i386) || defined(__i386__) || defined(__x86__) || defined(CONF_PLATFORM_WIN32)
+	#define CONF_ARCH_IA32 1
+	#define CONF_ARCH_STRING "ia32"
+	#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
+		#define CONF_ARCH_ENDIAN_LITTLE 1
+	#endif
+#endif
+
+#if defined(__ia64__) || defined(_M_IA64)
+	#define CONF_ARCH_IA64 1
+	#define CONF_ARCH_STRING "ia64"
+	#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
+		#define CONF_ARCH_ENDIAN_LITTLE 1
+	#endif
+#endif
+
+#if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64)
+	#define CONF_ARCH_AMD64 1
+	#define CONF_ARCH_STRING "amd64"
+	#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
+		#define CONF_ARCH_ENDIAN_LITTLE 1
+	#endif
+#endif
+
+#if defined(__powerpc__) || defined(__ppc__)
+	#define CONF_ARCH_PPC 1
+	#define CONF_ARCH_STRING "ppc"
+	#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
+		#define CONF_ARCH_ENDIAN_BIG 1
+	#endif
+#endif
+
+#if defined(__sparc__)
+	#define CONF_ARCH_SPARC 1
+	#define CONF_ARCH_STRING "sparc"
+	#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
+		#define CONF_ARCH_ENDIAN_BIG 1
+	#endif
+#endif
+
+
+#ifndef CONF_FAMILY_STRING
+#define CONF_FAMILY_STRING "unknown"
+#endif
+
+#ifndef CONF_PLATFORM_STRING
+#define CONF_PLATFORM_STRING "unknown"
+#endif
+
+#ifndef CONF_ARCH_STRING
+#define CONF_ARCH_STRING "unknown"
+#endif
+
+#endif

+ 269 - 0
server/include/json.h

@@ -0,0 +1,269 @@
+
+/* vim: set et ts=3 sw=3 sts=3 ft=c:
+ *
+ * Copyright (C) 2012, 2013, 2014 James McLaughlin et al.  All rights reserved.
+ * https://github.com/udp/json-parser
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _JSON_H
+#define _JSON_H
+
+#ifndef json_char
+   #define json_char char
+#endif
+
+#ifndef json_int_t
+   #ifndef _MSC_VER
+      #include <inttypes.h>
+      #define json_int_t int64_t
+   #else
+      #define json_int_t __int64
+   #endif
+#endif
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+
+   #include <string.h>
+
+   extern "C"
+   {
+
+#endif
+
+typedef struct
+{
+   unsigned long max_memory;
+   int settings;
+
+   /* Custom allocator support (leave null to use malloc/free)
+    */
+
+   void * (* mem_alloc) (size_t, int zero, void * user_data);
+   void (* mem_free) (void *, void * user_data);
+
+   void * user_data;  /* will be passed to mem_alloc and mem_free */
+
+} json_settings;
+
+#define json_enable_comments  0x01
+
+typedef enum
+{
+   json_none,
+   json_object,
+   json_array,
+   json_integer,
+   json_double,
+   json_string,
+   json_boolean,
+   json_null
+
+} json_type;
+
+extern const struct _json_value json_value_none;
+
+typedef struct _json_value
+{
+   struct _json_value * parent;
+
+   json_type type;
+
+   union
+   {
+      int boolean;
+      json_int_t integer;
+      double dbl;
+
+      struct
+      {
+         unsigned int length;
+         json_char * ptr; /* null terminated */
+
+      } string;
+
+      struct
+      {
+         unsigned int length;
+
+         struct
+         {
+            json_char * name;
+            unsigned int name_length;
+
+            struct _json_value * value;
+
+         } * values;
+
+         #if defined(__cplusplus) && __cplusplus >= 201103L
+         decltype(values) begin () const
+         {  return values;
+         }
+         decltype(values) end () const
+         {  return values + length;
+         }
+         #endif
+
+      } object;
+
+      struct
+      {
+         unsigned int length;
+         struct _json_value ** values;
+
+         #if defined(__cplusplus) && __cplusplus >= 201103L
+         decltype(values) begin () const
+         {  return values;
+         }
+         decltype(values) end () const
+         {  return values + length;
+         }
+         #endif
+
+      } array;
+
+   } u;
+
+   union
+   {
+      struct _json_value * next_alloc;
+      void * object_mem;
+
+   } _reserved;
+
+
+   /* Some C++ operator sugar */
+
+   #ifdef __cplusplus
+
+      public:
+
+         inline _json_value ()
+         {  memset (this, 0, sizeof (_json_value));
+         }
+
+         inline const struct _json_value &operator [] (int index) const
+         {
+            if (type != json_array || index < 0
+                     || ((unsigned int) index) >= u.array.length)
+            {
+               return json_value_none;
+            }
+
+            return *u.array.values [index];
+         }
+
+         inline const struct _json_value &operator [] (const char * index) const
+         { 
+            if (type != json_object)
+               return json_value_none;
+
+            for (unsigned int i = 0; i < u.object.length; ++ i)
+               if (!strcmp (u.object.values [i].name, index))
+                  return *u.object.values [i].value;
+
+            return json_value_none;
+         }
+
+         inline operator const char * () const
+         {  
+            switch (type)
+            {
+               case json_string:
+                  return u.string.ptr;
+
+               default:
+                  return "";
+            };
+         }
+
+         inline operator json_int_t () const
+         {  
+            switch (type)
+            {
+               case json_integer:
+                  return u.integer;
+
+               case json_double:
+                  return (json_int_t) u.dbl;
+
+               default:
+                  return 0;
+            };
+         }
+
+         inline operator bool () const
+         {  
+            if (type != json_boolean)
+               return false;
+
+            return u.boolean != 0;
+         }
+
+         inline operator double () const
+         {  
+            switch (type)
+            {
+               case json_integer:
+                  return (double) u.integer;
+
+               case json_double:
+                  return u.dbl;
+
+               default:
+                  return 0;
+            };
+         }
+
+   #endif
+
+} json_value;
+
+json_value * json_parse (const json_char * json,
+                         size_t length);
+
+#define json_error_max 128
+json_value * json_parse_ex (json_settings * settings,
+                            const json_char * json,
+                            size_t length,
+                            char * error);
+
+void json_value_free (json_value *);
+
+
+/* Not usually necessary, unless you used a custom mem_alloc and now want to
+ * use a custom mem_free.
+ */
+void json_value_free_ex (json_settings * settings,
+                         json_value *);
+
+
+#ifdef __cplusplus
+   } /* extern "C" */
+#endif
+
+#endif

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1299 - 0
server/include/system.h


+ 1 - 0
server/obj/.gitignore

@@ -0,0 +1 @@
+*.o

+ 322 - 0
server/src/argparse.c

@@ -0,0 +1,322 @@
+#include "argparse.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define OPT_UNSET 1
+
+static const char *
+prefix_skip(const char *str, const char *prefix)
+{
+    size_t len = strlen(prefix);
+    return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
+int
+prefix_cmp(const char *str, const char *prefix)
+{
+    for (;; str++, prefix++)
+        if (!*prefix)
+            return 0;
+        else if (*str != *prefix)
+            return (unsigned char)*prefix - (unsigned char)*str;
+}
+
+static void
+argparse_error(struct argparse *this_, const struct argparse_option *opt,
+               const char *reason)
+{
+    if (!strncmp(this_->argv[0], "--", 2)) {
+        fprintf(stderr, "error: option `%s` %s\n", opt->long_name, reason);
+        exit(-1);
+    } else {
+        fprintf(stderr, "error: option `%c` %s\n", opt->short_name, reason);
+        exit(-1);
+    }
+}
+
+static int
+argparse_getvalue(struct argparse *this_, const struct argparse_option *opt,
+                  int flags)
+{
+    const char *s = NULL;
+    if (!opt->value)
+        goto skipped;
+    switch (opt->type) {
+    case ARGPARSE_OPT_BOOLEAN:
+        if (flags & OPT_UNSET) {
+            *(int *)opt->value = *(int *)opt->value - 1;
+        } else {
+            *(int *)opt->value = *(int *)opt->value + 1;
+        }
+        if (*(int *)opt->value < 0) { 
+            *(int *)opt->value = 0;
+        }
+        break;
+    case ARGPARSE_OPT_BIT:
+        if (flags & OPT_UNSET) {
+            *(int *)opt->value &= ~opt->data;
+        } else {
+            *(int *)opt->value |= opt->data;
+        }
+        break;
+    case ARGPARSE_OPT_STRING:
+        if (this_->optvalue) {
+            *(const char **)opt->value = this_->optvalue;
+            this_->optvalue = NULL;
+        } else if (this_->argc > 1) {
+            this_->argc--;
+            *(const char **)opt->value = *++this_->argv;
+        } else {
+            argparse_error(this_, opt, "requires a value");
+        }
+        break;
+    case ARGPARSE_OPT_INTEGER:
+        if (this_->optvalue) {
+            *(int *)opt->value = strtol(this_->optvalue, (char **)&s, 0);
+            this_->optvalue = NULL;
+        } else if (this_->argc > 1) {
+            this_->argc--;
+            *(int *)opt->value = strtol(*++this_->argv, (char **)&s, 0);
+        } else {
+            argparse_error(this_, opt, "requires a value");
+        }
+        if (*s)
+            argparse_error(this_, opt, "expects a numerical value");
+        break;
+    default:
+        assert(0);
+    }
+
+skipped:
+    if (opt->callback) {
+        return opt->callback(this_, opt);
+    }
+
+    return 0;
+}
+
+static void
+argparse_options_check(const struct argparse_option *options)
+{
+    for (; options->type != ARGPARSE_OPT_END; options++) {
+        switch (options->type) {
+        case ARGPARSE_OPT_END:
+        case ARGPARSE_OPT_BOOLEAN:
+        case ARGPARSE_OPT_BIT:
+        case ARGPARSE_OPT_INTEGER:
+        case ARGPARSE_OPT_STRING:
+            continue;
+        default:
+            fprintf(stderr, "wrong option type: %d", options->type);
+            break;
+        }
+    }
+}
+
+static int
+argparse_short_opt(struct argparse *this_, const struct argparse_option *options)
+{
+    for (; options->type != ARGPARSE_OPT_END; options++) {
+        if (options->short_name == *this_->optvalue) {
+            this_->optvalue = this_->optvalue[1] ? this_->optvalue + 1 : NULL;
+            return argparse_getvalue(this_, options, 0);
+        }
+    }
+    return -2;
+}
+
+static int
+argparse_long_opt(struct argparse *this_, const struct argparse_option *options)
+{
+    for (; options->type != ARGPARSE_OPT_END; options++) {
+        const char *rest;
+        int opt_flags = 0;
+        if (!options->long_name)
+            continue;
+
+        rest = prefix_skip(this_->argv[0] + 2, options->long_name);
+        if (!rest) {
+            // Negation allowed?
+            if (options->flags & OPT_NONEG) {
+                continue;
+            }
+            // Only boolean/bit allow negation.
+            if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != ARGPARSE_OPT_BIT) {
+                continue;
+            }
+
+            if (!prefix_cmp(this_->argv[0] + 2, "no-")) {
+                rest = prefix_skip(this_->argv[0] + 2 + 3, options->long_name);
+                if (!rest)
+                    continue;
+                opt_flags |= OPT_UNSET;
+            } else {
+                continue;
+            }
+        }
+        if (*rest) {
+            if (*rest != '=')
+                continue;
+            this_->optvalue = rest + 1;
+        }
+        return argparse_getvalue(this_, options, opt_flags);
+    }
+    return -2;
+}
+
+int
+argparse_init(struct argparse *this_, struct argparse_option *options,
+              const char *usage, int flags)
+{
+    memset(this_, 0, sizeof(*this_));
+    this_->options = options;
+    this_->usage = usage;
+    this_->flags = flags;
+    return 0;
+}
+
+int
+argparse_parse(struct argparse *this_, int argc, const char **argv)
+{
+    this_->argc = argc - 1;
+    this_->argv = argv + 1;
+    this_->out = argv;
+
+    argparse_options_check(this_->options);
+
+    for (; this_->argc; this_->argc--, this_->argv++) {
+        const char *arg = this_->argv[0];
+        if (arg[0] != '-' || !arg[1]) {
+            if (this_->flags & ARGPARSE_STOP_AT_NON_OPTION) {
+                goto end;
+            }
+            // if it's not option or is a single char '-', copy verbatimly
+            this_->out[this_->cpidx++] = this_->argv[0];
+            continue;
+        }
+        // short option
+        if (arg[1] != '-') {
+            this_->optvalue = arg + 1;
+            switch (argparse_short_opt(this_, this_->options)) {
+            case -1:
+                break;
+            case -2:
+                goto unknown;
+            }
+            while (this_->optvalue) {
+                switch (argparse_short_opt(this_, this_->options)) {
+                case -1:
+                    break;
+                case -2:
+                    goto unknown;
+                }
+            }
+            continue;
+        }
+        // if '--' presents
+        if (!arg[2]) {
+            this_->argc--;
+            this_->argv++;
+            break;
+        }
+        // long option
+        switch (argparse_long_opt(this_, this_->options)) {
+        case -1:
+            break;
+        case -2:
+            goto unknown;
+        }
+        continue;
+
+unknown:
+        fprintf(stderr, "error: unknown option `%s`\n", this_->argv[0]);
+        argparse_usage(this_);
+        exit(0);
+    }
+
+end:
+    memmove(this_->out + this_->cpidx, this_->argv,
+            this_->argc * sizeof(*this_->out));
+    this_->out[this_->cpidx + this_->argc] = NULL;
+
+    return this_->cpidx + this_->argc;
+}
+
+void
+argparse_usage(struct argparse *this_)
+{
+    fprintf(stdout, "Usage: %s\n", this_->usage);
+    fputc('\n', stdout);
+
+    const struct argparse_option *options;
+
+    // figure out best width
+    size_t usage_opts_width = 0;
+    size_t len;
+    options = this_->options;
+    for (; options->type != ARGPARSE_OPT_END; options++) {
+        len = 0;
+        if ((options)->short_name) {
+            len += 2;
+        }
+        if ((options)->short_name && (options)->long_name) {
+            len += 2;           // separator ", "
+        }
+        if ((options)->long_name) {
+            len += strlen((options)->long_name) + 2;
+        }
+        if (options->type == ARGPARSE_OPT_INTEGER) {
+            len += strlen("=<int>");
+        } else if (options->type == ARGPARSE_OPT_STRING) {
+            len += strlen("=<str>");
+        }
+        len = ceil((float)len / 4) * 4;
+        if (usage_opts_width < len) {
+            usage_opts_width = len;
+        }
+    }
+    usage_opts_width += 4;      // 4 spaces prefix
+
+    options = this_->options;
+    for (; options->type != ARGPARSE_OPT_END; options++) {
+        size_t pos;
+        int pad;
+        pos = fprintf(stdout, "    ");
+        if (options->short_name) {
+            pos += fprintf(stdout, "-%c", options->short_name);
+        }
+        if (options->long_name && options->short_name) {
+            pos += fprintf(stdout, ", ");
+        }
+        if (options->long_name) {
+            pos += fprintf(stdout, "--%s", options->long_name);
+        }
+        if (options->type == ARGPARSE_OPT_INTEGER) {
+            pos += fprintf(stdout, "=<int>");
+        } else if (options->type == ARGPARSE_OPT_STRING) {
+            pos += fprintf(stdout, "=<str>");
+        }
+        if (pos <= usage_opts_width) {
+            pad = usage_opts_width - pos;
+        } else {
+            fputc('\n', stdout);
+            pad = usage_opts_width;
+        }
+        fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
+    }
+}
+
+int
+argparse_help_cb(struct argparse *this_, const struct argparse_option *option)
+{
+    (void)option;
+    argparse_usage(this_);
+    exit(0);
+    return 0;
+}
+
+#if defined(__cplusplus)
+}
+#endif

+ 949 - 0
server/src/json.c

@@ -0,0 +1,949 @@
+/* vim: set et ts=3 sw=3 sts=3 ft=c:
+ *
+ * Copyright (C) 2012, 2013, 2014 James McLaughlin et al.  All rights reserved.
+ * https://github.com/udp/json-parser
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "json.h"
+
+#ifdef _MSC_VER
+   #ifndef _CRT_SECURE_NO_WARNINGS
+      #define _CRT_SECURE_NO_WARNINGS
+   #endif
+#endif
+
+#ifdef __cplusplus
+   const struct _json_value json_value_none; /* zero-d by ctor */
+#else
+   const struct _json_value json_value_none = { 0 };
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+typedef unsigned short json_uchar;
+
+static unsigned char hex_value (json_char c)
+{
+   if (isdigit(c))
+      return c - '0';
+
+   switch (c) {
+      case 'a': case 'A': return 0x0A;
+      case 'b': case 'B': return 0x0B;
+      case 'c': case 'C': return 0x0C;
+      case 'd': case 'D': return 0x0D;
+      case 'e': case 'E': return 0x0E;
+      case 'f': case 'F': return 0x0F;
+      default: return 0xFF;
+   }
+}
+
+typedef struct
+{
+   unsigned long used_memory;
+
+   unsigned int uint_max;
+   unsigned long ulong_max;
+
+   json_settings settings;
+   int first_pass;
+
+} json_state;
+
+static void * default_alloc (size_t size, int zero, void * user_data)
+{
+   return zero ? calloc (1, size) : malloc (size);
+}
+
+static void default_free (void * ptr, void * user_data)
+{
+   free (ptr);
+}
+
+static void * json_alloc (json_state * state, unsigned long size, int zero)
+{
+   if ((state->ulong_max - state->used_memory) < size)
+      return 0;
+
+   if (state->settings.max_memory
+         && (state->used_memory += size) > state->settings.max_memory)
+   {
+      return 0;
+   }
+
+   return state->settings.mem_alloc (size, zero, state->settings.user_data);
+}
+
+static int new_value
+   (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type)
+{
+   json_value * value;
+   int values_size;
+
+   if (!state->first_pass)
+   {
+      value = *top = *alloc;
+      *alloc = (*alloc)->_reserved.next_alloc;
+
+      if (!*root)
+         *root = value;
+
+      switch (value->type)
+      {
+         case json_array:
+
+            if (! (value->u.array.values = (json_value **) json_alloc
+               (state, value->u.array.length * sizeof (json_value *), 0)) )
+            {
+               return 0;
+            }
+
+            value->u.array.length = 0;
+            break;
+
+         case json_object:
+
+            values_size = sizeof (*value->u.object.values) * value->u.object.length;
+
+            if (! ((*(void **) &value->u.object.values) = json_alloc
+                  (state, values_size + ((unsigned long) value->u.object.values), 0)) )
+            {
+               return 0;
+            }
+
+            value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size;
+
+            value->u.object.length = 0;
+            break;
+
+         case json_string:
+
+            if (! (value->u.string.ptr = (json_char *) json_alloc
+               (state, (value->u.string.length + 1) * sizeof (json_char), 0)) )
+            {
+               return 0;
+            }
+
+            value->u.string.length = 0;
+            break;
+
+         default:
+            break;
+      };
+
+      return 1;
+   }
+
+   value = (json_value *) json_alloc (state, sizeof (json_value), 1);
+
+   if (!value)
+      return 0;
+
+   if (!*root)
+      *root = value;
+
+   value->type = type;
+   value->parent = *top;
+
+   if (*alloc)
+      (*alloc)->_reserved.next_alloc = value;
+
+   *alloc = *top = value;
+
+   return 1;
+}
+
+#define e_off \
+   ((int) (i - cur_line_begin))
+
+#define whitespace \
+   case '\n': ++ cur_line;  cur_line_begin = i; \
+   case ' ': case '\t': case '\r'
+
+#define string_add(b)  \
+   do { if (!state.first_pass) string [string_length] = b;  ++ string_length; } while (0);
+
+static const long
+   flag_next             = 1 << 0,
+   flag_reproc           = 1 << 1,
+   flag_need_comma       = 1 << 2,
+   flag_seek_value       = 1 << 3, 
+   flag_escaped          = 1 << 4,
+   flag_string           = 1 << 5,
+   flag_need_colon       = 1 << 6,
+   flag_done             = 1 << 7,
+   flag_num_negative     = 1 << 8,
+   flag_num_zero         = 1 << 9,
+   flag_num_e            = 1 << 10,
+   flag_num_e_got_sign   = 1 << 11,
+   flag_num_e_negative   = 1 << 12,
+   flag_line_comment     = 1 << 13,
+   flag_block_comment    = 1 << 14;
+
+json_value * json_parse_ex (json_settings * settings,
+                            const json_char * json,
+                            size_t length,
+                            char * error_buf)
+{
+   json_char error [json_error_max];
+   unsigned int cur_line;
+   const json_char * cur_line_begin, * i, * end;
+   json_value * top, * root, * alloc = 0;
+   json_state state = { 0 };
+   long flags;
+   long num_digits = 0, num_e = 0;
+   json_int_t num_fraction = 0;
+
+   /* Skip UTF-8 BOM
+    */
+   if (length >= 3 && ((unsigned char) json [0]) == 0xEF
+                   && ((unsigned char) json [1]) == 0xBB
+                   && ((unsigned char) json [2]) == 0xBF)
+   {
+      json += 3;
+      length -= 3;
+   }
+
+   error[0] = '\0';
+   end = (json + length);
+
+   memcpy (&state.settings, settings, sizeof (json_settings));
+
+   if (!state.settings.mem_alloc)
+      state.settings.mem_alloc = default_alloc;
+
+   if (!state.settings.mem_free)
+      state.settings.mem_free = default_free;
+
+   memset (&state.uint_max, 0xFF, sizeof (state.uint_max));
+   memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max));
+
+   state.uint_max -= 8; /* limit of how much can be added before next check */
+   state.ulong_max -= 8;
+
+   for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass)
+   {
+      json_uchar uchar;
+      unsigned char uc_b1, uc_b2, uc_b3, uc_b4;
+      json_char * string = 0;
+      unsigned int string_length = 0;
+
+      top = root = 0;
+      flags = flag_seek_value;
+
+      cur_line = 1;
+      cur_line_begin = json;
+
+      for (i = json ;; ++ i)
+      {
+         json_char b = (i == end ? 0 : *i);
+         
+         if (flags & flag_string)
+         {
+            if (!b)
+            {  sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off);
+               goto e_failed;
+            }
+
+            if (string_length > state.uint_max)
+               goto e_overflow;
+
+            if (flags & flag_escaped)
+            {
+               flags &= ~ flag_escaped;
+
+               switch (b)
+               {
+                  case 'b':  string_add ('\b');  break;
+                  case 'f':  string_add ('\f');  break;
+                  case 'n':  string_add ('\n');  break;
+                  case 'r':  string_add ('\r');  break;
+                  case 't':  string_add ('\t');  break;
+                  case 'u':
+
+                    if (end - i < 4 || 
+                        (uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF
+                          || (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF)
+                    {
+                        sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off);
+                        goto e_failed;
+                    }
+
+                    uc_b1 = uc_b1 * 16 + uc_b2;
+                    uc_b2 = uc_b3 * 16 + uc_b4;
+
+                    uchar = ((json_char) uc_b1) * 256 + uc_b2;
+
+                    if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F))
+                    {
+                       string_add ((json_char) uchar);
+                       break;
+                    }
+
+                    if (uchar <= 0x7FF)
+                    {
+                        if (state.first_pass)
+                           string_length += 2;
+                        else
+                        {  string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x7) << 2);
+                           string [string_length ++] = 0x80 | (uc_b2 & 0x3F);
+                        }
+
+                        break;
+                    }
+
+                    if (state.first_pass)
+                       string_length += 3;
+                    else
+                    {  string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4);
+                       string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6);
+                       string [string_length ++] = 0x80 | (uc_b2 & 0x3F);
+                    }
+
+                    break;
+
+                  default:
+                     string_add (b);
+               };
+
+               continue;
+            }
+
+            if (b == '\\')
+            {
+               flags |= flag_escaped;
+               continue;
+            }
+
+            if (b == '"')
+            {
+               if (!state.first_pass)
+                  string [string_length] = 0;
+
+               flags &= ~ flag_string;
+               string = 0;
+
+               switch (top->type)
+               {
+                  case json_string:
+
+                     top->u.string.length = string_length;
+                     flags |= flag_next;
+
+                     break;
+
+                  case json_object:
+
+                     if (state.first_pass)
+                        (*(json_char **) &top->u.object.values) += string_length + 1;
+                     else
+                     {  
+                        top->u.object.values [top->u.object.length].name
+                           = (json_char *) top->_reserved.object_mem;
+
+                        top->u.object.values [top->u.object.length].name_length
+                           = string_length;
+
+                        (*(json_char **) &top->_reserved.object_mem) += string_length + 1;
+                     }
+
+                     flags |= flag_seek_value | flag_need_colon;
+                     continue;
+
+                  default:
+                     break;
+               };
+            }
+            else
+            {
+               string_add (b);
+               continue;
+            }
+         }
+
+         if (state.settings.settings & json_enable_comments)
+         {
+            if (flags & (flag_line_comment | flag_block_comment))
+            {
+               if (flags & flag_line_comment)
+               {
+                  if (b == '\r' || b == '\n' || !b)
+                  {
+                     flags &= ~ flag_line_comment;
+                     -- i;  /* so null can be reproc'd */
+                  }
+
+                  continue;
+               }
+
+               if (flags & flag_block_comment)
+               {
+                  if (!b)
+                  {  sprintf (error, "%d:%d: Unexpected EOF in block comment", cur_line, e_off);
+                     goto e_failed;
+                  }
+
+                  if (b == '*' && i < (end - 1) && i [1] == '/')
+                  {
+                     flags &= ~ flag_block_comment;
+                     ++ i;  /* skip closing sequence */
+                  }
+
+                  continue;
+               }
+            }
+            else if (b == '/')
+            {
+               if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object)
+               {
+                  sprintf (error, "%d:%d: Comment not allowed here", cur_line, e_off);
+                  goto e_failed;
+               }
+
+               if (++ i == end)
+               {  sprintf (error, "%d:%d: EOF unexpected", cur_line, e_off);
+                  goto e_failed;
+               }
+
+               switch (b = *i)
+               {
+                  case '/':
+                     flags |= flag_line_comment;
+                     continue;
+
+                  case '*':
+                     flags |= flag_block_comment;
+                     continue;
+
+                  default:
+                     sprintf (error, "%d:%d: Unexpected `%c` in comment opening sequence", cur_line, e_off, b);
+                     goto e_failed;
+               };
+            }
+         }
+
+         if (flags & flag_done)
+         {
+            if (!b)
+               break;
+
+            switch (b)
+            {
+               whitespace:
+                  continue;
+
+               default:
+                  sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b);
+                  goto e_failed;
+            };
+         }
+
+         if (flags & flag_seek_value)
+         {
+            switch (b)
+            {
+               whitespace:
+                  continue;
+
+               case ']':
+
+                  if (top->type == json_array)
+                     flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next;
+                  else
+                  {  sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off);
+                     goto e_failed;
+                  }
+
+                  break;
+
+               default:
+
+                  if (flags & flag_need_comma)
+                  {
+                     if (b == ',')
+                     {  flags &= ~ flag_need_comma;
+                        continue;
+                     }
+                     else
+                     {  sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b);
+                        goto e_failed;
+                     }
+                  }
+
+                  if (flags & flag_need_colon)
+                  {
+                     if (b == ':')
+                     {  flags &= ~ flag_need_colon;
+                        continue;
+                     }
+                     else
+                     {  sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b);
+                        goto e_failed;
+                     }
+                  }
+
+                  flags &= ~ flag_seek_value;
+
+                  switch (b)
+                  {
+                     case '{':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_object))
+                           goto e_alloc_failure;
+
+                        continue;
+
+                     case '[':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_array))
+                           goto e_alloc_failure;
+
+                        flags |= flag_seek_value;
+                        continue;
+
+                     case '"':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_string))
+                           goto e_alloc_failure;
+
+                        flags |= flag_string;
+
+                        string = top->u.string.ptr;
+                        string_length = 0;
+
+                        continue;
+
+                     case 't':
+
+                        if ((end - i) < 3 || *(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e')
+                           goto e_unknown_value;
+
+                        if (!new_value (&state, &top, &root, &alloc, json_boolean))
+                           goto e_alloc_failure;
+
+                        top->u.boolean = 1;
+
+                        flags |= flag_next;
+                        break;
+
+                     case 'f':
+
+                        if ((end - i) < 4 || *(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e')
+                           goto e_unknown_value;
+
+                        if (!new_value (&state, &top, &root, &alloc, json_boolean))
+                           goto e_alloc_failure;
+
+                        flags |= flag_next;
+                        break;
+
+                     case 'n':
+
+                        if ((end - i) < 3 || *(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l')
+                           goto e_unknown_value;
+
+                        if (!new_value (&state, &top, &root, &alloc, json_null))
+                           goto e_alloc_failure;
+
+                        flags |= flag_next;
+                        break;
+
+                     default:
+
+                        if (isdigit (b) || b == '-')
+                        {
+                           if (!new_value (&state, &top, &root, &alloc, json_integer))
+                              goto e_alloc_failure;
+
+                           if (!state.first_pass)
+                           {
+                              while (isdigit (b) || b == '+' || b == '-'
+                                        || b == 'e' || b == 'E' || b == '.')
+                              {
+                                 if ( (++ i) == end)
+                                 {
+                                    b = 0;
+                                    break;
+                                 }
+
+                                 b = *i;
+                              }
+
+                              flags |= flag_next | flag_reproc;
+                              break;
+                           }
+
+                           flags &= ~ (flag_num_negative | flag_num_e |
+                                        flag_num_e_got_sign | flag_num_e_negative |
+                                           flag_num_zero);
+
+                           num_digits = 0;
+                           num_fraction = 0;
+                           num_e = 0;
+
+                           if (b != '-')
+                           {
+                              flags |= flag_reproc;
+                              break;
+                           }
+
+                           flags |= flag_num_negative;
+                           continue;
+                        }
+                        else
+                        {  sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b);
+                           goto e_failed;
+                        }
+                  };
+            };
+         }
+         else
+         {
+            switch (top->type)
+            {
+            case json_object:
+               
+               switch (b)
+               {
+                  whitespace:
+                     continue;
+
+                  case '"':
+
+                     if (flags & flag_need_comma)
+                     {
+                        sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off);
+                        goto e_failed;
+                     }
+
+                     flags |= flag_string;
+
+                     string = (json_char *) top->_reserved.object_mem;
+                     string_length = 0;
+
+                     break;
+                  
+                  case '}':
+
+                     flags = (flags & ~ flag_need_comma) | flag_next;
+                     break;
+
+                  case ',':
+
+                     if (flags & flag_need_comma)
+                     {
+                        flags &= ~ flag_need_comma;
+                        break;
+                     }
+
+                  default:
+
+                     sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b);
+                     goto e_failed;
+               };
+
+               break;
+
+            case json_integer:
+            case json_double:
+
+               if (isdigit (b))
+               {
+                  ++ num_digits;
+
+                  if (top->type == json_integer || flags & flag_num_e)
+                  {
+                     if (! (flags & flag_num_e))
+                     {
+                        if (flags & flag_num_zero)
+                        {  sprintf (error, "%d:%d: Unexpected `0` before `%c`", cur_line, e_off, b);
+                           goto e_failed;
+                        }
+
+                        if (num_digits == 1 && b == '0')
+                           flags |= flag_num_zero;
+                     }
+                     else
+                     {
+                        flags |= flag_num_e_got_sign;
+                        num_e = (num_e * 10) + (b - '0');
+                        continue;
+                     }
+
+                     top->u.integer = (top->u.integer * 10) + (b - '0');
+                     continue;
+                  }
+
+                  num_fraction = (num_fraction * 10) + (b - '0');
+                  continue;
+               }
+
+               if (b == '+' || b == '-')
+               {
+                  if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign))
+                  {
+                     flags |= flag_num_e_got_sign;
+
+                     if (b == '-')
+                        flags |= flag_num_e_negative;
+
+                     continue;
+                  }
+               }
+               else if (b == '.' && top->type == json_integer)
+               {
+                  if (!num_digits)
+                  {  sprintf (error, "%d:%d: Expected digit before `.`", cur_line, e_off);
+                     goto e_failed;
+                  }
+
+                  top->type = json_double;
+                  top->u.dbl = (double) top->u.integer;
+
+                  num_digits = 0;
+                  continue;
+               }
+
+               if (! (flags & flag_num_e))
+               {
+                  if (top->type == json_double)
+                  {
+                     if (!num_digits)
+                     {  sprintf (error, "%d:%d: Expected digit after `.`", cur_line, e_off);
+                        goto e_failed;
+                     }
+
+                     top->u.dbl += ((double) num_fraction) / (pow (10, (double) num_digits));
+                  }
+
+                  if (b == 'e' || b == 'E')
+                  {
+                     flags |= flag_num_e;
+
+                     if (top->type == json_integer)
+                     {
+                        top->type = json_double;
+                        top->u.dbl = (double) top->u.integer;
+                     }
+
+                     num_digits = 0;
+                     flags &= ~ flag_num_zero;
+
+                     continue;
+                  }
+               }
+               else
+               {
+                  if (!num_digits)
+                  {  sprintf (error, "%d:%d: Expected digit after `e`", cur_line, e_off);
+                     goto e_failed;
+                  }
+
+                  top->u.dbl *= pow (10, (double) (flags & flag_num_e_negative ? - num_e : num_e));
+               }
+
+               if (flags & flag_num_negative)
+               {
+                  if (top->type == json_integer)
+                     top->u.integer = - top->u.integer;
+                  else
+                     top->u.dbl = - top->u.dbl;
+               }
+
+               flags |= flag_next | flag_reproc;
+               break;
+
+            default:
+               break;
+            };
+         }
+
+         if (flags & flag_reproc)
+         {
+            flags &= ~ flag_reproc;
+            -- i;
+         }
+
+         if (flags & flag_next)
+         {
+            flags = (flags & ~ flag_next) | flag_need_comma;
+
+            if (!top->parent)
+            {
+               /* root value done */
+
+               flags |= flag_done;
+               continue;
+            }
+
+            if (top->parent->type == json_array)
+               flags |= flag_seek_value;
+               
+            if (!state.first_pass)
+            {
+               json_value * parent = top->parent;
+
+               switch (parent->type)
+               {
+                  case json_object:
+
+                     parent->u.object.values
+                        [parent->u.object.length].value = top;
+
+                     break;
+
+                  case json_array:
+
+                     parent->u.array.values
+                           [parent->u.array.length] = top;
+
+                     break;
+
+                  default:
+                     break;
+               };
+            }
+
+            if ( (++ top->parent->u.array.length) > state.uint_max)
+               goto e_overflow;
+
+            top = top->parent;
+
+            continue;
+         }
+      }
+
+      alloc = root;
+   }
+
+   return root;
+
+e_unknown_value:
+
+   sprintf (error, "%d:%d: Unknown value", cur_line, e_off);
+   goto e_failed;
+
+e_alloc_failure:
+
+   strcpy (error, "Memory allocation failure");
+   goto e_failed;
+
+e_overflow:
+
+   sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off);
+   goto e_failed;
+
+e_failed:
+
+   if (error_buf)
+   {
+      if (*error)
+         strcpy (error_buf, error);
+      else
+         strcpy (error_buf, "Unknown error");
+   }
+
+   if (state.first_pass)
+      alloc = root;
+
+   while (alloc)
+   {
+      top = alloc->_reserved.next_alloc;
+      state.settings.mem_free (alloc, state.settings.user_data);
+      alloc = top;
+   }
+
+   if (!state.first_pass)
+      json_value_free_ex (&state.settings, root);
+
+   return 0;
+}
+
+json_value * json_parse (const json_char * json, size_t length)
+{
+   json_settings settings = { 0 };
+   return json_parse_ex (&settings, json, length, 0);
+}
+
+void json_value_free_ex (json_settings * settings, json_value * value)
+{
+   json_value * cur_value;
+
+   if (!value)
+      return;
+
+   value->parent = 0;
+
+   while (value)
+   {
+      switch (value->type)
+      {
+         case json_array:
+
+            if (!value->u.array.length)
+            {
+               settings->mem_free (value->u.array.values, settings->user_data);
+               break;
+            }
+
+            value = value->u.array.values [-- value->u.array.length];
+            continue;
+
+         case json_object:
+
+            if (!value->u.object.length)
+            {
+               settings->mem_free (value->u.object.values, settings->user_data);
+               break;
+            }
+
+            value = value->u.object.values [-- value->u.object.length].value;
+            continue;
+
+         case json_string:
+
+            settings->mem_free (value->u.string.ptr, settings->user_data);
+            break;
+
+         default:
+            break;
+      };
+
+      cur_value = value;
+      value = value->parent;
+      settings->mem_free (cur_value, settings->user_data);
+   }
+}
+
+void json_value_free (json_value * value)
+{
+   json_settings settings = { 0 };
+   settings.mem_free = default_free;
+   json_value_free_ex (&settings, value);
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 488 - 0
server/src/main.cpp


+ 103 - 0
server/src/main.h

@@ -0,0 +1,103 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <stdint.h>
+#include "server.h"
+
+class CConfig
+{
+public:
+	bool m_Verbose;
+	char m_aConfigFile[1024];
+	char m_aWebDir[1024];
+	char m_aTemplateFile[1024];
+	char m_aJSONFile[1024];
+	char m_aBindAddr[256];
+	int m_Port;
+
+	CConfig();
+};
+
+class CMain
+{
+	CConfig m_Config;
+	CServer m_Server;
+
+	struct CClient
+	{
+		bool m_Active;
+		bool m_Disabled;
+		bool m_Connected;
+		int m_ClientNetID;
+		int m_ClientNetType;
+		char m_aUsername[128];
+		char m_aName[128];
+		char m_aType[128];
+		char m_aHost[128];
+		char m_aLocation[128];
+		char m_aPassword[128];
+
+		int64 m_TimeConnected;
+		int64 m_LastUpdate;
+
+		struct CStats
+		{
+			bool m_Online4;
+			bool m_Online6;
+			bool m_IpStatus;    //mh361 or mh370, mourn mh370, 2014-03-08 01:20 lost from all over the world. by:cpp.la
+			int64_t m_Uptime;
+			double m_Load_1;
+			double m_Load_5;
+			double m_Load_15;
+			double m_ping_10010;
+			double m_ping_189;
+			double m_ping_10086;
+			int64_t m_time_10010;
+			int64_t m_time_189;
+			int64_t m_time_10086;
+			int64_t m_NetworkRx;
+			int64_t m_NetworkTx;
+			int64_t m_NetworkIN;
+			int64_t m_NetworkOUT;
+			int64_t m_MemTotal;
+			int64_t m_MemUsed;
+			int64_t m_SwapTotal;
+			int64_t m_SwapUsed;
+			int64_t m_HDDTotal;
+			int64_t m_HDDUsed;
+			int64_t m_tcpCount;
+			int64_t m_udpCount;
+			int64_t m_processCount;
+			int64_t m_threadCount;
+			double m_CPU;
+			char m_aCustom[512];
+			// Options
+			bool m_Pong;
+		} m_Stats;
+	} m_aClients[NET_MAX_CLIENTS];
+
+	struct CJSONUpdateThreadData
+	{
+		CClient *pClients;
+		CConfig *pConfig;
+		volatile short m_ReloadRequired;
+	} m_JSONUpdateThreadData;
+
+	static void JSONUpdateThread(void *pUser);
+public:
+	CMain(CConfig Config);
+
+	void OnNewClient(int ClienNettID, int ClientID);
+	void OnDelClient(int ClientNetID);
+	int HandleMessage(int ClientNetID, char *pMessage);
+	int ReadConfig();
+	int Run();
+
+	CClient *Client(int ClientID) { return &m_aClients[ClientID]; }
+	CClient *ClientNet(int ClientNetID);
+	const CConfig *Config() const { return &m_Config; }
+	int ClientNetToClient(int ClientNetID);
+};
+
+
+#endif

+ 463 - 0
server/src/netban.cpp

@@ -0,0 +1,463 @@
+#include <math.h>
+#include "netban.h"
+
+bool CNetBan::StrAllnum(const char *pStr)
+{
+	while(*pStr)
+	{
+		if(!(*pStr >= '0' && *pStr <= '9'))
+			return false;
+		pStr++;
+	}
+	return true;
+}
+
+
+CNetBan::CNetHash::CNetHash(const NETADDR *pAddr)
+{
+	if(pAddr->type==NETTYPE_IPV4)
+		m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3])&0xFF;
+	else
+		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]+
+			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;
+	m_HashIndex = 0;
+}
+
+CNetBan::CNetHash::CNetHash(const CNetRange *pRange)
+{
+	m_Hash = 0;
+	m_HashIndex = 0;
+	for(int i = 0; pRange->m_LB.ip[i] == pRange->m_UB.ip[i]; ++i)
+	{
+		m_Hash += pRange->m_LB.ip[i];
+		++m_HashIndex;
+	}
+	m_Hash &= 0xFF;
+}
+
+int CNetBan::CNetHash::MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17])
+{
+	int Length = pAddr->type==NETTYPE_IPV4 ? 4 : 16;
+	aHash[0].m_Hash = 0;
+	aHash[0].m_HashIndex = 0;
+	for(int i = 1, Sum = 0; i <= Length; ++i)
+	{
+		Sum += pAddr->ip[i-1];
+		aHash[i].m_Hash = Sum&0xFF;
+		aHash[i].m_HashIndex = i%Length;
+	}
+	return Length;
+}
+
+
+template<class T, int HashCount>
+typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Add(const T *pData, const CBanInfo *pInfo, const CNetHash *pNetHash)
+{
+	if(!m_pFirstFree)
+		return 0;
+
+	// create new ban
+	CBan<T> *pBan = m_pFirstFree;
+	pBan->m_Data = *pData;
+	pBan->m_Info = *pInfo;
+	pBan->m_NetHash = *pNetHash;
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstFree = pBan->m_pNext;
+
+	// add it to the hash list
+	if(m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash])
+		m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]->m_pHashPrev = pBan;
+	pBan->m_pHashPrev = 0;
+	pBan->m_pHashNext = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash];
+	m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash] = pBan;
+
+	// insert it into the used list
+	if(m_pFirstUsed)
+	{
+		for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext)
+		{
+			if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
+			{
+				// insert before
+				pBan->m_pNext = p;
+				pBan->m_pPrev = p->m_pPrev;
+				if(p->m_pPrev)
+					p->m_pPrev->m_pNext = pBan;
+				else
+					m_pFirstUsed = pBan;
+				p->m_pPrev = pBan;
+				break;
+			}
+
+			if(!p->m_pNext)
+			{
+				// last entry
+				p->m_pNext = pBan;
+				pBan->m_pPrev = p;
+				pBan->m_pNext = 0;
+				break;
+			}
+		}
+	}
+	else
+	{
+		m_pFirstUsed = pBan;
+		pBan->m_pNext = pBan->m_pPrev = 0;
+	}
+
+	// update ban count
+	++m_CountUsed;
+
+	return pBan;
+}
+
+template<class T, int HashCount>
+int CNetBan::CBanPool<T, HashCount>::Remove(CBan<T> *pBan)
+{
+	if(pBan == 0)
+		return -1;
+
+	// remove from hash list
+	if(pBan->m_pHashNext)
+		pBan->m_pHashNext->m_pHashPrev = pBan->m_pHashPrev;
+	if(pBan->m_pHashPrev)
+		pBan->m_pHashPrev->m_pHashNext = pBan->m_pHashNext;
+	else
+		m_paaHashList[pBan->m_NetHash.m_HashIndex][pBan->m_NetHash.m_Hash] = pBan->m_pHashNext;
+	pBan->m_pHashNext = pBan->m_pHashPrev = 0;
+
+	// remove from used list
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstUsed = pBan->m_pNext;
+
+	// add to recycle list
+	if(m_pFirstFree)
+		m_pFirstFree->m_pPrev = pBan;
+	pBan->m_pPrev = 0;
+	pBan->m_pNext = m_pFirstFree;
+	m_pFirstFree = pBan;
+
+	// update ban count
+	--m_CountUsed;
+
+	return 0;
+}
+
+template<class T, int HashCount>
+void CNetBan::CBanPool<T, HashCount>::Update(CBan<CDataType> *pBan, const CBanInfo *pInfo)
+{
+	pBan->m_Info = *pInfo;
+
+	// remove from used list
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstUsed = pBan->m_pNext;
+
+	// insert it into the used list
+	if(m_pFirstUsed)
+	{
+		for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext)
+		{
+			if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
+			{
+				// insert before
+				pBan->m_pNext = p;
+				pBan->m_pPrev = p->m_pPrev;
+				if(p->m_pPrev)
+					p->m_pPrev->m_pNext = pBan;
+				else
+					m_pFirstUsed = pBan;
+				p->m_pPrev = pBan;
+				break;
+			}
+
+			if(!p->m_pNext)
+			{
+				// last entry
+				p->m_pNext = pBan;
+				pBan->m_pPrev = p;
+				pBan->m_pNext = 0;
+				break;
+			}
+		}
+	}
+	else
+	{
+		m_pFirstUsed = pBan;
+		pBan->m_pNext = pBan->m_pPrev = 0;
+	}
+}
+
+template<class T, int HashCount>
+void CNetBan::CBanPool<T, HashCount>::Reset()
+{
+	mem_zero(m_paaHashList, sizeof(m_paaHashList));
+	mem_zero(m_aBans, sizeof(m_aBans));
+	m_pFirstUsed = 0;
+	m_CountUsed = 0;
+
+	for(int i = 1; i < MAX_BANS-1; ++i)
+	{
+		m_aBans[i].m_pNext = &m_aBans[i+1];
+		m_aBans[i].m_pPrev = &m_aBans[i-1];
+	}
+
+	m_aBans[0].m_pNext = &m_aBans[1];
+	m_aBans[MAX_BANS-1].m_pPrev = &m_aBans[MAX_BANS-2];
+	m_pFirstFree = &m_aBans[0];
+}
+
+template<class T, int HashCount>
+typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Get(int Index) const
+{
+	if(Index < 0 || Index >= Num())
+		return 0;
+
+	for(CNetBan::CBan<T> *pBan = m_pFirstUsed; pBan; pBan = pBan->m_pNext, --Index)
+	{
+		if(Index == 0)
+			return pBan;
+	}
+
+	return 0;
+}
+
+
+template<class T>
+void CNetBan::MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const
+{
+	if(pBan == 0 || pBuf == 0)
+	{
+		if(BuffSize > 0)
+			pBuf[0] = 0;
+		return;
+	}
+
+	// build type based part
+	char aBuf[256];
+	if(Type == MSGTYPE_PLAYER)
+		str_copy(aBuf, "You have been banned", sizeof(aBuf));
+	else
+	{
+		char aTemp[256];
+		switch(Type)
+		{
+		case MSGTYPE_LIST:
+			str_format(aBuf, sizeof(aBuf), "%s banned", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		case MSGTYPE_BANADD:
+			str_format(aBuf, sizeof(aBuf), "banned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		case MSGTYPE_BANREM:
+			str_format(aBuf, sizeof(aBuf), "unbanned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		default:
+			aBuf[0] = 0;
+		}
+	}
+
+	// add info part
+	if(pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER)
+	{
+		int Mins = ((pBan->m_Info.m_Expires-time_timestamp()) + 59) / 60;
+		if(Mins <= 1)
+			str_format(pBuf, BuffSize, "%s for 1 minute (%s)", aBuf, pBan->m_Info.m_aReason);
+		else
+			str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason);
+	}
+	else
+		str_format(pBuf, BuffSize, "%s for life (%s)", aBuf, pBan->m_Info.m_aReason);
+}
+
+template<class T>
+int CNetBan::Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason)
+{
+	// do not ban localhost
+	if(NetMatch(pData, &m_LocalhostIPV4) || NetMatch(pData, &m_LocalhostIPV6))
+	{
+		dbg_msg("net_ban", "ban failed (localhost)");
+		return -1;
+	}
+
+	int Stamp = Seconds > 0 ? time_timestamp()+Seconds : CBanInfo::EXPIRES_NEVER;
+
+	// set up info
+	CBanInfo Info = {0};
+	Info.m_Expires = Stamp;
+	str_copy(Info.m_aReason, pReason, sizeof(Info.m_aReason));
+
+	// check if it already exists
+	CNetHash NetHash(pData);
+	CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
+	if(pBan)
+	{
+		// adjust the ban
+		pBanPool->Update(pBan, &Info);
+		char aBuf[128];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
+		dbg_msg("net_ban", aBuf);
+		return 1;
+	}
+
+	// add ban and print result
+	pBan = pBanPool->Add(pData, &Info, &NetHash);
+	if(pBan)
+	{
+		char aBuf[128];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANADD);
+		dbg_msg("net_ban", aBuf);
+		return 0;
+	}
+	else
+		dbg_msg("net_ban", "ban failed (full banlist)");
+	return -1;
+}
+
+template<class T>
+int CNetBan::Unban(T *pBanPool, const typename T::CDataType *pData)
+{
+	CNetHash NetHash(pData);
+	CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
+	if(pBan)
+	{
+		char aBuf[256];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANREM);
+		pBanPool->Remove(pBan);
+		dbg_msg("net_ban", aBuf);
+		return 0;
+	}
+	else
+		dbg_msg("net_ban", "unban failed (invalid entry)");
+	return -1;
+}
+
+void CNetBan::Init()
+{
+	m_BanAddrPool.Reset();
+	m_BanRangePool.Reset();
+
+	net_host_lookup("localhost", &m_LocalhostIPV4, NETTYPE_IPV4);
+	net_host_lookup("localhost", &m_LocalhostIPV6, NETTYPE_IPV6);
+}
+
+void CNetBan::Update()
+{
+	int Now = time_timestamp();
+
+	// remove expired bans
+	char aBuf[256], aNetStr[256];
+	while(m_BanAddrPool.First() && m_BanAddrPool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanAddrPool.First()->m_Info.m_Expires < Now)
+	{
+		str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanAddrPool.First()->m_Data, aNetStr, sizeof(aNetStr)));
+		dbg_msg("net_ban", aBuf);
+		m_BanAddrPool.Remove(m_BanAddrPool.First());
+	}
+	while(m_BanRangePool.First() && m_BanRangePool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanRangePool.First()->m_Info.m_Expires < Now)
+	{
+		str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanRangePool.First()->m_Data, aNetStr, sizeof(aNetStr)));
+		dbg_msg("net_ban", aBuf);
+		m_BanRangePool.Remove(m_BanRangePool.First());
+	}
+}
+
+int CNetBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason)
+{
+	return Ban(&m_BanAddrPool, pAddr, Seconds, pReason);
+}
+
+int CNetBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason)
+{
+	if(pRange->IsValid())
+		return Ban(&m_BanRangePool, pRange, Seconds, pReason);
+
+	dbg_msg("net_ban", "ban failed (invalid range)");
+	return -1;
+}
+
+int CNetBan::UnbanByAddr(const NETADDR *pAddr)
+{
+	return Unban(&m_BanAddrPool, pAddr);
+}
+
+int CNetBan::UnbanByRange(const CNetRange *pRange)
+{
+	if(pRange->IsValid())
+		return Unban(&m_BanRangePool, pRange);
+
+	dbg_msg("net_ban", "ban failed (invalid range)");
+	return -1;
+}
+
+int CNetBan::UnbanByIndex(int Index)
+{
+	int Result;
+	char aBuf[256];
+	CBanAddr *pBan = m_BanAddrPool.Get(Index);
+	if(pBan)
+	{
+		NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
+		Result = m_BanAddrPool.Remove(pBan);
+	}
+	else
+	{
+		CBanRange *pBan = m_BanRangePool.Get(Index-m_BanAddrPool.Num());
+		if(pBan)
+		{
+			NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
+			Result = m_BanRangePool.Remove(pBan);
+		}
+		else
+		{
+			dbg_msg("net_ban", "unban failed (invalid index)");
+			return -1;
+		}
+	}
+
+	char aMsg[256];
+	str_format(aMsg, sizeof(aMsg), "unbanned index %i (%s)", Index, aBuf);
+	dbg_msg("net_ban", aMsg);
+	return Result;
+}
+
+void CNetBan::UnbanAll()
+{
+	m_BanAddrPool.Reset();
+	m_BanRangePool.Reset();
+}
+
+bool CNetBan::IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const
+{
+	CNetHash aHash[17];
+	int Length = CNetHash::MakeHashArray(pAddr, aHash);
+
+	// check ban adresses
+	CBanAddr *pBan = m_BanAddrPool.Find(pAddr, &aHash[Length]);
+	if(pBan)
+	{
+		MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
+		return true;
+	}
+
+	// check ban ranges
+	for(int i = Length-1; i >= 0; --i)
+	{
+		for(CBanRange *pBan = m_BanRangePool.First(&aHash[i]); pBan; pBan = pBan->m_pHashNext)
+		{
+			if(NetMatch(&pBan->m_Data, pAddr, i, Length))
+			{
+				MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
+				return true;
+			}
+		}
+	}
+	
+	return false;
+}

+ 179 - 0
server/src/netban.h

@@ -0,0 +1,179 @@
+#ifndef NETBAN_H
+#define NETBAN_H
+
+#include <system.h>
+
+inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2)
+{
+	return mem_comp(pAddr1, pAddr2, pAddr1->type==NETTYPE_IPV4 ? 8 : 20);
+}
+
+class CNetRange
+{
+public:
+	NETADDR m_LB;
+	NETADDR m_UB;
+
+	bool IsValid() const { return m_LB.type == m_UB.type && NetComp(&m_LB, &m_UB) < 0; }
+};
+
+inline int NetComp(const CNetRange *pRange1, const CNetRange *pRange2)
+{
+	return NetComp(&pRange1->m_LB, &pRange2->m_LB) || NetComp(&pRange1->m_UB, &pRange2->m_UB);
+}
+
+
+class CNetBan
+{
+protected:
+	bool NetMatch(const NETADDR *pAddr1, const NETADDR *pAddr2) const
+	{
+		return NetComp(pAddr1, pAddr2) == 0;
+	}
+
+	bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr, int Start, int Length) const
+	{
+		return pRange->m_LB.type == pAddr->type && (Start == 0 || mem_comp(&pRange->m_LB.ip[0], &pAddr->ip[0], Start) == 0) &&
+			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;
+	}
+
+	bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr) const
+	{
+		return NetMatch(pRange, pAddr, 0,  pRange->m_LB.type==NETTYPE_IPV4 ? 4 : 16);
+	}
+
+	const char *NetToString(const NETADDR *pData, char *pBuffer, unsigned BufferSize) const
+	{
+		char aAddrStr[NETADDR_MAXSTRSIZE];
+		net_addr_str(pData, aAddrStr, sizeof(aAddrStr), false);
+		str_format(pBuffer, BufferSize, "'%s'", aAddrStr);
+		return pBuffer;
+	}
+
+	const char *NetToString(const CNetRange *pData, char *pBuffer, unsigned BufferSize) const
+	{
+		char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
+		net_addr_str(&pData->m_LB, aAddrStr1, sizeof(aAddrStr1), false);
+		net_addr_str(&pData->m_UB, aAddrStr2, sizeof(aAddrStr2), false);
+		str_format(pBuffer, BufferSize, "'%s' - '%s'", aAddrStr1, aAddrStr2);
+		return pBuffer;
+	}
+
+	// todo: move?
+	static bool StrAllnum(const char *pStr);
+
+	class CNetHash
+	{
+	public:
+		int m_Hash;
+		int m_HashIndex;	// matching parts for ranges, 0 for addr
+
+		CNetHash() {}	
+		CNetHash(const NETADDR *pAddr);
+		CNetHash(const CNetRange *pRange);
+
+		static int MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]);
+	};
+
+	struct CBanInfo
+	{
+		enum
+		{
+			EXPIRES_NEVER=-1,
+			REASON_LENGTH=64,
+		};
+		int m_Expires;
+		char m_aReason[REASON_LENGTH];
+	};
+
+	template<class T> struct CBan
+	{
+		T m_Data;
+		CBanInfo m_Info;
+		CNetHash m_NetHash;
+
+		// hash list
+		CBan *m_pHashNext;
+		CBan *m_pHashPrev;
+
+		// used or free list
+		CBan *m_pNext;
+		CBan *m_pPrev;
+	};
+
+	template<class T, int HashCount> class CBanPool
+	{
+	public:
+		typedef T CDataType;
+
+		CBan<CDataType> *Add(const CDataType *pData, const CBanInfo *pInfo, const CNetHash *pNetHash);
+		int Remove(CBan<CDataType> *pBan);
+		void Update(CBan<CDataType> *pBan, const CBanInfo *pInfo);
+		void Reset();
+	
+		int Num() const { return m_CountUsed; }
+		bool IsFull() const { return m_CountUsed == MAX_BANS; }
+
+		CBan<CDataType> *First() const { return m_pFirstUsed; }
+		CBan<CDataType> *First(const CNetHash *pNetHash) const { return m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; }
+		CBan<CDataType> *Find(const CDataType *pData, const CNetHash *pNetHash) const
+		{
+			for(CBan<CDataType> *pBan = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; pBan; pBan = pBan->m_pHashNext)
+			{
+				if(NetComp(&pBan->m_Data, pData) == 0)
+					return pBan;
+			}
+
+			return 0;
+		}
+		CBan<CDataType> *Get(int Index) const;
+
+	private:
+		enum
+		{
+			MAX_BANS=1024,
+		};
+
+		CBan<CDataType> *m_paaHashList[HashCount][256];
+		CBan<CDataType> m_aBans[MAX_BANS];
+		CBan<CDataType> *m_pFirstFree;
+		CBan<CDataType> *m_pFirstUsed;
+		int m_CountUsed;
+	};
+
+	typedef CBanPool<NETADDR, 1> CBanAddrPool;
+	typedef CBanPool<CNetRange, 16> CBanRangePool;
+	typedef CBan<NETADDR> CBanAddr;
+	typedef CBan<CNetRange> CBanRange;
+	
+	template<class T> void MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const;
+	template<class T> int Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason);
+	template<class T> int Unban(T *pBanPool, const typename T::CDataType *pData);
+
+	CBanAddrPool m_BanAddrPool;
+	CBanRangePool m_BanRangePool;
+	NETADDR m_LocalhostIPV4, m_LocalhostIPV6;
+
+public:
+	enum
+	{
+		MSGTYPE_PLAYER=0,
+		MSGTYPE_LIST,
+		MSGTYPE_BANADD,
+		MSGTYPE_BANREM,
+	};
+
+	virtual ~CNetBan() {}
+	void Init();
+	void Update();
+
+	virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason);
+	virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason);
+	int UnbanByAddr(const NETADDR *pAddr);
+	int UnbanByRange(const CNetRange *pRange);
+	int UnbanByIndex(int Index);
+	void UnbanAll();
+	bool IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const;
+};
+
+#endif

+ 144 - 0
server/src/network.cpp

@@ -0,0 +1,144 @@
+#include <system.h>
+#include "netban.h"
+#include "network.h"
+
+bool CNetwork::Open(NETADDR BindAddr, CNetBan *pNetBan)
+{
+	// zero out the whole structure
+	mem_zero(this, sizeof(*this));
+	m_Socket.type = NETTYPE_INVALID;
+	m_Socket.ipv4sock = -1;
+	m_Socket.ipv6sock = -1;
+	m_pNetBan = pNetBan;
+
+	// open socket
+	m_Socket = net_tcp_create(BindAddr);
+	if(!m_Socket.type)
+		return false;
+	if(net_tcp_listen(m_Socket, NET_MAX_CLIENTS))
+		return false;
+	net_set_non_blocking(m_Socket);
+
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+		m_aSlots[i].m_Connection.Reset();
+
+	return true;
+}
+
+void CNetwork::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
+{
+	m_pfnNewClient = pfnNewClient;
+	m_pfnDelClient = pfnDelClient;
+	m_UserPtr = pUser;
+}
+
+int CNetwork::Close()
+{
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+		m_aSlots[i].m_Connection.Disconnect("Closing connection.");
+
+	net_tcp_close(m_Socket);
+
+	return 0;
+}
+
+int CNetwork::Drop(int ClientID, const char *pReason)
+{
+	if(m_pfnDelClient)
+		m_pfnDelClient(ClientID, pReason, m_UserPtr);
+
+	m_aSlots[ClientID].m_Connection.Disconnect(pReason);
+
+	return 0;
+}
+
+int CNetwork::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr)
+{
+	char aError[256] = { 0 };
+	int FreeSlot = -1;
+
+	// look for free slot or multiple client
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+	{
+		if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
+			FreeSlot = i;
+		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
+		{
+			if(net_addr_comp(pAddr, m_aSlots[i].m_Connection.PeerAddress()) == 0)
+			{
+				str_copy(aError, "Only one client per IP allowed.", sizeof(aError));
+				break;
+			}
+		}
+	}
+
+	// accept client
+	if(!aError[0] && FreeSlot != -1)
+	{
+		m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr);
+		if(m_pfnNewClient)
+			m_pfnNewClient(FreeSlot, m_UserPtr);
+		return 0;
+	}
+
+	// reject client
+	if(!aError[0])
+		str_copy(aError, "No free slot available.", sizeof(aError));
+
+	net_tcp_send(Socket, aError, str_length(aError));
+	net_tcp_close(Socket);
+
+	return -1;
+}
+
+int CNetwork::Update()
+{
+	NETSOCKET Socket;
+	NETADDR Addr;
+
+	if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0)
+	{
+		// check if we should just drop the packet
+		char aBuf[128];
+		if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
+		{
+			// banned, reply with a message and drop
+			net_tcp_send(Socket, aBuf, str_length(aBuf));
+			net_tcp_close(Socket);
+		}
+		else
+			AcceptClient(Socket, &Addr);
+	}
+
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE)
+			m_aSlots[i].m_Connection.Update();
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
+			Drop(i, m_aSlots[i].m_Connection.ErrorString());
+	}
+
+	return 0;
+}
+
+int CNetwork::Recv(char *pLine, int MaxLength, int *pClientID)
+{
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength))
+		{
+			if(pClientID)
+				*pClientID = i;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int CNetwork::Send(int ClientID, const char *pLine)
+{
+	if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE)
+		return m_aSlots[ClientID].m_Connection.Send(pLine);
+	else
+		return -1;
+}

+ 87 - 0
server/src/network.h

@@ -0,0 +1,87 @@
+#ifndef NETWORK_H
+#define NETWORK_H
+
+enum
+{
+	NET_CONNSTATE_OFFLINE=0,
+	NET_CONNSTATE_CONNECT=1,
+	NET_CONNSTATE_PENDING=2,
+	NET_CONNSTATE_ONLINE=3,
+	NET_CONNSTATE_ERROR=4,
+
+	NET_MAX_PACKETSIZE = 1400,
+	NET_MAX_CLIENTS = 128
+};
+
+typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser);
+typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser);
+
+class CNetworkClient
+{
+private:
+	int m_State;
+
+	NETADDR m_PeerAddr;
+	NETSOCKET m_Socket;
+
+	char m_aBuffer[NET_MAX_PACKETSIZE];
+	int m_BufferOffset;
+
+	char m_aErrorString[256];
+
+	bool m_LineEndingDetected;
+	char m_aLineEnding[3];
+
+public:
+	void Init(NETSOCKET Socket, const NETADDR *pAddr);
+	void Disconnect(const char *pReason);
+
+	int State() const { return m_State; }
+	const NETADDR *PeerAddress() const { return &m_PeerAddr; }
+	const char *ErrorString() const { return m_aErrorString; }
+
+	void Reset();
+	int Update();
+	int Send(const char *pLine);
+	int Recv(char *pLine, int MaxLength);
+};
+
+class CNetwork
+{
+private:
+	struct CSlot
+	{
+		CNetworkClient m_Connection;
+	};
+
+	NETSOCKET m_Socket;
+	class CNetBan *m_pNetBan;
+	CSlot m_aSlots[NET_MAX_CLIENTS];
+
+	NETFUNC_NEWCLIENT m_pfnNewClient;
+	NETFUNC_DELCLIENT m_pfnDelClient;
+	void *m_UserPtr;
+
+public:
+	void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
+
+	//
+	bool Open(NETADDR BindAddr, CNetBan *pNetBan);
+	int Close();
+
+	//
+	int Recv(char *pLine, int MaxLength, int *pClientID = 0);
+	int Send(int ClientID, const char *pLine);
+	int Update();
+
+	//
+	int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
+	int Drop(int ClientID, const char *pReason);
+
+	// status requests
+	const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+	const NETSOCKET *Socket() const { return &m_Socket; }
+	class CNetBan *NetBan() const { return m_pNetBan; }
+};
+
+#endif

+ 184 - 0
server/src/network_client.cpp

@@ -0,0 +1,184 @@
+#include <system.h>
+#include "network.h"
+
+void CNetworkClient::Reset()
+{
+	m_State = NET_CONNSTATE_OFFLINE;
+	mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
+	m_aErrorString[0] = 0;
+
+	m_Socket.type = NETTYPE_INVALID;
+	m_Socket.ipv4sock = -1;
+	m_Socket.ipv6sock = -1;
+	m_aBuffer[0] = 0;
+	m_BufferOffset = 0;
+
+	m_LineEndingDetected = false;
+	#if defined(CONF_FAMILY_WINDOWS)
+		m_aLineEnding[0] = '\r';
+		m_aLineEnding[1] = '\n';
+		m_aLineEnding[2] = 0;
+	#else
+		m_aLineEnding[0] = '\n';
+		m_aLineEnding[1] = 0;
+		m_aLineEnding[2] = 0;
+	#endif
+}
+
+void CNetworkClient::Init(NETSOCKET Socket, const NETADDR *pAddr)
+{
+	Reset();
+
+	m_Socket = Socket;
+	net_set_non_blocking(m_Socket);
+
+	m_PeerAddr = *pAddr;
+	m_State = NET_CONNSTATE_ONLINE;
+}
+
+void CNetworkClient::Disconnect(const char *pReason)
+{
+	if(State() == NET_CONNSTATE_OFFLINE)
+		return;
+
+	if(pReason && pReason[0])
+		Send(pReason);
+
+	net_tcp_close(m_Socket);
+
+	Reset();
+}
+
+int CNetworkClient::Update()
+{
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		if((int)(sizeof(m_aBuffer)) <= m_BufferOffset)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString));
+			return -1;
+		}
+
+		int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset);
+
+		if(Bytes > 0)
+		{
+			m_BufferOffset += Bytes;
+		}
+		else if(Bytes < 0)
+		{
+			if(net_would_block()) // no data received
+				return 0;
+
+			m_State = NET_CONNSTATE_ERROR; // error
+			str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString));
+			return -1;
+		}
+		else
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString));
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+int CNetworkClient::Recv(char *pLine, int MaxLength)
+{
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		if(m_BufferOffset)
+		{
+			// find message start
+			int StartOffset = 0;
+			while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n')
+			{
+				// detect clients line ending format
+				if(!m_LineEndingDetected)
+				{
+					m_aLineEnding[0] = m_aBuffer[StartOffset];
+					if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') &&
+						m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1])
+						m_aLineEnding[1] = m_aBuffer[StartOffset+1];
+					m_LineEndingDetected = true;
+				}
+
+				if(++StartOffset >= m_BufferOffset)
+				{
+					m_BufferOffset = 0;
+					return 0;
+				}
+			}
+
+			// find message end
+			int EndOffset = StartOffset;
+			while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n')
+			{
+				if(++EndOffset >= m_BufferOffset)
+				{
+					if(StartOffset > 0)
+					{
+						mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
+						m_BufferOffset -= StartOffset;
+					}
+					return 0;
+				}
+			}
+
+			// extract message and update buffer
+			if(MaxLength-1 < EndOffset-StartOffset)
+			{
+				if(StartOffset > 0)
+				{
+					mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
+					m_BufferOffset -= StartOffset;
+				}
+				return 0;
+			}
+			mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset);
+			pLine[EndOffset-StartOffset] = 0;
+			str_sanitize_cc(pLine);
+			mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset);
+			m_BufferOffset -= EndOffset;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int CNetworkClient::Send(const char *pLine)
+{
+	if(State() != NET_CONNSTATE_ONLINE)
+		return -1;
+
+	char aBuf[1024];
+	str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2);
+	int Length = str_length(aBuf);
+	aBuf[Length] = m_aLineEnding[0];
+	aBuf[Length+1] = m_aLineEnding[1];
+	aBuf[Length+2] = m_aLineEnding[2];
+	Length += 3;
+	const char *pData = aBuf;
+
+	while(1)
+	{
+		int Send = net_tcp_send(m_Socket, pData, Length);
+		if(Send < 0)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString));
+			return -1;
+		}
+
+		if(Send >= Length)
+			break;
+
+		pData += Send;
+		Length -= Send;
+	}
+
+	return 0;
+}

+ 204 - 0
server/src/server.cpp

@@ -0,0 +1,204 @@
+#include <system.h>
+#include "netban.h"
+#include "network.h"
+#include "main.h"
+#include "server.h"
+
+int CServer::NewClientCallback(int ClientID, void *pUser)
+{
+	CServer *pThis = (CServer *)pUser;
+
+	char aAddrStr[NETADDR_MAXSTRSIZE];
+	net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
+	if(pThis->Main()->Config()->m_Verbose)
+		dbg_msg("server", "Connection accepted. ncid=%d addr=%s'", ClientID, aAddrStr);
+
+	pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED;
+	pThis->m_aClients[ClientID].m_TimeConnected = time_get();
+	pThis->m_Network.Send(ClientID, "Authentication required:");
+
+	return 0;
+}
+
+int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser)
+{
+	CServer *pThis = (CServer *)pUser;
+
+	char aAddrStr[NETADDR_MAXSTRSIZE];
+	net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
+	if(pThis->Main()->Config()->m_Verbose)
+		dbg_msg("server", "Client dropped. ncid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason);
+
+	if(pThis->m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
+		pThis->Main()->OnDelClient(ClientID);
+	pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY;
+
+	return 0;
+}
+
+int CServer::Init(CMain *pMain, const char *Bind, int Port)
+{
+	m_pMain = pMain;
+	m_NetBan.Init();
+
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+		m_aClients[i].m_State = CClient::STATE_EMPTY;
+
+	m_Ready = false;
+
+	if(Port == 0)
+	{
+		dbg_msg("server", "Will not bind to port 0.");
+		return 1;
+	}
+
+	NETADDR BindAddr;
+	if(Bind[0] && net_host_lookup(Bind, &BindAddr, NETTYPE_ALL) == 0)
+	{
+		// got bindaddr
+		BindAddr.type = NETTYPE_ALL;
+		BindAddr.port = Port;
+	}
+	else
+	{
+		mem_zero(&BindAddr, sizeof(BindAddr));
+		BindAddr.type = NETTYPE_ALL;
+		BindAddr.port = Port;
+	}
+
+	if(m_Network.Open(BindAddr, &m_NetBan))
+	{
+		m_Network.SetCallbacks(NewClientCallback, DelClientCallback, this);
+		m_Ready = true;
+		dbg_msg("server", "Bound to %s:%d", Bind, Port);
+		return 0;
+	}
+	else
+		dbg_msg("server", "Couldn't open socket. Port (%d) might already be in use.", Port);
+
+	return 1;
+}
+
+void CServer::Update()
+{
+	if(!m_Ready)
+		return;
+
+	m_NetBan.Update();
+	m_Network.Update();
+
+	char aBuf[NET_MAX_PACKETSIZE];
+	int ClientID;
+
+	while(m_Network.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID))
+	{
+		dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "Got message from empty slot.");
+		if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED)
+		{
+			int ID = -1;
+			char aUsername[128] = {0};
+			char aPassword[128] = {0};
+			const char *pTmp;
+
+			if(!(pTmp = str_find(aBuf, ":"))
+				|| (unsigned)(pTmp - aBuf) > sizeof(aUsername) || (unsigned)(str_length(pTmp) - 1) > sizeof(aPassword))
+			{
+				m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away.");
+				m_Network.Drop(ClientID, "Fuck off.");
+				return;
+			}
+
+			str_copy(aUsername, aBuf, pTmp - aBuf + 1);
+			str_copy(aPassword, pTmp + 1, sizeof(aPassword));
+			if(!*aUsername || !*aPassword)
+			{
+				m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away.");
+				m_Network.Drop(ClientID, "Username and password must not be blank.");
+				return;
+			}
+
+			for(int i = 0; i < NET_MAX_CLIENTS; i++)
+			{
+				if(!Main()->Client(i)->m_Active)
+					continue;
+
+				if(str_comp(Main()->Client(i)->m_aUsername, aUsername) == 0 && str_comp(Main()->Client(i)->m_aPassword, aPassword) == 0)
+					ID = i;
+			}
+
+			if(ID == -1)
+			{
+				m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "Wrong username and/or password.");
+				m_Network.Drop(ClientID, "Wrong username and/or password.");
+			}
+			else if(Main()->Client(ID)->m_ClientNetID != -1)
+			{
+				m_Network.Drop(ClientID, "Only one connection per user allowed.");
+			}
+			else
+			{
+				m_aClients[ClientID].m_State = CClient::STATE_AUTHED;
+				m_aClients[ClientID].m_LastReceived = time_get();
+				m_Network.Send(ClientID, "Authentication successful. Access granted.");
+
+				if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV4)
+					m_Network.Send(ClientID, "You are connecting via: IPv4");
+				else if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV6)
+					m_Network.Send(ClientID, "You are connecting via: IPv6");
+
+				if(Main()->Config()->m_Verbose)
+					dbg_msg("server", "ncid=%d authed", ClientID);
+				Main()->OnNewClient(ClientID, ID);
+			}
+		}
+		else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
+		{
+			m_aClients[ClientID].m_LastReceived = time_get();
+			if(Main()->Config()->m_Verbose)
+				dbg_msg("server", "ncid=%d cmd='%s'", ClientID, aBuf);
+
+			if(str_comp(aBuf, "logout") == 0)
+				m_Network.Drop(ClientID, "Logout. Bye Bye ~");
+			else
+				Main()->HandleMessage(ClientID, aBuf);
+		}
+	}
+
+	for(int i = 0; i < NET_MAX_CLIENTS; ++i)
+	{
+		if(m_aClients[i].m_State == CClient::STATE_CONNECTED &&
+			time_get() > m_aClients[i].m_TimeConnected + 5 * time_freq())
+		{
+			m_Network.NetBan()->BanAddr(m_Network.ClientAddr(i), 30, "Authentication timeout.");
+			m_Network.Drop(i, "Authentication timeout.");
+		}
+		else if(m_aClients[i].m_State == CClient::STATE_AUTHED &&
+			time_get() > m_aClients[i].m_LastReceived + 15 * time_freq())
+			m_Network.Drop(i, "Timeout.");
+	}
+}
+
+void CServer::Send(int ClientID, const char *pLine)
+{
+	if(!m_Ready)
+		return;
+
+	if(ClientID == -1)
+	{
+		for(int i = 0; i < NET_MAX_CLIENTS; i++)
+		{
+			if(m_aClients[i].m_State == CClient::STATE_AUTHED)
+				m_Network.Send(i, pLine);
+		}
+	}
+	else if(ClientID >= 0 && ClientID < NET_MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
+		m_Network.Send(ClientID, pLine);
+}
+
+void CServer::Shutdown()
+{
+	if(!m_Ready)
+		return;
+
+	m_Network.Close();
+}

+ 46 - 0
server/src/server.h

@@ -0,0 +1,46 @@
+#ifndef SERVER_H
+#define SERVER_H
+
+#include "netban.h"
+#include "network.h"
+
+class CServer
+{
+	class CClient
+	{
+	public:
+		enum
+		{
+			STATE_EMPTY=0,
+			STATE_CONNECTED,
+			STATE_AUTHED,
+		};
+
+		int m_State;
+		int64 m_TimeConnected;
+		int64 m_LastReceived;
+	};
+	CClient m_aClients[NET_MAX_CLIENTS];
+
+	CNetwork m_Network;
+	CNetBan m_NetBan;
+
+	class CMain *m_pMain;
+
+	bool m_Ready;
+
+	static int NewClientCallback(int ClientID, void *pUser);
+	static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
+
+public:
+	int Init(CMain *pMain, const char *Bind, int Port);
+	void Update();
+	void Send(int ClientID, const char *pLine);
+	void Shutdown();
+
+	CNetwork *Network() { return &m_Network; }
+	CNetBan *NetBan() { return &m_NetBan; }
+	CMain *Main() { return m_pMain; }
+};
+
+#endif

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2001 - 0
server/src/system.c


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6 - 0
web/css/bootstrap-theme.min.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6 - 0
web/css/bootstrap.min.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 54 - 0
web/css/dark.css


+ 51 - 0
web/css/light.css

@@ -0,0 +1,51 @@
+body { background: #ebebeb url('../img/light.png'); }
+.navbar { min-height: 40px; }
+.navbar-brand { color: #fff; padding: 10px; font-size: 20px; }
+.dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; }
+.navbar-inverse .navbar-brand { color: #fff; padding: 10px; font-size: 20px; }
+.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; }
+.table { background: #ffffff; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; }
+.table th, .table td { text-align: center; }
+.table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #F9F9F9; }
+.table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #FFF; }
+.progress { margin-bottom: 0; }
+.progress-bar { color: #000; }
+.table-hover > tbody > tr:hover > td { background: #E6E6E6; }
+tr.even.expandRow > :hover { background: #F9F9F9 !important; }
+tr.odd.expandRow > :hover { background: #FFF !important; }
+.expandRow > td { padding: 0 !important; border-top: 0px !important; }
+#cpu, #ram, #hdd, #network { min-width: 55px; max-width: 100px; }
+#ping { min-width: 55px; max-width: 130px; }
+
+@media only screen and (max-width: 992px) {
+	#location, tr td:nth-child(5)		{ display:none; visibility:hidden; }
+}
+@media only screen and (max-width: 720px) {
+	#type, tr td:nth-child(4)			{ display:none; visibility:hidden; }
+	#location, tr td:nth-child(5)		{ display:none; visibility:hidden; }
+	#ping, tr td:nth-child(13)          { display:none; visibility:hidden; }
+}
+@media only screen and (max-width: 600px) {
+	#type, tr td:nth-child(4)			{ display:none; visibility:hidden; }
+	#location, tr td:nth-child(5)		{ display:none; visibility:hidden; }
+	#uptime, tr td:nth-child(6)			{ display:none; visibility:hidden; }
+	#ping, tr td:nth-child(13)          { display:none; visibility:hidden; }
+}
+@media only screen and (max-width: 533px) {
+	#type, tr td:nth-child(4)			{ display:none; visibility:hidden; }
+	#location, tr td:nth-child(5)		{ display:none; visibility:hidden; }
+	#uptime, tr td:nth-child(6)			{ display:none; visibility:hidden; }
+	#network, tr td:nth-child(8)		{ display:none; visibility:hidden; }
+	#ping, tr td:nth-child(13)          { display:none; visibility:hidden; }
+}
+@media only screen and (max-width: 450px) {
+	body								{ font-size: 10px; }
+	.content 							{ padding: 0; }
+	#name, tr td:nth-child(3)			{ min-width: 55px; max-width: 85px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
+	#type, tr td:nth-child(4)			{ display:none; visibility:hidden; }
+	#location, tr td:nth-child(5)		{ display:none; visibility:hidden; }
+	#uptime, tr td:nth-child(6)			{ display:none; visibility:hidden; }
+	#network, tr td:nth-child(8)		{ display:none; visibility:hidden; }
+	#cpu, #ram, #hdd { min-width: 25px; max-width: 50px; }
+	#ping, tr td:nth-child(13)          { display:none; visibility:hidden; }
+}

BIN
web/favicon.ico


BIN
web/img/dark.png


BIN
web/img/light.png


+ 111 - 0
web/index.html

@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<!--
+        json字段保持完整, 后期更新会向下兼容
+        可以自定义前端展示
+        ლ(•̀ _ •́ ლ)
+        ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)
+        ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)
+        ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)
+        ლ(•̀ _ •́ ლ)
+        by:https://www.cpp.la
+-->
+<html>
+	<head>
+		<title>云监控</title>
+		<meta charset="utf-8">
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
+		<meta name="description" content="云监控">
+		<meta name="author" content="BotoX">
+		<link rel="stylesheet" href="css/bootstrap.min.css">
+		<link rel="stylesheet" href="css/bootstrap-theme.min.css">
+		<link rel="stylesheet" href="css/dark.css" title="dark">
+		<link rel="stylesheet" href="css/light.css" title="light">
+		<style>
+			body {
+				padding-top: 70px;
+				padding-bottom: 30px;
+			}
+		</style>
+		<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+		<!--[if lt IE 9]>
+			<script src="js/html5shiv.js"></script>
+			<script src="js/respond.min.js"></script>
+		<![endif]-->
+	</head>
+	<body>
+		<div role="navigation" class="navbar navbar-inverse navbar-fixed-top">
+			<div class="navbar-inner">
+				<div class="container">
+					<div class="navbar-header">
+						<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
+							<span class="sr-only">Toggle navigation</span>
+							<span class="icon-bar"></span>
+							<span class="icon-bar"></span>
+							<span class="icon-bar"></span>
+						</button>
+						<a href="#" class="navbar-brand">云监控</a>
+					</div>
+					<div class="navbar-collapse collapse">
+						<ul class="nav navbar-nav">
+							<li class="dropdown">
+								<a data-toggle="dropdown" class="dropdown-toggle" href="#">风格<b class="caret"></b></a>
+								<ul class="dropdown-menu">
+									<li><a href="#" onclick="setActiveStyleSheet('dark')">黑夜</a></li>
+									<li><a href="#" onclick="setActiveStyleSheet('light')">白天</a></li>
+								</ul>
+							</li>
+						</ul>
+					</div><!--/.nav-collapse -->
+				</div>
+			</div>
+		</div>
+
+		<div class="container content">
+			<div id="loading-notice">
+				<noscript>
+					<div class="alert alert-danger" style="text-align: center;">
+						<strong>Enable JavaScript</strong> , please do it.
+					</div>
+				</noscript>
+				<div style="text-align: center;">
+					警告:如果出现此消息,请确保您已启用Javascript! <br />否则云监控主服务没启动或已关闭.
+				</div>
+				<p></p>
+			</div>
+			<table class="table table-striped table-condensed table-hover">
+				<thead>
+				<tr>
+					<th id="status4" style="text-align: center;">IPv4</th>
+					<th id="ipstatus" style="text-align: center;">Flight</th>
+					<th id="name">节点名</th>
+					<th id="type">虚拟化</th>
+					<th id="location">位置</th>
+					<th id="uptime">在线时间</th>
+					<th id="load">负载</th>
+					<th id="network">网络 ↓|↑</th>
+					<th id="traffic">流量 ↓|↑</th>
+					<th id="cpu">处理器</th>
+					<th id="ram">内存</th>
+					<th id="hdd">硬盘</th>
+					<th id="ping">丢包率(CU|CT|CM)</th>
+				</tr>
+				</thead>
+				<tbody id="servers">
+				<!-- Servers here \o/ -->
+				</tbody>
+			</table>
+			<br />
+			<div id="updated">Updating...</div>
+		</div>
+
+		<div class="container">
+			<p style="text-align: center; font-size: 10px;">
+				<a href="https://github.com/cppla/ServerStatus">ServerStatus中文版</a>
+			</p>
+		</div>
+		<script src="js/jquery.min.js"></script>
+		<script src="js/bootstrap.min.js"></script>
+		<script src="js/serverstatus.js"></script>
+	</body>
+</html>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 7 - 0
web/js/bootstrap.min.js


+ 326 - 0
web/js/html5shiv.js

@@ -0,0 +1,326 @@
+/**
+* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
+*/
+;(function(window, document) {
+/*jshint evil:true */
+  /** version */
+  var version = '3.7.3';
+
+  /** Preset options */
+  var options = window.html5 || {};
+
+  /** Used to skip problem elements */
+  var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
+
+  /** Not all elements can be cloned in IE **/
+  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;
+
+  /** Detect whether the browser supports default html5 styles */
+  var supportsHtml5Styles;
+
+  /** Name of the expando, to work with multiple documents or to re-shiv one document */
+  var expando = '_html5shiv';
+
+  /** The id for the the documents expando */
+  var expanID = 0;
+
+  /** Cached data for each document */
+  var expandoData = {};
+
+  /** Detect whether the browser supports unknown elements */
+  var supportsUnknownElements;
+
+  (function() {
+    try {
+        var a = document.createElement('a');
+        a.innerHTML = '<xyz></xyz>';
+        //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
+        supportsHtml5Styles = ('hidden' in a);
+
+        supportsUnknownElements = a.childNodes.length == 1 || (function() {
+          // assign a false positive if unable to shiv
+          (document.createElement)('a');
+          var frag = document.createDocumentFragment();
+          return (
+            typeof frag.cloneNode == 'undefined' ||
+            typeof frag.createDocumentFragment == 'undefined' ||
+            typeof frag.createElement == 'undefined'
+          );
+        }());
+    } catch(e) {
+      // assign a false positive if detection fails => unable to shiv
+      supportsHtml5Styles = true;
+      supportsUnknownElements = true;
+    }
+
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a style sheet with the given CSS text and adds it to the document.
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @param {String} cssText The CSS text.
+   * @returns {StyleSheet} The style element.
+   */
+  function addStyleSheet(ownerDocument, cssText) {
+    var p = ownerDocument.createElement('p'),
+        parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+    p.innerHTML = 'x<style>' + cssText + '</style>';
+    return parent.insertBefore(p.lastChild, parent.firstChild);
+  }
+
+  /**
+   * Returns the value of `html5.elements` as an array.
+   * @private
+   * @returns {Array} An array of shived element node names.
+   */
+  function getElements() {
+    var elements = html5.elements;
+    return typeof elements == 'string' ? elements.split(' ') : elements;
+  }
+
+  /**
+   * Extends the built-in list of html5 elements
+   * @memberOf html5
+   * @param {String|Array} newElements whitespace separated list or array of new element names to shiv
+   * @param {Document} ownerDocument The context document.
+   */
+  function addElements(newElements, ownerDocument) {
+    var elements = html5.elements;
+    if(typeof elements != 'string'){
+      elements = elements.join(' ');
+    }
+    if(typeof newElements != 'string'){
+      newElements = newElements.join(' ');
+    }
+    html5.elements = elements +' '+ newElements;
+    shivDocument(ownerDocument);
+  }
+
+   /**
+   * Returns the data associated to the given document
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @returns {Object} An object of data.
+   */
+  function getExpandoData(ownerDocument) {
+    var data = expandoData[ownerDocument[expando]];
+    if (!data) {
+        data = {};
+        expanID++;
+        ownerDocument[expando] = expanID;
+        expandoData[expanID] = data;
+    }
+    return data;
+  }
+
+  /**
+   * returns a shived element for the given nodeName and document
+   * @memberOf html5
+   * @param {String} nodeName name of the element
+   * @param {Document|DocumentFragment} ownerDocument The context document.
+   * @returns {Object} The shived element.
+   */
+  function createElement(nodeName, ownerDocument, data){
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    if(supportsUnknownElements){
+        return ownerDocument.createElement(nodeName);
+    }
+    if (!data) {
+        data = getExpandoData(ownerDocument);
+    }
+    var node;
+
+    if (data.cache[nodeName]) {
+        node = data.cache[nodeName].cloneNode();
+    } else if (saveClones.test(nodeName)) {
+        node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
+    } else {
+        node = data.createElem(nodeName);
+    }
+
+    // Avoid adding some elements to fragments in IE < 9 because
+    // * Attributes like `name` or `type` cannot be set/changed once an element
+    //   is inserted into a document/fragment
+    // * Link elements with `src` attributes that are inaccessible, as with
+    //   a 403 response, will cause the tab/window to crash
+    // * Script elements appended to fragments will execute when their `src`
+    //   or `text` property is set
+    return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
+  }
+
+  /**
+   * returns a shived DocumentFragment for the given document
+   * @memberOf html5
+   * @param {Document} ownerDocument The context document.
+   * @returns {Object} The shived DocumentFragment.
+   */
+  function createDocumentFragment(ownerDocument, data){
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    if(supportsUnknownElements){
+        return ownerDocument.createDocumentFragment();
+    }
+    data = data || getExpandoData(ownerDocument);
+    var clone = data.frag.cloneNode(),
+        i = 0,
+        elems = getElements(),
+        l = elems.length;
+    for(;i<l;i++){
+        clone.createElement(elems[i]);
+    }
+    return clone;
+  }
+
+  /**
+   * Shivs the `createElement` and `createDocumentFragment` methods of the document.
+   * @private
+   * @param {Document|DocumentFragment} ownerDocument The document.
+   * @param {Object} data of the document.
+   */
+  function shivMethods(ownerDocument, data) {
+    if (!data.cache) {
+        data.cache = {};
+        data.createElem = ownerDocument.createElement;
+        data.createFrag = ownerDocument.createDocumentFragment;
+        data.frag = data.createFrag();
+    }
+
+
+    ownerDocument.createElement = function(nodeName) {
+      //abort shiv
+      if (!html5.shivMethods) {
+          return data.createElem(nodeName);
+      }
+      return createElement(nodeName, ownerDocument, data);
+    };
+
+    ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
+      'var n=f.cloneNode(),c=n.createElement;' +
+      'h.shivMethods&&(' +
+        // unroll the `createElement` calls
+        getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
+          data.createElem(nodeName);
+          data.frag.createElement(nodeName);
+          return 'c("' + nodeName + '")';
+        }) +
+      ');return n}'
+    )(html5, data.frag);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Shivs the given document.
+   * @memberOf html5
+   * @param {Document} ownerDocument The document to shiv.
+   * @returns {Document} The shived document.
+   */
+  function shivDocument(ownerDocument) {
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    var data = getExpandoData(ownerDocument);
+
+    if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
+      data.hasCSS = !!addStyleSheet(ownerDocument,
+        // corrects block display not defined in IE6/7/8/9
+        'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
+        // adds styling not present in IE6/7/8/9
+        'mark{background:#FF0;color:#000}' +
+        // hides non-rendered elements
+        'template{display:none}'
+      );
+    }
+    if (!supportsUnknownElements) {
+      shivMethods(ownerDocument, data);
+    }
+    return ownerDocument;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The `html5` object is exposed so that more elements can be shived and
+   * existing shiving can be detected on iframes.
+   * @type Object
+   * @example
+   *
+   * // options can be changed before the script is included
+   * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
+   */
+  var html5 = {
+
+    /**
+     * An array or space separated string of node names of the elements to shiv.
+     * @memberOf html5
+     * @type Array|String
+     */
+    'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
+
+    /**
+     * current version of html5shiv
+     */
+    'version': version,
+
+    /**
+     * A flag to indicate that the HTML5 style sheet should be inserted.
+     * @memberOf html5
+     * @type Boolean
+     */
+    'shivCSS': (options.shivCSS !== false),
+
+    /**
+     * Is equal to true if a browser supports creating unknown/HTML5 elements
+     * @memberOf html5
+     * @type boolean
+     */
+    'supportsUnknownElements': supportsUnknownElements,
+
+    /**
+     * A flag to indicate that the document's `createElement` and `createDocumentFragment`
+     * methods should be overwritten.
+     * @memberOf html5
+     * @type Boolean
+     */
+    'shivMethods': (options.shivMethods !== false),
+
+    /**
+     * A string to describe the type of `html5` object ("default" or "default print").
+     * @memberOf html5
+     * @type String
+     */
+    'type': 'default',
+
+    // shivs the document according to the specified `html5` object options
+    'shivDocument': shivDocument,
+
+    //creates a shived element
+    createElement: createElement,
+
+    //creates a shived documentFragment
+    createDocumentFragment: createDocumentFragment,
+
+    //extends list of elements
+    addElements: addElements
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose html5
+  window.html5 = html5;
+
+  // shiv the document
+  shivDocument(document);
+
+  if(typeof module == 'object' && module.exports){
+    module.exports = html5;
+  }
+
+}(typeof window !== "undefined" ? window : this, document));

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2 - 0
web/js/jquery.min.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 5 - 0
web/js/respond.min.js


+ 423 - 0
web/js/serverstatus.js

@@ -0,0 +1,423 @@
+// serverstatus.js
+var error = 0;
+var d = 0;
+var server_status = new Array();
+
+function timeSince(date) {
+	if(date == 0)
+		return "从未.";
+
+	var seconds = Math.floor((new Date() - date) / 1000);
+	var interval = Math.floor(seconds / 31536000);
+
+	if (interval > 1)
+		return interval + " 年前.";
+	interval = Math.floor(seconds / 2592000);
+	if (interval > 1)
+		return interval + " 月前.";
+	interval = Math.floor(seconds / 86400);
+	if (interval > 1)
+		return interval + " 日前.";
+	interval = Math.floor(seconds / 3600);
+	if (interval > 1)
+		return interval + " 小时前.";
+	interval = Math.floor(seconds / 60);
+	if (interval > 1)
+		return interval + " 分钟前.";
+	/*if(Math.floor(seconds) >= 5)
+		return Math.floor(seconds) + " seconds";*/
+	else
+		return "几秒前.";
+}
+
+function bytesToSize(bytes, precision, si)
+{
+	var ret;
+	si = typeof si !== 'undefined' ? si : 0;
+	if(si != 0) {
+		var kilobyte = 1000;
+		var megabyte = kilobyte * 1000;
+		var gigabyte = megabyte * 1000;
+		var terabyte = gigabyte * 1000;
+	} else {
+		var kilobyte = 1024;
+		var megabyte = kilobyte * 1024;
+		var gigabyte = megabyte * 1024;
+		var terabyte = gigabyte * 1024;
+	}
+
+	if ((bytes >= 0) && (bytes < kilobyte)) {
+		return bytes + ' B';
+
+	} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
+		ret = (bytes / kilobyte).toFixed(precision) + ' K';
+
+	} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
+		ret = (bytes / megabyte).toFixed(precision) + ' M';
+
+	} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
+		ret = (bytes / gigabyte).toFixed(precision) + ' G';
+
+	} else if (bytes >= terabyte) {
+		ret = (bytes / terabyte).toFixed(precision) + ' T';
+
+	} else {
+		return bytes + ' B';
+	}
+	if(si != 0) {
+		return ret + 'B';
+	} else {
+		return ret + 'iB';
+	}
+}
+
+function uptime() {
+	$.getJSON("json/stats.json", function(result) {
+		$("#loading-notice").remove();
+		if(result.reload)
+			setTimeout(function() { location.reload(true) }, 1000);
+
+		for (var i = 0, rlen=result.servers.length; i < rlen; i++) {
+			var TableRow = $("#servers tr#r" + i);
+			var ExpandRow = $("#servers #rt" + i);
+			var hack; // fuck CSS for making me do this
+			if(i%2) hack="odd"; else hack="even";
+			if (!TableRow.length) {
+				$("#servers").append(
+					"<tr id=\"r" + i + "\" data-toggle=\"collapse\" data-target=\"#rt" + i + "\" class=\"accordion-toggle " + hack + "\">" +
+						"<td id=\"online4\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
+						"<td id=\"ip_status\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
+						"<td id=\"name\">加载中</td>" +
+						"<td id=\"type\">加载中</td>" +
+						"<td id=\"location\">加载中</td>" +
+						"<td id=\"uptime\">加载中</td>" +
+						"<td id=\"load\">加载中</td>" +
+						"<td id=\"network\">加载中</td>" +
+						"<td id=\"traffic\">加载中</td>" +
+						"<td id=\"cpu\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
+						"<td id=\"memory\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
+						"<td id=\"hdd\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
+						"<td id=\"ping\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
+					"</tr>" +
+					"<tr class=\"expandRow " + hack + "\"><td colspan=\"16\"><div class=\"accordian-body collapse\" id=\"rt" + i + "\">" +
+						"<div id=\"expand_mem\">加载中</div>" +
+						"<div id=\"expand_swap\">加载中</div>" +
+						"<div id=\"expand_hdd\">加载中</div>" +
+						"<div id=\"expand_tupd\">加载中</div>" +
+						"<div id=\"expand_custom\">加载中</div>" +
+					"</div></td></tr>"
+				);
+				TableRow = $("#servers tr#r" + i);
+				ExpandRow = $("#servers #rt" + i);
+				server_status[i] = true;
+			}
+			TableRow = TableRow[0];
+			if(error) {
+				TableRow.setAttribute("data-target", "#rt" + i);
+				server_status[i] = true;
+			}
+
+			// Online4
+			if (result.servers[i].online4) {
+				TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-success";
+				TableRow.children["online4"].children[0].children[0].innerHTML = "<small>开启</small>";
+			} else {
+				TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-danger";
+				TableRow.children["online4"].children[0].children[0].innerHTML = "<small>关闭</small>";
+			}
+
+			// Online6
+			//if (result.servers[i].online6) {
+			//	TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-success";
+			//	TableRow.children["online6"].children[0].children[0].innerHTML = "<small>开启</small>";
+			//} else {
+			//	TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-danger";
+			//	TableRow.children["online6"].children[0].children[0].innerHTML = "<small>关闭</small>";
+			//}
+
+			// Ipstatus
+			if (result.servers[i].ip_status) {
+				TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-success";
+				TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>MH361</small>";
+			} else {
+				TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-danger";
+				TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>MH370</small>";
+			}
+
+			// Name
+			TableRow.children["name"].innerHTML = result.servers[i].name;
+
+			// Type
+			TableRow.children["type"].innerHTML = result.servers[i].type;
+
+			// Location
+			TableRow.children["location"].innerHTML = result.servers[i].location;
+			if (!result.servers[i].online4 && !result.servers[i].online6) {
+				if (server_status[i]) {
+					TableRow.children["uptime"].innerHTML = "–";
+					TableRow.children["load"].innerHTML = "–";
+					TableRow.children["network"].innerHTML = "–";
+					TableRow.children["traffic"].innerHTML = "–";
+					TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger";
+					TableRow.children["cpu"].children[0].children[0].style.width = "100%";
+					TableRow.children["cpu"].children[0].children[0].innerHTML = "<small>关闭</small>";
+					TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger";
+					TableRow.children["memory"].children[0].children[0].style.width = "100%";
+					TableRow.children["memory"].children[0].children[0].innerHTML = "<small>关闭</small>";
+					TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger";
+					TableRow.children["hdd"].children[0].children[0].style.width = "100%";
+					TableRow.children["hdd"].children[0].children[0].innerHTML = "<small>关闭</small>";
+					TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-danger";
+					TableRow.children["ping"].children[0].children[0].style.width = "100%";
+					TableRow.children["ping"].children[0].children[0].innerHTML = "<small>关闭</small>";
+					if(ExpandRow.hasClass("in")) {
+						ExpandRow.collapse("hide");
+					}
+					TableRow.setAttribute("data-target", "");
+					server_status[i] = false;
+				}
+			} else {
+				if (!server_status[i]) {
+					TableRow.setAttribute("data-target", "#rt" + i);
+					server_status[i] = true;
+				}
+
+				// Uptime
+				TableRow.children["uptime"].innerHTML = result.servers[i].uptime;
+
+				// Load: default load_1, you can change show: load_1, load_5, load_15
+				if(result.servers[i].load == -1) {
+				    TableRow.children["load"].innerHTML = "–";
+				} else {
+				    TableRow.children["load"].innerHTML = result.servers[i].load_1.toFixed(2);
+				}
+
+				// Network
+				var netstr = "";
+				if(result.servers[i].network_rx < 1024)
+					netstr += result.servers[i].network_rx.toFixed(0) + "B";
+				else if(result.servers[i].network_rx < 1024*1024)
+					netstr += (result.servers[i].network_rx/1024).toFixed(0) + "K";
+				else
+					netstr += (result.servers[i].network_rx/1024/1024).toFixed(1) + "M";
+				netstr += " | "
+				if(result.servers[i].network_tx < 1024)
+					netstr += result.servers[i].network_tx.toFixed(0) + "B";
+				else if(result.servers[i].network_tx < 1024*1024)
+					netstr += (result.servers[i].network_tx/1024).toFixed(0) + "K";
+				else
+					netstr += (result.servers[i].network_tx/1024/1024).toFixed(1) + "M";
+				TableRow.children["network"].innerHTML = netstr;
+
+				//Traffic
+				var trafficstr = "";
+				if(result.servers[i].network_in < 1024)
+					trafficstr += result.servers[i].network_in.toFixed(0) + "B";
+				else if(result.servers[i].network_in < 1024*1024)
+					trafficstr += (result.servers[i].network_in/1024).toFixed(0) + "K";
+				else if(result.servers[i].network_in < 1024*1024*1024)
+					trafficstr += (result.servers[i].network_in/1024/1024).toFixed(1) + "M";
+				else if(result.servers[i].network_in < 1024*1024*1024*1024)
+					trafficstr += (result.servers[i].network_in/1024/1024/1024).toFixed(2) + "G";
+                else
+                    trafficstr += (result.servers[i].network_in/1024/1024/1024/1024).toFixed(2) + "T";
+				trafficstr += " | "
+				if(result.servers[i].network_out < 1024)
+					trafficstr += result.servers[i].network_out.toFixed(0) + "B";
+				else if(result.servers[i].network_out < 1024*1024)
+					trafficstr += (result.servers[i].network_out/1024).toFixed(0) + "K";
+				else if(result.servers[i].network_out < 1024*1024*1024)
+					trafficstr += (result.servers[i].network_out/1024/1024).toFixed(1) + "M";
+				else if(result.servers[i].network_out < 1024*1024*1024*1024)
+				    trafficstr += (result.servers[i].network_out/1024/1024/1024).toFixed(2) + "G";
+				else
+					trafficstr += (result.servers[i].network_out/1024/1024/1024/1024).toFixed(2) + "T";
+				TableRow.children["traffic"].innerHTML = trafficstr;
+
+				// CPU
+				if (result.servers[i].cpu >= 90)
+					TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger";
+				else if (result.servers[i].cpu >= 80)
+					TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-warning";
+				else
+					TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-success";
+				TableRow.children["cpu"].children[0].children[0].style.width = result.servers[i].cpu + "%";
+				TableRow.children["cpu"].children[0].children[0].innerHTML = result.servers[i].cpu + "%";
+
+				// Memory
+				var Mem = ((result.servers[i].memory_used/result.servers[i].memory_total)*100.0).toFixed(0);
+				if (Mem >= 90)
+					TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger";
+				else if (Mem >= 80)
+					TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-warning";
+				else
+					TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-success";
+				TableRow.children["memory"].children[0].children[0].style.width = Mem + "%";
+				TableRow.children["memory"].children[0].children[0].innerHTML = Mem + "%";
+				ExpandRow[0].children["expand_mem"].innerHTML = "内存: " + bytesToSize(result.servers[i].memory_used*1024, 2) + " / " + bytesToSize(result.servers[i].memory_total*1024, 2);
+				// Swap
+				ExpandRow[0].children["expand_swap"].innerHTML = "交换分区: " + bytesToSize(result.servers[i].swap_used*1024, 2) + " / " + bytesToSize(result.servers[i].swap_total*1024, 2);
+
+				// HDD
+				var HDD = ((result.servers[i].hdd_used/result.servers[i].hdd_total)*100.0).toFixed(0);
+				if (HDD >= 90)
+					TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger";
+				else if (HDD >= 80)
+					TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-warning";
+				else
+					TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-success";
+				TableRow.children["hdd"].children[0].children[0].style.width = HDD + "%";
+				TableRow.children["hdd"].children[0].children[0].innerHTML = HDD + "%";
+				ExpandRow[0].children["expand_hdd"].innerHTML = "硬盘: " + bytesToSize(result.servers[i].hdd_used*1024*1024, 2) + " / " + bytesToSize(result.servers[i].hdd_total*1024*1024, 2);
+
+                // delay time
+
+				// tcp, udp, process, thread count
+				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;
+
+
+                // ping
+                var PING_10010 = result.servers[i].ping_10010.toFixed(0);
+                var PING_189 = result.servers[i].ping_189.toFixed(0);
+                var PING_10086 = result.servers[i].ping_10086.toFixed(0);
+                var pingTime = result.servers[i].time_10010 + "ms💻" + result.servers[i].time_189 + "ms💻" + result.servers[i].time_10086 + "ms"
+                if (PING_10010 >= 10 || PING_189 >= 10 || PING_10086 >= 10)
+                    TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-danger";
+                else
+                    TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-success";
+                    TableRow.children["ping"].children[0].children[0].setAttribute("data-toggle", "tooltip");
+                    TableRow.children["ping"].children[0].children[0].setAttribute("data-placement", "right");
+                    TableRow.children["ping"].children[0].children[0].setAttribute("title", pingTime);
+				TableRow.children["ping"].children[0].children[0].innerHTML = PING_10010 + "%💻" + PING_189 + "%💻" + PING_10086 + "%";
+
+				// Custom
+				if (result.servers[i].custom) {
+					ExpandRow[0].children["expand_custom"].innerHTML = result.servers[i].custom
+				} else {
+					ExpandRow[0].children["expand_custom"].innerHTML = ""
+				}
+			}
+		};
+
+		d = new Date(result.updated*1000);
+		error = 0;
+	}).fail(function(update_error) {
+		if (!error) {
+			$("#servers > tr.accordion-toggle").each(function(i) {
+				var TableRow = $("#servers tr#r" + i)[0];
+				var ExpandRow = $("#servers #rt" + i);
+				TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["online4"].children[0].children[0].innerHTML = "<small>错误</small>";
+				//TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-error";
+				//TableRow.children["online6"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["uptime"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["uptime"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["load"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["load"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["network"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["network"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["traffic"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["traffic"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["cpu"].children[0].children[0].style.width = "100%";
+				TableRow.children["cpu"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["memory"].children[0].children[0].style.width = "100%";
+				TableRow.children["memory"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["hdd"].children[0].children[0].style.width = "100%";
+				TableRow.children["hdd"].children[0].children[0].innerHTML = "<small>错误</small>";
+				TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-error";
+				TableRow.children["ping"].children[0].children[0].style.width = "100%";
+				TableRow.children["ping"].children[0].children[0].innerHTML = "<small>错误</small>";
+				if(ExpandRow.hasClass("in")) {
+					ExpandRow.collapse("hide");
+				}
+				TableRow.setAttribute("data-target", "");
+				server_status[i] = false;
+			});
+		}
+		error = 1;
+		$("#updated").html("更新错误.");
+	});
+}
+
+function updateTime() {
+	if (!error)
+		$("#updated").html("最后更新: " + timeSince(d));
+}
+
+uptime();
+updateTime();
+setInterval(uptime, 2000);
+setInterval(updateTime, 2000);
+
+
+// styleswitcher.js
+function setActiveStyleSheet(title) {
+	var i, a, main;
+	for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
+		if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
+			a.disabled = true;
+			if(a.getAttribute("title") == title) a.disabled = false;
+		}
+	}
+}
+
+function getActiveStyleSheet() {
+	var i, a;
+	for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
+		if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled)
+			return a.getAttribute("title");
+	}
+	return null;
+}
+
+function getPreferredStyleSheet() {
+	var i, a;
+	for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
+		if(a.getAttribute("rel").indexOf("style") != -1	&& a.getAttribute("rel").indexOf("alt") == -1 && a.getAttribute("title"))
+			return a.getAttribute("title");
+	}
+return null;
+}
+
+function createCookie(name,value,days) {
+	if (days) {
+		var date = new Date();
+		date.setTime(date.getTime()+(days*24*60*60*1000));
+		var expires = "; expires="+date.toGMTString();
+	}
+	else expires = "";
+	document.cookie = name+"="+value+expires+"; path=/";
+}
+
+function readCookie(name) {
+	var nameEQ = name + "=";
+	var ca = document.cookie.split(';');
+	for(var i=0;i < ca.length;i++) {
+		var c = ca[i];
+		while (c.charAt(0)==' ')
+			c = c.substring(1,c.length);
+		if (c.indexOf(nameEQ) == 0)
+			return c.substring(nameEQ.length,c.length);
+	}
+	return null;
+}
+
+window.onload = function(e) {
+	var cookie = readCookie("style");
+	var title = cookie ? cookie : getPreferredStyleSheet();
+	setActiveStyleSheet(title);
+}
+
+window.onunload = function(e) {
+	var title = getActiveStyleSheet();
+	createCookie("style", title, 365);
+}
+
+var cookie = readCookie("style");
+var title = cookie ? cookie : getPreferredStyleSheet();
+setActiveStyleSheet(title);

+ 2 - 0
web/json/.gitignore

@@ -0,0 +1,2 @@
+stats.json
+stats.json~

+ 70 - 0
web/ssview.py

@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# coding: utf-8
+# Update by : https://github.com/cppla/ServerStatus
+# 支持Python版本:2.7 to 3.5; requirements.txt: requests, PrettyTable
+# 时间: 20180828
+'''
+maybe better by youself
+'''
+
+import os
+import sys
+import requests
+import time
+from prettytable import PrettyTable
+
+# todo: 程序在非gui环境下目前有闪屏的bug
+scroll = True
+clear = lambda: os.system('clear' if 'linux' in sys.platform else 'cls')
+
+def sscmd(address):
+    while True:
+        r = requests.get(
+            url=address,
+            headers={
+                "User-Agent": "ServerStatus/20181203",
+            }
+        )
+        jsonR = r.json()
+
+        ss = PrettyTable(
+            [
+                "Flight",
+                "节点名",
+                # "虚拟化",
+                "位置",
+                "在线时间",
+                "负载",
+                "网络",
+                "流量",
+                "处理器",
+                "内存",
+                "硬盘"
+            ],
+        )
+        for i in jsonR["servers"]:
+            ss.add_row(
+                [
+                    "%s" % 'MH361' if i["ip_status"] is True else 'MH370',
+                    "%s" % i["name"],
+                    # "%s" % i["type"],
+                    "%s" % i["location"],
+                    "%s" % i["uptime"],
+                    "%s" % (i["load_1"]),
+                    "%.2fM|%.2fM" % (float(i["network_rx"]) / 1000 / 1000, float(i["network_tx"]) / 1000 / 1000),
+                    "%.2fG|%.2fG" % (
+                    float(i["network_in"]) / 1024 / 1024 / 1024, float(i["network_out"]) / 1024 / 1024 / 1024),
+                    "%d%%" % (i["cpu"]),
+                    "%d%%" % (float(i["memory_used"]) / i["memory_total"] * 100),
+                    "%d%%" % (float(i["hdd_used"]) / i["hdd_total"] * 100),
+                ]
+            )
+        if scroll is True:
+            clear()
+        print(ss)
+        time.sleep(1)
+
+if __name__ == '__main__':
+    default = 'https://tz.cloudcpp.com/json/stats.json'
+    ads = sys.argv[1] if len(sys.argv)==2 else default
+    sscmd(ads)