├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.md ├── cfg.example.json ├── control ├── cron └── collector.go ├── custom.example.json ├── funcs ├── checker.go ├── common.go ├── custmetric.go ├── ecmc.go ├── funcs.go ├── n9e.go ├── n9ev3.go ├── swcpu.go ├── swifstat.go ├── swmem.go ├── swping.go └── swsystem.go ├── g ├── cfg.go ├── const.go ├── customcfg.go ├── g.go ├── hostcfg.go ├── push.go ├── rpc.go └── var.go ├── gitversion ├── go.mod ├── go.sum ├── hosts.example.json ├── http ├── admin.go ├── health.go ├── http.go ├── page.go ├── push.go └── sw.go ├── main.go └── public ├── css ├── bootstrap-responsive.min.css ├── bootstrap.min.css ├── font-awesome │ ├── css │ │ ├── archive │ │ │ ├── font-awesome-ie7.css │ │ │ ├── font-awesome-ie7.min.css │ │ │ └── font-awesome.css │ │ └── font-awesome.min.css │ └── font │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff ├── font │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.svg │ ├── OpenSans-Regular-webfont.ttf │ └── OpenSans-Regular-webfont.woff ├── g.css ├── odometer.css ├── pages │ └── dashboard.css └── style.css ├── img ├── code.png └── favicon.ico ├── index.html └── js ├── base.js ├── bootstrap.js ├── dashboard.js ├── jquery-ui.min.js ├── jquery.dataTables.min.js ├── jquery.js └── odometer.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.sw[po] 3 | 4 | .idea 5 | .DS_Store 6 | 7 | cfg.json 8 | 9 | var/app.pid 10 | var/app.log 11 | 12 | falcon-swcollector* 13 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog # 2 | ## 4.2.0 ## 3 | #### 新功能 #### 4 | 1. 现在支持通过 ecmc/n9e 来获取监控的 ip 列表,无需手动配置配置文件了,详见 [README](https://github.com/gaochao1/swcollector/blob/master/README.md}) 5 | 6 | # Changelog # 7 | ## 4.1.0 ## 8 | #### 新功能 #### 9 | 1. 现在支持推送数据到 n9e 了 10 | 11 | # Changelog # 12 | ## 4.0.6.3 ## 13 | #### bug修复 #### 14 | 1. 现在当 tag 为空时,debug 应该会正确打印对应的日志了 15 | 2. 修复了一个当自定义 oid 超过 2 项,且 oid 不正确无法采集到信息时,swcollector 会异常崩溃的 bug 16 | 3. 修复了一个当 speedlimit 使用指定值(而非自动采用接口速率 ifSpeed)作为限制时,接口的 speedPercent 无法正确采集的 bug 17 | #### 改进 #### 18 | 1. 配置热重载模式调整,现在 reload 后会在下一个采集周期时重载配置。并清空 AliveIp,因此配置中移除的 IP 可以正确生效了 19 | 2. swcollector 自带的 http 页,现在应该能更快速的显示了 20 | 3. swcollector 自带的 http 页,现在支持更多交换机的 sysmodle 了 21 | 4. cpu 和 mem 现在对于老版本的 H3C (H3C Comware Platform Software Comware software, Version 3.10),应该也能正确采集到了 22 | 23 | 24 | # Changelog # 25 | ## 4.0.6.2 ## 26 | #### 新功能 #### 27 | 1. 增加了动态重载配置的功能,详见 [README](https://github.com/gaochao1/swcollector/blob/master/README.md}) 28 | #### bug修复 #### 29 | 1. 现在当 tag 为空时,debug 的时候应该不会重复的打印日志了 30 | #### 改进 #### 31 | 1. 现在自定义 Oid 采集时,可以支持 string 类型的返回了。系统会强制转换成 float64 上报,如果转换出错则抛出错误 32 | 2. 现在对交换机类型的判断时,也会采取重试(由配置中的 retry 决定重试次数)来规避偶发性的异常了。 33 | 3. 现在对 Linux 的 snmp 监控能够采集到 cpu 和 mem 了 34 | 35 | 36 | ## 4.0.6.1 ## 37 | #### bug修复 #### 38 | 1. 现在当采集异常 Channel 关闭时,应该会正常的抛弃而不会给 transfer 上报一个空的 endpoint 了 39 | 40 | ## 4.0.6 ## 41 | #### 新功能 #### 42 | 1. 增加接口速率的采集 43 | * switch.if.Speed 44 | 2. 增加接口流量百分比的采集 45 | * switch.if.InSpeedPercent 46 | * switch.if.OutSpeedPercent 47 | 3. 增加 dell 交换机的内置 cpu/mem 采集 48 | 4. 现在支持自定义 oid 的采集了 49 | 5. 现在支持自定义交换机 host,以 host 作为 endpoint 上报 50 | 6. 现在支持地址段的方式配置采集列表,例如 ```"192.168.56.102-192.168.56.120"``` 51 | 7. 现在对于单台交换机的多个指标采集,也支持并发限制了。以减少对一些 snmp 响应较低的交换机采集时,由于并发太多产生的超时问题。 52 | 53 | #### 改进 #### 54 | 1. Counter 类型的数据上报逻辑大幅更改。现在 swcollector 将在本地计算出相应的数值,再以 Gauge 类型上报。如果出现异常的数据,则在本地直接抛弃。因此最终呈现的绘图至多只会出现断点,而不再会出现极端的异常图形。 55 | 2. 优化了 gosnmp 的端口采集,现在 gosnmp 端口采集异常的超时情况应该大幅度降低了 56 | 3. 现在如果并发采集的某个 goroutine 的耗时超过了采集周期,则该 goroutine 会超时退出,避免异常时大量 goroutine 耗尽资源 57 | 4. 移除了对 Cisco_ASA 的防火墙连接数内置采集,此类需求今后可通过自定义 oid 方式采集。 58 | 59 | #### bug修复 #### 60 | 1. 现在当 cpu 和 mem 采集异常的时候,应该能正确的抛弃。而不是上报一个 0 了 61 | 62 | 63 | ## 3.2.1.1 ## 64 | #### 新功能 #### 65 | 1. debugmetric 现在支持配置多个 endpoint 和 metric 了 66 | ## 3.2.1 ## 67 | #### 新功能 #### 68 | 1. 增加接口丢包数量的采集 69 | * IfInDiscards 70 | * IfOutDiscards 71 | 72 | 2. 增加接口错包数量的采集 73 | * IfInErrors 74 | * IfOutErros 75 | 76 | 3. 增加接口由于未知或不支持协议造成的丢包数量采集 77 | * IfInUnknownProtos 78 | 79 | 4. 增加接口输出队列中的包数量采集 80 | * IfOutQLen 81 | 82 | 5. 现在能够通过 debugmetric 配置选项,配置想要 debug 的 metric 了。配置后日志中会具体打印该条 metric 的日志 83 | 84 | 6. 现在能够通过 gosnmp 的配置选项,选择采用 gosnmp 还是 snmpwalk 进行数据采集了。两者效率相仿,snmpwalk稍微多占用点系统资源 85 | 86 | #### 改进 #### 87 | 1. 优化了 gosnmp 的端口采集,略微控制了一下并发速率,现在 gosnmp 采集端口超时的概率,应该有所降低了 88 | 2. 代码优化,删除了部分无关代码(比如 hbs 相关的部分……) 89 | 3. 部分日志的输出可读性更强了 90 | 91 | #### bug修复 #### 92 | 1. 修复了一个广播报文采集不正确的 bug 93 | 2. 修复了一个老版本思科交换机,CPU 内存采集不正确的 bug 94 | 3. 修复了一些偶尔进程崩溃的 bug 95 | 96 | ## 3.2.0 ## 97 | #### 新功能 #### 98 | 1. 增加接口广播包数量的采集 99 | * IfHCInBroadcastPkts 100 | * IfHCOutBroadcastPkts 101 | 102 | 2. 增加接口组播包数量的采集 103 | * IfHCInMulticastPkts 104 | * IfHCOutMulticastPkts 105 | 106 | 3. 增加接口状态的采集 107 | * IfOperStatus(1 up, 2 down, 3 testing, 4 unknown, 5 dormant, 6 notPresent, 7 lowerLayerDown) 108 | 109 | 4. 内置了更多交换机型号的 CPU, 内存的 OID 和计算方式。(锐捷,Juniper, 华为, 华三的一些型号等) 110 | 111 | PS: 虽然 if 采集是并发的,不过采集项开的太多还是可能会影响 snmp 的采集效率,尤其是华为等 snmp 返回比较慢的交换机…………故谨慎选择,按需开启。 112 | 113 | #### 改进 #### 114 | 1. 解决了 if 采集乱序的问题,现在即便使用 gosnmp 采集返回乱序也可以正确处理了。已测试过的华为型号现在均使用 gosnmp 采集。(v5.13,v5.70,v3.10) 115 | 2. 现在 log 中 打印 panic 信息的时候,应该会带上具体的 ip 地址了。 116 | 3. 现在默认采集 bit 单位的网卡流量了。 117 | 4. 去掉了默认配置文件里的 hostname 和 ip 选项,以免产生歧义,反正也没什么用………… 118 | 5. 修改默认 http 端口为 1989,避免和 agent 的端口冲突。 119 | 120 | PS: func/swifstat.go 151行的注释代码,会在 debug 模式下打印具体的 ifstat 输出。如果交换机采集数据出现不准确的情况,可开启这段代码来进行排查。 121 | 122 | #### bug修复 #### 123 | 1. 修复了在并发 ping 的情况下,即便 ip 地址不通,也有小概率 ping 通地址的 bug。(很神奇是不是……反正在我这里有出现这现象。。。)。方案是替换为 [go-fastping](https://github.com/tatsushid/go-fastping) 来做 ping 探测,通过 fastPingMode 配置选项开启。 124 | 2. 修复了思科 ASA-5585 9.1 和 9.2 两个版本 cpu, memory 的 oid 不一致带来的采集问题。(这坑爹玩意!)。现在应该可以根据他的版本号来选择不同的 oid 进行采集了。 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Swcollector 3 | 4 | 基于小米运维开源的[open-falcon](http://open-falcon.com),交换机专用agent。 5 | 6 | 感谢小米运维同学的杰出工作、开源、良好的文档和热心。 7 | 8 | 感谢[来炜](https://github.com/laiwei)的宝贵建议。 9 | 10 | #### 简介 11 | 采集的metric列表: 12 | 13 | * CPU利用率 14 | * 内存利用率 15 | * Ping延时(正常返回延时,超时返回 -1,可以用于存活告警) 16 | * IfHCInOctets 17 | * IfHCOutOctets 18 | * IfHCInUcastPkts 19 | * IfHCOutUcastPkts 20 | * IfHCInBroadcastPkts 21 | * IfHCOutBroadcastPkts 22 | * IfHCInMulticastPkts 23 | * IfHCOutMulticastPkts 24 | * IfInDiscards 25 | * IfOutDiscards 26 | * IfInErrors 27 | * IfOutErros 28 | * IfInUnknownProtos 29 | * IfOutQLen 30 | * IfSpeed 31 | * IfSpeedPercent 32 | * IfOperStatus(接口状态,1 up, 2 down, 3 testing, 4 unknown, 5 dormant, 6 notPresent, 7 lowerLayerDown) 33 | 34 | 35 | CPU和内存的OID私有,根据设备厂家和OS版本可能不同。目前测试过的设备: 36 | 37 | * Cisco IOS(Version 12) 38 | * Cisco NX-OS(Version 6) 39 | * Cisco IOS XR(Version 5) 40 | * Cisco IOS XE(Version 15) 41 | * Cisco ASA (Version 9) 42 | * Ruijie 10G Routing Switch 43 | * Huawei VRP(Version 8) 44 | * Huawei VRP(Version 5.20) 45 | * Huawei VRP(Version 5.120) 46 | * Huawei VRP(Version 5.130) 47 | * Huawei VRP(Version 5.70) 48 | * Juniper JUNOS(Version 10) 49 | * H3C(Version 3.1) 50 | * H3C(Version 5) 51 | * H3C(Version 5.20) 52 | * H3C(Version 7) 53 | * DELL 54 | * Linux 55 | 56 | #### 二进制安装 57 | 从[这里](https://github.com/gaochao1/swcollector/releases) 下载编译好的最新二进制版本即可。注意:这些二进制只能跑在64位Linux上 58 | 59 | #### 源码安装 60 | ``` 61 | 依赖$GOPATH/src/github.com/gaochao1/sw 62 | cd $GOPATH/src/github.com/gaochao1/swcollector 63 | go get ./... 64 | chmod +x control 65 | ./control build 66 | ./control pack 67 | 最后一步会pack出一个tar.gz的安装包,拿着这个包去部署服务即可。 68 | 69 | 升级时,确保先更新sw 70 | cd $GOPATH/src/github.com/gaochao1/sw 71 | git pull 72 | ``` 73 | 74 | #### 部署说明 75 | 76 | swcollector需要部署到有交换机SNMP访问权限的服务器上。 77 | 78 | 使用Go原生的ICMP协议进行Ping探测,swcollector需要root权限运行。 79 | 80 | 支持使用 Gosnmp 或 snmpwalk 进行数据采集,如果使用 snmpwalk 模式,需要在监控探针服务器上安装个snmpwalk命令 81 | 82 | #### 配置说明 83 | 84 | 配置文件请参照cfg.example.json,修改该文件名为cfg.json,将该文件里的IP换成实际使用的IP。 85 | 86 | 配置说明: 87 | ``` 88 | { 89 | "debug": true, 90 | "debugmetric":{ # 在日志中 debug 具体的指标 91 | "endpoints":["192.168.56.101","192.168.56.102"], # 必填 92 | "metrics":["switch.if.In","switch.if.Out"], # 必填 93 | "tags":"ifName=Fa0/1" # 有则匹配 tag,如为 "" 则打印该 metric 的全部信息 94 | }, 95 | "switch":{ 96 | "enabled": true, 97 | "ipRange":[ #交换机IP地址段,对该网段有效IP,先发Ping包探测,对存活IP发SNMP请求 98 | "192.168.56.101/32", 99 | "192.168.56.102-192.168.56.120",#现在支持这样的配置方式,对区域内的ip进行ping探测,对存活ip发起snmp请求。 100 | "172.16.114.233" 101 | ], 102 | "gosnmp":true, #是否使用 gosnmp 采集, false 则使用 snmpwalk 103 | "index_tag":false, #去掉 index tag 以在 n9e 上获得更好体验 104 | "pingTimeout":300, #Ping超时时间,单位毫秒 105 | "pingRetry":4, #Ping探测重试次数 106 | "community":"public", #SNMP认证字符串 107 | "snmpTimeout":1000, #SNMP超时时间,单位毫秒 108 | "snmpRetry":5, #SNMP重试次数 109 | "ignoreIface": ["Nu","NU","Vlan","Vl"], #忽略的接口,如Nu匹配ifName为*Nu*的接口 110 | "ignoreOperStatus": true, #不采集IfOperStatus 111 | "speedlimit":0, #流量的上限,如果采集计算出的流量超过这个数值,则抛弃不上报。如果填0,则以接口的速率(ifSpeed)作为上限计算。注意 interface vlan 这样的虚接口是没有 ifSpeed 的,因此不进行最大值的比较。 112 | "ignorePkt": true, #不采集IfHCInUcastPkts和IfHCOutUcastPkts 113 | "pktlimit": 0, #pkt的上限,如果采集计算出的包速率超过这个数值,则抛弃不上报。如果填0,则不进行最大值比较。 114 | "ignoreBroadcastPkt": true, #不采集IfHCInBroadcastPkts和IfHCOutBroadcastPkts 115 | "broadcastPktlimit": 0, #broadcastPkt的上限,如果采集计算出的包速率超过这个数值,则抛弃不上报。如果填0,则不进行最大值比较。 116 | "ignoreMulticastPkt": true, #不采集IfHCInMulticastPkts和IfHCOutMulticastPkts 117 | "multicastPktlimit": 0, #multicastPkt的上限,如果采集计算出的包速率超过这个数值,则抛弃不上报。如果填0,则不进行最大值比较。 118 | "ignoreDiscards": true, #不采集IfInDiscards和IfOutDiscards 119 | "discardsPktlimit": 0, #discardsPkt的上限,如果采集计算出的包速率超过这个数值,则抛弃不上报。如果填0,则不进行最大值比较。 120 | "ignoreErrors": true, #不采集IfInErrors和IfOutErros 121 | "errorsPktlimit": 0, #errorsPkt的上限,如果采集计算出的包速率超过这个数值,则抛弃不上报。如果填0,则不进行最大值比较。 122 | "ignoreUnknownProtos":true, #不采集IfInUnknownProtos 123 | "unknownProtosPktlimit": 0, #unknownProtosPkt的上限,如果采集计算出的包速率超过这个数值,则抛弃不上报。如果填0,则不进行最大值比较。 124 | "ignoreOutQLen":true, #不采集IfOutQLen 125 | "outQLenPktlimit": 0, #outQLenPkt的上限,如果采集计算出的包速率超过这个数值,则抛弃不上报。如果填0,则不进行最大值比较。 126 | "fastPingMode": true, 127 | "limitConcur": 1000, #交换机采集的并发限制 128 | "limitCon": 4 #对于单台交换机上,多个指标采集的并发限制 129 | }, 130 | "ecmc":{ # 从 ecmc,即夜莺的商业版上获取待监控的 ip 列表,此时 switch 中配置的 ipRange 会被忽略,当 ecmc 和 n9e 同时配置时,ecmc 优先,n9e 的配置忽略 131 | "enabled":false, # true 即开启 132 | "addr":"http://ecmc.example.com", # ecmc 的地址 133 | "token":"x-user-token", # ecmc 供 api 调用的 token,在个人设置-密钥管理中配置 134 | "nodes":[1,2,3,4] # 监控 ip 所在的 node id 列表,鼠标点在 node 上能看到节点 id 135 | }, 136 | "n9e":{ # 从 n9e ,即夜莺的开源版上获取待监控的 ip 列表,此时 switch 中配置的 ipRange 会被忽略。 137 | "enabled":false, # true 即开启 138 | "addr":"http://n9e.example.com", # n9e 的地址 139 | "user":"root", # 用户名 140 | "pass":"1234", # 密码 141 | "nodes":[1,2,3,4] # 监控 ip 所在的 node id 列表,可以先通过 curl -u root:1234 http://n9e.example.com/api/portal/tree 看下自己节点的 id 号 142 | }, 143 | "n9e_v3":{ # 从 n9e ,即夜莺的开源版上获取待监控的 ip 列表,此时 switch 中配置的 ipRange 会被忽略。 144 | "enabled":true, # true 即开启 145 | "addr":"http://n9ev3.example.com", # n9e 的地址 146 | "token":"x-user-token", # n9ev3 供 api 调用的 token,在个人设置-密钥管理中配置 147 | "nodes":[4] # 监控 ip 所在的 node id 列表,鼠标点在 node 上能看到节点 id 148 | }, 149 | "switchhosts":{ 150 | "enabled":false, 151 | "hosts":"./hosts.json" #自定义的host与Ip地址对应表,如果配置,则上报时会用这里的host替换ip地址 152 | }, 153 | "customMetrics":{ 154 | "enabled":false, 155 | "template":"./custom.json" #自定义的metric列表,如果配置,会根据这个配置文件,采集额外的自定义metric 156 | }, 157 | "transfer": { 158 | "enabled": true, 159 | "n9eMode":true, # 标为 true 时,将以 msgpack 方式发送数据,这样可以支持 n9e。注意将 addr 替换为 n9e 的地址和端口, n9e transfer msg接收默认端口是5811。 160 | "addr": "127.0.0.1:8009", 161 | "interval": 300, 162 | "timeout": 1000 163 | }, 164 | "http": { 165 | "enabled": false, 166 | "listen": ":1989", 167 | "trustIps":["192.168.0.1","192.168.0.2"] 168 | } 169 | } 170 | 171 | ``` 172 | 自定义 host 配置说明 173 | ``` 174 | { 175 | "hosts": 176 | { 177 | "192.168.160":"test1", 178 | "192.168.88.161":"test2", 179 | "192.168.33.2":"test3", 180 | "192.168.31.51":"test4" 181 | } 182 | } 183 | ``` 184 | 自定义 oid 配置说明 185 | 186 | ``` 187 | { 188 | "metrics": 189 | [ 190 | { 191 | "ipRange":[ 192 | "192.168.0.1-192.168.0.2", #使用该自定义 oid 采集的地址 193 | "192.168.1.1" 194 | ], 195 | "metric":"switch.AnyconnectSession", #自定义的 metric 196 | "tag":"", #自定义的 tag 197 | "type":"GAUGE", 198 | "oid":"1.3.6.1.4.1.9.9.392.1.3.35.0" #自定义的 oid 199 | }, 200 | #自定义的 oid 只支持 snmp get 方式采集,因此务必填写完整,建议先通过 snmpwalk 验证一下。 201 | #这是 cisco asa 上 anyconnect 的在线数量 202 | { 203 | "ipRange":[ 204 | "192.168.1.160-192.168.1.161" 205 | ], 206 | "metric":"switch.ConnectionStat", 207 | "tag":"", 208 | "type":"GAUGE", 209 | "oid":"1.3.6.1.4.1.9.9.147.1.2.2.2.1.5.40.6" 210 | }, 211 | #这是 cisco asa 上的防火墙连接数 212 | { 213 | "ipRange":[ 214 | "192.168.33.2" 215 | ], 216 | "metric":"switch.TempStatus", 217 | "tag":"", 218 | "type":"GAUGE", 219 | "oid":"1.3.6.1.4.1.9.9.13.1.3.1.3.1004" 220 | } 221 | #这是 cisco 交换机的温度,注意通用的 oid 是 "1.3.6.1.4.1.9.9.13.1.3.1.3",这里 1004 是硬件 index。框式交换机可能会有多个温度(多块线卡),请根据实际需要填具体的 oid 值和相应的 tag 222 | ] 223 | } 224 | ``` 225 | 226 | #### 配置的热重载 227 | 4.0.6.2 版本起,支持配置的热重载。修改配置后无需重启 swcollector 了。 228 | 开启配置热重载需要开启 swcollector 的 http 模块。然后使用以下接口重载配置。 229 | ``` 230 | # curl http://127.0.0.1:1990/config/reload 231 | ``` 232 | 注意对于 transfer 的 interval 修改,热重载无效,还是需要重启 swcollector 233 | 234 | 同时也可以使用下列接口来查看 swcollector 的相关信息(类似于 Open-Falcon 的官方的 agent) 235 | ``` 236 | # curl http://127.0.0.1:1990/ips 237 | 查看当前 trustIp 的列表 238 | # curl http://127.0.0.1:1990/workdir 239 | 查看当前的工作目录 240 | # curl http://127.0.0.1:1990/exit 241 | 远程退出进程 242 | # curl http://127.0.0.1:1990/health 243 | 查看当前状态 244 | # curl http://127.0.0.1:1990/version 245 | 查看当前版本 246 | ``` 247 | 248 | #### 部署说明 249 | 由于是并发采集,因此每个周期的采集耗时,主要取决于被采集的交换机中,最慢的那个。 250 | 因此我们可以在 debug 模式下观察每个交换机的采集耗时。 251 | ``` 252 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.10.1 PingResult: true len_list: 440 UsedTime: 5 253 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.10.252 PingResult: true len_list: 97 UsedTime: 2 254 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.13.1 PingResult: true len_list: 24 UsedTime: 1 255 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.14.1 PingResult: true len_list: 23 UsedTime: 1 256 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.23.1 PingResult: true len_list: 61 UsedTime: 2 257 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.1 PingResult: true len_list: 55 UsedTime: 1 258 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.5 PingResult: true len_list: 26 UsedTime: 2 259 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.6 PingResult: true len_list: 26 UsedTime: 2 260 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.11 PingResult: true len_list: 26 UsedTime: 2 261 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.12 PingResult: true len_list: 26 UsedTime: 2 262 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.13 PingResult: true len_list: 26 UsedTime: 2 263 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.14 PingResult: true len_list: 26 UsedTime: 2 264 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.15 PingResult: true len_list: 26 UsedTime: 2 265 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.16 PingResult: true len_list: 26 UsedTime: 2 266 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.17 PingResult: true len_list: 26 UsedTime: 2 267 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.18 PingResult: true len_list: 26 UsedTime: 2 268 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.19 PingResult: true len_list: 26 UsedTime: 2 269 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.20 PingResult: true len_list: 26 UsedTime: 2 270 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.21 PingResult: true len_list: 26 UsedTime: 2 271 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.22 PingResult: true len_list: 26 UsedTime: 2 272 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.23 PingResult: true len_list: 26 UsedTime: 2 273 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.24 PingResult: true len_list: 26 UsedTime: 2 274 | 2016/08/16 21:31:24 swifstat.go:121: IP: 192.168.12.25 PingResult: true len_list: 26 UsedTime: 2 275 | 2016/08/16 21:31:29 swifstat.go:121: IP: 192.168.11.2 PingResult: true len_list: 348 UsedTime: 10 276 | 2016/08/16 21:31:29 swifstat.go:177: UpdateIfStats complete. Process time 10.700895998s. 277 | ``` 278 | 如下所示,我们可以发现 192.168.11.2 这个交换机的采集耗时最长,用掉了 10 秒钟,这个占用了采集周期大部分的耗时。 279 | 280 | 我们可以根据这些信息将交换机做一下分类,snmp 响应快的放在一起用一个 swcollector 采集,snmp 的超时时间可以设置的短一些,采集周期也可以设的小一点,比如30秒 281 | 响应慢的放在一起,用另外一个 swcollector 采集,snmp 的超时时间可以设置的长一点,采集周期也可以适当的放久一点,比如5分钟。 282 | 283 | snmp 报文的响应需要消耗 cpu,因此交换机多少都对 snmp 报文的响应有速率限制,在 CPU 过高时,还可能会丢弃 snmp,icmp等优先级不高的请求。 284 | 不同品牌的对此的限定都有不同,有些型号可以通过配置修改。如果允许的话,也可以通过放开其对 snmp 的限速控制,来加快交换机对 snmp 报文的响应速度。 285 | 286 | 如此,在我们调节了 *“木桶” *中 *“木板”* 的长度后,选择了合适的采集周期后,swcollector 的单个实例可以很轻松的带起上百台乃至更多的交换机。 287 | 288 | #### v3-v4 升级说明(重要!!!!) 289 | 由于 v4 版本的 swcollector 修改了接口数据的上报格式,从Counter修改为GAUGE。因此如果同一个 endpoint,使用升级后的 swcollector 采集时,graph 内原有数据会**全部丢失!** 290 | 因此建议在升级时,开启自定义 host 功能,将交换机的 ip 地址通过 host 自定义为新的 endpoint,例如原先采集的 ip 为 291 | ``` 292 | "ipRange":[ 293 | "192.168.0.1-192.168.0.2", 294 | "192.168.1.1" 295 | ], 296 | ``` 297 | 开启 switchhosts.enabled = true,然后配置 hosts.json 298 | ``` 299 | { 300 | "hosts": 301 | { 302 | "192.168.0.1":"sw-192.168.0.1", 303 | "192.168.0.2":"sw-192.168.0.1", 304 | "192.168.1.1":"sw-192.168.1.1" 305 | } 306 | } 307 | ``` 308 | 这样他会以新的 "sw-192.168.x.x" 作为 endpoint 上报,原有的 192.168.x.x 依然继续保留,历史纪录不会丢失。 309 | -------------------------------------------------------------------------------- /cfg.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": false, 3 | "debugmetric":{ 4 | "endpoints":["endpoint-vpn"], 5 | "metrics":["AnyconnectSession","ConnectionStat"], 6 | "tags":"" 7 | }, 8 | "switch":{ 9 | "enabled": true, 10 | "ipRange":[ 11 | "10.10.88.168" 12 | ], 13 | "gosnmp":true, 14 | "index_tag":false, 15 | "pingTimeout":300, 16 | "pingRetry":4, 17 | "community":"123456", 18 | "snmpTimeout":1000, 19 | "snmpRetry":5, 20 | "ignoreIface": ["Nu","NU","Vlan","Vl"], 21 | "ignoreOperStatus": true, 22 | "speedlimit":0, 23 | "ignorePkt": true, 24 | "pktlimit": 0, 25 | "ignoreBroadcastPkt": true, 26 | "broadcastPktlimit": 0, 27 | "ignoreMulticastPkt": true, 28 | "multicastPktlimit": 0, 29 | "ignoreDiscards": true, 30 | "discardsPktlimit": 0, 31 | "ignoreErrors": true, 32 | "errorsPktlimit": 0, 33 | "ignoreUnknownProtos":true, 34 | "unknownProtosPktlimit": 0, 35 | "ignoreOutQLen":true, 36 | "outQLenPktlimit": 0, 37 | "fastPingMode": true, 38 | "limitConcur": 1000, 39 | "limitCon": 4 40 | }, 41 | "ecmc":{ 42 | "enabled":false, 43 | "addr":"http://ecmc.example.com", 44 | "token":"x-user-token", 45 | "nodes":[1,2,3,4] 46 | }, 47 | "n9e":{ 48 | "enabled":false, 49 | "addr":"http://n9e.example.com", 50 | "user":"root", 51 | "pass":"1234", 52 | "nodes":[1,2,3,4] 53 | }, 54 | "n9e_v3":{ 55 | "enabled":true, 56 | "addr":"http://n9ev3.example.com", 57 | "token":"x-user-token", 58 | "nodes":[4] 59 | }, 60 | "switchhosts":{ 61 | "enabled":false, 62 | "hosts":"./hosts.json" 63 | }, 64 | "customMetrics":{ 65 | "enabled":false, 66 | "template":"./custom.json" 67 | }, 68 | "transfer": { 69 | "enabled": true, 70 | "n9eMode":true, 71 | "rpcMethod":"Transfer.Push", 72 | "addr": "127.0.0.1:8009", 73 | "interval": 300, 74 | "timeout": 1000 75 | }, 76 | "http": { 77 | "enabled": false, 78 | "listen": ":1989", 79 | "trustIps":["192.168.0.1","192.168.0.2"] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORKSPACE=$(cd $(dirname $0)/; pwd) 4 | cd $WORKSPACE 5 | 6 | mkdir -p var 7 | 8 | module=swcollector 9 | app=falcon-$module 10 | conf=cfg.json 11 | pidfile=var/app.pid 12 | logfile=var/app.log 13 | 14 | function check_pid() { 15 | if [ -f $pidfile ];then 16 | pid=`cat $pidfile` 17 | if [ -n $pid ]; then 18 | running=`ps -p $pid|grep -v "PID TTY" |wc -l` 19 | return $running 20 | fi 21 | fi 22 | return 0 23 | } 24 | 25 | function start() { 26 | check_pid 27 | running=$? 28 | if [ $running -gt 0 ];then 29 | echo -n "$app now is running already, pid=" 30 | cat $pidfile 31 | return 1 32 | fi 33 | 34 | if ! [ -f $conf ];then 35 | echo "Config file $conf doesn't exist, creating one." 36 | cp cfg.example.json $conf 37 | fi 38 | nohup ./$app -c $conf &> $logfile & 39 | echo $! > $pidfile 40 | echo "$app started..., pid=$!" 41 | } 42 | 43 | function stop() { 44 | pid=`cat $pidfile` 45 | kill $pid 46 | echo "$app stoped..." 47 | } 48 | 49 | function restart() { 50 | stop 51 | sleep 1 52 | start 53 | } 54 | 55 | function status() { 56 | check_pid 57 | running=$? 58 | if [ $running -gt 0 ];then 59 | echo started 60 | else 61 | echo stoped 62 | fi 63 | } 64 | 65 | function tailf() { 66 | tail -f $logfile 67 | } 68 | 69 | function build() { 70 | go build 71 | if [ $? -ne 0 ]; then 72 | exit $? 73 | fi 74 | mv $module $app 75 | ./$app -v 76 | } 77 | 78 | function pack() { 79 | build 80 | git log -1 --pretty=%h > gitversion 81 | version=`./$app -v` 82 | file_list="public control cfg.example.json custom.example.json hosts.example.json $app" 83 | echo "...tar $app-$version.tar.gz <= $file_list" 84 | tar zcf $app-$version.tar.gz gitversion $file_list 85 | } 86 | 87 | function packbin() { 88 | build 89 | git log -1 --pretty=%h > gitversion 90 | version=`./$app -v` 91 | tar zcvf $app-bin-$version.tar.gz $app gitversion 92 | } 93 | 94 | function help() { 95 | echo "$0 build|pack|start|stop|restart|status|tail" 96 | } 97 | 98 | if [ "$1" == "" ]; then 99 | help 100 | elif [ "$1" == "stop" ];then 101 | stop 102 | elif [ "$1" == "start" ];then 103 | start 104 | elif [ "$1" == "restart" ];then 105 | restart 106 | elif [ "$1" == "status" ];then 107 | status 108 | elif [ "$1" == "tail" ];then 109 | tailf 110 | elif [ "$1" == "build" ];then 111 | build 112 | elif [ "$1" == "pack" ];then 113 | pack 114 | elif [ "$1" == "packbin" ];then 115 | packbin 116 | else 117 | help 118 | fi 119 | -------------------------------------------------------------------------------- /cron/collector.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "log" 5 | "math" 6 | "time" 7 | 8 | "github.com/gaochao1/swcollector/funcs" 9 | "github.com/gaochao1/swcollector/g" 10 | "github.com/open-falcon/common/model" 11 | ) 12 | 13 | func Collect() { 14 | if !g.Config().Transfer.Enabled { 15 | return 16 | } 17 | 18 | if g.Config().Transfer.Addr == "" { 19 | return 20 | } 21 | 22 | for _, v := range funcs.Mappers { 23 | go collect(int64(v.Interval), v.Fs) 24 | } 25 | } 26 | 27 | func collect(sec int64, fns []func() []*model.MetricValue) { 28 | for { 29 | go MetricToTransfer(sec, fns) 30 | time.Sleep(time.Duration(sec) * time.Second) 31 | } 32 | } 33 | 34 | func MetricToTransfer(sec int64, fns []func() []*model.MetricValue) { 35 | mvs := []*model.MetricValue{} 36 | 37 | for _, fn := range fns { 38 | items := fn() 39 | if items == nil { 40 | continue 41 | } 42 | 43 | if len(items) == 0 { 44 | continue 45 | } 46 | 47 | for _, mv := range items { 48 | mvs = append(mvs, mv) 49 | } 50 | } 51 | 52 | startTime := time.Now() 53 | 54 | //分批次传给transfer 55 | n := 5000 56 | lenMvs := len(mvs) 57 | 58 | div := lenMvs / n 59 | mod := math.Mod(float64(lenMvs), float64(n)) 60 | 61 | mvsSend := []*model.MetricValue{} 62 | for i := 1; i <= div+1; i++ { 63 | 64 | if i < div+1 { 65 | mvsSend = mvs[n*(i-1) : n*i] 66 | } else { 67 | mvsSend = mvs[n*(i-1) : (n*(i-1))+int(mod)] 68 | } 69 | time.Sleep(100 * time.Millisecond) 70 | if g.Config().Transfer.N9eMode { 71 | go g.N9ePush(mvsSend) 72 | } else { 73 | go g.SendToTransfer(mvsSend) 74 | } 75 | } 76 | 77 | endTime := time.Now() 78 | log.Println("INFO : Send metrics to transfer running in the background. Process time :", endTime.Sub(startTime), "Send metrics :", len(mvs)) 79 | } 80 | -------------------------------------------------------------------------------- /custom.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "metrics": 3 | [ 4 | { 5 | "ipRange":[ 6 | "192.168.1.160","192.168.1.161" 7 | ], 8 | "metric":"switch.AnyconnectSession", 9 | "tag":"", 10 | "type":"GAUGE", 11 | "oid":"1.3.6.1.4.1.9.9.392.1.3.35.0" 12 | }, 13 | { 14 | "ipRange":[ 15 | "192.168.1.160-192.168.1.161" 16 | ], 17 | "metric":"switch.ConnectionStat", 18 | "tag":"", 19 | "type":"GAUGE", 20 | "oid":"1.3.6.1.4.1.9.9.147.1.2.2.2.1.5.40.6" 21 | }, 22 | { 23 | "ipRange":[ 24 | "192.168.33.2" 25 | ], 26 | "metric":"switch.TempStatus", 27 | "tag":"", 28 | "type":"GAUGE", 29 | "oid":"1.3.6.1.4.1.9.9.13.1.3.1.3.1004" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /funcs/checker.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func CheckCollector() { 8 | 9 | output := make(map[string]bool) 10 | 11 | output["CpuMetrics "] = len(CpuMetrics()) > 0 12 | output["MemMetrics "] = len(MemMetrics()) > 0 13 | output["SwIfMetrics "] = len(SwIfMetrics()) > 0 14 | 15 | for k, v := range output { 16 | status := "fail" 17 | if v { 18 | status = "ok" 19 | } 20 | fmt.Println(k, "...", status) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /funcs/common.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/gaochao1/swcollector/g" 7 | "github.com/open-falcon/common/model" 8 | ) 9 | 10 | func NewMetricValue(metric string, val interface{}, dataType string, tags ...string) *model.MetricValue { 11 | mv := model.MetricValue{ 12 | Metric: metric, 13 | Value: val, 14 | Type: dataType, 15 | } 16 | 17 | size := len(tags) 18 | validTags := []string{} 19 | for _, t := range tags { 20 | if t != "" { 21 | validTags = append(validTags, t) 22 | } 23 | } 24 | if size > 0 { 25 | mv.Tags = strings.Join(validTags, ",") 26 | } 27 | 28 | return &mv 29 | } 30 | 31 | func GaugeValue(metric string, val interface{}, tags ...string) *model.MetricValue { 32 | return NewMetricValue(metric, val, "GAUGE", tags...) 33 | } 34 | 35 | func CounterValue(metric string, val interface{}, tags ...string) *model.MetricValue { 36 | return NewMetricValue(metric, val, "COUNTER", tags...) 37 | } 38 | 39 | func NewMetricValueIp(TS int64, ip, metric string, val interface{}, dataType string, tags ...string) *model.MetricValue { 40 | sec := int64(g.Config().Transfer.Interval) 41 | 42 | mv := model.MetricValue{ 43 | Metric: metric, 44 | Value: val, 45 | Type: dataType, 46 | Endpoint: ip, 47 | Step: sec, 48 | Timestamp: TS, 49 | } 50 | 51 | size := len(tags) 52 | validTags := []string{} 53 | for _, t := range tags { 54 | if t != "" { 55 | validTags = append(validTags, t) 56 | } 57 | } 58 | if size > 0 { 59 | mv.Tags = strings.Join(validTags, ",") 60 | } 61 | 62 | return &mv 63 | } 64 | 65 | func GaugeValueIp(TS int64, ip, metric string, val interface{}, tags ...string) *model.MetricValue { 66 | return NewMetricValueIp(TS, ip, metric, val, "GAUGE", tags...) 67 | } 68 | 69 | func CounterValueIp(TS int64, ip, metric string, val interface{}, tags ...string) *model.MetricValue { 70 | return NewMetricValueIp(TS, ip, metric, val, "COUNTER", tags...) 71 | } 72 | -------------------------------------------------------------------------------- /funcs/custmetric.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "strconv" 7 | 8 | "time" 9 | 10 | go_snmp "github.com/gaochao1/gosnmp" 11 | "github.com/gaochao1/sw" 12 | "github.com/gaochao1/swcollector/g" 13 | "github.com/open-falcon/common/model" 14 | ) 15 | 16 | type CustM struct { 17 | Ip string 18 | custmMetrics []CustmMetric 19 | } 20 | type CustmMetric struct { 21 | metric string 22 | tag string 23 | value float64 24 | metrictype string 25 | } 26 | 27 | func InArray(str string, array []string) bool { 28 | for _, s := range array { 29 | if str == s { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | func AllCustmIp(ipRange []string) (allIp []string) { 37 | if len(ipRange) > 0 { 38 | for _, sip := range ipRange { 39 | aip := sw.ParseIp(sip) 40 | for _, ip := range aip { 41 | allIp = append(allIp, ip) 42 | } 43 | } 44 | } 45 | return allIp 46 | } 47 | 48 | func CustMetrics() (L []*model.MetricValue) { 49 | if !g.Config().CustomMetrics.Enabled { 50 | return 51 | } 52 | chs := make([]chan CustM, 0) 53 | for _, ip := range AliveIp { 54 | if ip != "" { 55 | for _, metric := range g.CustConfig().Metrics { 56 | CustmIps := AllCustmIp(metric.IpRange) 57 | if InArray(ip, CustmIps) { 58 | chss := make(chan CustM) 59 | go custMetrics(ip, metric, chss) 60 | chs = append(chs, chss) 61 | } 62 | } 63 | 64 | } 65 | } 66 | for _, ch := range chs { 67 | custm, ok := <-ch 68 | if !ok { 69 | continue 70 | } 71 | for _, custmmetric := range custm.custmMetrics { 72 | if custmmetric.metrictype == "GAUGE" { 73 | L = append(L, GaugeValueIp(time.Now().Unix(), custm.Ip, custmmetric.metric, custmmetric.value, custmmetric.tag)) 74 | } 75 | if custmmetric.metrictype == "COUNTER" { 76 | L = append(L, CounterValueIp(time.Now().Unix(), custm.Ip, custmmetric.metric, custmmetric.value, custmmetric.tag)) 77 | } 78 | 79 | } 80 | 81 | } 82 | 83 | return L 84 | } 85 | 86 | func custMetrics(ip string, metric *g.MetricConfig, ch chan CustM) { 87 | var custm CustM 88 | var custmmetric CustmMetric 89 | var custmmetrics []CustmMetric 90 | value, err := GetCustMetric(ip, g.Config().Switch.Community, metric.Oid, g.Config().Switch.SnmpTimeout, g.Config().Switch.SnmpRetry) 91 | if err != nil { 92 | log.Println(ip, metric.Oid, err) 93 | close(ch) 94 | return 95 | } else { 96 | custmmetric.metric = metric.Metric 97 | custmmetric.metrictype = metric.Type 98 | custmmetric.tag = metric.Tag 99 | custmmetric.value = value 100 | custmmetrics = append(custmmetrics, custmmetric) 101 | } 102 | 103 | custm.Ip = ip 104 | custm.custmMetrics = custmmetrics 105 | ch <- custm 106 | return 107 | } 108 | 109 | func GetCustMetric(ip, community, oid string, timeout, retry int) (float64, error) { 110 | defer func() { 111 | if r := recover(); r != nil { 112 | log.Println(ip+" Recovered in CustomMetric, Oid is ", oid, r) 113 | } 114 | }() 115 | method := "get" 116 | var value float64 117 | var err error 118 | var snmpPDUs []go_snmp.SnmpPDU 119 | for i := 0; i < retry; i++ { 120 | snmpPDUs, err = sw.RunSnmp(ip, community, oid, method, timeout) 121 | if len(snmpPDUs) > 0 && err == nil { 122 | value, err = interfaceTofloat64(snmpPDUs[0].Value) 123 | break 124 | } 125 | time.Sleep(100 * time.Millisecond) 126 | } 127 | return value, err 128 | } 129 | 130 | func interfaceTofloat64(v interface{}) (float64, error) { 131 | var err error 132 | switch value := v.(type) { 133 | case int: 134 | return float64(value), nil 135 | case int8: 136 | return float64(value), nil 137 | case int16: 138 | return float64(value), nil 139 | case int32: 140 | return float64(value), nil 141 | case int64: 142 | return float64(value), nil 143 | case uint: 144 | return float64(value), nil 145 | case uint8: 146 | return float64(value), nil 147 | case uint16: 148 | return float64(value), nil 149 | case uint32: 150 | return float64(value), nil 151 | case uint64: 152 | return float64(value), nil 153 | case float32: 154 | return float64(value), nil 155 | case float64: 156 | return value, nil 157 | case string: 158 | value_parsed, err := strconv.ParseFloat(value, 64) 159 | if err != nil { 160 | return 0, err 161 | } else { 162 | return value_parsed, nil 163 | } 164 | default: 165 | err = errors.New("value cannot not Parse to digital") 166 | return 0, err 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /funcs/ecmc.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/gaochao1/swcollector/g" 14 | ) 15 | 16 | type EcmcRes struct { 17 | Dat json.RawMessage `json:"dat"` 18 | Err string `json:"err"` 19 | } 20 | 21 | type Host struct { 22 | ID int64 `json:"id"` 23 | SN string `json:"sn"` 24 | IP string `json:"ip"` 25 | Name string `json:"name"` 26 | Note string `json:"note"` 27 | Cpu string `json:"cpu"` 28 | Mem string `json:"mem"` 29 | Disk string `json:"disk"` 30 | Cate string `json:"cate"` 31 | Tenant string `json:"tenant"` 32 | } 33 | 34 | type n9eHost struct { 35 | ID int64 `json:"id"` 36 | Ident string `json:"ident"` 37 | Alias string `json:"alias"` 38 | } 39 | 40 | type NodeHosts struct { 41 | List []Host `json:"list"` 42 | Total int64 `json:"total"` 43 | } 44 | 45 | // HTTPGet 发起一个 http get 请求 46 | func HTTPGet(url string, headers map[string]string) (body []byte, err error) { 47 | body = []byte{} 48 | req, err := http.NewRequest("GET", url, nil) 49 | if err != nil { 50 | return 51 | } 52 | for k, v := range headers { 53 | req.Header.Set(k, v) 54 | } 55 | 56 | tr := &http.Transport{ 57 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 58 | } 59 | client := &http.Client{ 60 | Transport: tr, 61 | Timeout: 15 * time.Second, 62 | } 63 | resp, err := client.Do(req) 64 | if err != nil { 65 | return 66 | } 67 | defer resp.Body.Close() 68 | 69 | if resp.StatusCode != 200 { 70 | body, _ = ioutil.ReadAll(resp.Body) 71 | erroMsg := fmt.Sprintf("HTTP Connect Failed, Code is %d, body is %s", resp.StatusCode, string(body)) 72 | err = errors.New(erroMsg) 73 | return 74 | } 75 | body, err = ioutil.ReadAll(resp.Body) 76 | if err != nil { 77 | return 78 | } 79 | return 80 | } 81 | 82 | func GetNodeHosts(nodeID int64) (hosts []Host, err error) { 83 | p := 1 84 | for { 85 | apiAddr := fmt.Sprintf("%s/api/hsp/node/obj/%d/host?p=%d", g.Config().Ecmc.Addr, nodeID, p) 86 | headers := map[string]string{} 87 | headers["x-user-token"] = g.Config().Ecmc.Token 88 | var res []byte 89 | res, err = HTTPGet(apiAddr, headers) 90 | if err != nil { 91 | return 92 | } 93 | var ecmcRes EcmcRes 94 | if err = json.Unmarshal(res, &ecmcRes); err != nil { 95 | return 96 | } 97 | if ecmcRes.Err != "" { 98 | err = errors.New(ecmcRes.Err) 99 | return 100 | } 101 | var nodeHosts NodeHosts 102 | if err = json.Unmarshal(ecmcRes.Dat, &nodeHosts); err != nil { 103 | return 104 | } 105 | if len(nodeHosts.List) == 0 { 106 | break 107 | } 108 | hosts = append(hosts, nodeHosts.List...) 109 | p = p + 1 110 | } 111 | return 112 | } 113 | 114 | func GetAllByIpByEcmc() (ips []string) { 115 | ipMap := map[string]bool{} 116 | for _, id := range g.Config().Ecmc.Nodes { 117 | hosts, err := GetNodeHosts(id) 118 | if err != nil { 119 | log.Println(err) 120 | continue 121 | } 122 | for _, host := range hosts { 123 | if _, ok := ipMap[host.IP]; ok { 124 | continue 125 | } 126 | ipMap[host.IP] = true 127 | ips = append(ips, host.IP) 128 | } 129 | } 130 | return 131 | } 132 | -------------------------------------------------------------------------------- /funcs/funcs.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "github.com/gaochao1/swcollector/g" 5 | "github.com/open-falcon/common/model" 6 | ) 7 | 8 | type FuncsAndInterval struct { 9 | Fs []func() []*model.MetricValue 10 | Interval int 11 | } 12 | 13 | var Mappers []FuncsAndInterval 14 | 15 | func BuildMappers() { 16 | interval := g.Config().Transfer.Interval 17 | Mappers = []FuncsAndInterval{ 18 | FuncsAndInterval{ 19 | Fs: []func() []*model.MetricValue{ 20 | SwIfMetrics, 21 | CpuMetrics, 22 | MemMetrics, 23 | PingMetrics, 24 | CustMetrics, 25 | }, 26 | Interval: interval, 27 | }, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /funcs/n9e.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | 9 | "log" 10 | 11 | "github.com/gaochao1/swcollector/g" 12 | ) 13 | 14 | type N9eNodeHosts struct { 15 | List []N9eHost `json:"list"` 16 | Total int64 `json:"total"` 17 | } 18 | 19 | type N9eHost struct { 20 | ID int64 `json:"id"` 21 | IP string `json:"ident"` 22 | Alias string `json:"alias"` 23 | } 24 | 25 | func basicAuth(username, password string) string { 26 | auth := username + ":" + password 27 | return base64.StdEncoding.EncodeToString([]byte(auth)) 28 | } 29 | 30 | func GetN9eHosts(nodeID int64) (hosts []N9eHost, err error) { 31 | p := 1 32 | for { 33 | apiAddr := fmt.Sprintf("%s/api/portal/node/%d/endpoint?p=%d", g.Config().N9e.Addr, nodeID, p) 34 | headers := map[string]string{} 35 | headers["Authorization"] = "Basic " + basicAuth(g.Config().N9e.User, g.Config().N9e.Pass) 36 | var res []byte 37 | res, err = HTTPGet(apiAddr, headers) 38 | if err != nil { 39 | return 40 | } 41 | var ecmcRes EcmcRes 42 | if err = json.Unmarshal(res, &ecmcRes); err != nil { 43 | return 44 | } 45 | if ecmcRes.Err != "" { 46 | err = errors.New(ecmcRes.Err) 47 | return 48 | } 49 | var nodeHosts N9eNodeHosts 50 | if err = json.Unmarshal(ecmcRes.Dat, &nodeHosts); err != nil { 51 | return 52 | } 53 | if len(nodeHosts.List) == 0 { 54 | break 55 | } 56 | hosts = append(hosts, nodeHosts.List...) 57 | p = p + 1 58 | } 59 | return 60 | } 61 | 62 | func GetAllByIpByN9e() (ips []string) { 63 | ipMap := map[string]bool{} 64 | for _, id := range g.Config().N9e.Nodes { 65 | hosts, err := GetN9eHosts(id) 66 | if err != nil { 67 | log.Println(err) 68 | continue 69 | } 70 | for _, host := range hosts { 71 | if _, ok := ipMap[host.IP]; ok { 72 | continue 73 | } 74 | ipMap[host.IP] = true 75 | ips = append(ips, host.IP) 76 | } 77 | } 78 | return 79 | } 80 | -------------------------------------------------------------------------------- /funcs/n9ev3.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "log" 9 | 10 | "github.com/gaochao1/swcollector/g" 11 | ) 12 | 13 | type N9eV3NodeHosts struct { 14 | List []N9eV3Host `json:"list"` 15 | Total int64 `json:"total"` 16 | } 17 | 18 | type N9eV3Host struct { 19 | ID int64 `json:"id"` 20 | UUID string `json:"uuid"` 21 | Ident string `json:"ident"` 22 | Name string `json:"name"` 23 | Labels string `json:"labels"` 24 | Note string `json:"note"` 25 | Extend string `json:"extend"` 26 | Cate string `json:"cate"` 27 | Tenant string `json:"tenant"` 28 | LastUpdated string `json:"last_updated"` 29 | } 30 | 31 | func GetN9eV3NodeHosts(nodeID int64) (hosts []N9eV3Host, err error) { 32 | p := 1 33 | for { 34 | apiAddr := fmt.Sprintf("%s/api/rdb/node/%d/resources?p=%d", g.Config().N9eV3.Addr, nodeID, p) 35 | headers := map[string]string{} 36 | headers["x-user-token"] = g.Config().N9eV3.Token 37 | var res []byte 38 | res, err = HTTPGet(apiAddr, headers) 39 | if err != nil { 40 | return 41 | } 42 | var n9eV3Res EcmcRes 43 | if err = json.Unmarshal(res, &n9eV3Res); err != nil { 44 | return 45 | } 46 | if n9eV3Res.Err != "" { 47 | err = errors.New(n9eV3Res.Err) 48 | return 49 | } 50 | var nodeHosts N9eV3NodeHosts 51 | if err = json.Unmarshal(n9eV3Res.Dat, &nodeHosts); err != nil { 52 | return 53 | } 54 | if len(nodeHosts.List) == 0 { 55 | break 56 | } 57 | hosts = append(hosts, nodeHosts.List...) 58 | p = p + 1 59 | } 60 | return 61 | } 62 | 63 | func GetAllByIpByN9eV3() (ips []string) { 64 | ipMap := map[string]bool{} 65 | for _, id := range g.Config().N9eV3.Nodes { 66 | hosts, err := GetN9eV3NodeHosts(id) 67 | if err != nil { 68 | log.Println(err) 69 | continue 70 | } 71 | for _, host := range hosts { 72 | if _, ok := ipMap[host.Ident]; ok { 73 | continue 74 | } 75 | ipMap[host.Ident] = true 76 | ips = append(ips, host.Ident) 77 | } 78 | } 79 | return 80 | } 81 | -------------------------------------------------------------------------------- /funcs/swcpu.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/gaochao1/sw" 8 | "github.com/gaochao1/swcollector/g" 9 | "github.com/open-falcon/common/model" 10 | ) 11 | 12 | type SwCpu struct { 13 | Ip string 14 | CpuUtil int 15 | } 16 | 17 | func CpuMetrics() (L []*model.MetricValue) { 18 | 19 | chs := make([]chan SwCpu, len(AliveIp)) 20 | for i, ip := range AliveIp { 21 | if ip != "" { 22 | chs[i] = make(chan SwCpu) 23 | go cpuMetrics(ip, chs[i]) 24 | } 25 | } 26 | 27 | for _, ch := range chs { 28 | swCpu, ok := <-ch 29 | if !ok { 30 | continue 31 | } 32 | L = append(L, GaugeValueIp(time.Now().Unix(), swCpu.Ip, "switch.CpuUtilization", swCpu.CpuUtil)) 33 | } 34 | 35 | return L 36 | } 37 | 38 | func cpuMetrics(ip string, ch chan SwCpu) { 39 | var swCpu SwCpu 40 | 41 | cpuUtili, err := sw.CpuUtilization(ip, g.Config().Switch.Community, g.Config().Switch.SnmpTimeout, g.Config().Switch.SnmpRetry) 42 | if err != nil { 43 | log.Println(err) 44 | close(ch) 45 | return 46 | } 47 | 48 | swCpu.Ip = ip 49 | swCpu.CpuUtil = cpuUtili 50 | ch <- swCpu 51 | 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /funcs/swifstat.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | 7 | "github.com/gaochao1/swcollector/g" 8 | "github.com/open-falcon/common/model" 9 | 10 | "github.com/gaochao1/sw" 11 | "github.com/toolkits/slice" 12 | 13 | "strconv" 14 | "time" 15 | ) 16 | 17 | type ChIfStat struct { 18 | Ip string 19 | PingResult bool 20 | UseTime int64 21 | IfStatsList *[]sw.IfStats 22 | } 23 | 24 | type LastifMap struct { 25 | lock *sync.RWMutex 26 | ifstat map[string]*[]sw.IfStats 27 | } 28 | 29 | func NewLastifMap() { 30 | lastifmap = &LastifMap{ 31 | lock: new(sync.RWMutex), 32 | ifstat: make(map[string]*[]sw.IfStats), 33 | } 34 | } 35 | 36 | func (m *LastifMap) Get(k string) *[]sw.IfStats { 37 | m.lock.RLock() 38 | defer m.lock.RUnlock() 39 | if val, ok := m.ifstat[k]; ok { 40 | return val 41 | } 42 | return nil 43 | } 44 | 45 | func (m *LastifMap) Set(k string, v *[]sw.IfStats) { 46 | m.lock.Lock() 47 | defer m.lock.Unlock() 48 | m.ifstat[k] = v 49 | return 50 | } 51 | 52 | func (m *LastifMap) Check(k string) bool { 53 | m.lock.RLock() 54 | defer m.lock.RUnlock() 55 | if _, ok := m.ifstat[k]; !ok { 56 | return false 57 | } 58 | return true 59 | } 60 | 61 | var ( 62 | AliveIp []string 63 | pingTimeout int 64 | pingRetry int 65 | lastifmap *LastifMap 66 | community string 67 | snmpTimeout int 68 | snmpRetry int 69 | displayByBit bool 70 | gosnmp bool 71 | ignoreIface []string 72 | ignorePkt bool 73 | ignoreBroadcastPkt bool 74 | ignoreMulticastPkt bool 75 | ignoreDiscards bool 76 | ignoreErrors bool 77 | ignoreOperStatus bool 78 | ignoreUnknownProtos bool 79 | ignoreOutQLen bool 80 | ignoreSpeedPercent bool 81 | fastPingMode bool 82 | limitCon int 83 | ) 84 | 85 | func initVariable() { 86 | pingTimeout = g.Config().Switch.PingTimeout 87 | fastPingMode = g.Config().Switch.FastPingMode 88 | pingRetry = g.Config().Switch.PingRetry 89 | 90 | community = g.Config().Switch.Community 91 | snmpTimeout = g.Config().Switch.SnmpTimeout 92 | snmpRetry = g.Config().Switch.SnmpRetry 93 | limitCon = g.Config().Switch.LimitCon 94 | 95 | gosnmp = g.Config().Switch.Gosnmp 96 | ignoreIface = g.Config().Switch.IgnoreIface 97 | ignorePkt = g.Config().Switch.IgnorePkt 98 | ignoreOperStatus = g.Config().Switch.IgnoreOperStatus 99 | ignoreBroadcastPkt = g.Config().Switch.IgnoreBroadcastPkt 100 | ignoreMulticastPkt = g.Config().Switch.IgnoreMulticastPkt 101 | ignoreDiscards = g.Config().Switch.IgnoreDiscards 102 | ignoreErrors = g.Config().Switch.IgnoreErrors 103 | ignoreUnknownProtos = g.Config().Switch.IgnoreUnknownProtos 104 | ignoreOutQLen = g.Config().Switch.IgnoreOutQLen 105 | } 106 | 107 | func GetAllByIpRange() (allIp []string) { 108 | switchIp := g.Config().Switch.IpRange 109 | 110 | if len(switchIp) > 0 { 111 | for _, sip := range switchIp { 112 | aip := sw.ParseIp(sip) 113 | for _, ip := range aip { 114 | allIp = append(allIp, ip) 115 | } 116 | } 117 | } 118 | return allIp 119 | } 120 | 121 | func AllSwitchIp() (allIp []string) { 122 | if g.Config().N9eV3.Enabled { 123 | allIp = GetAllByIpByN9eV3() 124 | return allIp 125 | } 126 | if g.Config().Ecmc.Enabled { 127 | allIp = GetAllByIpByEcmc() 128 | return allIp 129 | } 130 | if g.Config().N9e.Enabled { 131 | allIp = GetAllByIpByN9e() 132 | return allIp 133 | } 134 | allIp = GetAllByIpRange() 135 | return allIp 136 | } 137 | 138 | func SwIfMetrics() (L []*model.MetricValue) { 139 | if g.Config().Switch.Enabled { 140 | return swIfMetrics() 141 | } 142 | return 143 | } 144 | 145 | func swIfMetrics() (L []*model.MetricValue) { 146 | if g.ReloadType() { 147 | g.ParseConfig(g.ConfigFile) 148 | if g.Config().SwitchHosts.Enabled { 149 | hostcfg := g.Config().SwitchHosts.Hosts 150 | g.ParseHostConfig(hostcfg) 151 | } 152 | if g.Config().CustomMetrics.Enabled { 153 | custMetrics := g.Config().CustomMetrics.Template 154 | g.ParseCustConfig(custMetrics) 155 | } 156 | AliveIp = nil 157 | } 158 | initVariable() 159 | ts := time.Now().Unix() 160 | allIp := AllSwitchIp() 161 | timeout := time.Duration(g.Config().Transfer.Interval) * time.Second 162 | 163 | chs := make([]chan ChIfStat, len(allIp)) 164 | limitCh := make(chan bool, g.Config().Switch.LimitConcur) 165 | startTime := time.Now() 166 | log.Printf("UpdateIfStats start. The number of concurrent limited to %d. IP addresses number is %d", g.Config().Switch.LimitConcur, len(allIp)) 167 | if gosnmp { 168 | log.Println("get snmp message by gosnmp") 169 | } else { 170 | log.Println("get snmp message by snmpwalk") 171 | } 172 | for i, ip := range allIp { 173 | chs[i] = make(chan ChIfStat) 174 | limitCh <- true 175 | go coreSwIfMetrics(ip, chs[i], limitCh) 176 | time.Sleep(5 * time.Millisecond) 177 | } 178 | for i, ch := range chs { 179 | select { 180 | case chIfStat, ok := <-ch: 181 | if !ok { 182 | continue 183 | } 184 | 185 | if chIfStat.PingResult == true && !slice.ContainsString(AliveIp, chIfStat.Ip) { 186 | AliveIp = append(AliveIp, chIfStat.Ip) 187 | } 188 | if chIfStat.IfStatsList != nil { 189 | if g.Config().Debug { 190 | log.Println("IP:", chIfStat.Ip, "PingResult:", chIfStat.PingResult, "len_list:", len(*chIfStat.IfStatsList), "UsedTime:", chIfStat.UseTime) 191 | } 192 | 193 | for _, ifStat := range *chIfStat.IfStatsList { 194 | ifNameTag := "ifName=" + ifStat.IfName 195 | ifIndexTag := "" 196 | if g.Config().Switch.IndexTag { 197 | ifIndexTag = "ifIndex=" + strconv.Itoa(ifStat.IfIndex) 198 | } 199 | ip := chIfStat.Ip 200 | if ignoreOperStatus == false { 201 | L = append(L, GaugeValueIp(ifStat.TS, ip, "switch.if.OperStatus", ifStat.IfOperStatus, ifNameTag, ifIndexTag)) 202 | } 203 | if ignoreSpeedPercent == false { 204 | L = append(L, GaugeValueIp(ifStat.TS, ip, "switch.if.Speed", ifStat.IfSpeed, ifNameTag, ifIndexTag)) 205 | } 206 | if ignoreBroadcastPkt == false { 207 | if lastIfStatList := lastifmap.Get(chIfStat.Ip); lastIfStatList != nil { 208 | for _, lastifStat := range *lastIfStatList { 209 | if ifStat.IfIndex == lastifStat.IfIndex { 210 | interval := ifStat.TS - lastifStat.TS 211 | broadcastlimit := g.Config().Switch.BroadcastPktlimit 212 | IfHCInBroadcastPkts := (float64(ifStat.IfHCInBroadcastPkts) - float64(lastifStat.IfHCInBroadcastPkts)) / float64(interval) 213 | IfHCOutBroadcastPkts := (float64(ifStat.IfHCOutBroadcastPkts) - float64(lastifStat.IfHCOutBroadcastPkts)) / float64(interval) 214 | if limitCheck(IfHCInBroadcastPkts, broadcastlimit) { 215 | L = append(L, GaugeValueIp(ts, ip, "switch.if.InBroadcastPkt", IfHCInBroadcastPkts, ifNameTag, ifIndexTag)) 216 | } else { 217 | log.Println(ip, ifNameTag, "switch.if.InBroadcastPkt ", "out of range, value is ", IfHCInBroadcastPkts, "Limit is ", broadcastlimit) 218 | log.Println("IfHCInBroadcastPkts This Time: ", ifStat.IfHCInBroadcastPkts) 219 | log.Println("IfHCInBroadcastPkts Last Time: ", lastifStat.IfHCInBroadcastPkts) 220 | } 221 | if limitCheck(IfHCOutBroadcastPkts, broadcastlimit) { 222 | L = append(L, GaugeValueIp(ts, ip, "switch.if.OutBroadcastPkt", IfHCOutBroadcastPkts, ifNameTag, ifIndexTag)) 223 | } else { 224 | log.Println(ip, ifNameTag, "switch.if.OutBroadcastPkt ", "out of range, value is ", IfHCOutBroadcastPkts, "Limit is ", broadcastlimit) 225 | log.Println("IfHCOutBroadcastPkts This Time: ", ifStat.IfHCOutBroadcastPkts) 226 | log.Println("IfHCOutBroadcastPkts Last Time: ", lastifStat.IfHCOutBroadcastPkts) 227 | } 228 | } 229 | } 230 | } 231 | } 232 | if ignoreMulticastPkt == false { 233 | if lastIfStatList := lastifmap.Get(chIfStat.Ip); lastIfStatList != nil { 234 | for _, lastifStat := range *lastIfStatList { 235 | if ifStat.IfIndex == lastifStat.IfIndex { 236 | interval := ifStat.TS - lastifStat.TS 237 | multicastlimit := g.Config().Switch.MulticastPktlimit 238 | IfHCInMulticastPkts := (float64(ifStat.IfHCInMulticastPkts) - float64(lastifStat.IfHCInMulticastPkts)) / float64(interval) 239 | IfHCOutMulticastPkts := (float64(ifStat.IfHCOutMulticastPkts) - float64(lastifStat.IfHCOutMulticastPkts)) / float64(interval) 240 | if limitCheck(IfHCInMulticastPkts, multicastlimit) { 241 | L = append(L, GaugeValueIp(ts, ip, "switch.if.InMulticastPkt", IfHCInMulticastPkts, ifNameTag, ifIndexTag)) 242 | } else { 243 | log.Println(ip, ifNameTag, "switch.if.InMulticastPkt ", "out of range, value is ", IfHCInMulticastPkts, "Limit is ", multicastlimit) 244 | log.Println("IfHCInMulticastPkts This Time: ", ifStat.IfHCInMulticastPkts) 245 | log.Println("IfHCInMulticastPkts Last Time: ", lastifStat.IfHCInMulticastPkts) 246 | } 247 | if limitCheck(IfHCOutMulticastPkts, multicastlimit) { 248 | L = append(L, GaugeValueIp(ts, ip, "switch.if.OutMulticastPkt", IfHCOutMulticastPkts, ifNameTag, ifIndexTag)) 249 | } else { 250 | log.Println(ip, ifNameTag, "switch.if.OutMulticastPkt ", "out of range, value is ", IfHCOutMulticastPkts, "Limit is ", multicastlimit) 251 | log.Println("IfHCOutMulticastPkts This Time: ", ifStat.IfHCOutMulticastPkts) 252 | log.Println("IfHCOutMulticastPkts Last Time: ", lastifStat.IfHCOutMulticastPkts) 253 | } 254 | } 255 | } 256 | } 257 | } 258 | 259 | if ignoreDiscards == false { 260 | if lastIfStatList := lastifmap.Get(chIfStat.Ip); lastIfStatList != nil { 261 | for _, lastifStat := range *lastIfStatList { 262 | if ifStat.IfIndex == lastifStat.IfIndex { 263 | interval := ifStat.TS - lastifStat.TS 264 | discardlimit := g.Config().Switch.DiscardsPktlimit 265 | IfInDiscards := (float64(ifStat.IfInDiscards) - float64(lastifStat.IfInDiscards)) / float64(interval) 266 | IfOutDiscards := (float64(ifStat.IfOutDiscards) - float64(lastifStat.IfOutDiscards)) / float64(interval) 267 | if limitCheck(IfInDiscards, discardlimit) { 268 | L = append(L, GaugeValueIp(ts, ip, "switch.if.InDiscards", IfInDiscards, ifNameTag, ifIndexTag)) 269 | } else { 270 | log.Println(ip, ifNameTag, "switch.if.InDiscards ", "out of range, value is ", IfInDiscards, "Limit is ", discardlimit) 271 | log.Println("IfInDiscards This Time: ", ifStat.IfInDiscards) 272 | log.Println("IfInDiscards Last Time: ", lastifStat.IfInDiscards) 273 | } 274 | if limitCheck(IfOutDiscards, discardlimit) { 275 | L = append(L, GaugeValueIp(ts, ip, "switch.if.OutDiscards", IfOutDiscards, ifNameTag, ifIndexTag)) 276 | } else { 277 | log.Println(ip, ifNameTag, "switch.if.OutDiscards ", "out of range, value is ", IfOutDiscards, "Limit is ", discardlimit) 278 | log.Println("IfOutDiscards This Time: ", ifStat.IfOutDiscards) 279 | log.Println("IfOutDiscards Last Time: ", lastifStat.IfOutDiscards) 280 | } 281 | } 282 | } 283 | } 284 | } 285 | 286 | if ignoreErrors == false { 287 | if lastIfStatList := lastifmap.Get(chIfStat.Ip); lastIfStatList != nil { 288 | for _, lastifStat := range *lastIfStatList { 289 | if ifStat.IfIndex == lastifStat.IfIndex { 290 | interval := ifStat.TS - lastifStat.TS 291 | errorlimit := g.Config().Switch.ErrorsPktlimit 292 | IfInErrors := (float64(ifStat.IfInErrors) - float64(lastifStat.IfInErrors)) / float64(interval) 293 | IfOutErrors := (float64(ifStat.IfOutErrors) - float64(lastifStat.IfOutErrors)) / float64(interval) 294 | if limitCheck(IfInErrors, errorlimit) { 295 | L = append(L, GaugeValueIp(ts, ip, "switch.if.InErrors", IfInErrors, ifNameTag, ifIndexTag)) 296 | } else { 297 | log.Println(ip, ifNameTag, "switch.if.InErrors ", "out of range, value is ", IfInErrors, "Limit is ", errorlimit) 298 | log.Println("IfInErrors This Time: ", ifStat.IfInErrors) 299 | log.Println("IfInErrors Last Time: ", lastifStat.IfInErrors) 300 | } 301 | if limitCheck(IfOutErrors, errorlimit) { 302 | L = append(L, GaugeValueIp(ts, ip, "switch.if.OutErrors", IfOutErrors, ifNameTag, ifIndexTag)) 303 | } else { 304 | log.Println(ip, ifNameTag, "switch.if.OutErrors ", "out of range, value is ", IfOutErrors, "Limit is ", errorlimit) 305 | log.Println("IfOutErrors This Time: ", ifStat.IfOutErrors) 306 | log.Println("IfOutErrors Last Time: ", lastifStat.IfOutErrors) 307 | } 308 | } 309 | } 310 | } 311 | } 312 | 313 | if ignoreUnknownProtos == false { 314 | if lastIfStatList := lastifmap.Get(chIfStat.Ip); lastIfStatList != nil { 315 | for _, lastifStat := range *lastIfStatList { 316 | if ifStat.IfIndex == lastifStat.IfIndex { 317 | interval := ifStat.TS - lastifStat.TS 318 | unknownProtoslimit := g.Config().Switch.UnknownProtosPktlimit 319 | IfInUnknownProtos := (float64(ifStat.IfInUnknownProtos) - float64(lastifStat.IfInUnknownProtos)) / float64(interval) 320 | if limitCheck(IfInUnknownProtos, unknownProtoslimit) { 321 | L = append(L, GaugeValueIp(ts, ip, "switch.if.InUnknownProtos", IfInUnknownProtos, ifNameTag, ifIndexTag)) 322 | } else { 323 | log.Println(ip, ifNameTag, "switch.if.InUnknownProtos ", "out of range, value is ", IfInUnknownProtos, "Limit is ", unknownProtoslimit) 324 | log.Println("IfOutQLen This Time: ", ifStat.IfInUnknownProtos) 325 | log.Println("IfOutQLen Last Time: ", lastifStat.IfInUnknownProtos) 326 | } 327 | } 328 | } 329 | } 330 | } 331 | 332 | if ignoreOutQLen == false { 333 | if lastIfStatList := lastifmap.Get(chIfStat.Ip); lastIfStatList != nil { 334 | for _, lastifStat := range *lastIfStatList { 335 | if ifStat.IfIndex == lastifStat.IfIndex { 336 | interval := ifStat.TS - lastifStat.TS 337 | outQlenlimit := g.Config().Switch.OutQLenPktlimit 338 | IfOutQLen := (float64(ifStat.IfOutQLen) - float64(lastifStat.IfOutQLen)) / float64(interval) 339 | if limitCheck(IfOutQLen, outQlenlimit) { 340 | L = append(L, GaugeValueIp(ts, ip, "switch.if.OutQLen", IfOutQLen, ifNameTag, ifIndexTag)) 341 | } else { 342 | log.Println(ip, ifNameTag, "switch.if.OutQLen ", "out of range, value is ", IfOutQLen, "Limit is ", outQlenlimit) 343 | log.Println("IfOutQLen This Time: ", ifStat.IfOutQLen) 344 | log.Println("IfOutQLen Last Time: ", lastifStat.IfOutQLen) 345 | } 346 | } 347 | } 348 | } 349 | } 350 | 351 | //如果IgnorePkt为false,采集Pkt 352 | if ignorePkt == false { 353 | if lastIfStatList := lastifmap.Get(chIfStat.Ip); lastIfStatList != nil { 354 | for _, lastifStat := range *lastIfStatList { 355 | if ifStat.IfIndex == lastifStat.IfIndex { 356 | interval := ifStat.TS - lastifStat.TS 357 | pktlimit := g.Config().Switch.Pktlimit 358 | IfHCInUcastPkts := (float64(ifStat.IfHCInUcastPkts) - float64(lastifStat.IfHCInUcastPkts)) / float64(interval) 359 | IfHCOutUcastPkts := (float64(ifStat.IfHCOutUcastPkts) - float64(lastifStat.IfHCOutUcastPkts)) / float64(interval) 360 | if limitCheck(IfHCInUcastPkts, pktlimit) { 361 | L = append(L, GaugeValueIp(ts, ip, "switch.if.InPkts", IfHCInUcastPkts, ifNameTag, ifIndexTag)) 362 | } else { 363 | log.Println(ip, ifNameTag, "switch.if.InPkts ", "out of range, value is ", IfHCInUcastPkts, "Limit is ", pktlimit) 364 | log.Println("IfHCInUcastPkts This Time: ", ifStat.IfHCInUcastPkts) 365 | log.Println("IfHCInUcastPkts Last Time: ", lastifStat.IfHCInUcastPkts) 366 | } 367 | if limitCheck(IfHCOutUcastPkts, pktlimit) { 368 | L = append(L, GaugeValueIp(ts, ip, "switch.if.OutPkts", IfHCOutUcastPkts, ifNameTag, ifIndexTag)) 369 | } else { 370 | log.Println(ip, ifNameTag, "switch.if.OutPkts ", "out of range, value is ", IfHCOutUcastPkts, "Limit is ", pktlimit) 371 | log.Println("IfHCOutUcastPkts This Time: ", ifStat.IfHCOutUcastPkts) 372 | log.Println("IfHCOutUcastPkts Last Time: ", lastifStat.IfHCOutUcastPkts) 373 | } 374 | } 375 | } 376 | } 377 | } 378 | if lastIfStatList := lastifmap.Get(chIfStat.Ip); lastIfStatList != nil { 379 | for _, lastifStat := range *lastIfStatList { 380 | if ifStat.IfIndex == lastifStat.IfIndex { 381 | interval := ifStat.TS - lastifStat.TS 382 | speedlimit := g.Config().Switch.Speedlimit 383 | if speedlimit == 0 { 384 | speedlimit = float64(ifStat.IfSpeed) 385 | } 386 | IfHCInOctets := 8 * (float64(ifStat.IfHCInOctets) - float64(lastifStat.IfHCInOctets)) / float64(interval) 387 | IfHCOutOctets := 8 * (float64(ifStat.IfHCOutOctets) - float64(lastifStat.IfHCOutOctets)) / float64(interval) 388 | if limitCheck(IfHCInOctets, speedlimit) { 389 | L = append(L, GaugeValueIp(ts, ip, "switch.if.In", IfHCInOctets, ifNameTag, ifIndexTag)) 390 | if ifStat.IfSpeed > 0 { 391 | InSpeedPercent := 100 * IfHCInOctets / float64(ifStat.IfSpeed) 392 | L = append(L, GaugeValueIp(ts, ip, "switch.if.InSpeedPercent", InSpeedPercent, ifNameTag, ifIndexTag)) 393 | } 394 | } else { 395 | log.Println(ip, ifNameTag, "switch.if.In ", "out of range, value is ", IfHCInOctets) 396 | log.Println("IfHCInOctets This Time: ", ifStat.IfHCInOctets) 397 | log.Println("IfHCInOctets Last Time: ", lastifStat.IfHCInOctets) 398 | } 399 | if limitCheck(IfHCOutOctets, speedlimit) { 400 | L = append(L, GaugeValueIp(ts, ip, "switch.if.Out", IfHCOutOctets, ifNameTag, ifIndexTag)) 401 | if ifStat.IfSpeed > 0 { 402 | OutSpeedPercent := 100 * IfHCOutOctets / float64(ifStat.IfSpeed) 403 | L = append(L, GaugeValueIp(ts, ip, "switch.if.OutSpeedPercent", OutSpeedPercent, ifNameTag, ifIndexTag)) 404 | } 405 | } else { 406 | log.Println(ip, ifNameTag, "switch.if.Out ", "out of range, value is ", IfHCOutOctets) 407 | log.Println("IfHCOutOctets This Time: ", ifStat.IfHCOutOctets) 408 | log.Println("IfHCOutOctets Last Time: ", lastifStat.IfHCOutOctets) 409 | } 410 | } 411 | } 412 | } 413 | } 414 | lastifmap.Set(chIfStat.Ip, chIfStat.IfStatsList) 415 | } 416 | case <-time.After(timeout): 417 | log.Println(allIp[i] + " go runtime timeout") 418 | } 419 | } 420 | 421 | endTime := time.Now() 422 | log.Printf("UpdateIfStats complete. Process time %s. Number of active ip is %d", endTime.Sub(startTime), len(AliveIp)) 423 | 424 | if g.Config().Debug { 425 | for i, v := range AliveIp { 426 | log.Println("AliveIp:", i, v) 427 | } 428 | } 429 | 430 | return 431 | } 432 | 433 | func pingCheck(ip string) bool { 434 | var pingResult bool 435 | for i := 0; i < pingRetry; i++ { 436 | pingResult = sw.Ping(ip, pingTimeout, fastPingMode) 437 | if pingResult == true { 438 | break 439 | } 440 | } 441 | return pingResult 442 | } 443 | 444 | func limitCheck(value float64, limit float64) bool { 445 | if value < 0 { 446 | return false 447 | } 448 | if limit > 0 { 449 | if value > limit { 450 | return false 451 | } 452 | } 453 | return true 454 | } 455 | 456 | func coreSwIfMetrics(ip string, ch chan ChIfStat, limitCh chan bool) { 457 | var startTime, endTime int64 458 | startTime = time.Now().Unix() 459 | 460 | var chIfStat ChIfStat 461 | 462 | pingResult := pingCheck(ip) 463 | 464 | chIfStat.Ip = ip 465 | chIfStat.PingResult = pingResult 466 | 467 | if !pingResult { 468 | endTime = time.Now().Unix() 469 | chIfStat.UseTime = (endTime - startTime) 470 | <-limitCh 471 | ch <- chIfStat 472 | return 473 | } else { 474 | var ifList []sw.IfStats 475 | var err error 476 | 477 | if gosnmp { 478 | ifList, err = sw.ListIfStats(ip, community, snmpTimeout, ignoreIface, snmpRetry, limitCon, ignorePkt, ignoreOperStatus, ignoreBroadcastPkt, ignoreMulticastPkt, ignoreDiscards, ignoreErrors, ignoreUnknownProtos, ignoreOutQLen) 479 | } else { 480 | ifList, err = sw.ListIfStatsSnmpWalk(ip, community, snmpTimeout*5, ignoreIface, snmpRetry, ignorePkt, ignoreOperStatus, ignoreBroadcastPkt, ignoreMulticastPkt, ignoreDiscards, ignoreErrors, ignoreUnknownProtos, ignoreOutQLen) 481 | } 482 | 483 | if err != nil { 484 | log.Printf(ip, err) 485 | close(ch) 486 | } 487 | 488 | if len(ifList) > 0 { 489 | chIfStat.IfStatsList = &ifList 490 | } 491 | 492 | endTime = time.Now().Unix() 493 | chIfStat.UseTime = (endTime - startTime) 494 | <-limitCh 495 | ch <- chIfStat 496 | return 497 | } 498 | 499 | return 500 | } 501 | -------------------------------------------------------------------------------- /funcs/swmem.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/gaochao1/sw" 8 | "github.com/gaochao1/swcollector/g" 9 | "github.com/open-falcon/common/model" 10 | ) 11 | 12 | type SwMem struct { 13 | Ip string 14 | MemUtili int 15 | } 16 | 17 | func MemMetrics() (L []*model.MetricValue) { 18 | 19 | chs := make([]chan SwMem, len(AliveIp)) 20 | for i, ip := range AliveIp { 21 | if ip != "" { 22 | chs[i] = make(chan SwMem) 23 | go memMetrics(ip, chs[i]) 24 | } 25 | } 26 | 27 | for _, ch := range chs { 28 | swMem, ok := <-ch 29 | if !ok { 30 | continue 31 | } 32 | L = append(L, GaugeValueIp(time.Now().Unix(), swMem.Ip, "switch.MemUtilization", swMem.MemUtili)) 33 | } 34 | 35 | return L 36 | } 37 | 38 | func memMetrics(ip string, ch chan SwMem) { 39 | var swMem SwMem 40 | 41 | memUtili, err := sw.MemUtilization(ip, g.Config().Switch.Community, g.Config().Switch.SnmpTimeout, g.Config().Switch.SnmpRetry) 42 | if err != nil { 43 | log.Println(err) 44 | close(ch) 45 | return 46 | } 47 | 48 | swMem.Ip = ip 49 | swMem.MemUtili = memUtili 50 | ch <- swMem 51 | 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /funcs/swping.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/gaochao1/sw" 8 | "github.com/gaochao1/swcollector/g" 9 | "github.com/open-falcon/common/model" 10 | ) 11 | 12 | type SwPing struct { 13 | Ip string 14 | Ping float64 15 | } 16 | 17 | func PingMetrics() (L []*model.MetricValue) { 18 | 19 | chs := make([]chan SwPing, len(AliveIp)) 20 | for i, ip := range AliveIp { 21 | if ip != "" { 22 | chs[i] = make(chan SwPing) 23 | go pingMetrics(ip, chs[i]) 24 | } 25 | } 26 | 27 | for _, ch := range chs { 28 | swPing := <-ch 29 | L = append(L, GaugeValueIp(time.Now().Unix(), swPing.Ip, "switch.Ping", swPing.Ping)) 30 | } 31 | 32 | return L 33 | } 34 | 35 | func pingMetrics(ip string, ch chan SwPing) { 36 | var swPing SwPing 37 | timeout := g.Config().Switch.PingTimeout * g.Config().Switch.PingRetry 38 | fastPingMode := g.Config().Switch.FastPingMode 39 | rtt, err := sw.PingRtt(ip, timeout, fastPingMode) 40 | if err != nil { 41 | log.Println(ip, err) 42 | swPing.Ip = ip 43 | swPing.Ping = -1 44 | ch <- swPing 45 | return 46 | } 47 | swPing.Ip = ip 48 | swPing.Ping = rtt 49 | ch <- swPing 50 | return 51 | 52 | } 53 | -------------------------------------------------------------------------------- /funcs/swsystem.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | 7 | "github.com/gaochao1/sw" 8 | "github.com/gaochao1/swcollector/g" 9 | ) 10 | 11 | type SwSystem struct { 12 | Ip string `json:"ip"` 13 | Hostname string `json:"hostname"` 14 | Model string `json:"model"` 15 | Uptime string `json:"uptime"` 16 | Cpu int `json:"cpu"` 17 | Mem int `json:"mem"` 18 | Ping string `json:"ping"` 19 | Conn int `json:"Conn"` 20 | } 21 | 22 | func SwSystemInfo() (swList []SwSystem) { 23 | 24 | chs := make([]chan SwSystem, len(AliveIp)) 25 | for i, ip := range AliveIp { 26 | chs[i] = make(chan SwSystem) 27 | go swSystemInfo(ip, chs[i]) 28 | } 29 | 30 | for _, ch := range chs { 31 | swSystem := <-ch 32 | swList = append(swList, swSystem) 33 | } 34 | 35 | return swList 36 | } 37 | 38 | func swSystemInfo(ip string, ch chan SwSystem) { 39 | var swSystem SwSystem 40 | swSystem.Ip = ip 41 | 42 | timeout := g.Config().Switch.PingTimeout * g.Config().Switch.PingRetry 43 | 44 | ping, err := sw.PingRtt(ip, timeout, fastPingMode) 45 | if err != nil { 46 | log.Println(err) 47 | ch <- swSystem 48 | return 49 | } else { 50 | swSystem.Ping = strconv.FormatFloat(ping, 'f', 2, 64) 51 | 52 | uptime, err := sw.SysUpTime(ip, g.Config().Switch.Community, timeout) 53 | if err != nil { 54 | log.Println(err) 55 | ch <- swSystem 56 | return 57 | } else { 58 | swSystem.Uptime = uptime 59 | 60 | cpuUtili, err := sw.CpuUtilization(ip, g.Config().Switch.Community, timeout, 1) 61 | if err != nil { 62 | log.Println(err) 63 | } else { 64 | swSystem.Cpu = cpuUtili 65 | } 66 | 67 | memUtili, err := sw.MemUtilization(ip, g.Config().Switch.Community, timeout, 1) 68 | if err != nil { 69 | log.Println(err) 70 | } else { 71 | swSystem.Mem = memUtili 72 | } 73 | 74 | swModel, err := sw.SysModel(ip, g.Config().Switch.Community, timeout, 1) 75 | if err != nil { 76 | log.Println(err) 77 | } else { 78 | swSystem.Model = swModel 79 | } 80 | 81 | swName, err := sw.SysName(ip, g.Config().Switch.Community, timeout) 82 | if err != nil { 83 | log.Println(err) 84 | } else { 85 | swSystem.Hostname = swName 86 | } 87 | 88 | } 89 | 90 | } 91 | 92 | ch <- swSystem 93 | return 94 | } 95 | -------------------------------------------------------------------------------- /g/cfg.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "sync" 7 | 8 | "github.com/toolkits/file" 9 | ) 10 | 11 | type DebugmetricConfig struct { 12 | Endpoints []string `json:"endpoints` 13 | Metrics []string `json:"metrics` 14 | Tags string `json:"tags"` 15 | } 16 | 17 | type SwitchConfig struct { 18 | Enabled bool `json:"enabled"` 19 | IpRange []string `json:"ipRange"` 20 | IndexTag bool `json:"index_tag"` 21 | Gosnmp bool `json:"gosnmp"` 22 | 23 | PingTimeout int `json:"pingTimeout"` 24 | PingRetry int `json:"pingRetry"` 25 | 26 | Community string `json:"community"` 27 | SnmpTimeout int `json:"snmpTimeout"` 28 | SnmpRetry int `json:"snmpRetry"` 29 | 30 | IgnoreIface []string `json:"ignoreIface"` 31 | IgnoreOperStatus bool `json:"ignoreOperStatus"` 32 | Speedlimit float64 `json:"speedlimit"` 33 | IgnorePkt bool `json:"ignorePkt"` 34 | Pktlimit float64 `json:"pktlimit"` 35 | IgnoreBroadcastPkt bool `json:"ignoreBroadcastPkt"` 36 | BroadcastPktlimit float64 `josn:"broadcastPktlimit"` 37 | IgnoreMulticastPkt bool `json:"ignoreMulticastPkt"` 38 | MulticastPktlimit float64 `json:"multicastPktlimit"` 39 | IgnoreDiscards bool `json:"ignoreDiscards"` 40 | DiscardsPktlimit float64 `json:"discardsPktlimit"` 41 | IgnoreErrors bool `json:"ignoreErrors"` 42 | ErrorsPktlimit float64 `json:"errorsPktlimit"` 43 | IgnoreUnknownProtos bool `json:"ignoreUnknownProtos` 44 | UnknownProtosPktlimit float64 `json:"unknownProtosPktlimit"` 45 | IgnoreOutQLen bool `json:"ignoreOutQLen` 46 | OutQLenPktlimit float64 `json:"outQLenPktlimit"` 47 | LimitCon int `json:limitCon` 48 | LimitConcur int `json:"limitConcur"` 49 | FastPingMode bool `json:"fastPingMode"` 50 | } 51 | 52 | type TransferConfig struct { 53 | Enabled bool `json:"enabled"` 54 | N9eMode bool `json:"n9eMode"` 55 | RpcMethod string `json:"rpcMethod"` 56 | Addr string `json:"addr"` 57 | Interval int `json:"interval"` 58 | Timeout int `json:"timeout"` 59 | } 60 | 61 | type HttpConfig struct { 62 | Enabled bool `json:"enabled"` 63 | Listen string `json:"listen"` 64 | TrustIps []string `json:trustIps` 65 | } 66 | 67 | type SwitchHostsConfig struct { 68 | Enabled bool `json:enabled` 69 | Hosts string `json:hosts` 70 | } 71 | 72 | type CustomMetricsConfig struct { 73 | Enabled bool `json:enbaled` 74 | Template string `json:template` 75 | } 76 | 77 | type EcmcConfig struct { 78 | Enabled bool `json:"enabled"` 79 | Addr string `json:"addr"` 80 | Token string `json:"token"` 81 | Nodes []int64 `json:"nodes"` 82 | } 83 | 84 | type N9eConfig struct { 85 | Enabled bool `json:"enabled"` 86 | Addr string `json:"addr"` 87 | User string `json:"user"` 88 | Pass string `json:"pass"` 89 | Nodes []int64 `json:"nodes"` 90 | } 91 | 92 | type GlobalConfig struct { 93 | Debug bool `json:"debug"` 94 | Debugmetric *DebugmetricConfig `json:"debugmetric` 95 | Switch *SwitchConfig `json:"switch"` 96 | Ecmc EcmcConfig `json:"ecmc"` 97 | N9e N9eConfig `json:"n9e"` 98 | N9eV3 N9eV3Config `json:"n9e_v3"` 99 | Transfer *TransferConfig `json:"transfer"` 100 | SwitchHosts *SwitchHostsConfig `json:switchhosts` 101 | CustomMetrics *CustomMetricsConfig `json:customMetrics` 102 | Http *HttpConfig `json:"http"` 103 | } 104 | 105 | type N9eV3Config struct { 106 | Enabled bool `json:"enabled"` 107 | Addr string `json:"addr"` 108 | Token string `json:"token"` 109 | Nodes []int64 `json:"nodes"` 110 | } 111 | 112 | var ( 113 | ConfigFile string 114 | config *GlobalConfig 115 | reloadType bool 116 | lock = new(sync.RWMutex) 117 | rlock = new(sync.RWMutex) 118 | ) 119 | 120 | func SetReloadType(t bool) { 121 | rlock.RLock() 122 | defer rlock.RUnlock() 123 | reloadType = t 124 | return 125 | } 126 | 127 | func ReloadType() bool { 128 | rlock.RLock() 129 | defer rlock.RUnlock() 130 | return reloadType 131 | } 132 | 133 | func Config() *GlobalConfig { 134 | lock.RLock() 135 | defer lock.RUnlock() 136 | return config 137 | } 138 | 139 | func ParseConfig(cfg string) { 140 | if cfg == "" { 141 | log.Fatalln("use -c to specify configuration file") 142 | } 143 | 144 | if !file.IsExist(cfg) { 145 | log.Fatalln("config file:", cfg, "is not existent. maybe you need `mv cfg.example.json cfg.json`") 146 | } 147 | 148 | ConfigFile = cfg 149 | 150 | configContent, err := file.ToTrimString(cfg) 151 | if err != nil { 152 | log.Fatalln("read config file:", cfg, "fail:", err) 153 | } 154 | 155 | var c GlobalConfig 156 | err = json.Unmarshal([]byte(configContent), &c) 157 | if err != nil { 158 | log.Fatalln("parse config file:", cfg, "fail:", err) 159 | } 160 | 161 | lock.Lock() 162 | defer lock.Unlock() 163 | 164 | config = &c 165 | SetReloadType(false) 166 | log.Println("read config file:", cfg, "successfully") 167 | 168 | } 169 | -------------------------------------------------------------------------------- /g/const.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // changelog: 8 | // 3.1.3: code refactor 9 | // 3.1.4: bugfix ignore configuration 10 | // 3.1.5: more sw support, DisplayByBit cfg 11 | // 3.1.6 12 | // 3.2.0: more sw support, fix ping bug, add ifOperStatus,ifBroadcastPkt,ifMulticastPkt 13 | // 3.2.1 add Discards,Error,UnknownProtos,QLen,fix some bugs 14 | // 3.2.1.1 debugmetric support multi endpoints and metrics 15 | // 3.2.1.2 gosnmp use getnext to walk snmp 16 | // 4.0.0 caculate counter type on swcollect local,add speedpercent 17 | // 4.0.1 fix sometimes ifstat pannic 18 | // 4.0.2 fix speedpercent bug 19 | // 4.0.4 add lock on map;add limconn for switch snmp request 20 | // 4.0.5 add custom metric,custom host 21 | // 4.0.6.1 fix channal closed bug 22 | // 4.0.6.2 fix Vendor bug;add remote config api 23 | // 4.0.6.3 fix bugs 24 | // 4.1.0 support n9e transfer mode 25 | // 4.1.1 n9e mode support debug 26 | // 4.2.0 support get allip from ecmc nodes 27 | // 4.3.0 support get allip from n9ev3 28 | // 4.3.2 more sw support, support aruba/cisco wireless controller 29 | // 4.3.3 support n9ev4 30 | const ( 31 | VERSION = "4.3.3" 32 | COLLECT_INTERVAL = time.Second 33 | ) 34 | -------------------------------------------------------------------------------- /g/customcfg.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | 7 | "sync" 8 | 9 | "github.com/toolkits/file" 10 | ) 11 | 12 | type MetricConfig struct { 13 | IpRange []string `json:"ipRange"` 14 | Metric string `json:metric` 15 | Tag string `json:tag` 16 | Type string `json:type` 17 | Oid string `json:oid` 18 | } 19 | type CustomConfig struct { 20 | Metrics []*MetricConfig `json:"metrics` 21 | } 22 | 23 | var ( 24 | CustConfigFile string 25 | custconfig *CustomConfig 26 | custlock = new(sync.RWMutex) 27 | ) 28 | 29 | func CustConfig() *CustomConfig { 30 | custlock.RLock() 31 | defer custlock.RUnlock() 32 | return custconfig 33 | } 34 | 35 | func ParseCustConfig(cfg string) { 36 | 37 | if !file.IsExist(cfg) { 38 | log.Fatalln("config file:", cfg, "is not existent") 39 | } 40 | CustConfigFile = cfg 41 | configContent, err := file.ToTrimString(cfg) 42 | if err != nil { 43 | log.Fatalln("read config file:", cfg, "fail:", err) 44 | } 45 | var c CustomConfig 46 | err = json.Unmarshal([]byte(configContent), &c) 47 | if err != nil { 48 | log.Fatalln("parse config file:", cfg, "fail:", err) 49 | } 50 | custlock.Lock() 51 | defer custlock.Unlock() 52 | custconfig = &c 53 | 54 | log.Println("read customconfig file:", cfg, "successfully") 55 | 56 | } 57 | -------------------------------------------------------------------------------- /g/g.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | ) 7 | 8 | func init() { 9 | runtime.GOMAXPROCS(runtime.NumCPU()) 10 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) 11 | } 12 | -------------------------------------------------------------------------------- /g/hostcfg.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | 7 | "sync" 8 | 9 | "github.com/toolkits/file" 10 | ) 11 | 12 | type HostsConfig struct { 13 | Hosts map[string]string `json:"hosts"` 14 | } 15 | 16 | var ( 17 | HostConfigFile string 18 | hostconfig *HostsConfig 19 | hostlock = new(sync.RWMutex) 20 | ) 21 | 22 | func HostConfig() *HostsConfig { 23 | hostlock.RLock() 24 | defer hostlock.RUnlock() 25 | return hostconfig 26 | } 27 | 28 | func ParseHostConfig(cfg string) { 29 | if !file.IsExist(cfg) { 30 | log.Fatalln("config file:", cfg, "is not existent") 31 | } 32 | 33 | HostConfigFile = cfg 34 | 35 | configContent, err := file.ToTrimString(cfg) 36 | if err != nil { 37 | log.Fatalln("read config file:", cfg, "fail:", err) 38 | } 39 | 40 | var c HostsConfig 41 | err = json.Unmarshal([]byte(configContent), &c) 42 | if err != nil { 43 | log.Fatalln("parse config file:", cfg, "fail:", err) 44 | } 45 | 46 | hostlock.Lock() 47 | defer hostlock.Unlock() 48 | 49 | hostconfig = &c 50 | 51 | log.Println("read hostconfig file:", cfg, "successfully") 52 | 53 | } 54 | -------------------------------------------------------------------------------- /g/push.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "log" 7 | "net" 8 | "net/rpc" 9 | "reflect" 10 | "strings" 11 | "time" 12 | 13 | "github.com/didi/nightingale/src/dataobj" 14 | 15 | "github.com/open-falcon/common/model" 16 | "github.com/ugorji/go/codec" 17 | ) 18 | 19 | func N9ePush(items []*model.MetricValue) { 20 | var mh codec.MsgpackHandle 21 | mh.MapType = reflect.TypeOf(map[string]interface{}(nil)) 22 | 23 | addr := config.Transfer.Addr 24 | retry := 0 25 | for { 26 | conn, err := net.DialTimeout("tcp", addr, time.Millisecond*3000) 27 | if err != nil { 28 | log.Println("dial transfer err:", err) 29 | continue 30 | } 31 | 32 | var bufconn = struct { // bufconn here is a buffered io.ReadWriteCloser 33 | io.Closer 34 | *bufio.Reader 35 | *bufio.Writer 36 | }{conn, bufio.NewReader(conn), bufio.NewWriter(conn)} 37 | 38 | rpcCodec := codec.MsgpackSpecRpc.ClientCodec(bufconn, &mh) 39 | client := rpc.NewClientWithCodec(rpcCodec) 40 | 41 | debug := Config().Debug 42 | debug_endpoints := Config().Debugmetric.Endpoints 43 | debug_items := Config().Debugmetric.Metrics 44 | debug_tags := Config().Debugmetric.Tags 45 | debug_Tags := strings.Split(debug_tags, ",") 46 | 47 | if Config().SwitchHosts.Enabled { 48 | hosts := HostConfig().Hosts 49 | for i, metric := range items { 50 | if hostname, ok := hosts[metric.Endpoint]; ok { 51 | items[i].Endpoint = hostname 52 | } 53 | } 54 | } 55 | 56 | if debug { 57 | for _, metric := range items { 58 | metric_tags := strings.Split(metric.Tags, ",") 59 | if in_array(metric.Endpoint, debug_endpoints) && in_array(metric.Metric, debug_items) { 60 | if debug_tags == "" { 61 | log.Printf("=> %v\n", len(items), metric) 62 | continue 63 | } 64 | if array_include(debug_Tags, metric_tags) { 65 | log.Printf("=> %v\n", len(items), metric) 66 | } 67 | } 68 | } 69 | } 70 | 71 | var reply dataobj.TransferResp 72 | err = client.Call(Config().Transfer.RpcMethod, items, &reply) 73 | client.Close() 74 | if err != nil { 75 | log.Println(err) 76 | continue 77 | } else { 78 | if reply.Msg != "ok" { 79 | log.Println("some item push err", reply) 80 | } 81 | return 82 | } 83 | time.Sleep(time.Millisecond * 500) 84 | 85 | retry += 1 86 | if retry == 3 { 87 | retry = 0 88 | break 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /g/rpc.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "github.com/toolkits/net" 5 | "log" 6 | "math" 7 | "net/rpc" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type SingleConnRpcClient struct { 13 | sync.Mutex 14 | rpcClient *rpc.Client 15 | RpcServer string 16 | Timeout time.Duration 17 | } 18 | 19 | func (this *SingleConnRpcClient) close() { 20 | if this.rpcClient != nil { 21 | this.rpcClient.Close() 22 | this.rpcClient = nil 23 | } 24 | } 25 | 26 | func (this *SingleConnRpcClient) insureConn() { 27 | if this.rpcClient != nil { 28 | return 29 | } 30 | 31 | var err error 32 | var retry int = 1 33 | 34 | for { 35 | if this.rpcClient != nil { 36 | return 37 | } 38 | 39 | this.rpcClient, err = net.JsonRpcClient("tcp", this.RpcServer, this.Timeout) 40 | if err == nil { 41 | return 42 | } 43 | 44 | log.Printf("dial %s fail: %v", this.RpcServer, err) 45 | 46 | if retry > 6 { 47 | retry = 1 48 | } 49 | 50 | time.Sleep(time.Duration(math.Pow(2.0, float64(retry))) * time.Second) 51 | 52 | retry++ 53 | } 54 | } 55 | 56 | func (this *SingleConnRpcClient) Call(method string, args interface{}, reply interface{}) error { 57 | 58 | this.Lock() 59 | defer this.Unlock() 60 | 61 | this.insureConn() 62 | 63 | timeout := time.Duration(50 * time.Second) 64 | done := make(chan error) 65 | 66 | go func() { 67 | err := this.rpcClient.Call(method, args, reply) 68 | done <- err 69 | }() 70 | 71 | select { 72 | case <-time.After(timeout): 73 | log.Printf("[WARN] rpc call timeout %v => %v", this.rpcClient, this.RpcServer) 74 | this.close() 75 | case err := <-done: 76 | if err != nil { 77 | this.close() 78 | return err 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /g/var.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/toolkits/slice" 10 | 11 | "time" 12 | 13 | "github.com/open-falcon/common/model" 14 | "github.com/toolkits/net" 15 | ) 16 | 17 | var Root string 18 | 19 | func InitRootDir() { 20 | var err error 21 | Root, err = os.Getwd() 22 | if err != nil { 23 | log.Fatalln("getwd fail:", err) 24 | } 25 | } 26 | 27 | var LocalIps []string 28 | 29 | func InitLocalIps() { 30 | var err error 31 | LocalIps, err = net.IntranetIP() 32 | if err != nil { 33 | log.Fatalln("get intranet ip fail:", err) 34 | } 35 | } 36 | 37 | var ( 38 | TransferClient *SingleConnRpcClient 39 | ) 40 | 41 | func InitRpcClients() { 42 | if Config().Transfer.Enabled { 43 | TransferClient = &SingleConnRpcClient{ 44 | RpcServer: Config().Transfer.Addr, 45 | Timeout: time.Duration(Config().Transfer.Timeout) * time.Millisecond, 46 | } 47 | } 48 | } 49 | 50 | func SendToTransfer(metrics []*model.MetricValue) { 51 | if len(metrics) == 0 { 52 | return 53 | } 54 | 55 | debug := Config().Debug 56 | debug_endpoints := Config().Debugmetric.Endpoints 57 | debug_metrics := Config().Debugmetric.Metrics 58 | debug_tags := Config().Debugmetric.Tags 59 | debug_Tags := strings.Split(debug_tags, ",") 60 | 61 | if Config().SwitchHosts.Enabled { 62 | hosts := HostConfig().Hosts 63 | for i, metric := range metrics { 64 | if hostname, ok := hosts[metric.Endpoint]; ok { 65 | metrics[i].Endpoint = hostname 66 | } 67 | } 68 | } 69 | 70 | if debug { 71 | for _, metric := range metrics { 72 | metric_tags := strings.Split(metric.Tags, ",") 73 | if in_array(metric.Endpoint, debug_endpoints) && in_array(metric.Metric, debug_metrics) { 74 | if debug_tags == "" { 75 | log.Printf("=> %v\n", len(metrics), metric) 76 | continue 77 | } 78 | if array_include(debug_Tags, metric_tags) { 79 | log.Printf("=> %v\n", len(metrics), metric) 80 | } 81 | } 82 | } 83 | } 84 | var resp model.TransferResponse 85 | err := TransferClient.Call("Transfer.Update", metrics, &resp) 86 | if err != nil { 87 | log.Println("call Transfer.Update fail", err) 88 | if debug { 89 | for _, metric := range metrics { 90 | log.Printf("=> %v\n", len(metrics), metric) 91 | } 92 | } 93 | } 94 | 95 | if debug { 96 | log.Println("<=", &resp) 97 | } 98 | } 99 | 100 | func array_include(array_a []string, array_b []string) bool { //b include a 101 | for _, v := range array_a { 102 | if in_array(v, array_b) { 103 | continue 104 | } else { 105 | return false 106 | } 107 | } 108 | return true 109 | } 110 | 111 | func in_array(a string, array []string) bool { 112 | for _, v := range array { 113 | if a == v { 114 | return true 115 | } 116 | } 117 | return false 118 | } 119 | 120 | var ( 121 | ips []string 122 | ipsLock = new(sync.Mutex) 123 | ) 124 | 125 | func TrustableIps() []string { 126 | ipsLock.Lock() 127 | defer ipsLock.Unlock() 128 | ips := Config().Http.TrustIps 129 | return ips 130 | } 131 | 132 | func IsTrustable(remoteAddr string) bool { 133 | ip := remoteAddr 134 | idx := strings.LastIndex(remoteAddr, ":") 135 | if idx > 0 { 136 | ip = remoteAddr[0:idx] 137 | } 138 | 139 | if ip == "127.0.0.1" { 140 | return true 141 | } 142 | 143 | return slice.ContainsString(TrustableIps(), ip) 144 | } 145 | -------------------------------------------------------------------------------- /gitversion: -------------------------------------------------------------------------------- 1 | 549546b 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gaochao1/swcollector 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 7 | github.com/alouca/gologger v0.0.0-20120904114645-7d4b7291de9c // indirect 8 | github.com/coreos/go-oidc v2.2.1+incompatible // indirect 9 | github.com/didi/nightingale v1.4.0 10 | github.com/eapache/go-resiliency v1.2.0 // indirect 11 | github.com/freedomkk-qfeng/go-fastping v0.0.0-20160109021039-d7bb493dee3e // indirect 12 | github.com/gaochao1/gosnmp v0.0.0-20150630013918-783a67a067fd 13 | github.com/gaochao1/sw v0.0.0-20210119072707-3276e779dfdf 14 | github.com/garyburd/redigo v1.6.2 // indirect 15 | github.com/gin-contrib/pprof v1.3.0 // indirect 16 | github.com/gin-gonic/gin v1.6.3 // indirect 17 | github.com/go-ole/go-ole v1.2.4 // indirect 18 | github.com/go-sql-driver/mysql v1.5.0 // indirect 19 | github.com/google/go-cmp v0.5.1 // indirect 20 | github.com/google/uuid v1.1.2 // indirect 21 | github.com/mattn/go-sqlite3 v1.14.0 // indirect 22 | github.com/open-falcon/common v0.0.0-20160912145637-b9ba65549217 23 | github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect 24 | github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect 25 | github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect 26 | github.com/shirou/gopsutil v2.20.7+incompatible // indirect 27 | github.com/spf13/viper v1.7.1 // indirect 28 | github.com/streadway/amqp v1.0.0 // indirect 29 | github.com/toolkits/file v0.0.0-20160325033739-a5b3c5147e07 30 | github.com/toolkits/net v0.0.0-20160910085801-3f39ab6fe3ce 31 | github.com/toolkits/pkg v1.1.3 // indirect 32 | github.com/toolkits/slice v0.0.0-20141116085117-e44a80af2484 33 | github.com/toolkits/time v0.0.0-20160524122720-c274716e8d7f // indirect 34 | github.com/ugorji/go/codec v1.1.7 35 | github.com/unrolled/render v1.0.3 // indirect 36 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect 37 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect 38 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect 39 | gopkg.in/square/go-jose.v2 v2.5.1 // indirect 40 | gopkg.in/yaml.v2 v2.3.0 // indirect 41 | k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /hosts.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosts": 3 | { 4 | "192.168.160":"test1", 5 | "192.168.88.161":"test2", 6 | "192.168.33.2":"test3", 7 | "192.168.31.51":"test4" 8 | } 9 | } -------------------------------------------------------------------------------- /http/admin.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "time" 8 | 9 | "github.com/gaochao1/swcollector/g" 10 | 11 | "github.com/toolkits/file" 12 | ) 13 | 14 | func configAdminRoutes() { 15 | 16 | http.HandleFunc("/workdir", func(w http.ResponseWriter, r *http.Request) { 17 | RenderDataJson(w, file.SelfDir()) 18 | }) 19 | http.HandleFunc("/ips", func(w http.ResponseWriter, r *http.Request) { 20 | RenderDataJson(w, g.TrustableIps()) 21 | }) 22 | http.HandleFunc("/exit", func(w http.ResponseWriter, r *http.Request) { 23 | if g.IsTrustable(r.RemoteAddr) { 24 | w.Write([]byte("exiting...")) 25 | go func() { 26 | time.Sleep(time.Second) 27 | os.Exit(0) 28 | }() 29 | } else { 30 | w.Write([]byte("no privilege")) 31 | } 32 | }) 33 | http.HandleFunc("/config/reload", func(w http.ResponseWriter, r *http.Request) { 34 | if g.IsTrustable(r.RemoteAddr) { 35 | g.SetReloadType(true) 36 | log.Println("config will be reload in next interval") 37 | RenderDataJson(w, "reload type on") 38 | } else { 39 | w.Write([]byte("no privilege")) 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /http/health.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/gaochao1/swcollector/g" 5 | "net/http" 6 | ) 7 | 8 | func configHealthRoutes() { 9 | http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { 10 | w.Write([]byte("ok")) 11 | }) 12 | 13 | http.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { 14 | w.Write([]byte(g.VERSION)) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /http/http.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | _ "net/http/pprof" 8 | 9 | "github.com/gaochao1/swcollector/g" 10 | ) 11 | 12 | type Dto struct { 13 | Msg string `json:"msg"` 14 | Data interface{} `json:"data"` 15 | } 16 | 17 | func init() { 18 | configAdminRoutes() 19 | configHealthRoutes() 20 | configPageRoutes() 21 | configPushRoutes() 22 | configSwRoutes() 23 | } 24 | 25 | func RenderJson(w http.ResponseWriter, v interface{}) { 26 | bs, err := json.Marshal(v) 27 | if err != nil { 28 | http.Error(w, err.Error(), http.StatusInternalServerError) 29 | return 30 | } 31 | 32 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 33 | w.Write(bs) 34 | } 35 | 36 | func RenderDataJson(w http.ResponseWriter, data interface{}) { 37 | RenderJson(w, Dto{Msg: "success", Data: data}) 38 | } 39 | 40 | func RenderMsgJson(w http.ResponseWriter, msg string) { 41 | RenderJson(w, map[string]string{"msg": msg}) 42 | } 43 | 44 | func AutoRender(w http.ResponseWriter, data interface{}, err error) { 45 | if err != nil { 46 | RenderMsgJson(w, err.Error()) 47 | return 48 | } 49 | 50 | RenderDataJson(w, data) 51 | } 52 | 53 | func Start() { 54 | if !g.Config().Http.Enabled { 55 | return 56 | } 57 | 58 | addr := g.Config().Http.Listen 59 | if addr == "" { 60 | return 61 | } 62 | 63 | s := &http.Server{ 64 | Addr: addr, 65 | MaxHeaderBytes: 1 << 30, 66 | } 67 | 68 | log.Println("listening", addr) 69 | log.Fatalln(s.ListenAndServe()) 70 | } 71 | -------------------------------------------------------------------------------- /http/page.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/gaochao1/swcollector/g" 5 | "github.com/toolkits/file" 6 | "net/http" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func configPageRoutes() { 12 | 13 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 14 | if strings.HasSuffix(r.URL.Path, "/") { 15 | if !file.IsExist(filepath.Join(g.Root, "/public", r.URL.Path, "index.html")) { 16 | http.NotFound(w, r) 17 | return 18 | } 19 | } 20 | http.FileServer(http.Dir(filepath.Join(g.Root, "/public"))).ServeHTTP(w, r) 21 | }) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /http/push.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/gaochao1/swcollector/g" 6 | "github.com/open-falcon/common/model" 7 | "net/http" 8 | ) 9 | 10 | func configPushRoutes() { 11 | http.HandleFunc("/v1/push", func(w http.ResponseWriter, req *http.Request) { 12 | if req.ContentLength == 0 { 13 | http.Error(w, "body is blank", http.StatusBadRequest) 14 | return 15 | } 16 | 17 | decoder := json.NewDecoder(req.Body) 18 | var metrics []*model.MetricValue 19 | err := decoder.Decode(&metrics) 20 | if err != nil { 21 | http.Error(w, "connot decode body", http.StatusBadRequest) 22 | return 23 | } 24 | 25 | g.SendToTransfer(metrics) 26 | w.Write([]byte("success")) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /http/sw.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gaochao1/swcollector/funcs" 6 | "github.com/gaochao1/swcollector/g" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func configSwRoutes() { 13 | 14 | http.HandleFunc("/page/sw/time", func(w http.ResponseWriter, req *http.Request) { 15 | RenderDataJson(w, time.Now().Format("2006-01-02 15:04:05")) 16 | }) 17 | 18 | http.HandleFunc("/page/sw/iprange", func(w http.ResponseWriter, req *http.Request) { 19 | RenderDataJson(w, strings.Join(g.Config().Switch.IpRange, "\n")) 20 | }) 21 | 22 | http.HandleFunc("/page/sw/live", func(w http.ResponseWriter, req *http.Request) { 23 | RenderDataJson(w, len(funcs.AliveIp)) 24 | }) 25 | 26 | http.HandleFunc("/page/sw/list", func(w http.ResponseWriter, r *http.Request) { 27 | 28 | var ret [][]interface{} = make([][]interface{}, 0) 29 | for _, swSystem := range funcs.SwSystemInfo() { 30 | ret = append(ret, 31 | []interface{}{ 32 | swSystem.Ip, 33 | swSystem.Hostname, 34 | swSystem.Model, 35 | swSystem.Uptime, 36 | fmt.Sprintf("%d%%", swSystem.Cpu), 37 | fmt.Sprintf("%d%%", swSystem.Mem), 38 | fmt.Sprintf("%sms", swSystem.Ping), 39 | }) 40 | } 41 | RenderDataJson(w, ret) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/gaochao1/swcollector/cron" 9 | "github.com/gaochao1/swcollector/funcs" 10 | "github.com/gaochao1/swcollector/g" 11 | "github.com/gaochao1/swcollector/http" 12 | ) 13 | 14 | func main() { 15 | 16 | cfg := flag.String("c", "cfg.json", "configuration file") 17 | version := flag.Bool("v", false, "show version") 18 | check := flag.Bool("check", false, "check collector") 19 | 20 | flag.Parse() 21 | 22 | if *version { 23 | fmt.Println(g.VERSION) 24 | os.Exit(0) 25 | } 26 | g.ParseConfig(*cfg) 27 | if g.Config().SwitchHosts.Enabled { 28 | hostcfg := g.Config().SwitchHosts.Hosts 29 | g.ParseHostConfig(hostcfg) 30 | } 31 | if g.Config().CustomMetrics.Enabled { 32 | custMetrics := g.Config().CustomMetrics.Template 33 | g.ParseCustConfig(custMetrics) 34 | } 35 | g.InitRootDir() 36 | g.InitLocalIps() 37 | g.InitRpcClients() 38 | 39 | if *check { 40 | funcs.CheckCollector() 41 | os.Exit(0) 42 | } 43 | 44 | funcs.NewLastifMap() 45 | funcs.BuildMappers() 46 | 47 | cron.Collect() 48 | 49 | go http.Start() 50 | 51 | select {} 52 | 53 | } 54 | -------------------------------------------------------------------------------- /public/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";} 2 | .clearfix:after{clear:both;} 3 | .hide-text{overflow:hidden;text-indent:100%;white-space:nowrap;} 4 | .input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} 5 | .hidden{display:none;visibility:hidden;} 6 | .visible-phone{display:none;} 7 | .visible-tablet{display:none;} 8 | .visible-desktop{display:block;} 9 | .hidden-phone{display:block;} 10 | .hidden-tablet{display:block;} 11 | .hidden-desktop{display:none;} 12 | @media (max-width:767px){.visible-phone{display:block;} .hidden-phone{display:none;} .hidden-desktop{display:block;} .visible-desktop{display:none;}}@media (min-width:768px) and (max-width:979px){.visible-tablet{display:block;} .hidden-tablet{display:none;} .hidden-desktop{display:block;} .visible-desktop{display:none;}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0);} .page-header h1 small{display:block;line-height:18px;} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc;} .form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:10px;padding-right:10px;} .modal{position:absolute;top:10px;left:10px;right:10px;width:auto;margin:0;}.modal.fade.in{top:auto;} .modal-header .close{padding:10px;margin:-10px;} .carousel-caption{position:static;}}@media (max-width:767px){body{padding-left:20px;padding-right:20px;} .navbar-fixed-top{margin-left:-20px;margin-right:-20px;} .container{width:auto;} .row-fluid{width:100%;} .row{margin-left:0;} .row>[class*="span"],.row-fluid>[class*="span"]{float:none;display:block;width:auto;margin:0;} .thumbnails [class*="span"]{width:auto;} input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} .input-prepend input[class*="span"],.input-append input[class*="span"]{width:auto;}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";} .row:after{clear:both;} [class*="span"]{float:left;margin-left:20px;} .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px;} .span12{width:724px;} .span11{width:662px;} .span10{width:600px;} .span9{width:538px;} .span8{width:476px;} .span7{width:414px;} .span6{width:352px;} .span5{width:290px;} .span4{width:228px;} .span3{width:166px;} .span2{width:104px;} .span1{width:42px;} .offset12{margin-left:764px;} .offset11{margin-left:702px;} .offset10{margin-left:640px;} .offset9{margin-left:578px;} .offset8{margin-left:516px;} .offset7{margin-left:454px;} .offset6{margin-left:392px;} .offset5{margin-left:330px;} .offset4{margin-left:268px;} .offset3{margin-left:206px;} .offset2{margin-left:144px;} .offset1{margin-left:82px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} .row-fluid:after{clear:both;} .row-fluid>[class*="span"]{float:left;margin-left:2.762430939%;} .row-fluid>[class*="span"]:first-child{margin-left:0;} .row-fluid > .span12{width:99.999999993%;} .row-fluid > .span11{width:91.436464082%;} .row-fluid > .span10{width:82.87292817100001%;} .row-fluid > .span9{width:74.30939226%;} .row-fluid > .span8{width:65.74585634900001%;} .row-fluid > .span7{width:57.182320438000005%;} .row-fluid > .span6{width:48.618784527%;} .row-fluid > .span5{width:40.055248616%;} .row-fluid > .span4{width:31.491712705%;} .row-fluid > .span3{width:22.928176794%;} .row-fluid > .span2{width:14.364640883%;} .row-fluid > .span1{width:5.801104972%;} input,textarea,.uneditable-input{margin-left:0;} input.span12, textarea.span12, .uneditable-input.span12{width:714px;} input.span11, textarea.span11, .uneditable-input.span11{width:652px;} input.span10, textarea.span10, .uneditable-input.span10{width:590px;} input.span9, textarea.span9, .uneditable-input.span9{width:528px;} input.span8, textarea.span8, .uneditable-input.span8{width:466px;} input.span7, textarea.span7, .uneditable-input.span7{width:404px;} input.span6, textarea.span6, .uneditable-input.span6{width:342px;} input.span5, textarea.span5, .uneditable-input.span5{width:280px;} input.span4, textarea.span4, .uneditable-input.span4{width:218px;} input.span3, textarea.span3, .uneditable-input.span3{width:156px;} input.span2, textarea.span2, .uneditable-input.span2{width:94px;} input.span1, textarea.span1, .uneditable-input.span1{width:32px;}}@media (max-width:979px){body{padding-top:0;} .navbar-fixed-top{position:static;margin-bottom:18px;} .navbar-fixed-top .navbar-inner{padding:5px;} .navbar .container{width:auto;padding:0;} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px;} .navbar .nav-collapse{clear:left;} .navbar .nav{float:none;margin:0 0 9px;} .navbar .nav>li{float:none;} .navbar .nav>li>a{margin-bottom:2px;} .navbar .nav>.divider-vertical{display:none;} .navbar .nav .nav-header{color:#999999;text-shadow:none;} .navbar .nav>li>a,.navbar .dropdown-menu a{padding:6px 15px;font-weight:bold;color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .navbar .dropdown-menu li+li a{margin-bottom:2px;} .navbar .nav>li>a:hover,.navbar .dropdown-menu a:hover{background-color:#222222;} .navbar .dropdown-menu{position:static;top:auto;left:auto;float:none;display:block;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} .navbar .dropdown-menu:before,.navbar .dropdown-menu:after{display:none;} .navbar .dropdown-menu .divider{display:none;} .navbar-form,.navbar-search{float:none;padding:9px 15px;margin:9px 0;border-top:1px solid #222222;border-bottom:1px solid #222222;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.1);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.1);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.1);} .navbar .nav.pull-right{float:none;margin-left:0;} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px;} .btn-navbar{display:block;} .nav-collapse{overflow:hidden;height:0;}}@media (min-width:980px){.nav-collapse.collapse{height:auto !important;overflow:visible !important;}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1;}.row:before,.row:after{display:table;content:"";} .row:after{clear:both;} [class*="span"]{float:left;margin-left:30px;} .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px;} .span12{width:1170px;} .span11{width:1070px;} .span10{width:970px;} .span9{width:870px;} .span8{width:770px;} .span7{width:670px;} .span6{width:570px;} .span5{width:470px;} .span4{width:370px;} .span3{width:270px;} .span2{width:170px;} .span1{width:70px;} .offset12{margin-left:1230px;} .offset11{margin-left:1130px;} .offset10{margin-left:1030px;} .offset9{margin-left:930px;} .offset8{margin-left:830px;} .offset7{margin-left:730px;} .offset6{margin-left:630px;} .offset5{margin-left:530px;} .offset4{margin-left:430px;} .offset3{margin-left:330px;} .offset2{margin-left:230px;} .offset1{margin-left:130px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} .row-fluid:after{clear:both;} .row-fluid>[class*="span"]{float:left;margin-left:2.564102564%;} .row-fluid>[class*="span"]:first-child{margin-left:0;} .row-fluid > .span12{width:100%;} .row-fluid > .span11{width:91.45299145300001%;} .row-fluid > .span10{width:82.905982906%;} .row-fluid > .span9{width:74.358974359%;} .row-fluid > .span8{width:65.81196581200001%;} .row-fluid > .span7{width:57.264957265%;} .row-fluid > .span6{width:48.717948718%;} .row-fluid > .span5{width:40.170940171000005%;} .row-fluid > .span4{width:31.623931624%;} .row-fluid > .span3{width:23.076923077%;} .row-fluid > .span2{width:14.529914530000001%;} .row-fluid > .span1{width:5.982905983%;} input,textarea,.uneditable-input{margin-left:0;} input.span12, textarea.span12, .uneditable-input.span12{width:1160px;} input.span11, textarea.span11, .uneditable-input.span11{width:1060px;} input.span10, textarea.span10, .uneditable-input.span10{width:960px;} input.span9, textarea.span9, .uneditable-input.span9{width:860px;} input.span8, textarea.span8, .uneditable-input.span8{width:760px;} input.span7, textarea.span7, .uneditable-input.span7{width:660px;} input.span6, textarea.span6, .uneditable-input.span6{width:560px;} input.span5, textarea.span5, .uneditable-input.span5{width:460px;} input.span4, textarea.span4, .uneditable-input.span4{width:360px;} input.span3, textarea.span3, .uneditable-input.span3{width:260px;} input.span2, textarea.span2, .uneditable-input.span2{width:160px;} input.span1, textarea.span1, .uneditable-input.span1{width:60px;} .thumbnails{margin-left:-30px;} .thumbnails>li{margin-left:30px;}} -------------------------------------------------------------------------------- /public/css/font-awesome/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'FontAwesome';src:url('../font/fontawesome-webfont.eot?v=3.2.1');src:url('../font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('../font/fontawesome-webfont.woff?v=3.2.1') format('woff'),url('../font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('../font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal;}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;} 2 | [class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none;} 3 | .icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em;} 4 | a [class^="icon-"],a [class*=" icon-"]{display:inline;} 5 | [class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:0.2857142857142857em;}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em;} 6 | .icons-ul{margin-left:2.142857142857143em;list-style-type:none;}.icons-ul>li{position:relative;} 7 | .icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit;} 8 | [class^="icon-"].hide,[class*=" icon-"].hide{display:none;} 9 | .icon-muted{color:#eeeeee;} 10 | .icon-light{color:#ffffff;} 11 | .icon-dark{color:#333333;} 12 | .icon-border{border:solid 1px #eeeeee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 13 | .icon-2x{font-size:2em;}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} 14 | .icon-3x{font-size:3em;}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} 15 | .icon-4x{font-size:4em;}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} 16 | .icon-5x{font-size:5em;}.icon-5x.icon-border{border-width:5px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;} 17 | .pull-right{float:right;} 18 | .pull-left{float:left;} 19 | [class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em;} 20 | [class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em;} 21 | [class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0% 0%;background-repeat:repeat;margin-top:0;} 22 | .icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none;} 23 | .btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em;} 24 | .btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block;} 25 | .nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em;} 26 | .btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em;} 27 | .btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em;} 28 | .btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em;} 29 | .btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0;}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em;} 30 | .btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em;} 31 | .btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em;} 32 | .nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit;} 33 | .icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%;}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em;} 34 | .icon-stack .icon-stack-base{font-size:2em;*line-height:1em;} 35 | .icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;} 36 | a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none;} 37 | @-moz-keyframes spin{0%{-moz-transform:rotate(0deg);} 100%{-moz-transform:rotate(359deg);}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);} 100%{-webkit-transform:rotate(359deg);}}@-o-keyframes spin{0%{-o-transform:rotate(0deg);} 100%{-o-transform:rotate(359deg);}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg);} 100%{-ms-transform:rotate(359deg);}}@keyframes spin{0%{transform:rotate(0deg);} 100%{transform:rotate(359deg);}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);} 38 | .icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);} 39 | .icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);} 40 | .icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1);} 41 | .icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1);} 42 | a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block;} 43 | .icon-glass:before{content:"\f000";} 44 | .icon-music:before{content:"\f001";} 45 | .icon-search:before{content:"\f002";} 46 | .icon-envelope-alt:before{content:"\f003";} 47 | .icon-heart:before{content:"\f004";} 48 | .icon-star:before{content:"\f005";} 49 | .icon-star-empty:before{content:"\f006";} 50 | .icon-user:before{content:"\f007";} 51 | .icon-film:before{content:"\f008";} 52 | .icon-th-large:before{content:"\f009";} 53 | .icon-th:before{content:"\f00a";} 54 | .icon-th-list:before{content:"\f00b";} 55 | .icon-ok:before{content:"\f00c";} 56 | .icon-remove:before{content:"\f00d";} 57 | .icon-zoom-in:before{content:"\f00e";} 58 | .icon-zoom-out:before{content:"\f010";} 59 | .icon-power-off:before,.icon-off:before{content:"\f011";} 60 | .icon-signal:before{content:"\f012";} 61 | .icon-gear:before,.icon-cog:before{content:"\f013";} 62 | .icon-trash:before{content:"\f014";} 63 | .icon-home:before{content:"\f015";} 64 | .icon-file-alt:before{content:"\f016";} 65 | .icon-time:before{content:"\f017";} 66 | .icon-road:before{content:"\f018";} 67 | .icon-download-alt:before{content:"\f019";} 68 | .icon-download:before{content:"\f01a";} 69 | .icon-upload:before{content:"\f01b";} 70 | .icon-inbox:before{content:"\f01c";} 71 | .icon-play-circle:before{content:"\f01d";} 72 | .icon-rotate-right:before,.icon-repeat:before{content:"\f01e";} 73 | .icon-refresh:before{content:"\f021";} 74 | .icon-list-alt:before{content:"\f022";} 75 | .icon-lock:before{content:"\f023";} 76 | .icon-flag:before{content:"\f024";} 77 | .icon-headphones:before{content:"\f025";} 78 | .icon-volume-off:before{content:"\f026";} 79 | .icon-volume-down:before{content:"\f027";} 80 | .icon-volume-up:before{content:"\f028";} 81 | .icon-qrcode:before{content:"\f029";} 82 | .icon-barcode:before{content:"\f02a";} 83 | .icon-tag:before{content:"\f02b";} 84 | .icon-tags:before{content:"\f02c";} 85 | .icon-book:before{content:"\f02d";} 86 | .icon-bookmark:before{content:"\f02e";} 87 | .icon-print:before{content:"\f02f";} 88 | .icon-camera:before{content:"\f030";} 89 | .icon-font:before{content:"\f031";} 90 | .icon-bold:before{content:"\f032";} 91 | .icon-italic:before{content:"\f033";} 92 | .icon-text-height:before{content:"\f034";} 93 | .icon-text-width:before{content:"\f035";} 94 | .icon-align-left:before{content:"\f036";} 95 | .icon-align-center:before{content:"\f037";} 96 | .icon-align-right:before{content:"\f038";} 97 | .icon-align-justify:before{content:"\f039";} 98 | .icon-list:before{content:"\f03a";} 99 | .icon-indent-left:before{content:"\f03b";} 100 | .icon-indent-right:before{content:"\f03c";} 101 | .icon-facetime-video:before{content:"\f03d";} 102 | .icon-picture:before{content:"\f03e";} 103 | .icon-pencil:before{content:"\f040";} 104 | .icon-map-marker:before{content:"\f041";} 105 | .icon-adjust:before{content:"\f042";} 106 | .icon-tint:before{content:"\f043";} 107 | .icon-edit:before{content:"\f044";} 108 | .icon-share:before{content:"\f045";} 109 | .icon-check:before{content:"\f046";} 110 | .icon-move:before{content:"\f047";} 111 | .icon-step-backward:before{content:"\f048";} 112 | .icon-fast-backward:before{content:"\f049";} 113 | .icon-backward:before{content:"\f04a";} 114 | .icon-play:before{content:"\f04b";} 115 | .icon-pause:before{content:"\f04c";} 116 | .icon-stop:before{content:"\f04d";} 117 | .icon-forward:before{content:"\f04e";} 118 | .icon-fast-forward:before{content:"\f050";} 119 | .icon-step-forward:before{content:"\f051";} 120 | .icon-eject:before{content:"\f052";} 121 | .icon-chevron-left:before{content:"\f053";} 122 | .icon-chevron-right:before{content:"\f054";} 123 | .icon-plus-sign:before{content:"\f055";} 124 | .icon-minus-sign:before{content:"\f056";} 125 | .icon-remove-sign:before{content:"\f057";} 126 | .icon-ok-sign:before{content:"\f058";} 127 | .icon-question-sign:before{content:"\f059";} 128 | .icon-info-sign:before{content:"\f05a";} 129 | .icon-screenshot:before{content:"\f05b";} 130 | .icon-remove-circle:before{content:"\f05c";} 131 | .icon-ok-circle:before{content:"\f05d";} 132 | .icon-ban-circle:before{content:"\f05e";} 133 | .icon-arrow-left:before{content:"\f060";} 134 | .icon-arrow-right:before{content:"\f061";} 135 | .icon-arrow-up:before{content:"\f062";} 136 | .icon-arrow-down:before{content:"\f063";} 137 | .icon-mail-forward:before,.icon-share-alt:before{content:"\f064";} 138 | .icon-resize-full:before{content:"\f065";} 139 | .icon-resize-small:before{content:"\f066";} 140 | .icon-plus:before{content:"\f067";} 141 | .icon-minus:before{content:"\f068";} 142 | .icon-asterisk:before{content:"\f069";} 143 | .icon-exclamation-sign:before{content:"\f06a";} 144 | .icon-gift:before{content:"\f06b";} 145 | .icon-leaf:before{content:"\f06c";} 146 | .icon-fire:before{content:"\f06d";} 147 | .icon-eye-open:before{content:"\f06e";} 148 | .icon-eye-close:before{content:"\f070";} 149 | .icon-warning-sign:before{content:"\f071";} 150 | .icon-plane:before{content:"\f072";} 151 | .icon-calendar:before{content:"\f073";} 152 | .icon-random:before{content:"\f074";} 153 | .icon-comment:before{content:"\f075";} 154 | .icon-magnet:before{content:"\f076";} 155 | .icon-chevron-up:before{content:"\f077";} 156 | .icon-chevron-down:before{content:"\f078";} 157 | .icon-retweet:before{content:"\f079";} 158 | .icon-shopping-cart:before{content:"\f07a";} 159 | .icon-folder-close:before{content:"\f07b";} 160 | .icon-folder-open:before{content:"\f07c";} 161 | .icon-resize-vertical:before{content:"\f07d";} 162 | .icon-resize-horizontal:before{content:"\f07e";} 163 | .icon-bar-chart:before{content:"\f080";} 164 | .icon-twitter-sign:before{content:"\f081";} 165 | .icon-facebook-sign:before{content:"\f082";} 166 | .icon-camera-retro:before{content:"\f083";} 167 | .icon-key:before{content:"\f084";} 168 | .icon-gears:before,.icon-cogs:before{content:"\f085";} 169 | .icon-comments:before{content:"\f086";} 170 | .icon-thumbs-up-alt:before{content:"\f087";} 171 | .icon-thumbs-down-alt:before{content:"\f088";} 172 | .icon-star-half:before{content:"\f089";} 173 | .icon-heart-empty:before{content:"\f08a";} 174 | .icon-signout:before{content:"\f08b";} 175 | .icon-linkedin-sign:before{content:"\f08c";} 176 | .icon-pushpin:before{content:"\f08d";} 177 | .icon-external-link:before{content:"\f08e";} 178 | .icon-signin:before{content:"\f090";} 179 | .icon-trophy:before{content:"\f091";} 180 | .icon-github-sign:before{content:"\f092";} 181 | .icon-upload-alt:before{content:"\f093";} 182 | .icon-lemon:before{content:"\f094";} 183 | .icon-phone:before{content:"\f095";} 184 | .icon-unchecked:before,.icon-check-empty:before{content:"\f096";} 185 | .icon-bookmark-empty:before{content:"\f097";} 186 | .icon-phone-sign:before{content:"\f098";} 187 | .icon-twitter:before{content:"\f099";} 188 | .icon-facebook:before{content:"\f09a";} 189 | .icon-github:before{content:"\f09b";} 190 | .icon-unlock:before{content:"\f09c";} 191 | .icon-credit-card:before{content:"\f09d";} 192 | .icon-rss:before{content:"\f09e";} 193 | .icon-hdd:before{content:"\f0a0";} 194 | .icon-bullhorn:before{content:"\f0a1";} 195 | .icon-bell:before{content:"\f0a2";} 196 | .icon-certificate:before{content:"\f0a3";} 197 | .icon-hand-right:before{content:"\f0a4";} 198 | .icon-hand-left:before{content:"\f0a5";} 199 | .icon-hand-up:before{content:"\f0a6";} 200 | .icon-hand-down:before{content:"\f0a7";} 201 | .icon-circle-arrow-left:before{content:"\f0a8";} 202 | .icon-circle-arrow-right:before{content:"\f0a9";} 203 | .icon-circle-arrow-up:before{content:"\f0aa";} 204 | .icon-circle-arrow-down:before{content:"\f0ab";} 205 | .icon-globe:before{content:"\f0ac";} 206 | .icon-wrench:before{content:"\f0ad";} 207 | .icon-tasks:before{content:"\f0ae";} 208 | .icon-filter:before{content:"\f0b0";} 209 | .icon-briefcase:before{content:"\f0b1";} 210 | .icon-fullscreen:before{content:"\f0b2";} 211 | .icon-group:before{content:"\f0c0";} 212 | .icon-link:before{content:"\f0c1";} 213 | .icon-cloud:before{content:"\f0c2";} 214 | .icon-beaker:before{content:"\f0c3";} 215 | .icon-cut:before{content:"\f0c4";} 216 | .icon-copy:before{content:"\f0c5";} 217 | .icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6";} 218 | .icon-save:before{content:"\f0c7";} 219 | .icon-sign-blank:before{content:"\f0c8";} 220 | .icon-reorder:before{content:"\f0c9";} 221 | .icon-list-ul:before{content:"\f0ca";} 222 | .icon-list-ol:before{content:"\f0cb";} 223 | .icon-strikethrough:before{content:"\f0cc";} 224 | .icon-underline:before{content:"\f0cd";} 225 | .icon-table:before{content:"\f0ce";} 226 | .icon-magic:before{content:"\f0d0";} 227 | .icon-truck:before{content:"\f0d1";} 228 | .icon-pinterest:before{content:"\f0d2";} 229 | .icon-pinterest-sign:before{content:"\f0d3";} 230 | .icon-google-plus-sign:before{content:"\f0d4";} 231 | .icon-google-plus:before{content:"\f0d5";} 232 | .icon-money:before{content:"\f0d6";} 233 | .icon-caret-down:before{content:"\f0d7";} 234 | .icon-caret-up:before{content:"\f0d8";} 235 | .icon-caret-left:before{content:"\f0d9";} 236 | .icon-caret-right:before{content:"\f0da";} 237 | .icon-columns:before{content:"\f0db";} 238 | .icon-sort:before{content:"\f0dc";} 239 | .icon-sort-down:before{content:"\f0dd";} 240 | .icon-sort-up:before{content:"\f0de";} 241 | .icon-envelope:before{content:"\f0e0";} 242 | .icon-linkedin:before{content:"\f0e1";} 243 | .icon-rotate-left:before,.icon-undo:before{content:"\f0e2";} 244 | .icon-legal:before{content:"\f0e3";} 245 | .icon-dashboard:before{content:"\f0e4";} 246 | .icon-comment-alt:before{content:"\f0e5";} 247 | .icon-comments-alt:before{content:"\f0e6";} 248 | .icon-bolt:before{content:"\f0e7";} 249 | .icon-sitemap:before{content:"\f0e8";} 250 | .icon-umbrella:before{content:"\f0e9";} 251 | .icon-paste:before{content:"\f0ea";} 252 | .icon-lightbulb:before{content:"\f0eb";} 253 | .icon-exchange:before{content:"\f0ec";} 254 | .icon-cloud-download:before{content:"\f0ed";} 255 | .icon-cloud-upload:before{content:"\f0ee";} 256 | .icon-user-md:before{content:"\f0f0";} 257 | .icon-stethoscope:before{content:"\f0f1";} 258 | .icon-suitcase:before{content:"\f0f2";} 259 | .icon-bell-alt:before{content:"\f0f3";} 260 | .icon-coffee:before{content:"\f0f4";} 261 | .icon-food:before{content:"\f0f5";} 262 | .icon-file-text-alt:before{content:"\f0f6";} 263 | .icon-building:before{content:"\f0f7";} 264 | .icon-hospital:before{content:"\f0f8";} 265 | .icon-ambulance:before{content:"\f0f9";} 266 | .icon-medkit:before{content:"\f0fa";} 267 | .icon-fighter-jet:before{content:"\f0fb";} 268 | .icon-beer:before{content:"\f0fc";} 269 | .icon-h-sign:before{content:"\f0fd";} 270 | .icon-plus-sign-alt:before{content:"\f0fe";} 271 | .icon-double-angle-left:before{content:"\f100";} 272 | .icon-double-angle-right:before{content:"\f101";} 273 | .icon-double-angle-up:before{content:"\f102";} 274 | .icon-double-angle-down:before{content:"\f103";} 275 | .icon-angle-left:before{content:"\f104";} 276 | .icon-angle-right:before{content:"\f105";} 277 | .icon-angle-up:before{content:"\f106";} 278 | .icon-angle-down:before{content:"\f107";} 279 | .icon-desktop:before{content:"\f108";} 280 | .icon-laptop:before{content:"\f109";} 281 | .icon-tablet:before{content:"\f10a";} 282 | .icon-mobile-phone:before{content:"\f10b";} 283 | .icon-circle-blank:before{content:"\f10c";} 284 | .icon-quote-left:before{content:"\f10d";} 285 | .icon-quote-right:before{content:"\f10e";} 286 | .icon-spinner:before{content:"\f110";} 287 | .icon-circle:before{content:"\f111";} 288 | .icon-mail-reply:before,.icon-reply:before{content:"\f112";} 289 | .icon-github-alt:before{content:"\f113";} 290 | .icon-folder-close-alt:before{content:"\f114";} 291 | .icon-folder-open-alt:before{content:"\f115";} 292 | .icon-expand-alt:before{content:"\f116";} 293 | .icon-collapse-alt:before{content:"\f117";} 294 | .icon-smile:before{content:"\f118";} 295 | .icon-frown:before{content:"\f119";} 296 | .icon-meh:before{content:"\f11a";} 297 | .icon-gamepad:before{content:"\f11b";} 298 | .icon-keyboard:before{content:"\f11c";} 299 | .icon-flag-alt:before{content:"\f11d";} 300 | .icon-flag-checkered:before{content:"\f11e";} 301 | .icon-terminal:before{content:"\f120";} 302 | .icon-code:before{content:"\f121";} 303 | .icon-reply-all:before{content:"\f122";} 304 | .icon-mail-reply-all:before{content:"\f122";} 305 | .icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123";} 306 | .icon-location-arrow:before{content:"\f124";} 307 | .icon-crop:before{content:"\f125";} 308 | .icon-code-fork:before{content:"\f126";} 309 | .icon-unlink:before{content:"\f127";} 310 | .icon-question:before{content:"\f128";} 311 | .icon-info:before{content:"\f129";} 312 | .icon-exclamation:before{content:"\f12a";} 313 | .icon-superscript:before{content:"\f12b";} 314 | .icon-subscript:before{content:"\f12c";} 315 | .icon-eraser:before{content:"\f12d";} 316 | .icon-puzzle-piece:before{content:"\f12e";} 317 | .icon-microphone:before{content:"\f130";} 318 | .icon-microphone-off:before{content:"\f131";} 319 | .icon-shield:before{content:"\f132";} 320 | .icon-calendar-empty:before{content:"\f133";} 321 | .icon-fire-extinguisher:before{content:"\f134";} 322 | .icon-rocket:before{content:"\f135";} 323 | .icon-maxcdn:before{content:"\f136";} 324 | .icon-chevron-sign-left:before{content:"\f137";} 325 | .icon-chevron-sign-right:before{content:"\f138";} 326 | .icon-chevron-sign-up:before{content:"\f139";} 327 | .icon-chevron-sign-down:before{content:"\f13a";} 328 | .icon-html5:before{content:"\f13b";} 329 | .icon-css3:before{content:"\f13c";} 330 | .icon-anchor:before{content:"\f13d";} 331 | .icon-unlock-alt:before{content:"\f13e";} 332 | .icon-bullseye:before{content:"\f140";} 333 | .icon-ellipsis-horizontal:before{content:"\f141";} 334 | .icon-ellipsis-vertical:before{content:"\f142";} 335 | .icon-rss-sign:before{content:"\f143";} 336 | .icon-play-sign:before{content:"\f144";} 337 | .icon-ticket:before{content:"\f145";} 338 | .icon-minus-sign-alt:before{content:"\f146";} 339 | .icon-check-minus:before{content:"\f147";} 340 | .icon-level-up:before{content:"\f148";} 341 | .icon-level-down:before{content:"\f149";} 342 | .icon-check-sign:before{content:"\f14a";} 343 | .icon-edit-sign:before{content:"\f14b";} 344 | .icon-external-link-sign:before{content:"\f14c";} 345 | .icon-share-sign:before{content:"\f14d";} 346 | .icon-compass:before{content:"\f14e";} 347 | .icon-collapse:before{content:"\f150";} 348 | .icon-collapse-top:before{content:"\f151";} 349 | .icon-expand:before{content:"\f152";} 350 | .icon-euro:before,.icon-eur:before{content:"\f153";} 351 | .icon-gbp:before{content:"\f154";} 352 | .icon-dollar:before,.icon-usd:before{content:"\f155";} 353 | .icon-rupee:before,.icon-inr:before{content:"\f156";} 354 | .icon-yen:before,.icon-jpy:before{content:"\f157";} 355 | .icon-renminbi:before,.icon-cny:before{content:"\f158";} 356 | .icon-won:before,.icon-krw:before{content:"\f159";} 357 | .icon-bitcoin:before,.icon-btc:before{content:"\f15a";} 358 | .icon-file:before{content:"\f15b";} 359 | .icon-file-text:before{content:"\f15c";} 360 | .icon-sort-by-alphabet:before{content:"\f15d";} 361 | .icon-sort-by-alphabet-alt:before{content:"\f15e";} 362 | .icon-sort-by-attributes:before{content:"\f160";} 363 | .icon-sort-by-attributes-alt:before{content:"\f161";} 364 | .icon-sort-by-order:before{content:"\f162";} 365 | .icon-sort-by-order-alt:before{content:"\f163";} 366 | .icon-thumbs-up:before{content:"\f164";} 367 | .icon-thumbs-down:before{content:"\f165";} 368 | .icon-youtube-sign:before{content:"\f166";} 369 | .icon-youtube:before{content:"\f167";} 370 | .icon-xing:before{content:"\f168";} 371 | .icon-xing-sign:before{content:"\f169";} 372 | .icon-youtube-play:before{content:"\f16a";} 373 | .icon-dropbox:before{content:"\f16b";} 374 | .icon-stackexchange:before{content:"\f16c";} 375 | .icon-instagram:before{content:"\f16d";} 376 | .icon-flickr:before{content:"\f16e";} 377 | .icon-adn:before{content:"\f170";} 378 | .icon-bitbucket:before{content:"\f171";} 379 | .icon-bitbucket-sign:before{content:"\f172";} 380 | .icon-tumblr:before{content:"\f173";} 381 | .icon-tumblr-sign:before{content:"\f174";} 382 | .icon-long-arrow-down:before{content:"\f175";} 383 | .icon-long-arrow-up:before{content:"\f176";} 384 | .icon-long-arrow-left:before{content:"\f177";} 385 | .icon-long-arrow-right:before{content:"\f178";} 386 | .icon-apple:before{content:"\f179";} 387 | .icon-windows:before{content:"\f17a";} 388 | .icon-android:before{content:"\f17b";} 389 | .icon-linux:before{content:"\f17c";} 390 | .icon-dribbble:before{content:"\f17d";} 391 | .icon-skype:before{content:"\f17e";} 392 | .icon-foursquare:before{content:"\f180";} 393 | .icon-trello:before{content:"\f181";} 394 | .icon-female:before{content:"\f182";} 395 | .icon-male:before{content:"\f183";} 396 | .icon-gittip:before{content:"\f184";} 397 | .icon-sun:before{content:"\f185";} 398 | .icon-moon:before{content:"\f186";} 399 | .icon-archive:before{content:"\f187";} 400 | .icon-bug:before{content:"\f188";} 401 | .icon-vk:before{content:"\f189";} 402 | .icon-weibo:before{content:"\f18a";} 403 | .icon-renren:before{content:"\f18b";} 404 | -------------------------------------------------------------------------------- /public/css/font-awesome/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/css/font-awesome/font/FontAwesome.otf -------------------------------------------------------------------------------- /public/css/font-awesome/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/css/font-awesome/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/css/font-awesome/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/css/font-awesome/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/css/font-awesome/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/css/font-awesome/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/css/font/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/css/font/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /public/css/font/OpenSans-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/css/font/OpenSans-Regular-webfont.ttf -------------------------------------------------------------------------------- /public/css/font/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/css/font/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /public/css/g.css: -------------------------------------------------------------------------------- 1 | *{ font:11pt Verdana,Arial,Microsoft YaHei,sans-serif; color:#111; word-wrap:break-word;} 2 | th {font-weight: bold;} 3 | h3.tit {font-weight: bold; border-left: 3px solid #FF8000; padding-left: 5px;} 4 | .mt0{ margin-top:0px!important;} 5 | .mt5{ margin-top:5px!important;} 6 | .mt10{ margin-top:10px!important;} 7 | .m10{ margin:10px!important;} 8 | .mt15{ margin-top:15px!important;} 9 | .mt20{ margin-top:20px!important;} 10 | .mb0{ margin-bottom:0px!important;} 11 | .mb5{ margin-bottom:5px!important;} 12 | .mb10{ margin-bottom:10px!important;} 13 | .mb15{ margin-bottom:15px!important;} 14 | .mb20{ margin-bottom:20px!important;} 15 | /*float*/ 16 | .fl{ float:left!important;} 17 | .fr{ float:right!important;} 18 | .fn{ float:none!important;} 19 | .pagination{display:inline-block;padding-left:0;margin:18px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#eee}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:17px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px} 20 | hr { 21 | height: 0; 22 | -moz-box-sizing: content-box; 23 | box-sizing: content-box; 24 | margin-top: 20px; 25 | margin-bottom: 20px; 26 | border: 0; 27 | border-top: 1px solid #eee; 28 | } 29 | -------------------------------------------------------------------------------- /public/css/odometer.css: -------------------------------------------------------------------------------- 1 | .odometer.odometer-auto-theme, .odometer.odometer-theme-default { 2 | display: -moz-inline-box; 3 | -moz-box-orient: vertical; 4 | display: inline-block; 5 | vertical-align: middle; 6 | *vertical-align: auto; 7 | position: relative; 8 | } 9 | .odometer.odometer-auto-theme, .odometer.odometer-theme-default { 10 | *display: inline; 11 | } 12 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-default .odometer-digit { 13 | display: -moz-inline-box; 14 | -moz-box-orient: vertical; 15 | display: inline-block; 16 | vertical-align: middle; 17 | *vertical-align: auto; 18 | position: relative; 19 | } 20 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-default .odometer-digit { 21 | *display: inline; 22 | } 23 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-default .odometer-digit .odometer-digit-spacer { 24 | display: -moz-inline-box; 25 | -moz-box-orient: vertical; 26 | display: inline-block; 27 | vertical-align: middle; 28 | *vertical-align: auto; 29 | visibility: hidden; 30 | } 31 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-default .odometer-digit .odometer-digit-spacer { 32 | *display: inline; 33 | } 34 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-default .odometer-digit .odometer-digit-inner { 35 | text-align: left; 36 | display: block; 37 | position: absolute; 38 | top: 0; 39 | left: 0; 40 | right: 0; 41 | bottom: 0; 42 | overflow: hidden; 43 | } 44 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon, .odometer.odometer-theme-default .odometer-digit .odometer-ribbon { 45 | display: block; 46 | } 47 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon-inner, .odometer.odometer-theme-default .odometer-digit .odometer-ribbon-inner { 48 | display: block; 49 | } 50 | .odometer.odometer-auto-theme .odometer-digit .odometer-value, .odometer.odometer-theme-default .odometer-digit .odometer-value { 51 | display: block; 52 | } 53 | .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-default .odometer-digit .odometer-value.odometer-last-value { 54 | position: absolute; 55 | } 56 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-default.odometer-animating-up .odometer-ribbon-inner { 57 | -webkit-transition: -webkit-transform 1s; 58 | -moz-transition: -moz-transform 1s; 59 | -ms-transition: -ms-transform 1s; 60 | -o-transition: -o-transform 1s; 61 | transition: transform 1s; 62 | } 63 | .odometer.odometer-auto-theme.odometer-animating-up.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-default.odometer-animating-up.odometer-animating .odometer-ribbon-inner { 64 | -webkit-transform: translateY(-100%); 65 | -moz-transform: translateY(-100%); 66 | -ms-transform: translateY(-100%); 67 | -o-transform: translateY(-100%); 68 | transform: translateY(-100%); 69 | } 70 | .odometer.odometer-auto-theme.odometer-animating-down .odometer-ribbon-inner, .odometer.odometer-theme-default.odometer-animating-down .odometer-ribbon-inner { 71 | -webkit-transform: translateY(-100%); 72 | -moz-transform: translateY(-100%); 73 | -ms-transform: translateY(-100%); 74 | -o-transform: translateY(-100%); 75 | transform: translateY(-100%); 76 | } 77 | .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-default.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 78 | -webkit-transition: -webkit-transform 1s; 79 | -moz-transition: -moz-transform 1s; 80 | -ms-transition: -ms-transform 1s; 81 | -o-transition: -o-transform 1s; 82 | -o-transition: -o-transform 1s; 83 | transition: transform 1s; 84 | -webkit-transform: translateY(0); 85 | -moz-transform: translateY(0); 86 | -ms-transform: translateY(0); 87 | -o-transform: translateY(0); 88 | transform: translateY(0); 89 | } 90 | 91 | .odometer.odometer-auto-theme, .odometer.odometer-theme-default { 92 | font-family: "Helvetica Neue", sans-serif; 93 | line-height: 1.1em; 94 | } 95 | -------------------------------------------------------------------------------- /public/css/pages/dashboard.css: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------ 2 | Bootstrap Admin Template by EGrappler.com 3 | ------------------------------------------------------------------*/ 4 | 5 | 6 | 7 | /*------------------------------------------------------------------ 8 | [1. Shortcuts / .shortcuts] 9 | */ 10 | 11 | .shortcuts { 12 | text-align: center; 13 | } 14 | 15 | .shortcuts .shortcut { 16 | width: 22.50%; 17 | display: inline-block; 18 | padding: 12px 0; 19 | margin: 0 .9% 1em; 20 | vertical-align: top; 21 | 22 | text-decoration: none; 23 | 24 | background: #f9f6f1; 25 | 26 | border-radius: 5px; 27 | } 28 | 29 | .shortcuts .shortcut .shortcut-icon { 30 | margin-top: .25em; 31 | margin-bottom: .25em; 32 | 33 | font-size: 32px; 34 | color: #545454; 35 | } 36 | 37 | .shortcuts .shortcut:hover { 38 | background: #00ba8b; 39 | } 40 | 41 | .shortcuts .shortcut:hover span{ 42 | color: #fff; 43 | } 44 | 45 | .shortcuts .shortcut:hover .shortcut-icon { 46 | color: #fff; 47 | } 48 | 49 | .shortcuts .shortcut-label { 50 | display: block; 51 | 52 | font-weight: 400; 53 | color: #545454; 54 | } 55 | 56 | 57 | 58 | /*------------------------------------------------------------------ 59 | [2. Stats / .stats] 60 | */ 61 | 62 | .stats { 63 | width: 100%; 64 | display: table; 65 | padding: 0 0 0 10px; 66 | margin-top: .5em; 67 | margin-bottom: 1.9em; 68 | } 69 | 70 | .stats .stat { 71 | display: table-cell; 72 | width: 40%; 73 | vertical-align: top; 74 | 75 | font-size: 11px; 76 | font-weight: bold; 77 | color: #999; 78 | } 79 | 80 | .stat-value { 81 | display: block; 82 | margin-bottom: .55em; 83 | 84 | font-size: 30px; 85 | font-weight: bold; 86 | letter-spacing: -2px; 87 | color: #444; 88 | } 89 | 90 | .stat-time { 91 | text-align: center; 92 | padding-top: 1.5em; 93 | } 94 | 95 | .stat-time .stat-value { 96 | color: #19bc9c; 97 | font-size: 40px; 98 | } 99 | 100 | .stats #donut-chart { 101 | height: 100px; 102 | margin-left: -20px; 103 | } 104 | 105 | 106 | 107 | 108 | 109 | /*------------------------------------------------------------------ 110 | [3. News Item / .news-items] 111 | */ 112 | 113 | .news-items { 114 | margin: 1em 0 0; 115 | } 116 | 117 | .news-items li { 118 | display: table; 119 | padding: 0 2em 0 1.5em; 120 | padding-bottom: 1em; 121 | margin-bottom: 1em; 122 | border-bottom: 1px dotted #CCC; 123 | } 124 | 125 | .news-items li:last-child { padding-bottom: 0; border: none; } 126 | 127 | .news-item-date { 128 | display: table-cell; 129 | } 130 | 131 | .news-item-detail { 132 | display: table-cell; 133 | } 134 | 135 | .news-item-title { 136 | font-size: 13px; 137 | font-weight: 600; 138 | } 139 | 140 | .news-item-date { 141 | width: 75px; 142 | vertical-align: middle; 143 | text-align: center; 144 | 145 | } 146 | 147 | .news-item-day { 148 | display: block; 149 | margin-bottom: .25em; 150 | 151 | font-size: 24px; 152 | color: #888; 153 | } 154 | 155 | .news-item-preview { 156 | margin-bottom: 0; 157 | 158 | color: #777; 159 | } 160 | 161 | .news-item-month { 162 | display: block; 163 | padding-right: 1px; 164 | 165 | font-size: 12px; 166 | font-weight: 600; 167 | color: #888; 168 | } 169 | 170 | 171 | 172 | /*------------------------------------------------------------------ 173 | [4. Action Table / .action-table] 174 | */ 175 | 176 | .action-table .btn-small { 177 | padding: 4px 5px 5px; 178 | 179 | font-size: 10px; 180 | } 181 | 182 | .action-table .td-actions { 183 | width: 80px; 184 | 185 | text-align: center; 186 | } 187 | 188 | .action-table .td-actions .btn { 189 | margin-right: .5em; 190 | } 191 | 192 | .action-table .td-actions .btn:last-child { 193 | margin-rigth: 0; 194 | } 195 | 196 | 197 | 198 | .big_stats { 199 | width: 100%; 200 | display: table; 201 | margin-top: 1.5em; 202 | } 203 | 204 | .big-stats-container .widget-content { 205 | border:0; 206 | } 207 | 208 | .big_stats .stat { 209 | width: 35%; 210 | height: auto; 211 | text-align: center; 212 | display: table-cell; 213 | padding: 0 0 1em; 214 | position: relative; 215 | 216 | border-right: 1px solid #CCC; 217 | border-left: 1px solid #FFF; 218 | } 219 | 220 | .big_stats i { font-size:20px; display:block; line-height: 30px; color:#b2afaa; } 221 | .big_stats .stat i { font: 20px/2em "Open Sans", sans-serif; color:#19bc9c; } 222 | 223 | h6.bigstats { 224 | margin: 20px; 225 | border-bottom: 1px solid #eee; 226 | padding-bottom: 20px; 227 | margin-bottom: 26px; 228 | } 229 | 230 | .big_stats .stat:first-child { 231 | border-left: none; 232 | width:33%; 233 | } 234 | 235 | .big_stats .stat:last-child { 236 | border-right: none; 237 | width:33%; 238 | } 239 | 240 | .big_stats .stat h4 { 241 | font-size: 11px; 242 | font-weight: bold; 243 | color: #777; 244 | margin-bottom: 1.5em; 245 | } 246 | 247 | .big_stats .stat .value { 248 | font-size: 20px; 249 | font-weight: bold; 250 | color: #545454; 251 | line-height: 1em; 252 | } 253 | 254 | 255 | 256 | @media all and (max-width: 950px) and (min-width: 1px) { 257 | 258 | .big_stats { 259 | display: block; 260 | margin-bottom: -10px; 261 | } 262 | 263 | .big_stats .stat { 264 | width: 32%; 265 | display: block; 266 | margin-bottom: 1em; 267 | float: left; 268 | } 269 | 270 | 271 | 272 | } 273 | 274 | @media (max-width: 767px) { 275 | .big_stats .stat .value { 276 | font-size: 25px; 277 | } 278 | } 279 | 280 | 281 | 282 | 283 | @media (max-width: 979px) { 284 | 285 | .shortcuts .shortcut { 286 | width: 31%; 287 | } 288 | } 289 | 290 | 291 | @media (max-width: 480px) { 292 | 293 | .stats .stat { 294 | 295 | margin-bottom: 3em; 296 | } 297 | 298 | .stats .stat .stat-value { 299 | margin-bottom: .15em; 300 | 301 | font-size: 20px; 302 | } 303 | 304 | .stats { 305 | float: left; 306 | 307 | display: block; 308 | 309 | margin-bottom: 0; 310 | } 311 | 312 | #chart-stats { 313 | margin: 2em 0 1em; 314 | } 315 | 316 | .shortcuts .shortcut { 317 | width: 48%; 318 | } 319 | } 320 | 321 | /* ADDED BY AFAQ */ 322 | .widget-content { padding:0px; } 323 | .dataTables_length { display:none; } 324 | .dataTable { margin-bottom:0px; } 325 | 326 | .pulse { 327 | background:#19bc9c; 328 | border:1px solid #19bc9c; 329 | color:white; 330 | } 331 | 332 | .pulse-border { 333 | border-color:#19bc9c; 334 | } 335 | 336 | .navbar { 337 | position:fixed; 338 | top:0; 339 | z-index:9999999; 340 | width:100%; 341 | } 342 | 343 | .subnavbar { 344 | position:fixed; 345 | top:50px; 346 | z-index:9999999; 347 | width:100%; 348 | } 349 | 350 | .main { 351 | margin-top:130px; 352 | } 353 | .dataTables_paginate{ width:100%; text-align:center; margin-top:10px; margin-left:-30px;} 354 | .dataTables_paginate a{ margin-left:8px;} 355 | .dataTables_paginate a:hover{ pointer:pointer;} 356 | .navbar-fixed-top{ margin-left:0px;} 357 | .widget-search{display: inline-block; vertical-align: middle; margin: 0 6px; width: 200px;} 358 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------ 2 | Bootstrap Admin Template by EGrappler.com 3 | ------------------------------------------------------------------*/ 4 | 5 | 6 | 7 | /*------------------------------------------------------------------ 8 | [1. Global] 9 | */ 10 | 11 | @font-face { 12 | font-family: "Open Sans"; 13 | font-style: normal; 14 | font-weight: normal; 15 | src: url("font/OpenSans-Regular-webfont.eot") format("embedded-opentype"), 16 | url("font/OpenSans-Regular-webfont.woff") format("woff"), 17 | url("font/OpenSans-Regular-webfont.ttf") format("truetype"), 18 | url("font/OpenSans-Regular-webfont.svg#OpenSansRegular") format("svg"); 19 | } 20 | 21 | body { 22 | background: #f9f6f1; 23 | font: 12px/1.5em "Open Sans", sans-serif; 24 | } 25 | 26 | a { 27 | cursor: pointer; 28 | } 29 | 30 | input, 31 | button, 32 | select, 33 | textarea { 34 | font-family: 'Open Sans'; 35 | } 36 | 37 | .dropdown .dropdown-menu { 38 | -webkit-border-radius: 6px; 39 | -moz-border-radius: 6px; 40 | border-radius: 6px; 41 | } 42 | 43 | .btn-icon-only { 44 | padding-right: 3px; 45 | padding-left: 3px; 46 | } 47 | 48 | .table td { 49 | vertical-align: middle; 50 | } 51 | 52 | .table-bordered th { 53 | background: #E9E9E9; 54 | background:-moz-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); /* FF3.6+ */ 55 | background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#FAFAFA), color-stop(100%,#E9E9E9)); /* Chrome,Safari4+ */ 56 | background:-webkit-linear-gradient(top, #FAFAFA 0%,#E9E9E9 100%); /* Chrome10+,Safari5.1+ */ 57 | background:-o-linear-gradient(top, #FAFAFA 0%,#E9E9E9 100%); /* Opera11.10+ */ 58 | background:-ms-linear-gradient(top, #FAFAFA 0%,#E9E9E9 100%); /* IE10+ */ 59 | background:linear-gradient(top, #FAFAFA 0%,#E9E9E9 100%); /* W3C */ 60 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FAFAFA', endColorstr='#E9E9E9'); 61 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#FAFAFA', endColorstr='#E9E9E9')"; 62 | 63 | font-size: 10px; 64 | color: #444; 65 | text-transform: uppercase; 66 | } 67 | 68 | 69 | /*------------------------------------------------------------------ 70 | [2. Navbar / .navbar] 71 | */ 72 | 73 | .navbar .container { 74 | position: relative; 75 | } 76 | 77 | .navbar-inner { 78 | padding: 7px 0; 79 | 80 | background: #00ba8b !important; 81 | 82 | -moz-border-radius: 0; 83 | -webkit-border-radius: 0; 84 | border-radius: 0; 85 | } 86 | 87 | .navbar-fixed-top { 88 | position: static; 89 | } 90 | 91 | .navbar .nav a { 92 | font-size: 11px; 93 | } 94 | .navbar .nav>li>a { color:#fff !important;} 95 | .navbar .brand { 96 | font-weight: 600; 97 | position: relative; 98 | top: 2px; 99 | } 100 | 101 | .navbar .search-query { 102 | background-color: #444; 103 | width: 150px; 104 | font-size: 11px; 105 | font-weight: bold; 106 | } 107 | 108 | .navbar .search-query::-webkit-input-placeholder { 109 | color: #666; 110 | } 111 | 112 | .navbar .search-query:-moz-placeholder { 113 | color: #666; 114 | } 115 | 116 | .navbar-search .search-query { background:#008866; border:0; color:#fff; line-height:normal;} 117 | 118 | 119 | /*------------------------------------------------------------------ 120 | [3. Subnavbar / .subnavbar] 121 | */ 122 | 123 | .subnavbar { 124 | margin-bottom: 2.5em; 125 | } 126 | 127 | .subnavbar-inner { 128 | height: 60px; 129 | background: #fff; 130 | border-bottom: 1px solid #d6d6d6; 131 | } 132 | 133 | .subnavbar .container > ul { 134 | display: inline-block; 135 | 136 | height: 80px; 137 | padding: 0; 138 | margin: 0; 139 | 140 | } 141 | 142 | .subnavbar .container > ul > li { 143 | float: left; 144 | 145 | min-width: 90px; 146 | height: 60px; 147 | padding: 0; 148 | margin: 0; 149 | 150 | text-align: center; 151 | list-style: none; 152 | 153 | border-left: 1px solid #d9d9d9; 154 | 155 | 156 | } 157 | 158 | .subnavbar .container > ul > li > a { 159 | display: block; 160 | 161 | height: 100%; 162 | padding: 0 15px; 163 | 164 | font-size: 12px; 165 | font-weight: bold; 166 | color: #b2afaa; 167 | } 168 | 169 | .subnavbar .container > ul > li > a:hover { 170 | color: #888; 171 | text-decoration: none; 172 | } 173 | 174 | .subnavbar .container > ul > li > a > i { 175 | display: inline-block; 176 | 177 | width: 24px; 178 | height: 24px; 179 | margin-top: 11px; 180 | margin-bottom: -3px; 181 | font-size: 20px; 182 | } 183 | 184 | .subnavbar .container > ul > li > a > span { 185 | display: block; 186 | 187 | } 188 | 189 | 190 | .subnavbar .container > ul > li:hover > a { 191 | 192 | border-bottom:3px solid #ff7f74; 193 | color: #383838; 194 | } 195 | 196 | 197 | .subnavbar .dropdown .dropdown-menu a { 198 | font-size: 12px; 199 | } 200 | 201 | 202 | .subnavbar .dropdown .dropdown-menu { 203 | text-align: left; 204 | 205 | -webkit-border-top-left-radius: 0; 206 | -webkit-border-top-right-radius: 0; 207 | -moz-border-radius-topleft: 0; 208 | -moz-border-radius-topright: 0; 209 | border-top-left-radius: 0; 210 | border-top-right-radius: 0; 211 | } 212 | 213 | 214 | 215 | .subnavbar .dropdown-menu::before { 216 | content: ''; 217 | display: inline-block; 218 | border-left: 7px solid transparent; 219 | border-right: 7px solid transparent; 220 | border-bottom: 7px solid #CCC; 221 | border-bottom-color: rgba(0, 0, 0, 0.2); 222 | position: absolute; 223 | top: -7px; 224 | left: 9px; 225 | } 226 | 227 | .subnavbar .dropdown-menu::after { 228 | content: ''; 229 | display: inline-block; 230 | border-left: 6px solid transparent; 231 | border-right: 6px solid transparent; 232 | border-bottom: 6px solid white; 233 | position: absolute; 234 | top: -6px; 235 | left: 10px; 236 | } 237 | 238 | 239 | .subnavbar .caret { 240 | margin-top: 4px; 241 | 242 | border-top-color: white; 243 | border-bottom-color: white; 244 | } 245 | 246 | .subnavbar .dropdown.open .caret { 247 | display: none; 248 | } 249 | 250 | 251 | 252 | 253 | 254 | /*------------------------------------------------------------------ 255 | [4. Main / .main] 256 | 257 | 258 | .main { 259 | padding-bottom: 2em; 260 | 261 | border-bottom: 1px solid #000; 262 | } 263 | */ 264 | 265 | 266 | /*------------------------------------------------------------------ 267 | [5. Extra / .extra] 268 | */ 269 | 270 | .extra { 271 | 272 | border-top: 1px solid #585858; 273 | border-bottom: 1px solid #000; 274 | 275 | } 276 | 277 | .extra-inner { 278 | padding: 20px 0; 279 | 280 | font-size: 11px; 281 | color: #BBB; 282 | 283 | background: #1A1A1A; 284 | } 285 | 286 | .extra a { 287 | color: #666; 288 | } 289 | 290 | .extra h4 { 291 | margin-bottom: 1em; 292 | 293 | font-weight: 400; 294 | } 295 | 296 | .extra ul { 297 | padding: 0; 298 | margin: 0; 299 | } 300 | 301 | .extra li { 302 | margin-bottom: .6em; 303 | 304 | list-style: none; 305 | } 306 | 307 | 308 | 309 | 310 | /*------------------------------------------------------------------ 311 | [6. Footer/ .footer] 312 | */ 313 | 314 | .footer { 315 | margin-top: 0; 316 | 317 | border-top: 1px solid #292929; 318 | } 319 | 320 | .footer-inner { 321 | padding: 15px 0; 322 | 323 | font-size: 12px; 324 | background: #111; 325 | color: #999; 326 | } 327 | 328 | .footer a { 329 | color: #999; 330 | } 331 | 332 | .footer a:hover { 333 | color: #FFF; 334 | text-decoration: none; 335 | } 336 | 337 | 338 | /*------------------------------------------------------------------ 339 | [6. Widget / .widget] 340 | */ 341 | 342 | .widget { 343 | 344 | position: relative; 345 | clear: both; 346 | 347 | width: auto; 348 | 349 | margin-bottom: 2em; 350 | 351 | overflow: hidden; 352 | } 353 | 354 | .widget-header { 355 | position: relative; 356 | cursor: move; 357 | height: 40px; 358 | line-height: 40px; 359 | 360 | background: #f9f6f1; 361 | background:-moz-linear-gradient(top, #f9f6f1 0%, #f2efea 100%); /* FF3.6+ */ 362 | background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#f9f6f1), color-stop(100%,#f2efea)); /* Chrome,Safari4+ */ 363 | background:-webkit-linear-gradient(top, #f9f6f1 0%,#f2efea 100%); /* Chrome10+,Safari5.1+ */ 364 | background:-o-linear-gradient(top, #f9f6f1 0%,#f2efea 100%); /* Opera11.10+ */ 365 | background:-ms-linear-gradient(top, #f9f6f1 0%,#f2efea 100%); /* IE10+ */ 366 | background:linear-gradient(top, #f9f6f1 0%,#f2efea 100%); /* W3C */ 367 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f9f6f1', endColorstr='#f2efea'); 368 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#f9f6f1', endColorstr='#f2efea')"; 369 | 370 | 371 | border: 1px solid #d6d6d6; 372 | 373 | 374 | -webkit-background-clip: padding-box; 375 | } 376 | 377 | .widget-header h3 { 378 | position: relative; 379 | top: 2px; 380 | left: 10px; 381 | 382 | display: inline-block; 383 | margin-right: 3em; 384 | 385 | font-size: 14px; 386 | font-weight: 800; 387 | color: #525252; 388 | line-height: 18px; 389 | 390 | text-shadow: 1px 1px 2px rgba(255,255,255,.5); 391 | } 392 | 393 | .widget-header [class^="icon-"], .widget-header [class*=" icon-"] { 394 | 395 | display: inline-block; 396 | margin-left: 13px; 397 | margin-right: -2px; 398 | 399 | font-size: 16px; 400 | color: #555; 401 | vertical-align: middle; 402 | } 403 | 404 | .widget-content { 405 | padding: 20px 15px 15px; 406 | background: #FFF; 407 | 408 | 409 | border: 1px solid #D5D5D5; 410 | 411 | -moz-border-radius: 5px; 412 | -webkit-border-radius: 5px; 413 | border-radius: 5px; 414 | } 415 | 416 | .widget-header+.widget-content { 417 | border-top: none; 418 | 419 | -webkit-border-top-left-radius: 0; 420 | -webkit-border-top-right-radius: 0; 421 | -moz-border-radius-topleft: 0; 422 | -moz-border-radius-topright: 0; 423 | border-top-left-radius: 0; 424 | border-top-right-radius: 0; 425 | } 426 | 427 | .widget-nopad .widget-content { 428 | padding: 0; 429 | } 430 | 431 | /* Widget Content Clearfix */ 432 | .widget-content:before, 433 | .widget-content:after { 434 | content:""; 435 | display:table; 436 | } 437 | 438 | .widget-content:after { 439 | clear:both; 440 | } 441 | 442 | /* For IE 6/7 (trigger hasLayout) */ 443 | .widget-content { 444 | zoom:1; 445 | } 446 | 447 | /* Widget Table */ 448 | 449 | .widget-table .widget-content { 450 | padding: 0; 451 | } 452 | 453 | .widget-table .table { 454 | margin-bottom: 0; 455 | 456 | border: none; 457 | } 458 | 459 | .widget-table .table tr td:first-child { 460 | border-left: none; 461 | } 462 | 463 | .widget-table .table tr th:first-child { 464 | border-left: none; 465 | } 466 | 467 | 468 | /* Widget Plain */ 469 | 470 | .widget-plain { 471 | 472 | background: transparent; 473 | 474 | border: none; 475 | } 476 | 477 | .widget-plain .widget-content { 478 | padding: 0; 479 | 480 | background: transparent; 481 | 482 | border: none; 483 | } 484 | 485 | 486 | /* Widget Box */ 487 | 488 | .widget-box { 489 | 490 | } 491 | 492 | .widget-box .widget-content { 493 | background: #E3E3E3; 494 | background: #FFF; 495 | } 496 | 497 | 498 | 499 | /*------------------------------------------------------------------ 500 | [7. Error / .error-container] 501 | */ 502 | 503 | .error-container { 504 | margin-top: 4em; 505 | margin-bottom: 4em; 506 | text-align: center; 507 | } 508 | 509 | .error-container h1 { 510 | margin-bottom: .5em; 511 | 512 | font-size: 120px; 513 | line-height: 1em; 514 | } 515 | 516 | .error-container h2 { 517 | margin-bottom: .75em; 518 | font-size: 28px; 519 | } 520 | 521 | .error-container .error-details { 522 | margin-bottom: 1.5em; 523 | 524 | font-size: 16px; 525 | } 526 | 527 | .error-container .error-actions a { 528 | margin: 0 .5em; 529 | } 530 | 531 | 532 | 533 | /* Message layout */ 534 | 535 | 536 | ul.messages_layout { 537 | position: relative; 538 | margin: 0; 539 | padding: 0 540 | } 541 | ul.messages_layout li { 542 | float: left; 543 | list-style: none; 544 | position: relative 545 | } 546 | ul.messages_layout li.left { 547 | padding-left: 75px 548 | } 549 | ul.messages_layout li.right { 550 | padding-right: 75px 551 | } 552 | ul.messages_layout li.right .avatar { 553 | right: 0; 554 | left: auto 555 | } 556 | ul.messages_layout li.right .message_wrap .arrow { 557 | right: -12px; 558 | left: auto; 559 | background-position: 0 -213px; 560 | height: 15px; 561 | width: 12px 562 | } 563 | ul.messages_layout li.by_myself .message_wrap { 564 | border: 1px solid #b3cdf8 565 | } 566 | ul.messages_layout li.by_myself .message_wrap .info a.name { 567 | color: #4a8cf7 568 | } 569 | ul.messages_layout li a.avatar { 570 | position: absolute; 571 | left: 0; 572 | top: 0 573 | } 574 | ul.messages_layout li a.avatar img { 575 | -webkit-border-radius: 5px; 576 | -moz-border-radius: 5px; 577 | border-radius: 5px 578 | } 579 | ul.messages_layout li .message_wrap { 580 | -webkit-border-radius: 3px; 581 | -moz-border-radius: 3px; 582 | border-radius: 3px; 583 | position: relative; 584 | border: 1px solid #e9e9e9; 585 | padding: 10px; 586 | border: 1px solid #cbcbcb; 587 | margin-bottom: 20px; 588 | float: left; 589 | background: #fefefe; 590 | -webkit-box-shadow: rgba(0,0,0,0.1) 0 1px 0px; 591 | -moz-box-shadow: rgba(0,0,0,0.1) 0 1px 0px; 592 | box-shadow: rgba(0,0,0,0.1) 0 1px 0px 593 | } 594 | ul.messages_layout li .message_wrap .arrow { 595 | background-position: 0 -228px; 596 | height: 15px; 597 | width: 12px; 598 | height: 15px; 599 | width: 12px; 600 | position: absolute; 601 | left: -12px; 602 | top: 13px 603 | } 604 | ul.messages_layout li .message_wrap .info { 605 | float: left; 606 | width: 100%; 607 | border-bottom: 1px solid #fff; 608 | line-height: 23px 609 | } 610 | ul.messages_layout li .message_wrap .info .name { 611 | float: left; 612 | font-weight: bold; 613 | color: #483734 614 | } 615 | ul.messages_layout li .message_wrap .info .time { 616 | float: left; 617 | font-size: 11px; 618 | margin-left: 6px 619 | } 620 | ul.messages_layout li .message_wrap .text { 621 | float: left; 622 | width: 100%; 623 | border-top: 1px solid #cfcfcf; 624 | padding-top: 5px 625 | } 626 | 627 | ul.messages_layout .dropdown-menu li{ width:100%; font-size:11px;} 628 | 629 | 630 | /* Full Calendar */ 631 | 632 | .fc { 633 | direction: ltr; 634 | text-align: left; 635 | position: relative 636 | } 637 | .fc table { 638 | border-collapse: collapse; 639 | border-spacing: 0 640 | } 641 | html .fc, .fc table { 642 | font-size: 1em 643 | } 644 | .fc td, .fc th { 645 | padding: 0; 646 | vertical-align: top 647 | } 648 | .fc-header td { 649 | white-space: nowrap; 650 | background: none 651 | } 652 | .fc-header-left { 653 | width: 100%; 654 | text-align: left; 655 | position: absolute; 656 | left: 0; 657 | top: 6px 658 | } 659 | .fc-header-left .fc-button { 660 | margin: 0; 661 | position: relative 662 | } 663 | .fc-header-left .fc-button-prev, .fc-header-left .fc-button-next { 664 | float: left; 665 | border: none; 666 | padding: 14px 10px; 667 | opacity: 0.5 668 | } 669 | .fc-header-left .fc-button-prev .fc-button-inner, .fc-header-left .fc-button-next .fc-button-inner { 670 | border: none 671 | } 672 | .fc-header-left .fc-button-prev .fc-button-inner .fc-button-content, .fc-header-left .fc-button-next .fc-button-inner .fc-button-content { 673 | display: none 674 | } 675 | .fc-header-left .fc-button-prev.fc-state-hover, .fc-header-left .fc-button-next.fc-state-hover { 676 | opacity: 1 677 | } 678 | .fc-header-left .fc-button-prev.fc-state-down, .fc-header-left .fc-button-next.fc-state-down { 679 | background: none !important; 680 | margin-top: -1px 681 | } 682 | .fc-header-left .fc-button-prev .fc-button-inner { 683 | background-position: 0 -351px; 684 | height: 16px; 685 | width: 11px 686 | } 687 | .fc-header-left .fc-button-next { 688 | float: right 689 | } 690 | .fc-header-left .fc-button-next .fc-button-inner { 691 | background-position: 0 -367px; 692 | height: 16px; 693 | width: 11px 694 | } 695 | .fc-header-center { 696 | text-align: center 697 | } 698 | .fc-header-right { 699 | text-align: right; 700 | position: absolute; 701 | top: -34px; 702 | right: 10px 703 | } 704 | .fc-header-title { 705 | display: inline-block; 706 | vertical-align: top 707 | } 708 | .fc-header-title h2 { 709 | margin-top: 0; 710 | white-space: nowrap; 711 | font-size: 1.1rem; 712 | color: #6C737F; 713 | line-height: 55px; 714 | } 715 | .fc .fc-header-space { 716 | padding-left: 10px 717 | } 718 | .fc-header .fc-button { 719 | margin-bottom: 1em; 720 | vertical-align: top 721 | } 722 | .fc-header .fc-button { 723 | margin-right: -1px 724 | } 725 | .fc-header .fc-corner-right { 726 | margin-right: 1px 727 | } 728 | .fc-header .ui-corner-right { 729 | margin-right: 0 730 | } 731 | .fc-header .fc-state-hover, .fc-header .ui-state-hover { 732 | z-index: 2 733 | } 734 | .fc-header .fc-state-down { 735 | z-index: 3 736 | } 737 | .fc-header .fc-state-active, .fc-header .ui-state-active { 738 | z-index: 4 739 | } 740 | .fc-content { 741 | clear: both; 742 | background: #f9f9f9 743 | } 744 | .fc-view { 745 | width: 100%; 746 | overflow: hidden 747 | } 748 | .fc-view thead { 749 | background:#e9ecf1; 750 | line-height: 35px 751 | } 752 | .fc-widget-header, .fc-widget-content { 753 | border: 1px solid #ccc 754 | } 755 | .fc-state-highlight { 756 | background: #F4F3E6 757 | } 758 | .fc-cell-overlay { 759 | background: #9cf; 760 | opacity: .2; 761 | filter: alpha(opacity=20) 762 | } 763 | .fc-button { 764 | position: relative; 765 | display: inline-block; 766 | cursor: pointer 767 | } 768 | .fc-button-today{margin-top: 8px !important;} 769 | .fc-state-default { 770 | border-style: solid; 771 | border-width: 1px 0 772 | } 773 | .fc-button-inner { 774 | position: relative; 775 | float: left; 776 | overflow: hidden 777 | } 778 | .fc-state-default .fc-button-inner { 779 | border-style: solid; 780 | border-width: 0 1px 781 | } 782 | .fc-button-content { 783 | position: relative; 784 | float: left; 785 | height: 1.9em; 786 | line-height: 1.9em; 787 | padding: 0 .6em; 788 | white-space: nowrap 789 | } 790 | .fc-button-content .fc-icon-wrap { 791 | position: relative; 792 | float: left; 793 | top: 50% 794 | } 795 | .fc-button-content .ui-icon { 796 | position: relative; 797 | float: left; 798 | margin-top: -50%; 799 | *margin-top:0; 800 | *top:-50% 801 | } 802 | .fc-state-default .fc-button-effect { 803 | position: absolute; 804 | top: 50%; 805 | left: 0 806 | } 807 | .fc-state-default .fc-button-effect span { 808 | position: absolute; 809 | top: -100px; 810 | left: 0; 811 | width: 500px; 812 | height: 100px; 813 | border-width: 100px 0 0 1px; 814 | border-style: solid; 815 | border-color: #fff; 816 | background: #444; 817 | opacity: .09; 818 | filter: alpha(opacity=9) 819 | } 820 | .fc-state-default, .fc-state-default .fc-button-inner { 821 | border-style: solid; 822 | border-color: #ccc #bbb #aaa; 823 | color: #000 824 | } 825 | .fc-state-hover, .fc-state-hover .fc-button-inner { 826 | border-color: #999 827 | } 828 | .fc-state-down { 829 | border-color: #555; 830 | background: #777 831 | } 832 | .fc-state-active, .fc-state-active .fc-button-inner { 833 | border-color: #555; 834 | background: #777; 835 | color: #fff 836 | } 837 | .fc-state-disabled, .fc-state-disabled .fc-button-inner { 838 | color: #999; 839 | border-color: #ddd 840 | } 841 | .fc-state-disabled { 842 | cursor: default 843 | } 844 | .fc-state-disabled .fc-button-effect { 845 | display: none 846 | } 847 | .fc-event { 848 | border-style: solid; 849 | border-width: 0; 850 | font-size: .85em; 851 | cursor: default 852 | } 853 | a.fc-event, .fc-event-draggable { 854 | cursor: pointer 855 | } 856 | a.fc-event { 857 | text-decoration: none 858 | } 859 | .fc-rtl .fc-event { 860 | text-align: right 861 | } 862 | .fc-event-skin { 863 | border-color: #3f85f5; 864 | background-color: #5e96ea; 865 | color: #fff 866 | } 867 | .fc-event-inner { 868 | position: relative; 869 | width: 100%; 870 | height: 100%; 871 | border-style: solid; 872 | border-width: 0; 873 | overflow: hidden 874 | } 875 | .fc-event-time, .fc-event-title { 876 | padding: 0 1px 877 | } 878 | .fc .ui-resizable-handle { 879 | display: block; 880 | position: absolute; 881 | z-index: 99999; 882 | overflow: hidden; 883 | font-size: 300%; 884 | line-height: 50% 885 | } 886 | .fc-event-hori { 887 | border-width: 1px 0; 888 | margin-bottom: 1px 889 | } 890 | .fc-event-hori .ui-resizable-e { 891 | top: 0 !important; 892 | right: -3px !important; 893 | width: 7px !important; 894 | height: 100% !important; 895 | cursor: e-resize 896 | } 897 | .fc-event-hori .ui-resizable-w { 898 | top: 0 !important; 899 | left: -3px !important; 900 | width: 7px !important; 901 | height: 100% !important; 902 | cursor: w-resize 903 | } 904 | .fc-event-hori .ui-resizable-handle { 905 | _padding-bottom: 14px 906 | } 907 | .fc-corner-left { 908 | margin-left: 1px 909 | } 910 | .fc-corner-left .fc-button-inner, .fc-corner-left .fc-event-inner { 911 | margin-left: -1px 912 | } 913 | .fc-corner-right { 914 | margin-right: 1px 915 | } 916 | .fc-corner-right .fc-button-inner, .fc-corner-right .fc-event-inner { 917 | margin-right: -1px 918 | } 919 | .fc-corner-top { 920 | margin-top: 1px 921 | } 922 | .fc-corner-top .fc-event-inner { 923 | margin-top: -1px 924 | } 925 | .fc-corner-bottom { 926 | margin-bottom: 1px 927 | } 928 | .fc-corner-bottom .fc-event-inner { 929 | margin-bottom: -1px 930 | } 931 | .fc-corner-left .fc-event-inner { 932 | border-left-width: 1px 933 | } 934 | .fc-corner-right .fc-event-inner { 935 | border-right-width: 1px 936 | } 937 | .fc-corner-top .fc-event-inner { 938 | border-top-width: 1px 939 | } 940 | .fc-corner-bottom .fc-event-inner { 941 | border-bottom-width: 1px 942 | } 943 | table.fc-border-separate { 944 | border-collapse: separate 945 | } 946 | .fc-border-separate th, .fc-border-separate td { 947 | border-width: 1px 0 0 1px 948 | } 949 | .fc-border-separate th.fc-last, .fc-border-separate td.fc-last { 950 | border-right-width: 1px 951 | } 952 | .fc-border-separate tr.fc-last th, .fc-border-separate tr.fc-last td { 953 | border-bottom-width: 0px 954 | } 955 | .fc-first { 956 | border-left-width: 0 !important 957 | } 958 | .fc-last { 959 | border-right-width: 0 !important 960 | } 961 | .fc-grid th { 962 | text-align: center 963 | } 964 | .fc-grid .fc-day-number { 965 | float: right; 966 | padding: 0 2px 967 | } 968 | .fc-grid .fc-other-month .fc-day-number { 969 | opacity: 0.3; 970 | filter: alpha(opacity=30) 971 | } 972 | .fc-grid .fc-day-content { 973 | clear: both; 974 | padding: 2px 2px 1px 975 | } 976 | .fc-grid .fc-event-time { 977 | font-weight: bold 978 | } 979 | .fc-rtl .fc-grid .fc-day-number { 980 | float: left 981 | } 982 | .fc-rtl .fc-grid .fc-event-time { 983 | float: right 984 | } 985 | .fc-agenda table { 986 | border-collapse: separate 987 | } 988 | .fc-agenda-days th { 989 | text-align: center 990 | } 991 | .fc-agenda .fc-agenda-axis { 992 | width: 60px !important; 993 | padding: 0 4px; 994 | vertical-align: middle; 995 | text-align: right; 996 | white-space: nowrap; 997 | font-weight: normal 998 | } 999 | .fc-agenda .fc-day-content { 1000 | padding: 2px 2px 1px 1001 | } 1002 | .fc-agenda-days .fc-agenda-axis { 1003 | border-right-width: 1px 1004 | } 1005 | .fc-agenda-days .fc-col0 { 1006 | border-left-width: 0 1007 | } 1008 | .fc-agenda-allday th { 1009 | border-width: 0 1px 1010 | } 1011 | .fc-agenda-allday .fc-day-content { 1012 | min-height: 34px; 1013 | _height: 34px 1014 | } 1015 | .fc-agenda-divider-inner { 1016 | height: 2px; 1017 | overflow: hidden 1018 | } 1019 | .fc-widget-header .fc-agenda-divider-inner { 1020 | background: #eee 1021 | } 1022 | .fc-agenda-slots th { 1023 | border-width: 1px 1px 0 1024 | } 1025 | .fc-agenda-slots td { 1026 | border-width: 1px 0 0; 1027 | background: none 1028 | } 1029 | .fc-agenda-slots td div { 1030 | height: 20px 1031 | } 1032 | .fc-agenda-slots tr.fc-slot0 th, .fc-agenda-slots tr.fc-slot0 td { 1033 | border-top-width: 0 1034 | } 1035 | .fc-agenda-slots tr.fc-minor th, .fc-agenda-slots tr.fc-minor td { 1036 | border-top-style: dotted 1037 | } 1038 | .fc-agenda-slots tr.fc-minor th.ui-widget-header { 1039 | *border-top-style:solid 1040 | } 1041 | .fc-event-vert { 1042 | border-width: 0 1px 1043 | } 1044 | .fc-event-vert .fc-event-head, .fc-event-vert .fc-event-content { 1045 | position: relative; 1046 | z-index: 2; 1047 | width: 100%; 1048 | overflow: hidden 1049 | } 1050 | .fc-event-vert .fc-event-time { 1051 | white-space: nowrap; 1052 | font-size: 10px 1053 | } 1054 | .fc-event-vert .fc-event-bg { 1055 | position: absolute; 1056 | z-index: 1; 1057 | top: 0; 1058 | left: 0; 1059 | width: 100%; 1060 | height: 100%; 1061 | background: #fff; 1062 | opacity: .3; 1063 | filter: alpha(opacity=30) 1064 | } 1065 | .fc .ui-draggable-dragging .fc-event-bg, .fc-select-helper .fc-event-bg { 1066 | display: none\9 1067 | } 1068 | .fc-event-vert .ui-resizable-s { 1069 | bottom: 0 !important; 1070 | width: 100% !important; 1071 | height: 8px !important; 1072 | overflow: hidden !important; 1073 | line-height: 8px !important; 1074 | font-size: 11px !important; 1075 | font-family: monospace; 1076 | text-align: center; 1077 | cursor: s-resize 1078 | } 1079 | .fc-agenda .ui-resizable-resizing { 1080 | _overflow: hidden 1081 | } 1082 | .fc-header-left .fc-button-prev .fc-button-inner {background: url('../img/icons-sa7c41345d9.png') no-repeat; background-position: 0 -351px; 1083 | height: 16px; 1084 | width: 11px;} 1085 | 1086 | .fc-header-left .fc-button-next .fc-button-inner {background: url('../img/icons-sa7c41345d9.png') no-repeat; background-position: 0 -367px; 1087 | height: 16px; 1088 | width: 11px;} 1089 | 1090 | /*------------------------------------------------------------------ 1091 | [8. Miscellaneous] 1092 | */ 1093 | 1094 | .chart-holder { 1095 | width: 100%; 1096 | height: 250px; 1097 | } 1098 | 1099 | .dropdown-menu li>a:hover, .dropdown-menu .active>a, .dropdown-menu .active>a:hover { background:#00ba8b;} 1100 | 1101 | .accordion-heading { background:#e5e5e5; } 1102 | .accordion-heading a { color:#545454; text-decoration:none; font-weight:bold; } 1103 | 1104 | .btn-facebook-alt i { 1105 | color: #23386a; 1106 | } 1107 | .btn-twitter-alt i { 1108 | color: #0098d0; 1109 | } 1110 | .btn-google-alt i { 1111 | color: #b6362d; 1112 | } 1113 | .btn-linkedin-alt i { 1114 | color: #0073b2; 1115 | } 1116 | .btn-pinterest-alt i { 1117 | color: #ab171e; 1118 | } 1119 | .btn-github-alt i { 1120 | color: #333; 1121 | } 1122 | 1123 | .all-icons li { list-style:none;} 1124 | 1125 | .ML0 { margin-left:0} 1126 | .MR0 { margin-right:0;} 1127 | 1128 | .paginate_active { text-decoration: underline; } 1129 | 1130 | #refresh-ispeed .icon-refresh, #refresh-ping .icon-refresh, #refresh-bandwidth .icon-refresh { 1131 | margin: 0; 1132 | } 1133 | 1134 | /*------------------------------------------------------------------ 1135 | [1. Max Width: 480px] 1136 | */ 1137 | 1138 | @media (max-width: 480px) { 1139 | 1140 | .error-container h1 { 1141 | font-size: 72px; 1142 | } 1143 | 1144 | } 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | /*------------------------------------------------------------------ 1151 | [1. Max Width: 767px] 1152 | */ 1153 | 1154 | @media (max-width: 767px) { 1155 | 1156 | #main { 1157 | padding: 0 10px; 1158 | margin-right: -20px; 1159 | margin-left: -20px; 1160 | } 1161 | 1162 | 1163 | .subnavbar { 1164 | margin-left: -20px; 1165 | margin-right: -20px; 1166 | } 1167 | 1168 | 1169 | .subnavbar-inner { 1170 | height: auto; 1171 | } 1172 | 1173 | .subnavbar .container > ul { 1174 | width: 100%; 1175 | height: auto; 1176 | 1177 | border: none; 1178 | } 1179 | 1180 | .subnavbar .container > ul > li { 1181 | width: 33%; 1182 | height: 70px; 1183 | margin-bottom: 0; 1184 | 1185 | border: none; 1186 | } 1187 | 1188 | 1189 | 1190 | .subnavbar .container > ul > li.active > a { 1191 | font-size: 11px; 1192 | background: transparent; 1193 | } 1194 | 1195 | .subnavbar .container > ul > li > a > i { 1196 | display: inline-block; 1197 | margin-bottom: 0; 1198 | 1199 | font-size: 20px; 1200 | } 1201 | 1202 | 1203 | .subnavbar-open-right .dropdown-menu { 1204 | left: auto; 1205 | right: 0; 1206 | } 1207 | 1208 | .subnavbar-open-right .dropdown-menu:before { 1209 | left: auto; 1210 | right: 12px; 1211 | } 1212 | .subnavbar-open-right .dropdown-menu:after { 1213 | left: auto; 1214 | right: 13px; 1215 | } 1216 | 1217 | .extra { 1218 | margin-right: -20px; 1219 | margin-left: -20px; 1220 | } 1221 | 1222 | .extra .container { 1223 | padding: 0 20px; 1224 | } 1225 | 1226 | .footer { 1227 | margin-right: -20px; 1228 | margin-left: -20px; 1229 | } 1230 | 1231 | .footer .container { 1232 | padding: 0 20px; 1233 | } 1234 | 1235 | .footer .footer-terms { 1236 | text-align: left; 1237 | } 1238 | 1239 | .footer .footer-terms a { 1240 | margin-left: 0; 1241 | margin-right: 1em; 1242 | } 1243 | 1244 | } 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | /*------------------------------------------------------------------ 1251 | [2. Max Width: 979px] 1252 | */ 1253 | 1254 | @media (max-width: 979px) { 1255 | 1256 | .navbar-fixed-top { 1257 | position: static; 1258 | 1259 | margin-bottom: 0; 1260 | } 1261 | 1262 | .subnavbar { 1263 | } 1264 | 1265 | .subnavbar .container { 1266 | width: auto; 1267 | } 1268 | 1269 | .widget-header { 1270 | height: 100%; 1271 | } 1272 | 1273 | .widget-header h3 { 1274 | margin-right: 1em; 1275 | } 1276 | 1277 | .js-refresh-info { 1278 | margin-left: 25px; 1279 | } 1280 | } 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | /*------------------------------------------------------------------ 1288 | [3. Max Width: 1199px] 1289 | */ 1290 | 1291 | @media (max-width: 1199px) { 1292 | .navbar .search-query { 1293 | width: 200px; 1294 | } 1295 | 1296 | .widget-header { 1297 | height: 100%; 1298 | } 1299 | 1300 | .js-refresh-info { 1301 | margin-left: 25px; 1302 | } 1303 | } 1304 | 1305 | /* ---------------------------------------- 1306 | Subnavbar CSS fix/work-around 1307 | */ 1308 | 1309 | .container.nav-collapse.in { 1310 | overflow:visible; 1311 | } 1312 | .btn-navbar { 1313 | margin-top:-38px; 1314 | } 1315 | 1316 | /*------------------------------------------------------------------ 1317 | general-info widget specific style 1318 | */ 1319 | 1320 | #general-info-widget .general-title { 1321 | font-weight: bold; 1322 | } 1323 | 1324 | #general-info-widget .general-title:after { 1325 | content:":"; 1326 | } 1327 | 1328 | #general-info-widget .widget-content { 1329 | padding:5px; 1330 | } 1331 | 1332 | #general-info-widget .widget-content div { 1333 | margin-bottom:5px; 1334 | } 1335 | 1336 | footer { 1337 | border-top: 1px solid #D8D7CF; 1338 | clear: both; 1339 | color: #9A9994; 1340 | font-size: 12px; 1341 | line-height: 15.4px; 1342 | margin-top: 15px; 1343 | overflow: hidden; 1344 | padding: 20px 0 40px; 1345 | } 1346 | footer .site-source { 1347 | background: url("/img/code.png") no-repeat scroll 0 2px transparent; 1348 | float: left; 1349 | padding-left: 46px; 1350 | } 1351 | footer .sfc-member {float: right;text-align: right;} 1352 | footer a {color: #403F3C;} 1353 | -------------------------------------------------------------------------------- /public/img/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/img/code.png -------------------------------------------------------------------------------- /public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaochao1/swcollector/1c7dd404bcb43fc1a2b1911a2519bad90fff903c/public/img/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | linux-dash : Server Monitoring Web Dashboard 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 39 | 62 |
63 |
64 |
65 |
66 |
67 |
68 | A simple web dashboard to monitor your switch. 69 |
70 |
71 |
72 | 73 |
74 |
75 |
76 |
77 | 78 |

