├── client ├── .gitignore ├── config │ ├── ping.go │ ├── ws.go │ ├── struct.go │ └── info.go ├── tool │ ├── Decimal.go │ └── mem.go ├── proc │ ├── ping.go │ └── status.go ├── go.mod ├── go.sum └── main.go ├── .gitignore ├── web ├── public │ ├── robots.txt │ └── index.html ├── src │ ├── config.js │ ├── class │ │ ├── byteToSize.js │ │ ├── timestampToDate.js │ │ └── timeTransform.js │ └── index.js ├── .gitignore └── package.json ├── server ├── composer.json ├── Applications │ ├── config.php │ ├── start_register.php │ ├── Includes │ │ └── Tool.php │ ├── start_businessworker.php │ ├── start_gateway.php │ ├── start_web.php │ └── Events.php ├── start.php └── composer.lock ├── README.md ├── .github └── workflows │ └── go.yml └── LICENSE /client/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | serverStatus -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | vendor 3 | server/Applications/Web/* -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "workerman/gateway-worker": "^3.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/config/ping.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Ping struct { 4 | Type string `json:"type"` 5 | Msg string `json:"msg"` 6 | } 7 | -------------------------------------------------------------------------------- /web/src/config.js: -------------------------------------------------------------------------------- 1 | 2 | const config = { 3 | server: 'ws://127.0.0.1:8282', //服务器信息 ws(s)://ip:port 4 | title: 'serverStatus' 5 | } 6 | 7 | export default config 8 | -------------------------------------------------------------------------------- /server/Applications/config.php: -------------------------------------------------------------------------------- 1 | { 3 | if (bytes === 0) return '0 B'; 4 | var k = 1024, 5 | sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 6 | i = Math.floor(Math.log(bytes) / Math.log(k)); 7 | 8 | return Number((bytes / Math.pow(k, i)).toPrecision(3)).toLocaleString() + ' ' + sizes[i]; 9 | } 10 | export default bytesToSize; -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | serverStatus 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /client/proc/ping.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "encoding/json" 5 | "serverStatus/config" 6 | 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | // 查询服务器是否在线 11 | func Ping(conn *config.WsConn) { 12 | serverBaseInfo, _ := json.Marshal(config.Ping{ 13 | Type: "ping", 14 | Msg: "pong", 15 | }) 16 | 17 | conn.Lock.Lock() //锁 18 | conn.Conn.WriteMessage(websocket.TextMessage, serverBaseInfo) 19 | conn.Lock.Unlock() 20 | } 21 | -------------------------------------------------------------------------------- /client/go.mod: -------------------------------------------------------------------------------- 1 | module serverStatus 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gorilla/websocket v1.4.2 7 | github.com/shirou/gopsutil v3.21.11+incompatible 8 | ) 9 | 10 | require ( 11 | github.com/go-ole/go-ole v1.2.6 // indirect 12 | github.com/stretchr/testify v1.7.0 // indirect 13 | github.com/tklauser/go-sysconf v0.3.9 // indirect 14 | github.com/tklauser/numcpus v0.3.0 // indirect 15 | github.com/yusufpapurcu/wmi v1.2.2 // indirect 16 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /server/Applications/Includes/Tool.php: -------------------------------------------------------------------------------- 1 | { 3 | return m < 10 ? '0' + m : m; 4 | } 5 | 6 | const timestampToDate = (timestamp) => { 7 | var time = new Date(timestamp * 1000); 8 | var y = time.getFullYear(); 9 | var M = time.getMonth() + 1; 10 | var d = time.getDate(); 11 | var h = time.getHours(); 12 | var m = time.getMinutes(); 13 | var s = time.getSeconds(); 14 | return y + '-' + addZero(M) + '-' + addZero(d) + ' ' + addZero(h) + ':' + addZero(m) + ':' + addZero(s); 15 | } 16 | 17 | export default timestampToDate -------------------------------------------------------------------------------- /web/src/class/timeTransform.js: -------------------------------------------------------------------------------- 1 | const timeTransform = (second) => { 2 | var duration 3 | var days = Math.floor(second / 86400); 4 | var hours = Math.floor((second % 86400) / 3600); 5 | var minutes = Math.floor(((second % 86400) % 3600) / 60); 6 | var seconds = Math.floor(((second % 86400) % 3600) % 60); 7 | if (days > 0) duration = days + "天"; 8 | else if (hours > 0) duration = hours + "小时"; 9 | else if (minutes > 0) duration = minutes + "分钟"; 10 | else if (seconds > 0) duration = seconds + "秒"; 11 | return duration; 12 | } 13 | 14 | export default timeTransform -------------------------------------------------------------------------------- /server/Applications/start_businessworker.php: -------------------------------------------------------------------------------- 1 | name = 'Server_Bussiness_Worker'; 17 | // bussinessWorker进程数量 18 | $worker->count = 2; 19 | // 服务注册地址 20 | $worker->registerAddress = '127.0.0.1:1238'; 21 | $worker->reusePort = true; 22 | 23 | // 如果不是在根目录启动,则运行runAll方法 24 | if (!defined('GLOBAL_START')) { 25 | Worker::runAll(); 26 | } 27 | -------------------------------------------------------------------------------- /server/Applications/start_gateway.php: -------------------------------------------------------------------------------- 1 | name = 'Server_GateWay'; 15 | // gateway进程数 16 | $gateway->count = 1; 17 | $gateway->lanIp = '127.0.0.1'; 18 | $gateway->startPort = 2900; 19 | $gateway->registerAddress = '127.0.0.1:1238'; 20 | $gateway->reusePort = true; 21 | 22 | // 如果不是在根目录启动,则运行runAll方法 23 | if (!defined('GLOBAL_START')) { 24 | Worker::runAll(); 25 | } 26 | -------------------------------------------------------------------------------- /server/start.php: -------------------------------------------------------------------------------- 1 | 0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/config/info.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // 获取服务器基本信息 4 | type ServerInfo struct { 5 | Type string `json:"type"` 6 | Data ServerInfoData `json:"data"` 7 | } 8 | 9 | type ServerInfoData struct { 10 | Cpu CpuData 11 | Load LoadData 12 | Memory MemData 13 | Swap SwapData 14 | Host HostData 15 | Time int64 16 | } 17 | 18 | type CpuData struct { 19 | Percent float64 20 | LogicalCores int 21 | PhysicalCores int 22 | ModalName string 23 | } 24 | 25 | type LoadData struct { 26 | M1 float64 27 | M5 float64 28 | M15 float64 29 | } 30 | 31 | type MemData struct { 32 | Percent float64 33 | Total uint64 34 | Free uint64 35 | Used uint64 36 | } 37 | 38 | type SwapData struct { 39 | Percent float64 40 | Total uint64 41 | Free uint64 42 | Used uint64 43 | } 44 | 45 | type HostData struct { 46 | HostName string 47 | UpTime uint64 48 | BootTime uint64 49 | Procs uint64 50 | Os string 51 | Platform string 52 | PlatformFamily string 53 | PlatformVersion string 54 | KernelArch string 55 | KernelVersion string 56 | VirtualizationRole string 57 | VirtualizationSystem string 58 | } 59 | 60 | /* 61 | host {"hostname":"xcsoftsMBP","uptime":687914,"bootTime":1640163711,"procs":543,"os":"darwin","platform":"darwin","platformFamily":"Standalone Workstation","platformVersion":"12.1","kernelVersion":"21.2.0","kernelArch":"arm64","virtualizationSystem":"","virtualizationRole":"","hostid":"587318cb-673c-59d4-a062-31388a36e0ae"} 62 | */ 63 | 64 | type ServerBaseInfo struct { 65 | LogicalCores int 66 | PhysicalCores int 67 | ModalName string 68 | } 69 | -------------------------------------------------------------------------------- /server/Applications/start_web.php: -------------------------------------------------------------------------------- 1 | count = 1; 17 | 18 | define("WEBROOT", __DIR__ . "/Web/"); 19 | 20 | $http_worker->onMessage = function (TcpConnection $connection, Request $request) { 21 | $path = $request->path(); 22 | $request_method = $request->method(); // 请求方式 post,get etc 23 | $userIp = $request->header('x-real-ip'); //获取真实ip 24 | 25 | //Tool::out("{$userIp} > {$request_method} > {$path}", 'WEB'); 26 | 27 | $response = new Response(200); 28 | 29 | if ($path == "/" || $path == "") $path = "index.html"; //默认文档 30 | if (file_exists(WEBROOT . $path)) { 31 | $ext = pathinfo($path, PATHINFO_EXTENSION); 32 | switch ($ext) { 33 | case 'js': 34 | $content_type = 'application/javascript'; 35 | break; 36 | case 'css': 37 | $content_type = 'text/css'; 38 | break; 39 | default: 40 | $content_type = 'text/html'; 41 | break; 42 | } 43 | $response->withHeaders([ 44 | 'Content-Type' => $content_type, 45 | ]); 46 | $response->withBody(Tool::exec_php_file(WEBROOT . $path)); 47 | } else { 48 | $response->withStatus(404); 49 | $response->withBody("

404 Not Found

"); 50 | } 51 | return $connection->send($response); 52 | }; 53 | 54 | if (!defined('GLOBAL_START')) { 55 | Worker::runAll(); 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverStatus 2 | 3 | > 一个 简易的服务器探针 4 | 5 | ## PATH 6 | 7 | ``` 8 | |_ web 网页 React 源码 9 | |_ server 服务端 10 | |_ client 服务器客户端 11 | ``` 12 | 13 | ## 使用方法 14 | 15 | > 需要部署三样, 分别为Client端, Web端, server端. 分别对应了根目录下的三个文件夹 16 | 17 | - Client端为客户端, 用于推送服务器当前状态到server端. Client端不需要公网环境. 仅支持Linux服务器 18 | - Server端分别与Client端和Web端构建Websocket通信, 用于转发信息. Server端需要处于公网环境 19 | - Web端为状态监控面板 20 | 21 | ### Server端 22 | 23 | > Server端使用 Workerman构建 24 | 25 | 将本项目下载到本地, 修改config.php中的token等信息, Server端提供了一个基于Workerman的简易Web服务器, 您可以将Web端编译后, 拷贝至/sever/Web/Applications/Web/ 目录下. 26 | 27 | #### 启动Server端 28 | 29 | 在/Server目录下, 使用`composer install`补全环境依赖 30 | 31 | 通过`php start.php start`启动服务端. Websocket默认端口为8282, 内建Web端默认端口为8283. 32 | 33 | #### 其他Server端常见命令 34 | ```shell 35 | php start.php start -d # 以守护模式运行 36 | php start.php stop # 停止服务端 37 | php start.php status -d # 查看服务器状态 38 | ``` 39 | 40 | ### Web端 41 | 42 | > Web端采用React构建 43 | 44 | 在/web目录下使用`npm install`补全依赖, 修改/web/src/config.js中的服务器信息, 使用`npm run build`编译打包. 45 | 46 | 默认会打包至/web/build文件夹内. 您可以选择使用nginx等环境,或直接拷贝至 /server/Applications/Web/ 目录下 使用内建web服务器 47 | 48 | ### client 端 49 | 50 | > 您可以clone本项目后, 自行前往client文件夹编译运行, 也可以直接在Release中下载已经编译过的二进制文件. 51 | 52 | ```shell 53 | Usage of serverStatus: 54 | -duration int 55 | Data send Duration, Unit:ms (default 5000) 56 | -host string 57 | Server address (including protocol,ip and port), ex: ws://127.0.0.1:8282 58 | -tag string 59 | Server tag (default "xcsoftsMBP") 60 | -token string 61 | Server token 62 | ``` 63 | - 其中host用于指明服务器地址, tag用于表注服务器名称, token为服务端验证token, dutation为服务器信息获取间隔(单位毫秒) 64 | - 命令执行格式: `./serverStatus -host=ws://127.0.0.1:8282 -tag=server1 -token=myToken` 65 | 66 | ## copyright 67 | 68 | - 服务端基于 Gateway-workerman 69 | - 网页使用 React 框架构建 70 | - 前端样式采用 Ant Design 与 Ant Design Charts 71 | - 客户端采用 Golang 开发 -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: serverStatus client build 2 | 3 | on: 4 | push: 5 | branchs: 6 | - main 7 | 8 | jobs: 9 | golang: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: checkout 13 | uses: actions/checkout@master 14 | - name: install go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: "1.16.5" 18 | - name: build 19 | run: | 20 | cd ./client 21 | CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o serverStatus_linux_386 22 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o serverStatus_linux_amd64 23 | CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o serverStatus_linux_arm 24 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o serverStatus_linux_arm64 25 | CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -o serverStatus_freebsd_386 26 | CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -o serverStatus_freebsd_amd64 27 | CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -o serverStatus_freebsd_arm 28 | - if: startsWith(github.ref, 'refs/tags/') 29 | name: Release 30 | uses: softprops/action-gh-release@v1 31 | with: 32 | files: | 33 | ./client/serverStatus_linux_386 34 | ./client/serverStatus_linux_amd64 35 | ./client/serverStatus_linux_arm 36 | ./client/serverStatus_linux_arm64 37 | ./client/serverStatus_freebsd_386 38 | ./client/serverStatus_freebsd_amd64 39 | ./client/serverStatus_freebsd_arm 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | react: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: checkout 47 | uses: actions/checkout@master 48 | - name: install node 49 | uses: actions/setup-node@v2 50 | with: 51 | node-version: '16.13.1' 52 | - name: install env 53 | run: | 54 | cd ./web 55 | npm install 56 | - name: build 57 | run: | 58 | cd ./web 59 | npm run build -------------------------------------------------------------------------------- /client/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 4 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 5 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 6 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= 10 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 12 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 13 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 14 | github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= 15 | github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= 16 | github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= 17 | github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= 18 | github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= 19 | github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 20 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 22 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 23 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 25 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 26 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /client/proc/status.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "serverStatus/config" 7 | "serverStatus/tool" 8 | "time" 9 | 10 | "github.com/gorilla/websocket" 11 | "github.com/shirou/gopsutil/cpu" 12 | "github.com/shirou/gopsutil/host" 13 | "github.com/shirou/gopsutil/load" 14 | "github.com/shirou/gopsutil/mem" 15 | ) 16 | 17 | // @description 获取服务器基本信息 18 | // @param conn *config.WsConn "ws连接信息" 19 | // @param baseInfo *config.ServerBaseInfo "服务器基本信息" 20 | // @param conn int64 "发送间隔" 21 | // @return void 22 | func GetServerInfo(conn *config.WsConn, baseInfo *config.ServerBaseInfo, duration int64) { 23 | Exit: 24 | for { 25 | select { 26 | case <-*conn.Down: 27 | break Exit 28 | case <-time.After(time.Duration(duration) * time.Millisecond): 29 | memInfo, _ := mem.VirtualMemory() //内存信息 30 | cpuPercent, _ := cpu.Percent(time.Second, false) //cpu占用率 31 | loadInfo, _ := load.Avg() //负载信息 32 | swapInfo, _ := mem.SwapMemory() //SWAP 33 | hostInfo, _ := host.Info() //系统信息 34 | 35 | serverBaseInfo, _ := json.Marshal(config.ServerInfo{ 36 | Type: "server_info", 37 | Data: config.ServerInfoData{ 38 | Cpu: config.CpuData{ 39 | Percent: tool.Decimal(cpuPercent[0], 2), 40 | PhysicalCores: baseInfo.PhysicalCores, 41 | LogicalCores: baseInfo.LogicalCores, 42 | ModalName: baseInfo.ModalName, 43 | }, 44 | Memory: config.MemData{ 45 | Percent: tool.Decimal(memInfo.UsedPercent, 2), 46 | Total: memInfo.Total, 47 | Free: memInfo.Free, 48 | Used: memInfo.Used, 49 | }, 50 | Swap: config.SwapData{ 51 | Percent: tool.Decimal(swapInfo.UsedPercent, 2), 52 | Total: swapInfo.Total, 53 | Free: swapInfo.Free, 54 | Used: swapInfo.Used, 55 | }, 56 | Load: config.LoadData{ 57 | M1: tool.Decimal(loadInfo.Load1, 2), 58 | M5: tool.Decimal(loadInfo.Load5, 2), 59 | M15: tool.Decimal(loadInfo.Load15, 2), 60 | }, 61 | Host: config.HostData{ 62 | HostName: hostInfo.Hostname, 63 | UpTime: uint64(time.Now().Unix()) - hostInfo.BootTime, 64 | BootTime: hostInfo.BootTime, 65 | Procs: hostInfo.Procs, 66 | Os: hostInfo.OS, 67 | Platform: hostInfo.Platform, 68 | PlatformFamily: hostInfo.PlatformFamily, 69 | PlatformVersion: hostInfo.PlatformVersion, 70 | KernelArch: hostInfo.KernelArch, 71 | KernelVersion: hostInfo.KernelVersion, 72 | VirtualizationRole: hostInfo.VirtualizationRole, 73 | VirtualizationSystem: hostInfo.VirtualizationSystem, 74 | }, 75 | Time: time.Now().Unix(), 76 | }, 77 | }) 78 | 79 | conn.Lock.Lock() //锁 80 | err := conn.Conn.WriteMessage(websocket.TextMessage, serverBaseInfo) 81 | conn.Lock.Unlock() 82 | 83 | if err != nil { 84 | log.Println("Error during send server base info", err) 85 | } 86 | } 87 | } 88 | } 89 | 90 | // 获取一些基本信息 91 | func GetBaseInfo() *config.ServerBaseInfo { 92 | cpuInfo, cpuErr := cpu.Info() //cpu详细信息 93 | cpuPhysicalCores, _ := cpu.Counts(false) //cpu物理核心 94 | cpuLogicalCores, _ := cpu.Counts(true) //cpu逻辑数量 95 | 96 | var cpuModalName string 97 | if cpuErr == nil { 98 | cpuModalName = cpuInfo[0].ModelName 99 | } else { 100 | cpuModalName = "" 101 | } 102 | return &config.ServerBaseInfo{ 103 | LogicalCores: cpuLogicalCores, 104 | PhysicalCores: cpuPhysicalCores, 105 | ModalName: cpuModalName, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /server/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "920472fd21314487b4894117dca2067a", 8 | "packages": [ 9 | { 10 | "name": "workerman/gateway-worker", 11 | "version": "v3.0.21", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/walkor/GatewayWorker.git", 15 | "reference": "2a27f8537cfce725c17d48a9b5ead21ca4f2e10c" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/2a27f8537cfce725c17d48a9b5ead21ca4f2e10c", 20 | "reference": "2a27f8537cfce725c17d48a9b5ead21ca4f2e10c", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "workerman/workerman": ">=3.5.0" 25 | }, 26 | "type": "library", 27 | "autoload": { 28 | "psr-4": { 29 | "GatewayWorker\\": "./src" 30 | } 31 | }, 32 | "notification-url": "https://packagist.org/downloads/", 33 | "license": [ 34 | "MIT" 35 | ], 36 | "homepage": "http://www.workerman.net", 37 | "keywords": [ 38 | "communication", 39 | "distributed" 40 | ], 41 | "support": { 42 | "issues": "https://github.com/walkor/GatewayWorker/issues", 43 | "source": "https://github.com/walkor/GatewayWorker/tree/v3.0.21" 44 | }, 45 | "time": "2021-12-08T02:52:16+00:00" 46 | }, 47 | { 48 | "name": "workerman/workerman", 49 | "version": "v4.0.26", 50 | "source": { 51 | "type": "git", 52 | "url": "https://github.com/walkor/workerman.git", 53 | "reference": "27573e9f985f9ec0665b1f9924308d359bd0fdaa" 54 | }, 55 | "dist": { 56 | "type": "zip", 57 | "url": "https://api.github.com/repos/walkor/workerman/zipball/27573e9f985f9ec0665b1f9924308d359bd0fdaa", 58 | "reference": "27573e9f985f9ec0665b1f9924308d359bd0fdaa", 59 | "shasum": "" 60 | }, 61 | "require": { 62 | "php": ">=5.3" 63 | }, 64 | "suggest": { 65 | "ext-event": "For better performance. " 66 | }, 67 | "type": "library", 68 | "autoload": { 69 | "psr-4": { 70 | "Workerman\\": "./" 71 | } 72 | }, 73 | "notification-url": "https://packagist.org/downloads/", 74 | "license": [ 75 | "MIT" 76 | ], 77 | "authors": [ 78 | { 79 | "name": "walkor", 80 | "email": "walkor@workerman.net", 81 | "homepage": "http://www.workerman.net", 82 | "role": "Developer" 83 | } 84 | ], 85 | "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", 86 | "homepage": "http://www.workerman.net", 87 | "keywords": [ 88 | "asynchronous", 89 | "event-loop" 90 | ], 91 | "support": { 92 | "email": "walkor@workerman.net", 93 | "forum": "http://wenda.workerman.net/", 94 | "issues": "https://github.com/walkor/workerman/issues", 95 | "source": "https://github.com/walkor/workerman", 96 | "wiki": "http://doc.workerman.net/" 97 | }, 98 | "funding": [ 99 | { 100 | "url": "https://opencollective.com/walkor", 101 | "type": "open_collective" 102 | }, 103 | { 104 | "url": "https://www.patreon.com/walkor", 105 | "type": "patreon" 106 | } 107 | ], 108 | "time": "2021-12-21T03:39:14+00:00" 109 | } 110 | ], 111 | "packages-dev": [], 112 | "aliases": [], 113 | "minimum-stability": "stable", 114 | "stability-flags": [], 115 | "prefer-stable": false, 116 | "prefer-lowest": false, 117 | "platform": [], 118 | "platform-dev": [], 119 | "plugin-api-version": "2.0.0" 120 | } 121 | -------------------------------------------------------------------------------- /client/main.go: -------------------------------------------------------------------------------- 1 | /// @author xcsoft 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "errors" 9 | "flag" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "os/signal" 14 | "serverStatus/config" 15 | "serverStatus/proc" 16 | "sync" 17 | 18 | "strings" 19 | "time" 20 | 21 | "github.com/gorilla/websocket" 22 | ) 23 | 24 | var connectingDown chan bool //服务器是否掉线 25 | var connected chan bool //标识 是否连接到服务器 26 | var interrupt chan os.Signal 27 | var lock sync.Mutex 28 | 29 | // 定义命令行参数 > 服务器信息 30 | var cliToken string 31 | var cliTag string 32 | var cliHost string 33 | var cliDuration int64 34 | 35 | func main() { 36 | 37 | // 解析命令行 38 | tag, err := os.Hostname() //获取主机名 用于default tag 39 | if err != nil { 40 | tag = "unknown" 41 | } 42 | flag.StringVar(&cliToken, "token", "", "Server token") 43 | flag.StringVar(&cliHost, "host", "", "Server address (including protocol,ip and port), ex: ws://127.0.0.1:8282") 44 | flag.StringVar(&cliTag, "tag", tag, "Server tag") 45 | flag.Int64Var(&cliDuration, "duration", 5000, "Data send Duration, Unit:ms") 46 | 47 | flag.Parse() 48 | if flag.NArg() > 0 || cliToken == "" || cliHost == "" { //未知信息 49 | flag.Usage() 50 | os.Exit(0) 51 | } 52 | 53 | // 解析命令行END 54 | 55 | interrupt = make(chan os.Signal) 56 | signal.Notify(interrupt, os.Interrupt) 57 | 58 | Exit: 59 | for { 60 | connected = make(chan bool) 61 | connectingDown = make(chan bool) 62 | 63 | var wsconn *websocket.Conn 64 | var err error 65 | 66 | log.Println("尝试连接到服务器") 67 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 68 | 69 | go func(_ context.Context) { 70 | defer cancel() 71 | wsconn, _, err = websocket.DefaultDialer.Dial(cliHost, nil) 72 | if err == nil { 73 | connected <- true 74 | } 75 | }(ctx) 76 | 77 | ConnectedDone: // 防止连接超时 78 | for { 79 | select { 80 | case <-connected: 81 | break ConnectedDone 82 | case <-ctx.Done(): 83 | err = errors.New("连接超时") 84 | break ConnectedDone 85 | case <-interrupt: 86 | log.Println("Received SIGINT signal. Closing all pending connections and exiting...") 87 | break Exit 88 | } 89 | } 90 | 91 | if err != nil { //检测连接信息 92 | log.Println("无法连接到服务器,将在5秒后尝试重连 > ", err) 93 | select { 94 | case <-time.After(time.Duration(5) * time.Second): 95 | continue 96 | case <-interrupt: 97 | log.Println("Received SIGINT signal.Exiting...") 98 | break Exit 99 | } 100 | } 101 | 102 | defer wsconn.Close() 103 | 104 | //连接成功 105 | conn := config.WsConn{ 106 | Conn: wsconn, 107 | Lock: &lock, 108 | Down: &connectingDown, 109 | } 110 | 111 | go receiveHandler(&conn) 112 | 113 | data, _ := json.Marshal(config.Login{ 114 | Type: "login", 115 | Platform: "server", 116 | Tag: cliTag, 117 | Token: cliToken, 118 | }) 119 | err = conn.Conn.WriteMessage(websocket.TextMessage, []byte(data)) 120 | if err != nil { 121 | log.Println("登录到服务器失败") 122 | } 123 | 124 | mainLoop: 125 | for { 126 | select { 127 | case <-time.After(time.Duration(1) * time.Millisecond * 15000): 128 | //心跳 129 | conn.Lock.Lock() 130 | err := conn.Conn.WriteMessage(websocket.TextMessage, []byte("{\"type\": \"ping\"}")) 131 | conn.Lock.Unlock() 132 | if err != nil { 133 | log.Println("Error during writing to websocket:", err) 134 | break mainLoop 135 | } 136 | case <-connectingDown: 137 | log.Println("与服务器失去连接,2秒后重连") 138 | select { 139 | case <-time.After(time.Duration(2) * time.Second): 140 | break mainLoop 141 | case <-interrupt: 142 | log.Println("Received SIGINT signal. Closing all pending connections and exiting...") 143 | _ = conn.Conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 144 | 145 | break Exit 146 | } 147 | case <-interrupt: 148 | log.Println("Received SIGINT signal. Closing all pending connections and exiting...") 149 | _ = conn.Conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 150 | 151 | break Exit 152 | } 153 | } 154 | } 155 | } 156 | 157 | // 接受处理Handler 158 | func receiveHandler(conn *config.WsConn) { 159 | for { 160 | msgType, msg, err := conn.Conn.ReadMessage() 161 | if msgType == -1 { 162 | close(connectingDown) //掉线 重连 163 | return 164 | } 165 | if err != nil { 166 | log.Println("Error in receive:", err) 167 | close(connectingDown) //重连 168 | return 169 | } 170 | 171 | jsonData, _ := ioutil.ReadAll(strings.NewReader(string(msg))) 172 | var re map[string]interface{} 173 | err = json.Unmarshal(jsonData, &re) 174 | if err != nil { 175 | log.Println("dataErr") 176 | } 177 | 178 | if re["type"] == "login_success" { 179 | log.Println("连接已建立") 180 | // 主动推送信息 > 181 | go proc.GetServerInfo(conn, proc.GetBaseInfo(), cliDuration) 182 | } else if re["type"] == "ping" { 183 | go proc.Ping(conn) 184 | } else if re["type"] == "invalid_token" { 185 | log.Fatal("错误的TOKEN,请检查token是否正确") 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /server/Applications/Events.php: -------------------------------------------------------------------------------- 1 | 'auth_timeout'])); 15 | }, array($client_id), false); 16 | Gateway::updateSession($client_id, array('auth_timer_id' => $auth_timer_id)); 17 | } 18 | 19 | public static function onMessage($client_id, $message) 20 | { 21 | $recv = json_decode($message, true); 22 | $client_ip = $_SERVER['REMOTE_ADDR'] ?? '1.1.1.1'; 23 | 24 | if (!$recv || !isset($recv['type'])) { 25 | Tool::out("$client_id 非法的请求,断开连接"); 26 | Gateway::closeClient($client_id, json_encode(['type' => 'invalid_request'])); 27 | return; 28 | } 29 | $type = $recv['type']; 30 | 31 | if (!in_array($type, ['ping', 'server_info', 'login'])) { 32 | Tool::out("$client_id $message"); //TODO debug 33 | } 34 | switch ($type) { 35 | case "ping": 36 | //心跳 37 | Gateway::sendToClient($client_id, json_encode(['type' => 'pong'])); 38 | break; 39 | 40 | case "login": 41 | //登录到服务器 42 | $token = $recv['token'] ?? ''; 43 | $platform = $recv['platform'] ?? ''; 44 | 45 | if ($platform == "server") { 46 | //服务器节点 47 | if ($token !== TOKEN) { 48 | Tool::out("$client_id 非法的token,断开连接"); 49 | Gateway::closeClient($client_id, json_encode(['type' => 'invalid_token'])); 50 | return; 51 | } 52 | $tag = $recv['tag'] ?? 'unKnow Server'; 53 | Gateway::joinGroup($client_id, 'server'); // 加入server群组 54 | Gateway::updateSession($client_id, [ //标识设备信息 55 | 'platform' => 'server', 56 | 'tag' => $tag, 57 | 'ip' => $client_ip, 58 | 'online_time' => time(), 59 | ]); // 设置用户标签 60 | Gateway::sendToClient($client_id, json_encode(['type' => 'login_success'])); //通知服务器 连接成功 61 | Gateway::sendToGroup("web", json_encode([ 62 | 'type' => 'server_online', 63 | 'client_id' => $client_id, 64 | "data" => [ 65 | 'tag' => $tag, 66 | 'ip' => $client_ip, 67 | 'online_time' => time(), 68 | ], 69 | ])); //通知前端网页 70 | Tool::out("${client_id} ${tag} 连接到服务器"); 71 | } else { 72 | //网页 73 | Gateway::joinGroup($client_id, 'web'); 74 | Gateway::updateSession($client_id, [ 75 | 'platform' => 'web', 76 | 'ip' => $client_ip, 77 | ]); // 设置连接者信息 78 | 79 | // 发送当前连接者信息 80 | $list = Gateway::getClientIdListByGroup('server'); 81 | $server_lists = []; 82 | foreach ($list as $key => $server_client_id) { 83 | $client_id_info = Gateway::getSession($server_client_id); 84 | $server_lists[$server_client_id] = [ 85 | "tag" => $client_id_info['tag'], 86 | "ip" => $client_id_info['ip'], 87 | "online_time" => $client_id_info['online_time'] 88 | ]; 89 | } 90 | Gateway::sendToCurrentClient(json_encode(['type' => 'login_success', 'server_list' => $server_lists])); 91 | //Tool::out("${client_id} (web) 连接到服务器"); 92 | } 93 | Timer::del(Gateway::getSession($client_id)['auth_timer_id'] ?? ''); //删除Timer 94 | break; 95 | 96 | case 'server_info': 97 | //服务器回传 基本信息 98 | $server_info = $recv['data'] ?? []; 99 | $tag = Gateway::getSession($client_id)['tag'] ?? ''; 100 | Gateway::sendToGroup("web", json_encode(['type' => 'server_info', 'tag' => $tag, 'client_id' => $client_id, 'data' => $server_info])); 101 | break; 102 | 103 | default: 104 | Tool::out("$client_id 未知的请求类型"); 105 | } 106 | } 107 | 108 | public static function onClose($client_id) 109 | { 110 | $client_platform = $_SESSION['platform'] ?? null; 111 | if ($client_platform == 'server') { 112 | $tag = $_SESSION['tag'] ?? null; 113 | 114 | GateWay::sendToGroup("web", json_encode(['type' => 'server_offline', 'client_id' => $client_id])); 115 | Tool::out("${client_id} ${tag} 失去连接"); 116 | } 117 | } 118 | } 119 | 120 | 121 | /* 122 | 三个 Group 123 | 124 | server: 服务器节点 125 | web: 网页节点 126 | */ -------------------------------------------------------------------------------- /web/src/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Helmet } from "react-helmet"; 4 | import bTS from './class/byteToSize'; 5 | import timeTransform from './class/timeTransform'; 6 | import timestampToDate from './class/timestampToDate'; 7 | import config from './config'; 8 | import { RingProgress } from '@ant-design/charts'; 9 | 10 | import { Card, Col, Row, Spin, Popover, Divider, Button, Typography, message } from 'antd'; 11 | import { LoadingOutlined } from '@ant-design/icons'; 12 | import 'antd/dist/antd.css'; 13 | 14 | import ReconnectingWebSocket from 'reconnecting-websocket' 15 | 16 | const { Text, Link } = Typography; 17 | 18 | var ws = new ReconnectingWebSocket(config.server); 19 | 20 | ws.onopen = function () { 21 | ws.send(JSON.stringify({ 'type': 'login' })); 22 | }; 23 | 24 | const connNotify = () => { 25 | message.loading('正在与服务器建立连接', 0); 26 | } 27 | 28 | connNotify() 29 | 30 | const App = () => { 31 | 32 | const [serverList, setServerList] = useState([]); 33 | 34 | ws.onclose = function () { 35 | console.error('connection down') 36 | message.destroy() 37 | connNotify() 38 | } 39 | 40 | ws.onmessage = function (evt) { 41 | var resData = JSON.parse(evt.data) 42 | //console.log(resData) 43 | var type = resData.type 44 | 45 | if (type === 'login_success') { 46 | //登录成功 47 | message.destroy() 48 | message.success('连接已建立'); 49 | setServerList(resData.server_list) 50 | } else if (type === 'server_offline') { 51 | // 服务器掉线 52 | let serverListTemp = { ...serverList } 53 | delete serverListTemp[resData.client_id] 54 | setServerList(serverListTemp); 55 | } else if (type === 'server_online') { 56 | // 服务器上线 57 | let serverListTemp = { ...serverList } 58 | serverListTemp[resData.client_id] = resData.data 59 | setServerList(serverListTemp) 60 | } else if (type === 'server_info') { 61 | let serverListTemp = { ...serverList } 62 | serverListTemp[resData.client_id]['data'] = resData.data 63 | setServerList(serverListTemp) 64 | } 65 | }; 66 | 67 | return <> 68 | 69 | {config.title} - Powered By ServerStatus 70 | 71 | 72 | 73 | { 74 | Object.keys(serverList).map((client_id, index) => { 75 | if (!client_id) return <>; 76 | let serverInfo = serverList[client_id] 77 | let itemData = serverInfo['data'] 78 | 79 | let RingProgressBaseConfig = { 80 | height: 85, 81 | width: 85, 82 | innerRadius: 0.83, 83 | radius: 0.98, 84 | autoFit: false, 85 | color: ['#5B8FF9', '#E8EDF3'], 86 | } 87 | 88 | if (itemData === undefined) { 89 | return ( 90 | 93 | }> 94 | 95 | 99 | 103 | 107 |
108 | CPU占用率 109 | 110 | 111 | 115 | 119 |
120 | 内存占用率 121 | 122 | 123 | 127 | 131 |
132 | swap占用率 133 | 134 |
135 | 136 |

进程数: -

137 |

在线时间: -

138 |

负载:-, -, -

139 |
140 |
141 | 142 | ); 143 | } 144 | // 服务器已经推送数据 145 | let cpu_percent = itemData.Cpu.Percent / 100 146 | let memory_percent = itemData.Memory.Percent / 100 147 | let swap_percent = itemData.Swap.Percent / 100 148 | 149 | if (cpu_percent === 0) cpu_percent = 0.00001 //防止样式BUG 150 | 151 | return ( 152 | 155 | 162 |