79 | General Info 80 |

81 |
82 |
83 |
84 |
85 | 活跃交换机数 86 | 87 |
88 | 89 |
90 | IP段 91 | 92 |
93 | 94 |
95 | Server Time 96 | 97 |
98 | 99 |
100 | Agent Version 101 | 102 |
103 |
104 |
105 |
106 |
107 | 108 | 109 |
110 |
111 |
112 |
113 | 114 |

115 | Switch 116 |

117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | 126 | 127 |
128 | 129 | 130 |
131 |
132 |
133 | Falcon-swcollector is open source on GitHub.
134 | Patches, suggestions, and comments are welcome. 135 |
136 |
137 | Powered by gaochao1 freedomkk-qfeng 138 |
139 |
140 | 141 | 142 |
143 |
144 |
145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /public/js/base.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | // enable popovers 4 | $(".pop").popover(); 5 | 6 | // activate tooltips on hover 7 | $("[data-toggle='tooltip']").tooltip({trigger: 'hover', placement:'right'}); 8 | 9 | dashboard.getAll(); 10 | }).on("click", ".js-smoothscroll", function(event) { 11 | event.preventDefault(); 12 | var target = $(this.hash).parent(); 13 | pulseElement(target, 8, 400); 14 | 15 | $("html,body").animate({ 16 | scrollTop: target.offset().top - 130 17 | }, 1000); 18 | }).on("click", ".js-refresh-info", function(event) { 19 | event.preventDefault(); 20 | var target = event.target; 21 | var item = target.id.split("-").splice(-1)[0]; 22 | 23 | // if the refresh icon is click (where in a ) target will not have an id, so grab its parent instead 24 | if(target.id == "") { 25 | var parent = $(target).parent()[0]; 26 | item = parent.id.split("-").splice(-1)[0]; 27 | } 28 | 29 | dashboard.fnMap[item](); 30 | }); 31 | 32 | // Handle for cancelling active effect. 33 | var pulsing = { 34 | element: null, 35 | timeoutIDs: [], 36 | resetfn: function() { 37 | pulsing.element = null; 38 | pulsing.timeoutIDs = []; 39 | } 40 | }; 41 | 42 | /** 43 | * Applies a pulse effect to the specified element. If triggered while already 44 | * active the ongoing effect is cancelled immediately. 45 | * 46 | * @param {HTMLElement} element The element to apply the effect to. 47 | * @param {Number} times How many pulses. 48 | * @param {Number} interval Milliseconds between pulses. 49 | */ 50 | function pulseElement(element, times, interval) { 51 | if (pulsing.element) { 52 | pulsing.element.removeClass("pulse"). 53 | parent().removeClass("pulse-border"); 54 | pulsing.timeoutIDs.forEach(function(ID) { 55 | clearTimeout(ID); 56 | }); 57 | pulsing.timeoutIDs = []; 58 | } 59 | pulsing.element = element; 60 | var parent = element.parent(); 61 | var f = function() { 62 | element.toggleClass("pulse"); 63 | parent.toggleClass("pulse-border"); 64 | }; 65 | 66 | pulsing.timeoutIDs.push(setTimeout(pulsing.resetfn, 67 | (times + 1) * interval)); 68 | for (; times > 0; --times) { 69 | pulsing.timeoutIDs.push(setTimeout(f, times * interval)); 70 | } 71 | } 72 | 73 | function isInArray(array, search) 74 | { 75 | return (array.indexOf(search) >= 0) ? true : false; 76 | } 77 | -------------------------------------------------------------------------------- /public/js/dashboard.js: -------------------------------------------------------------------------------- 1 | // Gets data from provided url and updates DOM element. 2 | function generate_os_data(url, element) { 3 | $.get(url, function (d) { 4 | if (d.msg == "success") { 5 | $(element).text(d.data); 6 | } else { 7 | $(element).text(d.msg); 8 | } 9 | }, "json"); 10 | } 11 | 12 | // If dataTable with provided ID exists, destroy it. 13 | function destroy_dataTable(table_id) { 14 | var table = $("#" + table_id); 15 | var ex = document.getElementById(table_id); 16 | if ($.fn.DataTable.fnIsDataTable(ex)) { 17 | table.hide().dataTable().fnClearTable(); 18 | table.dataTable().fnDestroy(); 19 | } 20 | } 21 | 22 | //DataTables 23 | //Sort file size data. 24 | jQuery.extend(jQuery.fn.dataTableExt.oSort, { 25 | "file-size-units": { 26 | K: 1024, 27 | M: Math.pow(1024, 2), 28 | G: Math.pow(1024, 3), 29 | T: Math.pow(1024, 4), 30 | P: Math.pow(1024, 5), 31 | E: Math.pow(1024, 6) 32 | }, 33 | 34 | "file-size-pre": function (a) { 35 | var x = a.substring(0, a.length - 1); 36 | var x_unit = a.substring(a.length - 1, a.length); 37 | if (jQuery.fn.dataTableExt.oSort['file-size-units'][x_unit]) { 38 | return parseInt(x * jQuery.fn.dataTableExt.oSort['file-size-units'][x_unit], 10); 39 | } 40 | else { 41 | return parseInt(x + x_unit, 10); 42 | } 43 | }, 44 | 45 | "file-size-asc": function (a, b) { 46 | return ((a < b) ? -1 : ((a > b) ? 1 : 0)); 47 | }, 48 | 49 | "file-size-desc": function (a, b) { 50 | return ((a < b) ? 1 : ((a > b) ? -1 : 0)); 51 | } 52 | }); 53 | 54 | //DataTables 55 | //Sort numeric data which has a percent sign with it. 56 | jQuery.extend(jQuery.fn.dataTableExt.oSort, { 57 | "percent-pre": function (a) { 58 | var x = (a === "-") ? 0 : a.replace(/%/, ""); 59 | return parseFloat(x); 60 | }, 61 | 62 | "percent-asc": function (a, b) { 63 | return ((a < b) ? -1 : ((a > b) ? 1 : 0)); 64 | }, 65 | 66 | "percent-desc": function (a, b) { 67 | return ((a < b) ? 1 : ((a > b) ? -1 : 0)); 68 | } 69 | }); 70 | 71 | //DataTables 72 | //Sort IP addresses 73 | jQuery.extend(jQuery.fn.dataTableExt.oSort, { 74 | "ip-address-pre": function (a) { 75 | // split the address into octets 76 | // 77 | var x = a.split('.'); 78 | 79 | // pad each of the octets to three digits in length 80 | // 81 | function zeroPad(num, places) { 82 | var zero = places - num.toString().length + 1; 83 | return Array(+(zero > 0 && zero)).join("0") + num; 84 | } 85 | 86 | // build the resulting IP 87 | var r = ''; 88 | for (var i = 0; i < x.length; i++) 89 | r = r + zeroPad(x[i], 3); 90 | 91 | // return the formatted IP address 92 | // 93 | return r; 94 | }, 95 | 96 | "ip-address-asc": function (a, b) { 97 | return ((a < b) ? -1 : ((a > b) ? 1 : 0)); 98 | }, 99 | 100 | "ip-address-desc": function (a, b) { 101 | return ((a < b) ? 1 : ((a > b) ? -1 : 0)); 102 | } 103 | }); 104 | 105 | /******************************* 106 | Data Call Functions 107 | *******************************/ 108 | 109 | var dashboard = {}; 110 | 111 | 112 | 113 | dashboard.getOs = function () { 114 | generate_os_data("/page/sw/live", "#live-info"); 115 | generate_os_data("/page/sw/iprange", "#ip-range"); 116 | generate_os_data("/page/sw/time", "#os-time"); 117 | 118 | $.get("/version", function(d){ 119 | $("#agent-version").text(d); 120 | }); 121 | } 122 | 123 | 124 | dashboard.getSwitch = function () { 125 | $.get("/page/sw/list", function (data) { 126 | var table = $("#switch_list"); 127 | var ex = document.getElementById("switch_list"); 128 | if ($.fn.DataTable.fnIsDataTable(ex)) { 129 | table.hide().dataTable().fnClearTable(); 130 | table.dataTable().fnDestroy(); 131 | } 132 | 133 | table.dataTable({ 134 | aaData: data.data, 135 | aoColumns: [ 136 | { sTitle: "ip", sType: "ip-address" }, 137 | { sTitle: "hostname" }, 138 | { sTitle: "model" }, 139 | { sTitle: "uptime" }, 140 | { sTitle: "CPU%", sType: "percent" }, 141 | { sTitle: "Mem%", sType: "percent" }, 142 | { sTitle: "Ping", sType: "numeric" }, 143 | ], 144 | bPaginate: false, 145 | bFilter: false, 146 | bAutoWidth: false, 147 | bInfo: false 148 | }).fadeIn(); 149 | }, "json"); 150 | } 151 | 152 | /** 153 | * Refreshes all widgets. Does not call itself recursively. 154 | */ 155 | dashboard.getAll = function () { 156 | for (var item in dashboard.fnMap) { 157 | if (dashboard.fnMap.hasOwnProperty(item) && item !== "all") { 158 | dashboard.fnMap[item](); 159 | } 160 | } 161 | } 162 | 163 | dashboard.fnMap = { 164 | all: dashboard.getAll, 165 | os: dashboard.getOs, 166 | sw: dashboard.getSwitch, 167 | }; 168 | -------------------------------------------------------------------------------- /public/js/odometer.js: -------------------------------------------------------------------------------- 1 | /*! odometer 0.3.6 */ 2 | (function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y;p='',m=''+p+"",d='8'+m+"",g='',c=",ddd",h=60,f=2e3,a=20,i=2,e=.5,j=1e3/h,b=1e3/a,n="transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd",t=document.createElement("div").style,o=null!=t.transition||null!=t.webkitTransition||null!=t.mozTransition||null!=t.oTransition,s=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,k=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver,q=function(a){var b;return b=document.createElement("div"),b.innerHTML=a,b.children[0]},r=function(){var a,b;return null!=(a=null!=(b=window.performance)?b.now():void 0)?a:+new Date},v=!1,(u=function(){var a,b,c,d,e;if(!v&&null!=window.jQuery){for(v=!0,d=["html","text"],e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(function(a){var b;return b=window.jQuery.fn[a],window.jQuery.fn[a]=function(a){return null==a||null==this[0].odometer?b.apply(this,arguments):this[0].odometer.update(a)}}(a));return e}})(),setTimeout(u,0),l=function(){function a(b){var d,e,g,h,k,l,m,n,o,p,q,r,s,t,u,v=this;if(this.options=b,this.el=this.options.el,null!=this.el.odometer)return this.el.odometer;for(this.el.odometer=this,s=a.options,h=o=0,q=s.length;q>o;h=++o)e=s[h],null==this.options[e]&&(this.options[e]=h);this.value=this.cleanValue(null!=(t=this.options.value)?t:""),null==(k=this.options).format&&(k.format=c),(l=this.options).format||(l.format="d"),null==(m=this.options).duration&&(m.duration=f),this.MAX_VALUES=0|this.options.duration/j/i,this.renderInside(),this.render();try{for(u=["HTML","Text"],n=function(a){return Object.defineProperty(v.el,"inner"+a,{get:function(){return v.inside["outer"+a]},set:function(a){return v.update(v.cleanValue(a))}})},p=0,r=u.length;r>p;p++)g=u[p],n(g)}catch(w){d=w,this.watchForMutations()}}return a.prototype.renderInside=function(){return this.inside=document.createElement("div"),this.inside.className="odometer-inside",this.el.innerHTML="",this.el.appendChild(this.inside)},a.prototype.watchForMutations=function(){var a,b=this;if(null!=k)try{return null==this.observer&&(this.observer=new k(function(){var a;return a=b.el.innerText,b.renderInside(),b.render(b.value),b.update(a)})),this.watchMutations=!0,this.startWatchingMutations()}catch(c){a=c}},a.prototype.startWatchingMutations=function(){return this.watchMutations?this.observer.observe(this.el,{childList:!0}):void 0},a.prototype.stopWatchingMutations=function(){var a;return null!=(a=this.observer)?a.disconnect():void 0},a.prototype.cleanValue=function(a){return parseInt(a.toString().replace(/[.,]/g,""),10)||0},a.prototype.bindTransitionEnd=function(){var a,b,c,d,e,f,g=this;if(!this.transitionEndBound){for(this.transitionEndBound=!0,b=!1,e=n.split(" "),f=[],c=0,d=e.length;d>c;c++)a=e[c],f.push(this.el.addEventListener(a,function(){return b?!0:(b=!0,setTimeout(function(){return g.render(),b=!1},0),!0)},!1));return f}},a.prototype.resetFormat=function(){return this.format=this.options.format.split("").reverse().join("")},a.prototype.render=function(a){var b,c,d,e,f,g,h,i,j;for(null==a&&(a=this.value),this.stopWatchingMutations(),this.resetFormat(),this.inside.innerHTML="",b=this.el.className.split(" "),e=[],f=0,h=b.length;h>f;f++)c=b[f],c.length&&(/^odometer(-|$)/.test(c)||e.push(c));for(e.push("odometer"),o||e.push("odometer-no-transitions"),this.options.theme?e.push("odometer-theme-"+this.options.theme):e.push("odometer-auto-theme"),this.el.className=e.join(" "),this.ribbons={},this.digits=[],j=a.toString().split("").reverse(),g=0,i=j.length;i>g;g++)d=j[g],this.addDigit(d);return this.startWatchingMutations()},a.prototype.update=function(a){var b,c=this;return a=this.cleanValue(a),(b=a-this.value)?(this.el.className+=b>0?" odometer-animating-up":" odometer-animating-down",this.stopWatchingMutations(),this.animate(a),this.startWatchingMutations(),setTimeout(function(){return c.el.offsetHeight,c.el.className+=" odometer-animating"},0),this.value=a):void 0},a.prototype.renderDigit=function(){return q(d)},a.prototype.insertDigit=function(a){return this.inside.children.length?this.inside.insertBefore(a,this.inside.children[0]):this.inside.appendChild(a)},a.prototype.addSpacer=function(a){var b;return b=q(g),b.innerHTML=a,this.insertDigit(b)},a.prototype.addDigit=function(a){var b,c,d;if("-"===a)return this.addSpacer("-"),void 0;for(d=!1;;){if("-"===a)break;if(!this.format.length){if(d)throw new Error("Bad odometer format without digits");this.resetFormat(),d=!0}if(b=this.format[0],this.format=this.format.substring(1),"d"===b)break;this.addSpacer(b)}return c=this.renderDigit(),c.querySelector(".odometer-value").innerHTML=a,this.digits.push(c),this.insertDigit(c)},a.prototype.animate=function(a){return o?this.animateSlide(a):this.animateCount(a)},a.prototype.animateCount=function(a){var c,d,e,f,g,h=this;if(d=+a-this.value)return f=e=r(),c=this.value,(g=function(){var i,j,k;return r()-f>h.options.duration?(h.value=a,h.render(),void 0):(i=r()-e,i>b&&(e=r(),k=i/h.options.duration,j=d*k,c+=j,h.render(Math.round(c))),null!=window.requestAnimationFrame?s(g):setTimeout(g,b))})()},a.prototype.animateSlide=function(a){var b,c,d,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y;if(d=a-this.value){for(this.bindTransitionEnd(),f=Math.ceil(Math.log(Math.max(Math.abs(a),Math.abs(this.value))+1)/Math.log(10)),g=[],b=0,l=r=0;f>=0?f>r:r>f;l=f>=0?++r:--r){if(p=Math.floor(this.value/Math.pow(10,f-l-1)),i=Math.floor(a/Math.pow(10,f-l-1)),h=i-p,Math.abs(h)>this.MAX_VALUES){for(k=[],m=h/(this.MAX_VALUES+this.MAX_VALUES*b*e),c=p;h>0&&i>c||0>h&&c>i;)k.push(Math.round(c)),c+=m;k[k.length-1]!==i&&k.push(i),b++}else k=function(){x=[];for(var a=p;i>=p?i>=a:a>=i;i>=p?a++:a--)x.push(a);return x}.apply(this);for(l=s=0,u=k.length;u>s;l=++s)j=k[l],k[l]=Math.abs(j%10);g.push(k)}for(w=g.reverse(),y=[],l=t=0,v=w.length;v>t;l=++t)k=w[l],this.digits[l]||this.addDigit(" "),null==(q=this.ribbons)[l]&&(q[l]=this.digits[l].querySelector(".odometer-ribbon-inner")),this.ribbons[l].innerHTML="",0>d&&(k=k.reverse()),y.push(function(){var a,b,c;for(c=[],n=b=0,a=k.length;a>b;n=++b)j=k[n],o=document.createElement("div"),o.className="odometer-value",o.innerHTML=j,this.ribbons[l].appendChild(o),n===k.length-1&&(o.className+=" odometer-last-value"),0===n?c.push(o.className+=" odometer-first-value"):c.push(void 0);return c}.call(this));return y}},a}(),l.options=null!=(x=window.odometerOptions)?x:{},setTimeout(function(){var a,b,c,d,e;if(window.odometerOptions){d=window.odometerOptions,e=[];for(a in d)b=d[a],e.push(null!=(c=l.options)[a]?(c=l.options)[a]:c[a]=b);return e}},0),l.init=function(){var a,b,c,d,e;for(b=document.querySelectorAll(l.options.selector||".odometer"),e=[],c=0,d=b.length;d>c;c++)a=b[c],e.push(a.odometer=new l({el:a,value:a.innerText}));return e},null!=(null!=(y=document.documentElement)?y.doScroll:void 0)&&null!=document.createEventObject?(w=document.onreadystatechange,document.onreadystatechange=function(){return"complete"===document.readyState&&l.options.auto!==!1&&l.init(),null!=w?w.apply(this,arguments):void 0}):document.addEventListener("DOMContentLoaded",function(){return l.options.auto!==!1?l.init():void 0},!1),window.Odometer=l}).call(this); --------------------------------------------------------------------------------