主机名: {itemData.Host.HostName}

163 |

开机时间: {timestampToDate(itemData.Host.BootTime)}

164 | 165 |

内核架构: {itemData.Host.KernelArch}

166 |

内核版本: {itemData.Host.KernelVersion}

167 |

系统类型: {itemData.Host.Os}

168 | 169 |

平台: {itemData.Host.Platform}

170 |

平台家族: {itemData.Host.PlatformFamily}

171 |

平台版本: {itemData.Host.PlatformVersion}

172 | 173 |

虚拟化角色: {itemData.Host.VirtualizationRole}

174 |

虚拟化类型: {itemData.Host.VirtualizationSystem}

175 | 176 |

数据更新时间: {timestampToDate(itemData.Time)}

177 | 178 | )} 179 | > 180 | 181 | 182 | )} 183 | > 184 | 185 | 189 | 193 | 197 |

{itemData.Cpu.ModalName}

198 |

{itemData.Cpu.PhysicalCores}个物理核心

199 |

{itemData.Cpu.LogicalCores}个逻辑核心

200 | 201 | )} 202 | > 203 | 207 |
208 | CPU占用率 209 |
210 | 211 | 212 | 216 | 220 | 224 |
225 | 内存占用率 226 |
227 | 228 | 229 | 233 | 237 | 241 |
242 | swap占用率 243 |
244 | 245 |
246 | 247 |

进程数: {itemData.Host.Procs}

248 |

在线时间: {timeTransform(itemData.Host.UpTime)}

249 |

负载:{itemData.Load.M1}, {itemData.Load.M5}, {itemData.Load.M15}

250 |
251 | 252 | ) 253 | }) 254 | } 255 |
256 |
257 | 258 | 259 |   CopyRight 2021 xcsoft All Rights Reserved. 260 | 261 |
262 | ; 263 | } 264 | 265 | 266 | ReactDOM.render( 267 | , 268 | document.getElementById('root') 269 | ); 270 | 271 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------