================================================
FILE: docs/use.md
================================================
# 基本使用
## 无配置文件模式
此模式的各种配置在服务端web管理中完成,客户端除运行一条命令外无需任何其他设置
```
./npc -server=ip:port -vkey=web界面中显示的密钥
```
## 注册到系统服务(开机启动、守护进程)
对于linux、darwin
- 注册:`sudo ./npc install 其他参数(例如-server=xx -vkey=xx或者-config=xxx)`
- 启动:`sudo npc start`
- 停止:`sudo npc stop`
- 如果需要更换命令内容需要先卸载`./npc uninstall`,再重新注册
对于windows,使用管理员身份运行cmd
- 注册:`npc.exe install 其他参数(例如-server=xx -vkey=xx或者-config=xxx)`
- 启动:`npc.exe start`
- 停止:`npc.exe stop`
- 如果需要更换命令内容需要先卸载`npc.exe uninstall`,再重新注册
- 如果需要当客户端退出时自动重启客户端,请按照如图所示配置

注册到服务后,日志文件windows位于当前目录下,linux和darwin位于/var/log/npc.log
## 客户端更新
首先进入到对于的客户端二进制文件目录
请首先执行`sudo npc stop`或者`npc.exe stop`停止运行,然后
对于linux
```shell
sudo npc-update update
```
对于windows
```shell
npc-update.exe update
```
更新完成后,执行执行`sudo npc start`或者`npc.exe start`重新运行即可完成升级
如果无法更新成功,可以直接自行下载releases压缩包然后覆盖原有的npc二进制文件
## 配置文件模式
此模式使用nps的公钥或者客户端私钥验证,各种配置在客户端完成,同时服务端web也可以进行管理
```
./npc -config=npc配置文件路径
```
## 配置文件说明
[示例配置文件](https://github.com/ehang-io/nps/tree/master/conf/npc.conf)
#### 全局配置
```ini
[common]
server_addr=1.1.1.1:8024
conn_type=tcp
vkey=123
username=111
password=222
compress=true
crypt=true
rate_limit=10000
flow_limit=100
remark=test
max_conn=10
#pprof_addr=0.0.0.0:9999
```
项 | 含义
---|---
server_addr | 服务端ip/域名:port
conn_type | 与服务端通信模式(tcp或kcp)
vkey|服务端配置文件中的密钥(非web)
username|socks5或http(s)密码保护用户名(可忽略)
password|socks5或http(s)密码保护密码(可忽略)
compress|是否压缩传输(true或false或忽略)
crypt|是否加密传输(true或false或忽略)
rate_limit|速度限制,可忽略
flow_limit|流量限制,可忽略
remark|客户端备注,可忽略
max_conn|最大连接数,可忽略
pprof_addr|debug pprof ip:port
#### 域名代理
```ini
[common]
server_addr=1.1.1.1:8024
vkey=123
[web1]
host=a.proxy.com
target_addr=127.0.0.1:8080,127.0.0.1:8082
host_change=www.proxy.com
header_set_proxy=nps
```
项 | 含义
---|---
web1 | 备注
host | 域名(http|https都可解析)
target_addr|内网目标,负载均衡时多个目标,逗号隔开
host_change|请求host修改
header_xxx|请求header修改或添加,header_proxy表示添加header proxy:nps
#### tcp隧道模式
```ini
[common]
server_addr=1.1.1.1:8024
vkey=123
[tcp]
mode=tcp
target_addr=127.0.0.1:8080
server_port=9001
```
项 | 含义
---|---
mode | tcp
server_port | 在服务端的代理端口
tartget_addr|内网目标
#### udp隧道模式
```ini
[common]
server_addr=1.1.1.1:8024
vkey=123
[udp]
mode=udp
target_addr=127.0.0.1:8080
server_port=9002
```
项 | 含义
---|---
mode | udp
server_port | 在服务端的代理端口
target_addr|内网目标
#### http代理模式
```ini
[common]
server_addr=1.1.1.1:8024
vkey=123
[http]
mode=httpProxy
server_port=9003
```
项 | 含义
---|---
mode | httpProxy
server_port | 在服务端的代理端口
#### socks5代理模式
```ini
[common]
server_addr=1.1.1.1:8024
vkey=123
[socks5]
mode=socks5
server_port=9004
multi_account=multi_account.conf
```
项 | 含义
---|---
mode | socks5
server_port | 在服务端的代理端口
multi_account | socks5多账号配置文件(可选),配置后使用basic_username和basic_password无法通过认证
#### 私密代理模式
```ini
[common]
server_addr=1.1.1.1:8024
vkey=123
[secret_ssh]
mode=secret
password=ssh2
target_addr=10.1.50.2:22
```
项 | 含义
---|---
mode | secret
password | 唯一密钥
target_addr|内网目标
#### p2p代理模式
```ini
[common]
server_addr=1.1.1.1:8024
vkey=123
[p2p_ssh]
mode=p2p
password=ssh2
target_addr=10.1.50.2:22
```
项 | 含义
---|---
mode | p2p
password | 唯一密钥
target_addr|内网目标
#### 文件访问模式
利用nps提供一个公网可访问的本地文件服务,此模式仅客户端使用配置文件模式方可启动
```ini
[common]
server_addr=1.1.1.1:8024
vkey=123
[file]
mode=file
server_port=9100
local_path=/tmp/
strip_pre=/web/
````
项 | 含义
---|---
mode | file
server_port | 服务端开启的端口
local_path|本地文件目录
strip_pre|前缀
对于`strip_pre`,访问公网`ip:9100/web/`相当于访问`/tmp/`目录
#### 断线重连
```ini
[common]
auto_reconnection=true
```
================================================
FILE: docs/webapi.md
================================================
获取客户端列表
```
POST /client/list/
```
| 参数 | 含义 |
| --- | --- |
| search | 搜索 |
| order | 排序asc 正序 desc倒序 |
| offset | 分页(第几页) |
| limit | 条数(分页显示的条数) |
***
获取单个客户端
```
POST /client/getclient/
```
| 参数 | 含义 |
| --- | --- |
| id | 客户端id |
***
添加客户端
```
POST /client/add/
```
| 参数 | 含义 |
| --- | --- |
| remark | 备注 |
| u | basic权限认证用户名 |
| p | basic权限认证密码 |
| limit | 条数(分页显示的条数) |
| vkey | 客户端验证密钥 |
| config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 |
| compress | 压缩1允许 0不允许 |
| crypt | 是否加密(1或者0)1允许 0不允许 |
| rate\_limit | 带宽限制 单位KB/S 空则为不限制 |
| flow\_limit | 流量限制 单位M 空则为不限制 |
| max\_conn | 客户端最大连接数量 空则为不限制 |
| max\_tunnel | 客户端最大隧道数量 空则为不限制 |
***
修改客户端
```
POST /client/edit/
```
| 参数 | 含义 |
| --- | --- |
| remark | 备注 |
| u | basic权限认证用户名 |
| p | basic权限认证密码 |
| limit | 条数(分页显示的条数) |
| vkey | 客户端验证密钥 |
| config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 |
| compress | 压缩1允许 0不允许 |
| crypt | 是否加密(1或者0)1允许 0不允许 |
| rate\_limit | 带宽限制 单位KB/S 空则为不限制 |
| flow\_limit | 流量限制 单位M 空则为不限制 |
| max\_conn | 客户端最大连接数量 空则为不限制 |
| max\_tunnel | 客户端最大隧道数量 空则为不限制 |
| id | 要修改的客户端id |
***
删除客户端
```
POST /client/del/
```
| 参数 | 含义 |
| --- | --- |
| id | 要删除的客户端id |
***
获取域名解析列表
```
POST /index/hostlist/
```
| 参数 | 含义 |
| --- | --- |
| search | 搜索(可以搜域名/备注什么的) |
| offset | 分页(第几页) |
| limit | 条数(分页显示的条数) |
***
添加域名解析
```
POST /index/addhost/
```
| 参数 | 含义 |
| --- | --- |
| remark | 备注 |
| host | 域名 |
| scheme | 协议类型(三种 all http https) |
| location | url路由 空则为不限制 |
| client\_id | 客户端id |
| target | 内网目标(ip:端口) |
| header | request header 请求头 |
| hostchange | request host 请求主机 |
***
修改域名解析
```
POST /index/edithost/
```
| 参数 | 含义 |
| --- | --- |
| remark | 备注 |
| host | 域名 |
| scheme | 协议类型(三种 all http https) |
| location | url路由 空则为不限制 |
| client\_id | 客户端id |
| target | 内网目标(ip:端口) |
| header | request header 请求头 |
| hostchange | request host 请求主机 |
| id | 需要修改的域名解析id |
***
删除域名解析
```
POST /index/delhost/
```
| 参数 | 含义 |
| --- | --- |
| id | 需要删除的域名解析id |
***
获取单条隧道信息
```
POST /index/getonetunnel/
```
| 参数 | 含义 |
| --- | --- |
| id | 隧道的id |
***
获取隧道列表
```
POST /index/gettunnel/
```
| 参数 | 含义 |
| --- | --- |
| client\_id | 穿透隧道的客户端id |
| type | 类型tcp udp httpProx socks5 secret p2p |
| search | 搜索 |
| offset | 分页(第几页) |
| limit | 条数(分页显示的条数) |
***
添加隧道
```
POST /index/add/
```
| 参数 | 含义 |
| --- | --- |
| type | 类型tcp udp httpProx socks5 secret p2p |
| remark | 备注 |
| port | 服务端端口 |
| target | 目标(ip:端口) |
| client\_id | 客户端id |
***
修改隧道
```
POST /index/edit/
```
| 参数 | 含义 |
| --- | --- |
| type | 类型tcp udp httpProx socks5 secret p2p |
| remark | 备注 |
| port | 服务端端口 |
| target | 目标(ip:端口) |
| client\_id | 客户端id |
| id | 隧道id |
***
删除隧道
```
POST /index/del/
```
| 参数 | 含义 |
| --- | --- |
| id | 隧道id |
***
隧道停止工作
```
POST /index/stop/
```
| 参数 | 含义 |
| --- | --- |
| id | 隧道id |
***
隧道开始工作
```
POST /index/start/
```
| 参数 | 含义 |
| --- | --- |
| id | 隧道id |
================================================
FILE: go.mod
================================================
module ehang.io/nps
go 1.15
require (
ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992
fyne.io/fyne/v2 v2.0.2
github.com/astaxie/beego v1.12.0
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d
github.com/dsnet/compress v0.0.1 // indirect
github.com/golang/snappy v0.0.3
github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect
github.com/kardianos/service v1.2.0
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
github.com/klauspost/pgzip v1.2.1 // indirect
github.com/klauspost/reedsolomon v1.9.12 // indirect
github.com/panjf2000/ants/v2 v2.4.2
github.com/pkg/errors v0.9.1
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/shirou/gopsutil/v3 v3.21.3
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
github.com/tjfoc/gmsm v1.4.0 // indirect
github.com/xtaci/kcp-go v5.4.20+incompatible
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
)
replace github.com/astaxie/beego => github.com/exfly/beego v1.12.0-export-init
================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992 h1:LvlcB+8JveSBprHnva0g+OyLwAH8CRxEwtWzTe6ocoE=
ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992/go.mod h1:v54y/8ICChiM/aVUuKxGIcWwjm4HGNRyyAwbgLBoMbI=
fyne.io/fyne/v2 v2.0.2 h1:6pDvFuCmL1odyT/fPI+2L54hMJW1Zt9Dno41HmLInRs=
fyne.io/fyne/v2 v2.0.2/go.mod h1:3+FYmLJVgeb8EvTPJ5YzZeo7LkAq4bbuY3Zrir6xHbg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c h1:aprLqMn7gSPT+vdDSl+/E6NLEuArwD/J7IWd8bJt5lQ=
github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c/go.mod h1:Ie6SubJv/NTO9Q0UBH0QCl3Ve50lu9hjbi5YJUw03TE=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io=
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/exfly/beego v1.12.0-export-init h1:VQNYKdXhAwZGUaFmQv8Aj921O3rQJZRIF8xeGrhsjrI=
github.com/exfly/beego v1.12.0-export-init/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f h1:rguJ/t99j/6zRSFzsBKlsmmyl+vOvCeTJ+2uTBvuXFI=
github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 h1:WgfvpuKg42WVLkxNwzfFraXkTXPK36bMqXvMFN67clI=
github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214/go.mod h1:kj6hFWqfwSjFjLnYW5PK1DoxZ4O0uapwHRmd9jhln4E=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.2 h1:pd2FBxFydtPn2ywTLStbFg9CJKrojATnpeJWSP7Ys4k=
github.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM=
github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/reedsolomon v1.9.12 h1:EyOucRmcrLH+2hqKGdoA5SM8pwPKR6BJsf3r6zpYOA0=
github.com/klauspost/reedsolomon v1.9.12/go.mod h1:nLvuzNvy1ZDNQW30IuMc2ZWCbiqrJgdLoUS2X8HAUVg=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/panjf2000/ants/v2 v2.4.2 h1:kesjjo8JipN3vNNg1XaiXaeSs6xJweBTgenkBtsrHf8=
github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/shirou/gopsutil/v3 v3.21.3 h1:wgcdAHZS2H6qy4JFewVTtqfiYxFzCeEJod/mLztdPG8=
github.com/shirou/gopsutil/v3 v3.21.3/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/tjfoc/gmsm v1.4.0 h1:8nbaiZG+iVdh+fXVw0DZoZZa7a4TGm3Qab+xdrdzj8s=
github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg=
github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
================================================
FILE: lib/cache/lru.go
================================================
package cache
import (
"container/list"
"sync"
)
// Cache is an LRU cache. It is safe for concurrent access.
type Cache struct {
// MaxEntries is the maximum number of cache entries before
// an item is evicted. Zero means no limit.
MaxEntries int
//Execute this callback function when an element is culled
OnEvicted func(key Key, value interface{})
ll *list.List //list
cache sync.Map
}
// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators
type Key interface{}
type entry struct {
key Key
value interface{}
}
// New creates a new Cache.
// If maxEntries is 0, the cache has no length limit.
// that eviction is done by the caller.
func New(maxEntries int) *Cache {
return &Cache{
MaxEntries: maxEntries,
ll: list.New(),
//cache: make(map[interface{}]*list.Element),
}
}
// If the key value already exists, move the key to the front
func (c *Cache) Add(key Key, value interface{}) {
if ee, ok := c.cache.Load(key); ok {
c.ll.MoveToFront(ee.(*list.Element)) // move to the front
ee.(*list.Element).Value.(*entry).value = value
return
}
ele := c.ll.PushFront(&entry{key, value})
c.cache.Store(key, ele)
if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { // Remove the oldest element if the limit is exceeded
c.RemoveOldest()
}
}
// Get looks up a key's value from the cache.
func (c *Cache) Get(key Key) (value interface{}, ok bool) {
if ele, hit := c.cache.Load(key); hit {
c.ll.MoveToFront(ele.(*list.Element))
return ele.(*list.Element).Value.(*entry).value, true
}
return
}
// Remove removes the provided key from the cache.
func (c *Cache) Remove(key Key) {
if ele, hit := c.cache.Load(key); hit {
c.removeElement(ele.(*list.Element))
}
}
// RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.removeElement(ele)
}
}
func (c *Cache) removeElement(e *list.Element) {
c.ll.Remove(e)
kv := e.Value.(*entry)
c.cache.Delete(kv.key)
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
// Len returns the number of items in the cache.
func (c *Cache) Len() int {
return c.ll.Len()
}
// Clear purges all stored items from the cache.
func (c *Cache) Clear() {
if c.OnEvicted != nil {
c.cache.Range(func(key, value interface{}) bool {
kv := value.(*list.Element).Value.(*entry)
c.OnEvicted(kv.key, kv.value)
return true
})
}
c.ll = nil
}
================================================
FILE: lib/common/const.go
================================================
package common
const (
CONN_DATA_SEQ = "*#*" //Separator
VERIFY_EER = "vkey"
VERIFY_SUCCESS = "sucs"
WORK_MAIN = "main"
WORK_CHAN = "chan"
WORK_CONFIG = "conf"
WORK_REGISTER = "rgst"
WORK_SECRET = "sert"
WORK_FILE = "file"
WORK_P2P = "p2pm"
WORK_P2P_VISITOR = "p2pv"
WORK_P2P_PROVIDER = "p2pp"
WORK_P2P_CONNECT = "p2pc"
WORK_P2P_SUCCESS = "p2ps"
WORK_P2P_END = "p2pe"
WORK_P2P_LAST = "p2pl"
WORK_STATUS = "stus"
RES_MSG = "msg0"
RES_CLOSE = "clse"
NEW_UDP_CONN = "udpc" //p2p udp conn
NEW_TASK = "task"
NEW_CONF = "conf"
NEW_HOST = "host"
CONN_TCP = "tcp"
CONN_UDP = "udp"
CONN_TEST = "TST"
UnauthorizedBytes = `HTTP/1.1 401 Unauthorized
Content-Type: text/plain; charset=utf-8
WWW-Authenticate: Basic realm="easyProxy"
401 Unauthorized`
ConnectionFailBytes = `HTTP/1.1 404 Not Found
`
)
================================================
FILE: lib/common/logs.go
================================================
package common
import (
"github.com/astaxie/beego/logs"
"time"
)
const MaxMsgLen = 5000
var logMsgs string
func init() {
logs.Register("store", func() logs.Logger {
return new(StoreMsg)
})
}
func GetLogMsg() string {
return logMsgs
}
type StoreMsg struct {
}
func (lg *StoreMsg) Init(config string) error {
return nil
}
func (lg *StoreMsg) WriteMsg(when time.Time, msg string, level int) error {
m := when.Format("2006-01-02 15:04:05") + " " + msg + "\r\n"
if len(logMsgs) > MaxMsgLen {
start := MaxMsgLen - len(m)
if start <= 0 {
start = MaxMsgLen
}
logMsgs = logMsgs[start:]
}
logMsgs += m
return nil
}
func (lg *StoreMsg) Destroy() {
return
}
func (lg *StoreMsg) Flush() {
return
}
================================================
FILE: lib/common/netpackager.go
================================================
package common
import (
"bytes"
"encoding/binary"
"errors"
"io"
"io/ioutil"
"net"
"strconv"
)
type NetPackager interface {
Pack(writer io.Writer) (err error)
UnPack(reader io.Reader) (err error)
}
const (
ipV4 = 1
domainName = 3
ipV6 = 4
)
type UDPHeader struct {
Rsv uint16
Frag uint8
Addr *Addr
}
func NewUDPHeader(rsv uint16, frag uint8, addr *Addr) *UDPHeader {
return &UDPHeader{
Rsv: rsv,
Frag: frag,
Addr: addr,
}
}
type Addr struct {
Type uint8
Host string
Port uint16
}
func (addr *Addr) String() string {
return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port)))
}
func (addr *Addr) Decode(b []byte) error {
addr.Type = b[0]
pos := 1
switch addr.Type {
case ipV4:
addr.Host = net.IP(b[pos : pos+net.IPv4len]).String()
pos += net.IPv4len
case ipV6:
addr.Host = net.IP(b[pos : pos+net.IPv6len]).String()
pos += net.IPv6len
case domainName:
addrlen := int(b[pos])
pos++
addr.Host = string(b[pos : pos+addrlen])
pos += addrlen
default:
return errors.New("decode error")
}
addr.Port = binary.BigEndian.Uint16(b[pos:])
return nil
}
func (addr *Addr) Encode(b []byte) (int, error) {
b[0] = addr.Type
pos := 1
switch addr.Type {
case ipV4:
ip4 := net.ParseIP(addr.Host).To4()
if ip4 == nil {
ip4 = net.IPv4zero.To4()
}
pos += copy(b[pos:], ip4)
case domainName:
b[pos] = byte(len(addr.Host))
pos++
pos += copy(b[pos:], []byte(addr.Host))
case ipV6:
ip16 := net.ParseIP(addr.Host).To16()
if ip16 == nil {
ip16 = net.IPv6zero.To16()
}
pos += copy(b[pos:], ip16)
default:
b[0] = ipV4
copy(b[pos:pos+4], net.IPv4zero.To4())
pos += 4
}
binary.BigEndian.PutUint16(b[pos:], addr.Port)
pos += 2
return pos, nil
}
func (h *UDPHeader) Write(w io.Writer) error {
b := BufPoolUdp.Get().([]byte)
defer BufPoolUdp.Put(b)
binary.BigEndian.PutUint16(b[:2], h.Rsv)
b[2] = h.Frag
addr := h.Addr
if addr == nil {
addr = &Addr{}
}
length, _ := addr.Encode(b[3:])
_, err := w.Write(b[:3+length])
return err
}
type UDPDatagram struct {
Header *UDPHeader
Data []byte
}
func ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) {
b := BufPoolUdp.Get().([]byte)
defer BufPoolUdp.Put(b)
// when r is a streaming (such as TCP connection), we may read more than the required data,
// but we don't know how to handle it. So we use io.ReadFull to instead of io.ReadAtLeast
// to make sure that no redundant data will be discarded.
n, err := io.ReadFull(r, b[:5])
if err != nil {
return nil, err
}
header := &UDPHeader{
Rsv: binary.BigEndian.Uint16(b[:2]),
Frag: b[2],
}
atype := b[3]
hlen := 0
switch atype {
case ipV4:
hlen = 10
case ipV6:
hlen = 22
case domainName:
hlen = 7 + int(b[4])
default:
return nil, errors.New("addr not support")
}
dlen := int(header.Rsv)
if dlen == 0 { // standard SOCKS5 UDP datagram
extra, err := ioutil.ReadAll(r) // we assume no redundant data
if err != nil {
return nil, err
}
copy(b[n:], extra)
n += len(extra) // total length
dlen = n - hlen // data length
} else { // extended feature, for UDP over TCP, using reserved field as data length
if _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil {
return nil, err
}
n = hlen + dlen
}
header.Addr = new(Addr)
if err := header.Addr.Decode(b[3:hlen]); err != nil {
return nil, err
}
data := make([]byte, dlen)
copy(data, b[hlen:n])
d := &UDPDatagram{
Header: header,
Data: data,
}
return d, nil
}
func NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram {
return &UDPDatagram{
Header: header,
Data: data,
}
}
func (d *UDPDatagram) Write(w io.Writer) error {
h := d.Header
if h == nil {
h = &UDPHeader{}
}
buf := bytes.Buffer{}
if err := h.Write(&buf); err != nil {
return err
}
if _, err := buf.Write(d.Data); err != nil {
return err
}
_, err := buf.WriteTo(w)
return err
}
func ToSocksAddr(addr net.Addr) *Addr {
host := "0.0.0.0"
port := 0
if addr != nil {
h, p, _ := net.SplitHostPort(addr.String())
host = h
port, _ = strconv.Atoi(p)
}
return &Addr{
Type: ipV4,
Host: host,
Port: uint16(port),
}
}
================================================
FILE: lib/common/pool.go
================================================
package common
import (
"sync"
)
const PoolSize = 64 * 1024
const PoolSizeSmall = 100
const PoolSizeUdp = 1472 + 200
const PoolSizeCopy = 32 << 10
var BufPool = sync.Pool{
New: func() interface{} {
return make([]byte, PoolSize)
},
}
var BufPoolUdp = sync.Pool{
New: func() interface{} {
return make([]byte, PoolSizeUdp)
},
}
var BufPoolMax = sync.Pool{
New: func() interface{} {
return make([]byte, PoolSize)
},
}
var BufPoolSmall = sync.Pool{
New: func() interface{} {
return make([]byte, PoolSizeSmall)
},
}
var BufPoolCopy = sync.Pool{
New: func() interface{} {
return make([]byte, PoolSizeCopy)
},
}
func PutBufPoolUdp(buf []byte) {
if cap(buf) == PoolSizeUdp {
BufPoolUdp.Put(buf[:PoolSizeUdp])
}
}
func PutBufPoolCopy(buf []byte) {
if cap(buf) == PoolSizeCopy {
BufPoolCopy.Put(buf[:PoolSizeCopy])
}
}
func GetBufPoolCopy() []byte {
return (BufPoolCopy.Get().([]byte))[:PoolSizeCopy]
}
func PutBufPoolMax(buf []byte) {
if cap(buf) == PoolSize {
BufPoolMax.Put(buf[:PoolSize])
}
}
type copyBufferPool struct {
pool sync.Pool
}
func (Self *copyBufferPool) New() {
Self.pool = sync.Pool{
New: func() interface{} {
return make([]byte, PoolSizeCopy, PoolSizeCopy)
},
}
}
func (Self *copyBufferPool) Get() []byte {
buf := Self.pool.Get().([]byte)
return buf[:PoolSizeCopy] // just like make a new slice, but data may not be 0
}
func (Self *copyBufferPool) Put(x []byte) {
if len(x) == PoolSizeCopy {
Self.pool.Put(x)
} else {
x = nil // buf is not full, not allowed, New method returns a full buf
}
}
var once = sync.Once{}
var CopyBuff = copyBufferPool{}
func newPool() {
CopyBuff.New()
}
func init() {
once.Do(newPool)
}
================================================
FILE: lib/common/pprof.go
================================================
package common
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"net/http"
_ "net/http/pprof"
)
func InitPProfFromFile() {
ip := beego.AppConfig.String("pprof_ip")
p := beego.AppConfig.String("pprof_port")
if len(ip) > 0 && len(p) > 0 && IsPort(p) {
runPProf(ip + ":" + p)
}
}
func InitPProfFromArg(arg string) {
if len(arg) > 0 {
runPProf(arg)
}
}
func runPProf(ipPort string) {
go func() {
_ = http.ListenAndServe(ipPort, nil)
}()
logs.Info("PProf debug listen on", ipPort)
}
================================================
FILE: lib/common/run.go
================================================
package common
import (
"os"
"path/filepath"
"runtime"
)
//Get the currently selected configuration file directory
//For non-Windows systems, select the /etc/nps as config directory if exist, or select ./
//windows system, select the C:\Program Files\nps as config directory if exist, or select ./
func GetRunPath() string {
var path string
if path = GetInstallPath(); !FileExists(path) {
return GetAppPath()
}
return path
}
//Different systems get different installation paths
func GetInstallPath() string {
var path string
if IsWindows() {
path = `C:\Program Files\nps`
} else {
path = "/etc/nps"
}
return path
}
//Get the absolute path to the running directory
func GetAppPath() string {
if path, err := filepath.Abs(filepath.Dir(os.Args[0])); err == nil {
return path
}
return os.Args[0]
}
//Determine whether the current system is a Windows system?
func IsWindows() bool {
if runtime.GOOS == "windows" {
return true
}
return false
}
//interface log file path
func GetLogPath() string {
var path string
if IsWindows() {
path = filepath.Join(GetAppPath(), "nps.log")
} else {
path = "/var/log/nps.log"
}
return path
}
//interface npc log file path
func GetNpcLogPath() string {
var path string
if IsWindows() {
path = filepath.Join(GetAppPath(), "npc.log")
} else {
path = "/var/log/npc.log"
}
return path
}
//interface pid file path
func GetTmpPath() string {
var path string
if IsWindows() {
path = GetAppPath()
} else {
path = "/tmp"
}
return path
}
//config file path
func GetConfigPath() string {
var path string
if IsWindows() {
path = filepath.Join(GetAppPath(), "conf/npc.conf")
} else {
path = "conf/npc.conf"
}
return path
}
================================================
FILE: lib/common/util.go
================================================
package common
import (
"bytes"
"ehang.io/nps/lib/version"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"html/template"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"sync"
"ehang.io/nps/lib/crypt"
)
//Get the corresponding IP address through domain name
func GetHostByName(hostname string) string {
if !DomainCheck(hostname) {
return hostname
}
ips, _ := net.LookupIP(hostname)
if ips != nil {
for _, v := range ips {
if v.To4() != nil {
return v.String()
}
}
}
return ""
}
//Check the legality of domain
func DomainCheck(domain string) bool {
var match bool
IsLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(/)"
NotLine := "^((http://)|(https://))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}"
match, _ = regexp.MatchString(IsLine, domain)
if !match {
match, _ = regexp.MatchString(NotLine, domain)
}
return match
}
//Check if the Request request is validated
func CheckAuth(r *http.Request, user, passwd string) bool {
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
s = strings.SplitN(r.Header.Get("Proxy-Authorization"), " ", 2)
if len(s) != 2 {
return false
}
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return false
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return false
}
return pair[0] == user && pair[1] == passwd
}
//get bool by str
func GetBoolByStr(s string) bool {
switch s {
case "1", "true":
return true
}
return false
}
//get str by bool
func GetStrByBool(b bool) string {
if b {
return "1"
}
return "0"
}
//int
func GetIntNoErrByStr(str string) int {
i, _ := strconv.Atoi(strings.TrimSpace(str))
return i
}
//Get verify value
func Getverifyval(vkey string) string {
return crypt.Md5(vkey)
}
//Change headers and host of request
func ChangeHostAndHeader(r *http.Request, host string, header string, addr string, addOrigin bool) {
if host != "" {
r.Host = host
}
if header != "" {
h := strings.Split(header, "\n")
for _, v := range h {
hd := strings.Split(v, ":")
if len(hd) == 2 {
r.Header.Set(hd[0], hd[1])
}
}
}
addr = strings.Split(addr, ":")[0]
if prior, ok := r.Header["X-Forwarded-For"]; ok {
addr = strings.Join(prior, ", ") + ", " + addr
}
if addOrigin {
r.Header.Set("X-Forwarded-For", addr)
r.Header.Set("X-Real-IP", addr)
}
}
//Read file content by file path
func ReadAllFromFile(filePath string) ([]byte, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer f.Close()
return ioutil.ReadAll(f)
}
// FileExists reports whether the named file or directory exists.
func FileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
//Judge whether the TCP port can open normally
func TestTcpPort(port int) bool {
l, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), port, ""})
defer func() {
if l != nil {
l.Close()
}
}()
if err != nil {
return false
}
return true
}
//Judge whether the UDP port can open normally
func TestUdpPort(port int) bool {
l, err := net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), port, ""})
defer func() {
if l != nil {
l.Close()
}
}()
if err != nil {
return false
}
return true
}
//Write length and individual byte data
//Length prevents sticking
//# Characters are used to separate data
func BinaryWrite(raw *bytes.Buffer, v ...string) {
b := GetWriteStr(v...)
binary.Write(raw, binary.LittleEndian, int32(len(b)))
binary.Write(raw, binary.LittleEndian, b)
}
// get seq str
func GetWriteStr(v ...string) []byte {
buffer := new(bytes.Buffer)
var l int32
for _, v := range v {
l += int32(len([]byte(v))) + int32(len([]byte(CONN_DATA_SEQ)))
binary.Write(buffer, binary.LittleEndian, []byte(v))
binary.Write(buffer, binary.LittleEndian, []byte(CONN_DATA_SEQ))
}
return buffer.Bytes()
}
//inArray str interface
func InStrArr(arr []string, val string) bool {
for _, v := range arr {
if v == val {
return true
}
}
return false
}
//inArray int interface
func InIntArr(arr []int, val int) bool {
for _, v := range arr {
if v == val {
return true
}
}
return false
}
//format ports str to a int array
func GetPorts(p string) []int {
var ps []int
arr := strings.Split(p, ",")
for _, v := range arr {
fw := strings.Split(v, "-")
if len(fw) == 2 {
if IsPort(fw[0]) && IsPort(fw[1]) {
start, _ := strconv.Atoi(fw[0])
end, _ := strconv.Atoi(fw[1])
for i := start; i <= end; i++ {
ps = append(ps, i)
}
} else {
continue
}
} else if IsPort(v) {
p, _ := strconv.Atoi(v)
ps = append(ps, p)
}
}
return ps
}
//is the string a port
func IsPort(p string) bool {
pi, err := strconv.Atoi(p)
if err != nil {
return false
}
if pi > 65536 || pi < 1 {
return false
}
return true
}
//if the s is just a port,return 127.0.0.1:s
func FormatAddress(s string) string {
if strings.Contains(s, ":") {
return s
}
return "127.0.0.1:" + s
}
//get address from the complete address
func GetIpByAddr(addr string) string {
arr := strings.Split(addr, ":")
return arr[0]
}
//get port from the complete address
func GetPortByAddr(addr string) int {
arr := strings.Split(addr, ":")
if len(arr) < 2 {
return 0
}
p, err := strconv.Atoi(arr[1])
if err != nil {
return 0
}
return p
}
func CopyBuffer(dst io.Writer, src io.Reader, label ...string) (written int64, err error) {
buf := CopyBuff.Get()
defer CopyBuff.Put(buf)
for {
nr, er := src.Read(buf)
//if len(pr)>0 && pr[0] && nr > 50 {
// logs.Warn(string(buf[:50]))
//}
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er != nil {
err = er
break
}
}
return written, err
}
//send this ip forget to get a local udp port
func GetLocalUdpAddr() (net.Conn, error) {
tmpConn, err := net.Dial("udp", "114.114.114.114:53")
if err != nil {
return nil, err
}
return tmpConn, tmpConn.Close()
}
//parse template
func ParseStr(str string) (string, error) {
tmp := template.New("npc")
var err error
w := new(bytes.Buffer)
if tmp, err = tmp.Parse(str); err != nil {
return "", err
}
if err = tmp.Execute(w, GetEnvMap()); err != nil {
return "", err
}
return w.String(), nil
}
//get env
func GetEnvMap() map[string]string {
m := make(map[string]string)
environ := os.Environ()
for i := range environ {
tmp := strings.Split(environ[i], "=")
if len(tmp) == 2 {
m[tmp[0]] = tmp[1]
}
}
return m
}
//throw the empty element of the string array
func TrimArr(arr []string) []string {
newArr := make([]string, 0)
for _, v := range arr {
if v != "" {
newArr = append(newArr, v)
}
}
return newArr
}
//
func IsArrContains(arr []string, val string) bool {
if arr == nil {
return false
}
for _, v := range arr {
if v == val {
return true
}
}
return false
}
//remove value from string array
func RemoveArrVal(arr []string, val string) []string {
for k, v := range arr {
if v == val {
arr = append(arr[:k], arr[k+1:]...)
return arr
}
}
return arr
}
//convert bytes to num
func BytesToNum(b []byte) int {
var str string
for i := 0; i < len(b); i++ {
str += strconv.Itoa(int(b[i]))
}
x, _ := strconv.Atoi(str)
return int(x)
}
//get the length of the sync map
func GeSynctMapLen(m sync.Map) int {
var c int
m.Range(func(key, value interface{}) bool {
c++
return true
})
return c
}
func GetExtFromPath(path string) string {
s := strings.Split(path, ".")
re, err := regexp.Compile(`(\w+)`)
if err != nil {
return ""
}
return string(re.Find([]byte(s[0])))
}
var externalIp string
func GetExternalIp() string {
if externalIp != "" {
return externalIp
}
resp, err := http.Get("http://myexternalip.com/raw")
if err != nil {
return ""
}
defer resp.Body.Close()
content, _ := ioutil.ReadAll(resp.Body)
externalIp = string(content)
return externalIp
}
func GetIntranetIp() (error, string) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, ""
}
for _, address := range addrs {
// 检查ip地址判断是否回环地址
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return nil, ipnet.IP.To4().String()
}
}
}
return errors.New("get intranet ip error"), ""
}
func IsPublicIP(IP net.IP) bool {
if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() {
return false
}
if ip4 := IP.To4(); ip4 != nil {
switch true {
case ip4[0] == 10:
return false
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
return false
case ip4[0] == 192 && ip4[1] == 168:
return false
default:
return true
}
}
return false
}
func GetServerIpByClientIp(clientIp net.IP) string {
if IsPublicIP(clientIp) {
return GetExternalIp()
}
_, ip := GetIntranetIp()
return ip
}
func PrintVersion() {
fmt.Printf("Version: %s\nCore version: %s\nSame core version of client and server can connect each other\n", version.VERSION, version.GetVersion())
}
================================================
FILE: lib/config/config.go
================================================
package config
import (
"errors"
"fmt"
"regexp"
"strings"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
)
type CommonConfig struct {
Server string
VKey string
Tp string //bridgeType kcp or tcp
AutoReconnection bool
ProxyUrl string
Client *file.Client
DisconnectTime int
}
type LocalServer struct {
Type string
Port int
Ip string
Password string
Target string
}
type Config struct {
content string
title []string
CommonConfig *CommonConfig
Hosts []*file.Host
Tasks []*file.Tunnel
Healths []*file.Health
LocalServer []*LocalServer
}
func NewConfig(path string) (c *Config, err error) {
c = new(Config)
var b []byte
if b, err = common.ReadAllFromFile(path); err != nil {
return
} else {
if c.content, err = common.ParseStr(string(b)); err != nil {
return nil, err
}
if c.title, err = getAllTitle(c.content); err != nil {
return
}
var nowIndex int
var nextIndex int
var nowContent string
for i := 0; i < len(c.title); i++ {
nowIndex = strings.Index(c.content, c.title[i]) + len(c.title[i])
if i < len(c.title)-1 {
nextIndex = strings.Index(c.content, c.title[i+1])
} else {
nextIndex = len(c.content)
}
nowContent = c.content[nowIndex:nextIndex]
if strings.Index(getTitleContent(c.title[i]), "secret") == 0 && !strings.Contains(nowContent, "mode") {
local := delLocalService(nowContent)
local.Type = "secret"
c.LocalServer = append(c.LocalServer, local)
continue
}
//except mode
if strings.Index(getTitleContent(c.title[i]), "p2p") == 0 && !strings.Contains(nowContent, "mode") {
local := delLocalService(nowContent)
local.Type = "p2p"
c.LocalServer = append(c.LocalServer, local)
continue
}
//health set
if strings.Index(getTitleContent(c.title[i]), "health") == 0 {
c.Healths = append(c.Healths, dealHealth(nowContent))
continue
}
switch c.title[i] {
case "[common]":
c.CommonConfig = dealCommon(nowContent)
default:
if strings.Index(nowContent, "host") > -1 {
h := dealHost(nowContent)
h.Remark = getTitleContent(c.title[i])
c.Hosts = append(c.Hosts, h)
} else {
t := dealTunnel(nowContent)
t.Remark = getTitleContent(c.title[i])
c.Tasks = append(c.Tasks, t)
}
}
}
}
return
}
func getTitleContent(s string) string {
re, _ := regexp.Compile(`[\[\]]`)
return re.ReplaceAllString(s, "")
}
func dealCommon(s string) *CommonConfig {
c := &CommonConfig{}
c.Client = file.NewClient("", true, true)
c.Client.Cnf = new(file.Config)
for _, v := range splitStr(s) {
item := strings.Split(v, "=")
if len(item) == 0 {
continue
} else if len(item) == 1 {
item = append(item, "")
}
switch item[0] {
case "server_addr":
c.Server = item[1]
case "vkey":
c.VKey = item[1]
case "conn_type":
c.Tp = item[1]
case "auto_reconnection":
c.AutoReconnection = common.GetBoolByStr(item[1])
case "basic_username":
c.Client.Cnf.U = item[1]
case "basic_password":
c.Client.Cnf.P = item[1]
case "web_password":
c.Client.WebPassword = item[1]
case "web_username":
c.Client.WebUserName = item[1]
case "compress":
c.Client.Cnf.Compress = common.GetBoolByStr(item[1])
case "crypt":
c.Client.Cnf.Crypt = common.GetBoolByStr(item[1])
case "proxy_url":
c.ProxyUrl = item[1]
case "rate_limit":
c.Client.RateLimit = common.GetIntNoErrByStr(item[1])
case "flow_limit":
c.Client.Flow.FlowLimit = int64(common.GetIntNoErrByStr(item[1]))
case "max_conn":
c.Client.MaxConn = common.GetIntNoErrByStr(item[1])
case "remark":
c.Client.Remark = item[1]
case "pprof_addr":
common.InitPProfFromArg(item[1])
case "disconnect_timeout":
c.DisconnectTime = common.GetIntNoErrByStr(item[1])
}
}
return c
}
func dealHost(s string) *file.Host {
h := &file.Host{}
h.Target = new(file.Target)
h.Scheme = "all"
var headerChange string
for _, v := range splitStr(s) {
item := strings.Split(v, "=")
if len(item) == 0 {
continue
} else if len(item) == 1 {
item = append(item, "")
}
switch strings.TrimSpace(item[0]) {
case "host":
h.Host = item[1]
case "target_addr":
h.Target.TargetStr = strings.Replace(item[1], ",", "\n", -1)
case "host_change":
h.HostChange = item[1]
case "scheme":
h.Scheme = item[1]
case "location":
h.Location = item[1]
default:
if strings.Contains(item[0], "header") {
headerChange += strings.Replace(item[0], "header_", "", -1) + ":" + item[1] + "\n"
}
h.HeaderChange = headerChange
}
}
return h
}
func dealHealth(s string) *file.Health {
h := &file.Health{}
for _, v := range splitStr(s) {
item := strings.Split(v, "=")
if len(item) == 0 {
continue
} else if len(item) == 1 {
item = append(item, "")
}
switch strings.TrimSpace(item[0]) {
case "health_check_timeout":
h.HealthCheckTimeout = common.GetIntNoErrByStr(item[1])
case "health_check_max_failed":
h.HealthMaxFail = common.GetIntNoErrByStr(item[1])
case "health_check_interval":
h.HealthCheckInterval = common.GetIntNoErrByStr(item[1])
case "health_http_url":
h.HttpHealthUrl = item[1]
case "health_check_type":
h.HealthCheckType = item[1]
case "health_check_target":
h.HealthCheckTarget = item[1]
}
}
return h
}
func dealTunnel(s string) *file.Tunnel {
t := &file.Tunnel{}
t.Target = new(file.Target)
for _, v := range splitStr(s) {
item := strings.Split(v, "=")
if len(item) == 0 {
continue
} else if len(item) == 1 {
item = append(item, "")
}
switch strings.TrimSpace(item[0]) {
case "server_port":
t.Ports = item[1]
case "server_ip":
t.ServerIp = item[1]
case "mode":
t.Mode = item[1]
case "target_addr":
t.Target.TargetStr = strings.Replace(item[1], ",", "\n", -1)
case "target_port":
t.Target.TargetStr = item[1]
case "target_ip":
t.TargetAddr = item[1]
case "password":
t.Password = item[1]
case "local_path":
t.LocalPath = item[1]
case "strip_pre":
t.StripPre = item[1]
case "multi_account":
t.MultiAccount = &file.MultiAccount{}
if common.FileExists(item[1]) {
if b, err := common.ReadAllFromFile(item[1]); err != nil {
panic(err)
} else {
if content, err := common.ParseStr(string(b)); err != nil {
panic(err)
} else {
t.MultiAccount.AccountMap = dealMultiUser(content)
}
}
}
}
}
return t
}
func dealMultiUser(s string) map[string]string {
multiUserMap := make(map[string]string)
for _, v := range splitStr(s) {
item := strings.Split(v, "=")
if len(item) == 0 {
continue
} else if len(item) == 1 {
item = append(item, "")
}
multiUserMap[strings.TrimSpace(item[0])] = item[1]
}
return multiUserMap
}
func delLocalService(s string) *LocalServer {
l := new(LocalServer)
for _, v := range splitStr(s) {
item := strings.Split(v, "=")
if len(item) == 0 {
continue
} else if len(item) == 1 {
item = append(item, "")
}
switch item[0] {
case "local_port":
l.Port = common.GetIntNoErrByStr(item[1])
case "local_ip":
l.Ip = item[1]
case "password":
l.Password = item[1]
case "target_addr":
l.Target = item[1]
}
}
return l
}
func getAllTitle(content string) (arr []string, err error) {
var re *regexp.Regexp
re, err = regexp.Compile(`(?m)^\[[^\[\]\r\n]+\]`)
if err != nil {
return
}
arr = re.FindAllString(content, -1)
m := make(map[string]bool)
for _, v := range arr {
if _, ok := m[v]; ok {
err = errors.New(fmt.Sprintf("Item names %s are not allowed to be duplicated", v))
return
}
m[v] = true
}
return
}
func splitStr(s string) (configDataArr []string) {
if common.IsWindows() {
configDataArr = strings.Split(s, "\r\n")
}
if len(configDataArr) < 3 {
configDataArr = strings.Split(s, "\n")
}
return
}
================================================
FILE: lib/config/config_test.go
================================================
package config
import (
"log"
"regexp"
"testing"
)
func TestReg(t *testing.T) {
content := `
[common]
server=127.0.0.1:8284
tp=tcp
vkey=123
[web2]
host=www.baidu.com
host_change=www.sina.com
target=127.0.0.1:8080,127.0.0.1:8082
header_cookkile=122123
header_user-Agent=122123
[web2]
host=www.baidu.com
host_change=www.sina.com
target=127.0.0.1:8080,127.0.0.1:8082
header_cookkile="122123"
header_user-Agent=122123
[tunnel1]
type=udp
target=127.0.0.1:8080
port=9001
compress=snappy
crypt=true
u=1
p=2
[tunnel2]
type=tcp
target=127.0.0.1:8080
port=9001
compress=snappy
crypt=true
u=1
p=2
`
re, err := regexp.Compile(`\[.+?\]`)
if err != nil {
t.Fail()
}
log.Println(re.FindAllString(content, -1))
}
func TestDealCommon(t *testing.T) {
s := `server=127.0.0.1:8284
tp=tcp
vkey=123`
f := new(CommonConfig)
f.Server = "127.0.0.1:8284"
f.Tp = "tcp"
f.VKey = "123"
if c := dealCommon(s); *c != *f {
t.Fail()
}
}
func TestGetTitleContent(t *testing.T) {
s := "[common]"
if getTitleContent(s) != "common" {
t.Fail()
}
}
================================================
FILE: lib/conn/conn.go
================================================
package conn
import (
"bufio"
"bytes"
"ehang.io/nps/lib/goroutine"
"encoding/binary"
"encoding/json"
"errors"
"github.com/astaxie/beego/logs"
"io"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/pmux"
"ehang.io/nps/lib/rate"
"github.com/xtaci/kcp-go"
)
type Conn struct {
Conn net.Conn
Rb []byte
}
//new conn
func NewConn(conn net.Conn) *Conn {
return &Conn{Conn: conn}
}
func (s *Conn) readRequest(buf []byte) (n int, err error) {
var rd int
for {
rd, err = s.Read(buf[n:])
if err != nil {
return
}
n += rd
if n < 4 {
continue
}
if string(buf[n-4:n]) == "\r\n\r\n" {
return
}
// buf is full, can't contain the request
if n == cap(buf) {
err = io.ErrUnexpectedEOF
return
}
}
}
//get host 、connection type、method...from connection
func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.Request) {
var b [32 * 1024]byte
var n int
if n, err = s.readRequest(b[:]); err != nil {
return
}
rb = b[:n]
r, err = http.ReadRequest(bufio.NewReader(bytes.NewReader(rb)))
if err != nil {
return
}
hostPortURL, err := url.Parse(r.Host)
if err != nil {
address = r.Host
err = nil
return
}
if hostPortURL.Opaque == "443" {
if strings.Index(r.Host, ":") == -1 {
address = r.Host + ":443"
} else {
address = r.Host
}
} else {
if strings.Index(r.Host, ":") == -1 {
address = r.Host + ":80"
} else {
address = r.Host
}
}
return
}
func (s *Conn) GetShortLenContent() (b []byte, err error) {
var l int
if l, err = s.GetLen(); err != nil {
return
}
if l < 0 || l > 32<<10 {
err = errors.New("read length error")
return
}
return s.GetShortContent(l)
}
func (s *Conn) GetShortContent(l int) (b []byte, err error) {
buf := make([]byte, l)
return buf, binary.Read(s, binary.LittleEndian, &buf)
}
//读取指定长度内容
func (s *Conn) ReadLen(cLen int, buf []byte) (int, error) {
if cLen > len(buf) || cLen <= 0 {
return 0, errors.New("长度错误" + strconv.Itoa(cLen))
}
if n, err := io.ReadFull(s, buf[:cLen]); err != nil || n != cLen {
return n, errors.New("Error reading specified length " + err.Error())
}
return cLen, nil
}
func (s *Conn) GetLen() (int, error) {
var l int32
err := binary.Read(s, binary.LittleEndian, &l)
return int(l), err
}
func (s *Conn) WriteLenContent(buf []byte) (err error) {
var b []byte
if b, err = GetLenBytes(buf); err != nil {
return
}
return binary.Write(s.Conn, binary.LittleEndian, b)
}
//read flag
func (s *Conn) ReadFlag() (string, error) {
buf := make([]byte, 4)
return string(buf), binary.Read(s, binary.LittleEndian, &buf)
}
//set alive
func (s *Conn) SetAlive(tp string) {
switch s.Conn.(type) {
case *kcp.UDPSession:
s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Time{})
case *net.TCPConn:
conn := s.Conn.(*net.TCPConn)
conn.SetReadDeadline(time.Time{})
//conn.SetKeepAlive(false)
//conn.SetKeepAlivePeriod(time.Duration(2 * time.Second))
case *pmux.PortConn:
s.Conn.(*pmux.PortConn).SetReadDeadline(time.Time{})
}
}
//set read deadline
func (s *Conn) SetReadDeadlineBySecond(t time.Duration) {
switch s.Conn.(type) {
case *kcp.UDPSession:
s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
case *net.TCPConn:
s.Conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
case *pmux.PortConn:
s.Conn.(*pmux.PortConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
}
}
//get link info from conn
func (s *Conn) GetLinkInfo() (lk *Link, err error) {
err = s.getInfo(&lk)
return
}
//send info for link
func (s *Conn) SendHealthInfo(info, status string) (int, error) {
raw := bytes.NewBuffer([]byte{})
common.BinaryWrite(raw, info, status)
return s.Write(raw.Bytes())
}
//get health info from conn
func (s *Conn) GetHealthInfo() (info string, status bool, err error) {
var l int
buf := common.BufPoolMax.Get().([]byte)
defer common.PutBufPoolMax(buf)
if l, err = s.GetLen(); err != nil {
return
} else if _, err = s.ReadLen(l, buf); err != nil {
return
} else {
arr := strings.Split(string(buf[:l]), common.CONN_DATA_SEQ)
if len(arr) >= 2 {
return arr[0], common.GetBoolByStr(arr[1]), nil
}
}
return "", false, errors.New("receive health info error")
}
//get task info
func (s *Conn) GetHostInfo() (h *file.Host, err error) {
err = s.getInfo(&h)
h.Id = int(file.GetDb().JsonDb.GetHostId())
h.Flow = new(file.Flow)
h.NoStore = true
return
}
//get task info
func (s *Conn) GetConfigInfo() (c *file.Client, err error) {
err = s.getInfo(&c)
c.NoStore = true
c.Status = true
if c.Flow == nil {
c.Flow = new(file.Flow)
}
c.NoDisplay = false
return
}
//get task info
func (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) {
err = s.getInfo(&t)
t.Id = int(file.GetDb().JsonDb.GetTaskId())
t.NoStore = true
t.Flow = new(file.Flow)
return
}
//send info
func (s *Conn) SendInfo(t interface{}, flag string) (int, error) {
/*
The task info is formed as follows:
+----+-----+---------+
|type| len | content |
+----+---------------+
| 4 | 4 | ... |
+----+---------------+
*/
raw := bytes.NewBuffer([]byte{})
if flag != "" {
binary.Write(raw, binary.LittleEndian, []byte(flag))
}
b, err := json.Marshal(t)
if err != nil {
return 0, err
}
lenBytes, err := GetLenBytes(b)
if err != nil {
return 0, err
}
binary.Write(raw, binary.LittleEndian, lenBytes)
return s.Write(raw.Bytes())
}
//get task info
func (s *Conn) getInfo(t interface{}) (err error) {
var l int
buf := common.BufPoolMax.Get().([]byte)
defer common.PutBufPoolMax(buf)
if l, err = s.GetLen(); err != nil {
return
} else if _, err = s.ReadLen(l, buf); err != nil {
return
} else {
json.Unmarshal(buf[:l], &t)
}
return
}
//close
func (s *Conn) Close() error {
return s.Conn.Close()
}
//write
func (s *Conn) Write(b []byte) (int, error) {
return s.Conn.Write(b)
}
//read
func (s *Conn) Read(b []byte) (n int, err error) {
if s.Rb != nil {
//if the rb is not nil ,read rb first
if len(s.Rb) > 0 {
n = copy(b, s.Rb)
s.Rb = s.Rb[n:]
return
}
s.Rb = nil
}
return s.Conn.Read(b)
}
//write sign flag
func (s *Conn) WriteClose() (int, error) {
return s.Write([]byte(common.RES_CLOSE))
}
//write main
func (s *Conn) WriteMain() (int, error) {
return s.Write([]byte(common.WORK_MAIN))
}
//write main
func (s *Conn) WriteConfig() (int, error) {
return s.Write([]byte(common.WORK_CONFIG))
}
//write chan
func (s *Conn) WriteChan() (int, error) {
return s.Write([]byte(common.WORK_CHAN))
}
//get task or host result of add
func (s *Conn) GetAddStatus() (b bool) {
binary.Read(s.Conn, binary.LittleEndian, &b)
return
}
func (s *Conn) WriteAddOk() error {
return binary.Write(s.Conn, binary.LittleEndian, true)
}
func (s *Conn) WriteAddFail() error {
defer s.Close()
return binary.Write(s.Conn, binary.LittleEndian, false)
}
func (s *Conn) LocalAddr() net.Addr {
return s.Conn.LocalAddr()
}
func (s *Conn) RemoteAddr() net.Addr {
return s.Conn.RemoteAddr()
}
func (s *Conn) SetDeadline(t time.Time) error {
return s.Conn.SetDeadline(t)
}
func (s *Conn) SetWriteDeadline(t time.Time) error {
return s.Conn.SetWriteDeadline(t)
}
func (s *Conn) SetReadDeadline(t time.Time) error {
return s.Conn.SetReadDeadline(t)
}
//get the assembled amount data(len 4 and content)
func GetLenBytes(buf []byte) (b []byte, err error) {
raw := bytes.NewBuffer([]byte{})
if err = binary.Write(raw, binary.LittleEndian, int32(len(buf))); err != nil {
return
}
if err = binary.Write(raw, binary.LittleEndian, buf); err != nil {
return
}
b = raw.Bytes()
return
}
//udp connection setting
func SetUdpSession(sess *kcp.UDPSession) {
sess.SetStreamMode(true)
sess.SetWindowSize(1024, 1024)
sess.SetReadBuffer(64 * 1024)
sess.SetWriteBuffer(64 * 1024)
sess.SetNoDelay(1, 10, 2, 1)
sess.SetMtu(1600)
sess.SetACKNoDelay(true)
sess.SetWriteDelay(false)
}
//conn1 mux conn
func CopyWaitGroup(conn1, conn2 net.Conn, crypt bool, snappy bool, rate *rate.Rate, flow *file.Flow, isServer bool, rb []byte) {
//var in, out int64
//var wg sync.WaitGroup
connHandle := GetConn(conn1, crypt, snappy, rate, isServer)
if rb != nil {
connHandle.Write(rb)
}
//go func(in *int64) {
// wg.Add(1)
// *in, _ = common.CopyBuffer(connHandle, conn2)
// connHandle.Close()
// conn2.Close()
// wg.Done()
//}(&in)
//out, _ = common.CopyBuffer(conn2, connHandle)
//connHandle.Close()
//conn2.Close()
//wg.Wait()
//if flow != nil {
// flow.Add(in, out)
//}
wg := new(sync.WaitGroup)
wg.Add(1)
err := goroutine.CopyConnsPool.Invoke(goroutine.NewConns(connHandle, conn2, flow, wg))
wg.Wait()
if err != nil {
logs.Error(err)
}
}
//get crypt or snappy conn
func GetConn(conn net.Conn, cpt, snappy bool, rt *rate.Rate, isServer bool) io.ReadWriteCloser {
if cpt {
if isServer {
return rate.NewRateConn(crypt.NewTlsServerConn(conn), rt)
}
return rate.NewRateConn(crypt.NewTlsClientConn(conn), rt)
} else if snappy {
return rate.NewRateConn(NewSnappyConn(conn), rt)
}
return rate.NewRateConn(conn, rt)
}
type LenConn struct {
conn io.Writer
Len int
}
func NewLenConn(conn io.Writer) *LenConn {
return &LenConn{conn: conn}
}
func (c *LenConn) Write(p []byte) (n int, err error) {
n, err = c.conn.Write(p)
c.Len += n
return
}
================================================
FILE: lib/conn/link.go
================================================
package conn
import "time"
type Secret struct {
Password string
Conn *Conn
}
func NewSecret(p string, conn *Conn) *Secret {
return &Secret{
Password: p,
Conn: conn,
}
}
type Link struct {
ConnType string //连接类型
Host string //目标
Crypt bool //加密
Compress bool
LocalProxy bool
RemoteAddr string
Option Options
}
type Option func(*Options)
type Options struct {
Timeout time.Duration
}
var defaultTimeOut = time.Second * 5
func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool, opts ...Option) *Link {
options := newOptions(opts...)
return &Link{
RemoteAddr: remoteAddr,
ConnType: connType,
Host: host,
Crypt: crypt,
Compress: compress,
LocalProxy: localProxy,
Option: options,
}
}
func newOptions(opts ...Option) Options {
opt := Options{
Timeout: defaultTimeOut,
}
for _, o := range opts {
o(&opt)
}
return opt
}
func LinkTimeout(t time.Duration) Option {
return func(opt *Options) {
opt.Timeout = t
}
}
================================================
FILE: lib/conn/listener.go
================================================
package conn
import (
"net"
"strings"
"github.com/astaxie/beego/logs"
"github.com/xtaci/kcp-go"
)
func NewTcpListenerAndProcess(addr string, f func(c net.Conn), listener *net.Listener) error {
var err error
*listener, err = net.Listen("tcp", addr)
if err != nil {
return err
}
Accept(*listener, f)
return nil
}
func NewKcpListenerAndProcess(addr string, f func(c net.Conn)) error {
kcpListener, err := kcp.ListenWithOptions(addr, nil, 150, 3)
if err != nil {
logs.Error(err)
return err
}
for {
c, err := kcpListener.AcceptKCP()
SetUdpSession(c)
if err != nil {
logs.Warn(err)
continue
}
go f(c)
}
return nil
}
func Accept(l net.Listener, f func(c net.Conn)) {
for {
c, err := l.Accept()
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
break
}
if strings.Contains(err.Error(), "the mux has closed") {
break
}
logs.Warn(err)
continue
}
if c == nil {
logs.Warn("nil connection")
break
}
go f(c)
}
}
================================================
FILE: lib/conn/snappy.go
================================================
package conn
import (
"errors"
"io"
"github.com/golang/snappy"
)
type SnappyConn struct {
w *snappy.Writer
r *snappy.Reader
c io.Closer
}
func NewSnappyConn(conn io.ReadWriteCloser) *SnappyConn {
c := new(SnappyConn)
c.w = snappy.NewBufferedWriter(conn)
c.r = snappy.NewReader(conn)
c.c = conn.(io.Closer)
return c
}
//snappy压缩写
func (s *SnappyConn) Write(b []byte) (n int, err error) {
if n, err = s.w.Write(b); err != nil {
return
}
if err = s.w.Flush(); err != nil {
return
}
return
}
//snappy压缩读
func (s *SnappyConn) Read(b []byte) (n int, err error) {
return s.r.Read(b)
}
func (s *SnappyConn) Close() error {
err := s.w.Close()
err2 := s.c.Close()
if err != nil && err2 == nil {
return err
}
if err == nil && err2 != nil {
return err2
}
if err != nil && err2 != nil {
return errors.New(err.Error() + err2.Error())
}
return nil
}
================================================
FILE: lib/crypt/clientHello.go
================================================
package crypt
import (
"strings"
)
type CurveID uint16
type SignatureScheme uint16
const (
statusTypeOCSP uint8 = 1
extensionServerName uint16 = 0
extensionStatusRequest uint16 = 5
extensionSupportedCurves uint16 = 10
extensionSupportedPoints uint16 = 11
extensionSignatureAlgorithms uint16 = 13
extensionALPN uint16 = 16
extensionSCT uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6
extensionSessionTicket uint16 = 35
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
extensionRenegotiationInfo uint16 = 0xff01
scsvRenegotiation uint16 = 0x00ff
)
type ClientHelloMsg struct {
raw []byte
vers uint16
random []byte
sessionId []byte
cipherSuites []uint16
compressionMethods []uint8
nextProtoNeg bool
serverName string
ocspStapling bool
scts bool
supportedCurves []CurveID
supportedPoints []uint8
ticketSupported bool
sessionTicket []uint8
supportedSignatureAlgorithms []SignatureScheme
secureRenegotiation []byte
secureRenegotiationSupported bool
alpnProtocols []string
}
func (m *ClientHelloMsg) GetServerName() string {
return m.serverName
}
func (m *ClientHelloMsg) Unmarshal(data []byte) bool {
if len(data) < 42 {
return false
}
m.raw = data
m.vers = uint16(data[4])<<8 | uint16(data[5])
m.random = data[6:38]
sessionIdLen := int(data[38])
if sessionIdLen > 32 || len(data) < 39+sessionIdLen {
return false
}
m.sessionId = data[39 : 39+sessionIdLen]
data = data[39+sessionIdLen:]
if len(data) < 2 {
return false
}
// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
// they are uint16s, the number must be even.
cipherSuiteLen := int(data[0])<<8 | int(data[1])
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
return false
}
numCipherSuites := cipherSuiteLen / 2
m.cipherSuites = make([]uint16, numCipherSuites)
for i := 0; i < numCipherSuites; i++ {
m.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])
if m.cipherSuites[i] == scsvRenegotiation {
m.secureRenegotiationSupported = true
}
}
data = data[2+cipherSuiteLen:]
if len(data) < 1 {
return false
}
compressionMethodsLen := int(data[0])
if len(data) < 1+compressionMethodsLen {
return false
}
m.compressionMethods = data[1 : 1+compressionMethodsLen]
data = data[1+compressionMethodsLen:]
m.nextProtoNeg = false
m.serverName = ""
m.ocspStapling = false
m.ticketSupported = false
m.sessionTicket = nil
m.supportedSignatureAlgorithms = nil
m.alpnProtocols = nil
m.scts = false
if len(data) == 0 {
// ClientHello is optionally followed by extension data
return true
}
if len(data) < 2 {
return false
}
extensionsLength := int(data[0])<<8 | int(data[1])
data = data[2:]
if extensionsLength != len(data) {
return false
}
for len(data) != 0 {
if len(data) < 4 {
return false
}
extension := uint16(data[0])<<8 | uint16(data[1])
length := int(data[2])<<8 | int(data[3])
data = data[4:]
if len(data) < length {
return false
}
switch extension {
case extensionServerName:
d := data[:length]
if len(d) < 2 {
return false
}
namesLen := int(d[0])<<8 | int(d[1])
d = d[2:]
if len(d) != namesLen {
return false
}
for len(d) > 0 {
if len(d) < 3 {
return false
}
nameType := d[0]
nameLen := int(d[1])<<8 | int(d[2])
d = d[3:]
if len(d) < nameLen {
return false
}
if nameType == 0 {
m.serverName = string(d[:nameLen])
// An SNI value may not include a
// trailing dot. See
// https://tools.ietf.org/html/rfc6066#section-3.
if strings.HasSuffix(m.serverName, ".") {
return false
}
break
}
d = d[nameLen:]
}
case extensionNextProtoNeg:
if length > 0 {
return false
}
m.nextProtoNeg = true
case extensionStatusRequest:
m.ocspStapling = length > 0 && data[0] == statusTypeOCSP
case extensionSupportedCurves:
// https://tools.ietf.org/html/rfc4492#section-5.5.1
if length < 2 {
return false
}
l := int(data[0])<<8 | int(data[1])
if l%2 == 1 || length != l+2 {
return false
}
numCurves := l / 2
m.supportedCurves = make([]CurveID, numCurves)
d := data[2:]
for i := 0; i < numCurves; i++ {
m.supportedCurves[i] = CurveID(d[0])<<8 | CurveID(d[1])
d = d[2:]
}
case extensionSupportedPoints:
// https://tools.ietf.org/html/rfc4492#section-5.5.2
if length < 1 {
return false
}
l := int(data[0])
if length != l+1 {
return false
}
m.supportedPoints = make([]uint8, l)
copy(m.supportedPoints, data[1:])
case extensionSessionTicket:
// https://tools.ietf.org/html/rfc5077#section-3.2
m.ticketSupported = true
m.sessionTicket = data[:length]
case extensionSignatureAlgorithms:
// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
if length < 2 || length&1 != 0 {
return false
}
l := int(data[0])<<8 | int(data[1])
if l != length-2 {
return false
}
n := l / 2
d := data[2:]
m.supportedSignatureAlgorithms = make([]SignatureScheme, n)
for i := range m.supportedSignatureAlgorithms {
m.supportedSignatureAlgorithms[i] = SignatureScheme(d[0])<<8 | SignatureScheme(d[1])
d = d[2:]
}
case extensionRenegotiationInfo:
if length == 0 {
return false
}
d := data[:length]
l := int(d[0])
d = d[1:]
if l != len(d) {
return false
}
m.secureRenegotiation = d
m.secureRenegotiationSupported = true
case extensionALPN:
if length < 2 {
return false
}
l := int(data[0])<<8 | int(data[1])
if l != length-2 {
return false
}
d := data[2:length]
for len(d) != 0 {
stringLen := int(d[0])
d = d[1:]
if stringLen == 0 || stringLen > len(d) {
return false
}
m.alpnProtocols = append(m.alpnProtocols, string(d[:stringLen]))
d = d[stringLen:]
}
case extensionSCT:
m.scts = true
if length != 0 {
return false
}
}
data = data[length:]
}
return true
}
================================================
FILE: lib/crypt/crypt.go
================================================
package crypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/hex"
"errors"
"math/rand"
"time"
)
//en
func AesEncrypt(origData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = PKCS5Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
//de
func AesDecrypt(crypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
err, origData = PKCS5UnPadding(origData)
return origData, err
}
//Completion when the length is insufficient
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
//Remove excess
func PKCS5UnPadding(origData []byte) (error, []byte) {
length := len(origData)
unpadding := int(origData[length-1])
if (length - unpadding) < 0 {
return errors.New("len error"), nil
}
return nil, origData[:(length - unpadding)]
}
//Generate 32-bit MD5 strings
func Md5(s string) string {
h := md5.New()
h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}
//Generating Random Verification Key
func GetRandomString(l int) string {
str := "0123456789abcdefghijklmnopqrstuvwxyz"
bytes := []byte(str)
result := []byte{}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return string(result)
}
================================================
FILE: lib/crypt/tls.go
================================================
package crypt
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"net"
"os"
"time"
"github.com/astaxie/beego/logs"
)
var (
cert tls.Certificate
)
func InitTls() {
c, k, err := generateKeyPair("NPS Org")
if err == nil {
cert, err = tls.X509KeyPair(c, k)
}
if err != nil {
log.Fatalln("Error initializing crypto certs", err)
}
}
func NewTlsServerConn(conn net.Conn) net.Conn {
var err error
if err != nil {
logs.Error(err)
os.Exit(0)
return nil
}
config := &tls.Config{Certificates: []tls.Certificate{cert}}
return tls.Server(conn, config)
}
func NewTlsClientConn(conn net.Conn) net.Conn {
conf := &tls.Config{
InsecureSkipVerify: true,
}
return tls.Client(conn, conf)
}
func generateKeyPair(CommonName string) (rawCert, rawKey []byte, err error) {
// Create private key and self-signed certificate
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return
}
validFor := time.Hour * 24 * 365 * 10 // ten years
notBefore := time.Now()
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"My Company Name LTD."},
CommonName: CommonName,
Country: []string{"US"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return
}
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
return
}
================================================
FILE: lib/daemon/daemon.go
================================================
package daemon
import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"ehang.io/nps/lib/common"
)
func InitDaemon(f string, runPath string, pidPath string) {
if len(os.Args) < 2 {
return
}
var args []string
args = append(args, os.Args[0])
if len(os.Args) >= 2 {
args = append(args, os.Args[2:]...)
}
args = append(args, "-log=file")
switch os.Args[1] {
case "start":
start(args, f, pidPath, runPath)
os.Exit(0)
case "stop":
stop(f, args[0], pidPath)
os.Exit(0)
case "restart":
stop(f, args[0], pidPath)
start(args, f, pidPath, runPath)
os.Exit(0)
case "status":
if status(f, pidPath) {
log.Printf("%s is running", f)
} else {
log.Printf("%s is not running", f)
}
os.Exit(0)
case "reload":
reload(f, pidPath)
os.Exit(0)
}
}
func reload(f string, pidPath string) {
if f == "nps" && !common.IsWindows() && !status(f, pidPath) {
log.Println("reload fail")
return
}
var c *exec.Cmd
var err error
b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid"))
if err == nil {
c = exec.Command("/bin/bash", "-c", `kill -30 `+string(b))
} else {
log.Fatalln("reload error,pid file does not exist")
}
if c.Run() == nil {
log.Println("reload success")
} else {
log.Println("reload fail")
}
}
func status(f string, pidPath string) bool {
var cmd *exec.Cmd
b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid"))
if err == nil {
if !common.IsWindows() {
cmd = exec.Command("/bin/sh", "-c", "ps -ax | awk '{ print $1 }' | grep "+string(b))
} else {
cmd = exec.Command("tasklist")
}
out, _ := cmd.Output()
if strings.Index(string(out), string(b)) > -1 {
return true
}
}
return false
}
func start(osArgs []string, f string, pidPath, runPath string) {
if status(f, pidPath) {
log.Printf(" %s is running", f)
return
}
cmd := exec.Command(osArgs[0], osArgs[1:]...)
cmd.Start()
if cmd.Process.Pid > 0 {
log.Println("start ok , pid:", cmd.Process.Pid, "config path:", runPath)
d1 := []byte(strconv.Itoa(cmd.Process.Pid))
ioutil.WriteFile(filepath.Join(pidPath, f+".pid"), d1, 0600)
} else {
log.Println("start error")
}
}
func stop(f string, p string, pidPath string) {
if !status(f, pidPath) {
log.Printf(" %s is not running", f)
return
}
var c *exec.Cmd
var err error
if common.IsWindows() {
p := strings.Split(p, `\`)
c = exec.Command("taskkill", "/F", "/IM", p[len(p)-1])
} else {
b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid"))
if err == nil {
c = exec.Command("/bin/bash", "-c", `kill -9 `+string(b))
} else {
log.Fatalln("stop error,pid file does not exist")
}
}
err = c.Run()
if err != nil {
log.Println("stop error,", err)
} else {
log.Println("stop ok")
}
}
================================================
FILE: lib/daemon/reload.go
================================================
// +build !windows
package daemon
import (
"os"
"os/signal"
"path/filepath"
"syscall"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego"
)
func init() {
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGUSR1)
go func() {
for {
<-s
beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf"))
}
}()
}
================================================
FILE: lib/file/db.go
================================================
package file
import (
"errors"
"fmt"
"net/http"
"sort"
"strings"
"sync"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/rate"
)
type DbUtils struct {
JsonDb *JsonDb
}
var (
Db *DbUtils
once sync.Once
)
//init csv from file
func GetDb() *DbUtils {
once.Do(func() {
jsonDb := NewJsonDb(common.GetRunPath())
jsonDb.LoadClientFromJsonFile()
jsonDb.LoadTaskFromJsonFile()
jsonDb.LoadHostFromJsonFile()
Db = &DbUtils{JsonDb: jsonDb}
})
return Db
}
func GetMapKeys(m sync.Map, isSort bool, sortKey, order string) (keys []int) {
if sortKey != "" && isSort {
return sortClientByKey(m, sortKey, order)
}
m.Range(func(key, value interface{}) bool {
keys = append(keys, key.(int))
return true
})
sort.Ints(keys)
return
}
func (s *DbUtils) GetClientList(start, length int, search, sort, order string, clientId int) ([]*Client, int) {
list := make([]*Client, 0)
var cnt int
keys := GetMapKeys(s.JsonDb.Clients, true, sort, order)
for _, key := range keys {
if value, ok := s.JsonDb.Clients.Load(key); ok {
v := value.(*Client)
if v.NoDisplay {
continue
}
if clientId != 0 && clientId != v.Id {
continue
}
if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || strings.Contains(v.VerifyKey, search) || strings.Contains(v.Remark, search)) {
continue
}
cnt++
if start--; start < 0 {
if length--; length >= 0 {
list = append(list, v)
}
}
}
}
return list, cnt
}
func (s *DbUtils) GetIdByVerifyKey(vKey string, addr string) (id int, err error) {
var exist bool
s.JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*Client)
if common.Getverifyval(v.VerifyKey) == vKey && v.Status {
v.Addr = common.GetIpByAddr(addr)
id = v.Id
exist = true
return false
}
return true
})
if exist {
return
}
return 0, errors.New("not found")
}
func (s *DbUtils) NewTask(t *Tunnel) (err error) {
s.JsonDb.Tasks.Range(func(key, value interface{}) bool {
v := value.(*Tunnel)
if (v.Mode == "secret" || v.Mode == "p2p") && v.Password == t.Password {
err = errors.New(fmt.Sprintf("secret mode keys %s must be unique", t.Password))
return false
}
return true
})
if err != nil {
return
}
t.Flow = new(Flow)
s.JsonDb.Tasks.Store(t.Id, t)
s.JsonDb.StoreTasksToJsonFile()
return
}
func (s *DbUtils) UpdateTask(t *Tunnel) error {
s.JsonDb.Tasks.Store(t.Id, t)
s.JsonDb.StoreTasksToJsonFile()
return nil
}
func (s *DbUtils) DelTask(id int) error {
s.JsonDb.Tasks.Delete(id)
s.JsonDb.StoreTasksToJsonFile()
return nil
}
//md5 password
func (s *DbUtils) GetTaskByMd5Password(p string) (t *Tunnel) {
s.JsonDb.Tasks.Range(func(key, value interface{}) bool {
if crypt.Md5(value.(*Tunnel).Password) == p {
t = value.(*Tunnel)
return false
}
return true
})
return
}
func (s *DbUtils) GetTask(id int) (t *Tunnel, err error) {
if v, ok := s.JsonDb.Tasks.Load(id); ok {
t = v.(*Tunnel)
return
}
err = errors.New("not found")
return
}
func (s *DbUtils) DelHost(id int) error {
s.JsonDb.Hosts.Delete(id)
s.JsonDb.StoreHostToJsonFile()
return nil
}
func (s *DbUtils) IsHostExist(h *Host) bool {
var exist bool
s.JsonDb.Hosts.Range(func(key, value interface{}) bool {
v := value.(*Host)
if v.Id != h.Id && v.Host == h.Host && h.Location == v.Location && (v.Scheme == "all" || v.Scheme == h.Scheme) {
exist = true
return false
}
return true
})
return exist
}
func (s *DbUtils) NewHost(t *Host) error {
if t.Location == "" {
t.Location = "/"
}
if s.IsHostExist(t) {
return errors.New("host has exist")
}
t.Flow = new(Flow)
s.JsonDb.Hosts.Store(t.Id, t)
s.JsonDb.StoreHostToJsonFile()
return nil
}
func (s *DbUtils) GetHost(start, length int, id int, search string) ([]*Host, int) {
list := make([]*Host, 0)
var cnt int
keys := GetMapKeys(s.JsonDb.Hosts, false, "", "")
for _, key := range keys {
if value, ok := s.JsonDb.Hosts.Load(key); ok {
v := value.(*Host)
if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || strings.Contains(v.Host, search) || strings.Contains(v.Remark, search)) {
continue
}
if id == 0 || v.Client.Id == id {
cnt++
if start--; start < 0 {
if length--; length >= 0 {
list = append(list, v)
}
}
}
}
}
return list, cnt
}
func (s *DbUtils) DelClient(id int) error {
s.JsonDb.Clients.Delete(id)
s.JsonDb.StoreClientsToJsonFile()
return nil
}
func (s *DbUtils) NewClient(c *Client) error {
var isNotSet bool
if c.WebUserName != "" && !s.VerifyUserName(c.WebUserName, c.Id) {
return errors.New("web login username duplicate, please reset")
}
reset:
if c.VerifyKey == "" || isNotSet {
isNotSet = true
c.VerifyKey = crypt.GetRandomString(16)
}
if c.RateLimit == 0 {
c.Rate = rate.NewRate(int64(2 << 23))
} else if c.Rate == nil {
c.Rate = rate.NewRate(int64(c.RateLimit * 1024))
}
c.Rate.Start()
if !s.VerifyVkey(c.VerifyKey, c.Id) {
if isNotSet {
goto reset
}
return errors.New("Vkey duplicate, please reset")
}
if c.Id == 0 {
c.Id = int(s.JsonDb.GetClientId())
}
if c.Flow == nil {
c.Flow = new(Flow)
}
s.JsonDb.Clients.Store(c.Id, c)
s.JsonDb.StoreClientsToJsonFile()
return nil
}
func (s *DbUtils) VerifyVkey(vkey string, id int) (res bool) {
res = true
s.JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*Client)
if v.VerifyKey == vkey && v.Id != id {
res = false
return false
}
return true
})
return res
}
func (s *DbUtils) VerifyUserName(username string, id int) (res bool) {
res = true
s.JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*Client)
if v.WebUserName == username && v.Id != id {
res = false
return false
}
return true
})
return res
}
func (s *DbUtils) UpdateClient(t *Client) error {
s.JsonDb.Clients.Store(t.Id, t)
if t.RateLimit == 0 {
t.Rate = rate.NewRate(int64(2 << 23))
t.Rate.Start()
}
return nil
}
func (s *DbUtils) IsPubClient(id int) bool {
client, err := s.GetClient(id)
if err == nil {
return client.NoDisplay
}
return false
}
func (s *DbUtils) GetClient(id int) (c *Client, err error) {
if v, ok := s.JsonDb.Clients.Load(id); ok {
c = v.(*Client)
return
}
err = errors.New("未找到客户端")
return
}
func (s *DbUtils) GetClientIdByVkey(vkey string) (id int, err error) {
var exist bool
s.JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*Client)
if crypt.Md5(v.VerifyKey) == vkey {
exist = true
id = v.Id
return false
}
return true
})
if exist {
return
}
err = errors.New("未找到客户端")
return
}
func (s *DbUtils) GetHostById(id int) (h *Host, err error) {
if v, ok := s.JsonDb.Hosts.Load(id); ok {
h = v.(*Host)
return
}
err = errors.New("The host could not be parsed")
return
}
//get key by host from x
func (s *DbUtils) GetInfoByHost(host string, r *http.Request) (h *Host, err error) {
var hosts []*Host
//Handling Ported Access
host = common.GetIpByAddr(host)
s.JsonDb.Hosts.Range(func(key, value interface{}) bool {
v := value.(*Host)
if v.IsClose {
return true
}
//Remove http(s) http(s)://a.proxy.com
//*.proxy.com *.a.proxy.com Do some pan-parsing
if v.Scheme != "all" && v.Scheme != r.URL.Scheme {
return true
}
tmpHost := v.Host
if strings.Contains(tmpHost, "*") {
tmpHost = strings.Replace(tmpHost, "*", "", -1)
if strings.Contains(host, tmpHost) {
hosts = append(hosts, v)
}
} else if v.Host == host {
hosts = append(hosts, v)
}
return true
})
for _, v := range hosts {
//If not set, default matches all
if v.Location == "" {
v.Location = "/"
}
if strings.Index(r.RequestURI, v.Location) == 0 {
if h == nil || (len(v.Location) > len(h.Location)) {
h = v
}
}
}
if h != nil {
return
}
err = errors.New("The host could not be parsed")
return
}
================================================
FILE: lib/file/file.go
================================================
package file
import (
"encoding/json"
"errors"
"github.com/astaxie/beego/logs"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/rate"
)
func NewJsonDb(runPath string) *JsonDb {
return &JsonDb{
RunPath: runPath,
TaskFilePath: filepath.Join(runPath, "conf", "tasks.json"),
HostFilePath: filepath.Join(runPath, "conf", "hosts.json"),
ClientFilePath: filepath.Join(runPath, "conf", "clients.json"),
}
}
type JsonDb struct {
Tasks sync.Map
Hosts sync.Map
HostsTmp sync.Map
Clients sync.Map
RunPath string
ClientIncreaseId int32 //client increased id
TaskIncreaseId int32 //task increased id
HostIncreaseId int32 //host increased id
TaskFilePath string //task file path
HostFilePath string //host file path
ClientFilePath string //client file path
}
func (s *JsonDb) LoadTaskFromJsonFile() {
loadSyncMapFromFile(s.TaskFilePath, func(v string) {
var err error
post := new(Tunnel)
if json.Unmarshal([]byte(v), &post) != nil {
return
}
if post.Client, err = s.GetClient(post.Client.Id); err != nil {
return
}
s.Tasks.Store(post.Id, post)
if post.Id > int(s.TaskIncreaseId) {
s.TaskIncreaseId = int32(post.Id)
}
})
}
func (s *JsonDb) LoadClientFromJsonFile() {
loadSyncMapFromFile(s.ClientFilePath, func(v string) {
post := new(Client)
if json.Unmarshal([]byte(v), &post) != nil {
return
}
if post.RateLimit > 0 {
post.Rate = rate.NewRate(int64(post.RateLimit * 1024))
} else {
post.Rate = rate.NewRate(int64(2 << 23))
}
post.Rate.Start()
post.NowConn = 0
s.Clients.Store(post.Id, post)
if post.Id > int(s.ClientIncreaseId) {
s.ClientIncreaseId = int32(post.Id)
}
})
}
func (s *JsonDb) LoadHostFromJsonFile() {
loadSyncMapFromFile(s.HostFilePath, func(v string) {
var err error
post := new(Host)
if json.Unmarshal([]byte(v), &post) != nil {
return
}
if post.Client, err = s.GetClient(post.Client.Id); err != nil {
return
}
s.Hosts.Store(post.Id, post)
if post.Id > int(s.HostIncreaseId) {
s.HostIncreaseId = int32(post.Id)
}
})
}
func (s *JsonDb) GetClient(id int) (c *Client, err error) {
if v, ok := s.Clients.Load(id); ok {
c = v.(*Client)
return
}
err = errors.New("未找到客户端")
return
}
var hostLock sync.Mutex
func (s *JsonDb) StoreHostToJsonFile() {
hostLock.Lock()
storeSyncMapToFile(s.Hosts, s.HostFilePath)
hostLock.Unlock()
}
var taskLock sync.Mutex
func (s *JsonDb) StoreTasksToJsonFile() {
taskLock.Lock()
storeSyncMapToFile(s.Tasks, s.TaskFilePath)
taskLock.Unlock()
}
var clientLock sync.Mutex
func (s *JsonDb) StoreClientsToJsonFile() {
clientLock.Lock()
storeSyncMapToFile(s.Clients, s.ClientFilePath)
clientLock.Unlock()
}
func (s *JsonDb) GetClientId() int32 {
return atomic.AddInt32(&s.ClientIncreaseId, 1)
}
func (s *JsonDb) GetTaskId() int32 {
return atomic.AddInt32(&s.TaskIncreaseId, 1)
}
func (s *JsonDb) GetHostId() int32 {
return atomic.AddInt32(&s.HostIncreaseId, 1)
}
func loadSyncMapFromFile(filePath string, f func(value string)) {
b, err := common.ReadAllFromFile(filePath)
if err != nil {
panic(err)
}
for _, v := range strings.Split(string(b), "\n"+common.CONN_DATA_SEQ) {
f(v)
}
}
func storeSyncMapToFile(m sync.Map, filePath string) {
file, err := os.Create(filePath + ".tmp")
// first create a temporary file to store
if err != nil {
panic(err)
}
m.Range(func(key, value interface{}) bool {
var b []byte
var err error
switch value.(type) {
case *Tunnel:
obj := value.(*Tunnel)
if obj.NoStore {
return true
}
b, err = json.Marshal(obj)
case *Host:
obj := value.(*Host)
if obj.NoStore {
return true
}
b, err = json.Marshal(obj)
case *Client:
obj := value.(*Client)
if obj.NoStore {
return true
}
b, err = json.Marshal(obj)
default:
return true
}
if err != nil {
return true
}
_, err = file.Write(b)
if err != nil {
panic(err)
}
_, err = file.Write([]byte("\n" + common.CONN_DATA_SEQ))
if err != nil {
panic(err)
}
return true
})
_ = file.Sync()
_ = file.Close()
// must close file first, then rename it
err = os.Rename(filePath+".tmp", filePath)
if err != nil {
logs.Error(err, "store to file err, data will lost")
}
// replace the file, maybe provides atomic operation
}
================================================
FILE: lib/file/obj.go
================================================
package file
import (
"strings"
"sync"
"sync/atomic"
"time"
"ehang.io/nps/lib/rate"
"github.com/pkg/errors"
)
type Flow struct {
ExportFlow int64
InletFlow int64
FlowLimit int64
sync.RWMutex
}
func (s *Flow) Add(in, out int64) {
s.Lock()
defer s.Unlock()
s.InletFlow += int64(in)
s.ExportFlow += int64(out)
}
type Config struct {
U string
P string
Compress bool
Crypt bool
}
type Client struct {
Cnf *Config
Id int //id
VerifyKey string //verify key
Addr string //the ip of client
Remark string //remark
Status bool //is allow connect
IsConnect bool //is the client connect
RateLimit int //rate /kb
Flow *Flow //flow setting
Rate *rate.Rate //rate limit
NoStore bool //no store to file
NoDisplay bool //no display on web
MaxConn int //the max connection num of client allow
NowConn int32 //the connection num of now
WebUserName string //the username of web login
WebPassword string //the password of web login
ConfigConnAllow bool //is allow connected by config file
MaxTunnelNum int
Version string
sync.RWMutex
}
func NewClient(vKey string, noStore bool, noDisplay bool) *Client {
return &Client{
Cnf: new(Config),
Id: 0,
VerifyKey: vKey,
Addr: "",
Remark: "",
Status: true,
IsConnect: false,
RateLimit: 0,
Flow: new(Flow),
Rate: nil,
NoStore: noStore,
RWMutex: sync.RWMutex{},
NoDisplay: noDisplay,
}
}
func (s *Client) CutConn() {
atomic.AddInt32(&s.NowConn, 1)
}
func (s *Client) AddConn() {
atomic.AddInt32(&s.NowConn, -1)
}
func (s *Client) GetConn() bool {
if s.MaxConn == 0 || int(s.NowConn) < s.MaxConn {
s.CutConn()
return true
}
return false
}
func (s *Client) HasTunnel(t *Tunnel) (exist bool) {
GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
v := value.(*Tunnel)
if v.Client.Id == s.Id && v.Port == t.Port && t.Port != 0 {
exist = true
return false
}
return true
})
return
}
func (s *Client) GetTunnelNum() (num int) {
GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
v := value.(*Tunnel)
if v.Client.Id == s.Id {
num++
}
return true
})
return
}
func (s *Client) HasHost(h *Host) bool {
var has bool
GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
v := value.(*Host)
if v.Client.Id == s.Id && v.Host == h.Host && h.Location == v.Location {
has = true
return false
}
return true
})
return has
}
type Tunnel struct {
Id int
Port int
ServerIp string
Mode string
Status bool
RunStatus bool
Client *Client
Ports string
Flow *Flow
Password string
Remark string
TargetAddr string
NoStore bool
LocalPath string
StripPre string
Target *Target
MultiAccount *MultiAccount
Health
sync.RWMutex
}
type Health struct {
HealthCheckTimeout int
HealthMaxFail int
HealthCheckInterval int
HealthNextTime time.Time
HealthMap map[string]int
HttpHealthUrl string
HealthRemoveArr []string
HealthCheckType string
HealthCheckTarget string
sync.RWMutex
}
type Host struct {
Id int
Host string //host
HeaderChange string //header change
HostChange string //host change
Location string //url router
Remark string //remark
Scheme string //http https all
CertFilePath string
KeyFilePath string
NoStore bool
IsClose bool
Flow *Flow
Client *Client
Target *Target //目标
Health `json:"-"`
sync.RWMutex
}
type Target struct {
nowIndex int
TargetStr string
TargetArr []string
LocalProxy bool
sync.RWMutex
}
type MultiAccount struct {
AccountMap map[string]string // multi account and pwd
}
func (s *Target) GetRandomTarget() (string, error) {
if s.TargetArr == nil {
s.TargetArr = strings.Split(s.TargetStr, "\n")
}
if len(s.TargetArr) == 1 {
return s.TargetArr[0], nil
}
if len(s.TargetArr) == 0 {
return "", errors.New("all inward-bending targets are offline")
}
s.Lock()
defer s.Unlock()
if s.nowIndex >= len(s.TargetArr)-1 {
s.nowIndex = -1
}
s.nowIndex++
return s.TargetArr[s.nowIndex], nil
}
================================================
FILE: lib/file/sort.go
================================================
package file
import (
"reflect"
"sort"
"sync"
)
// A data structure to hold a key/value pair.
type Pair struct {
key string //sort key
cId int
order string
clientFlow *Flow
}
// A slice of Pairs that implements sort.Interface to sort by Value.
type PairList []*Pair
func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p PairList) Len() int { return len(p) }
func (p PairList) Less(i, j int) bool {
if p[i].order == "desc" {
return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() < reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int()
}
return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() > reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int()
}
// A function to turn a map into a PairList, then sort and return it.
func sortClientByKey(m sync.Map, sortKey, order string) (res []int) {
p := make(PairList, 0)
m.Range(func(key, value interface{}) bool {
p = append(p, &Pair{sortKey, value.(*Client).Id, order, value.(*Client).Flow})
return true
})
sort.Sort(p)
for _, v := range p {
res = append(res, v.cId)
}
return
}
================================================
FILE: lib/goroutine/pool.go
================================================
package goroutine
import (
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"github.com/panjf2000/ants/v2"
"io"
"net"
"sync"
)
type connGroup struct {
src io.ReadWriteCloser
dst io.ReadWriteCloser
wg *sync.WaitGroup
n *int64
}
func newConnGroup(dst, src io.ReadWriteCloser, wg *sync.WaitGroup, n *int64) connGroup {
return connGroup{
src: src,
dst: dst,
wg: wg,
n: n,
}
}
func copyConnGroup(group interface{}) {
cg, ok := group.(connGroup)
if !ok {
return
}
var err error
*cg.n, err = common.CopyBuffer(cg.dst, cg.src)
if err != nil {
cg.src.Close()
cg.dst.Close()
//logs.Warn("close npc by copy from nps", err, c.connId)
}
cg.wg.Done()
}
type Conns struct {
conn1 io.ReadWriteCloser // mux connection
conn2 net.Conn // outside connection
flow *file.Flow
wg *sync.WaitGroup
}
func NewConns(c1 io.ReadWriteCloser, c2 net.Conn, flow *file.Flow, wg *sync.WaitGroup) Conns {
return Conns{
conn1: c1,
conn2: c2,
flow: flow,
wg: wg,
}
}
func copyConns(group interface{}) {
conns := group.(Conns)
wg := new(sync.WaitGroup)
wg.Add(2)
var in, out int64
_ = connCopyPool.Invoke(newConnGroup(conns.conn1, conns.conn2, wg, &in))
// outside to mux : incoming
_ = connCopyPool.Invoke(newConnGroup(conns.conn2, conns.conn1, wg, &out))
// mux to outside : outgoing
wg.Wait()
if conns.flow != nil {
conns.flow.Add(in, out)
}
conns.wg.Done()
}
var connCopyPool, _ = ants.NewPoolWithFunc(200000, copyConnGroup, ants.WithNonblocking(false))
var CopyConnsPool, _ = ants.NewPoolWithFunc(100000, copyConns, ants.WithNonblocking(false))
================================================
FILE: lib/install/install.go
================================================
package install
import (
"ehang.io/nps/lib/common"
"encoding/json"
"errors"
"fmt"
"github.com/c4milo/unpackit"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
)
// Keep it in sync with the template from service_sysv_linux.go file
// Use "ps | grep -v grep | grep $(get_pid)" because "ps PID" may not work on OpenWrt
const SysvScript = `#!/bin/sh
# For RedHat and cousins:
# chkconfig: - 99 01
# description: {{.Description}}
# processname: {{.Path}}
### BEGIN INIT INFO
# Provides: {{.Path}}
# Required-Start:
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: {{.DisplayName}}
# Description: {{.Description}}
### END INIT INFO
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
name=$(basename $(readlink -f $0))
pid_file="/var/run/$name.pid"
stdout_log="/var/log/$name.log"
stderr_log="/var/log/$name.err"
[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name
get_pid() {
cat "$pid_file"
}
is_running() {
[ -f "$pid_file" ] && ps | grep -v grep | grep $(get_pid) > /dev/null 2>&1
}
case "$1" in
start)
if is_running; then
echo "Already started"
else
echo "Starting $name"
{{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}}
$cmd >> "$stdout_log" 2>> "$stderr_log" &
echo $! > "$pid_file"
if ! is_running; then
echo "Unable to start, see $stdout_log and $stderr_log"
exit 1
fi
fi
;;
stop)
if is_running; then
echo -n "Stopping $name.."
kill $(get_pid)
for i in $(seq 1 10)
do
if ! is_running; then
break
fi
echo -n "."
sleep 1
done
echo
if is_running; then
echo "Not stopped; may still be shutting down or shutdown may have failed"
exit 1
else
echo "Stopped"
if [ -f "$pid_file" ]; then
rm "$pid_file"
fi
fi
else
echo "Not running"
fi
;;
restart)
$0 stop
if is_running; then
echo "Unable to stop, will not attempt to start"
exit 1
fi
$0 start
;;
status)
if is_running; then
echo "Running"
else
echo "Stopped"
exit 1
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit 0
`
const SystemdScript = `[Unit]
Description={{.Description}}
ConditionFileIsExecutable={{.Path|cmdEscape}}
{{range $i, $dep := .Dependencies}}
{{$dep}} {{end}}
[Service]
LimitNOFILE=65536
StartLimitInterval=5
StartLimitBurst=10
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
{{if .UserName}}User={{.UserName}}{{end}}
{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
{{if and .LogOutput .HasOutputFileSupport -}}
StandardOutput=file:/var/log/{{.Name}}.out
StandardError=file:/var/log/{{.Name}}.err
{{- end}}
Restart=always
RestartSec=120
[Install]
WantedBy=multi-user.target
`
func UpdateNps() {
destPath := downloadLatest("server")
//复制文件到对应目录
copyStaticFile(destPath, "nps")
fmt.Println("Update completed, please restart")
}
func UpdateNpc() {
destPath := downloadLatest("client")
//复制文件到对应目录
copyStaticFile(destPath, "npc")
fmt.Println("Update completed, please restart")
}
type release struct {
TagName string `json:"tag_name"`
}
func downloadLatest(bin string) string {
// get version
data, err := http.Get("https://api.github.com/repos/ehang-io/nps/releases/latest")
if err != nil {
log.Fatal(err.Error())
}
b, err := ioutil.ReadAll(data.Body)
if err != nil {
log.Fatal(err)
}
rl := new(release)
json.Unmarshal(b, &rl)
version := rl.TagName
fmt.Println("the latest version is", version)
filename := runtime.GOOS + "_" + runtime.GOARCH + "_" + bin + ".tar.gz"
// download latest package
downloadUrl := fmt.Sprintf("https://ehang.io/nps/releases/download/%s/%s", version, filename)
fmt.Println("download package from ", downloadUrl)
resp, err := http.Get(downloadUrl)
if err != nil {
log.Fatal(err.Error())
}
destPath, err := unpackit.Unpack(resp.Body, "")
if err != nil {
log.Fatal(err)
}
if bin == "server" {
destPath = strings.Replace(destPath, "/web", "", -1)
destPath = strings.Replace(destPath, `\web`, "", -1)
destPath = strings.Replace(destPath, "/views", "", -1)
destPath = strings.Replace(destPath, `\views`, "", -1)
} else {
destPath = strings.Replace(destPath, `\conf`, "", -1)
destPath = strings.Replace(destPath, "/conf", "", -1)
}
return destPath
}
func copyStaticFile(srcPath, bin string) string {
path := common.GetInstallPath()
if bin == "nps" {
//复制文件到对应目录
if err := CopyDir(filepath.Join(srcPath, "web", "views"), filepath.Join(path, "web", "views")); err != nil {
log.Fatalln(err)
}
chMod(filepath.Join(path, "web", "views"), 0766)
if err := CopyDir(filepath.Join(srcPath, "web", "static"), filepath.Join(path, "web", "static")); err != nil {
log.Fatalln(err)
}
chMod(filepath.Join(path, "web", "static"), 0766)
}
binPath, _ := filepath.Abs(os.Args[0])
if !common.IsWindows() {
if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin); err != nil {
if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin); err != nil {
log.Fatalln(err)
} else {
copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin+"-update")
chMod("/usr/local/bin/"+bin+"-update", 0755)
binPath = "/usr/local/bin/" + bin
}
} else {
copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin+"-update")
chMod("/usr/bin/"+bin+"-update", 0755)
binPath = "/usr/bin/" + bin
}
} else {
copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+"-update.exe"))
copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+".exe"))
}
chMod(binPath, 0755)
return binPath
}
func InstallNpc() {
path := common.GetInstallPath()
if !common.FileExists(path) {
err := os.Mkdir(path, 0755)
if err != nil {
log.Fatal(err)
}
}
copyStaticFile(common.GetAppPath(), "npc")
}
func InstallNps() string {
path := common.GetInstallPath()
if common.FileExists(path) {
MkidrDirAll(path, "web/static", "web/views")
} else {
MkidrDirAll(path, "conf", "web/static", "web/views")
// not copy config if the config file is exist
if err := CopyDir(filepath.Join(common.GetAppPath(), "conf"), filepath.Join(path, "conf")); err != nil {
log.Fatalln(err)
}
chMod(filepath.Join(path, "conf"), 0766)
}
binPath := copyStaticFile(common.GetAppPath(), "nps")
log.Println("install ok!")
log.Println("Static files and configuration files in the current directory will be useless")
log.Println("The new configuration file is located in", path, "you can edit them")
if !common.IsWindows() {
log.Println(`You can start with:
nps start|stop|restart|uninstall|update or nps-update update
anywhere!`)
} else {
log.Println(`You can copy executable files to any directory and start working with:
nps.exe start|stop|restart|uninstall|update or nps-update.exe update
now!`)
}
chMod(common.GetLogPath(), 0777)
return binPath
}
func MkidrDirAll(path string, v ...string) {
for _, item := range v {
if err := os.MkdirAll(filepath.Join(path, item), 0755); err != nil {
log.Fatalf("Failed to create directory %s error:%s", path, err.Error())
}
}
}
func CopyDir(srcPath string, destPath string) error {
//检测目录正确性
if srcInfo, err := os.Stat(srcPath); err != nil {
fmt.Println(err.Error())
return err
} else {
if !srcInfo.IsDir() {
e := errors.New("SrcPath is not the right directory!")
return e
}
}
if destInfo, err := os.Stat(destPath); err != nil {
return err
} else {
if !destInfo.IsDir() {
e := errors.New("DestInfo is not the right directory!")
return e
}
}
err := filepath.Walk(srcPath, func(path string, f os.FileInfo, err error) error {
if f == nil {
return err
}
if !f.IsDir() {
destNewPath := strings.Replace(path, srcPath, destPath, -1)
log.Println("copy file ::" + path + " to " + destNewPath)
copyFile(path, destNewPath)
if !common.IsWindows() {
chMod(destNewPath, 0766)
}
}
return nil
})
return err
}
//生成目录并拷贝文件
func copyFile(src, dest string) (w int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}
defer srcFile.Close()
//分割path目录
destSplitPathDirs := strings.Split(dest, string(filepath.Separator))
//检测时候存在目录
destSplitPath := ""
for index, dir := range destSplitPathDirs {
if index < len(destSplitPathDirs)-1 {
destSplitPath = destSplitPath + dir + string(filepath.Separator)
b, _ := pathExists(destSplitPath)
if b == false {
log.Println("mkdir:" + destSplitPath)
//创建目录
err := os.Mkdir(destSplitPath, os.ModePerm)
if err != nil {
log.Fatalln(err)
}
}
}
}
dstFile, err := os.Create(dest)
if err != nil {
return
}
defer dstFile.Close()
return io.Copy(dstFile, srcFile)
}
//检测文件夹路径时候存在
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func chMod(name string, mode os.FileMode) {
if !common.IsWindows() {
os.Chmod(name, mode)
}
}
================================================
FILE: lib/pmux/pconn.go
================================================
package pmux
import (
"net"
"time"
)
type PortConn struct {
Conn net.Conn
rs []byte
readMore bool
start int
}
func newPortConn(conn net.Conn, rs []byte, readMore bool) *PortConn {
return &PortConn{
Conn: conn,
rs: rs,
readMore: readMore,
}
}
func (pConn *PortConn) Read(b []byte) (n int, err error) {
if len(b) < len(pConn.rs)-pConn.start {
defer func() {
pConn.start = pConn.start + len(b)
}()
return copy(b, pConn.rs), nil
}
if pConn.start < len(pConn.rs) {
defer func() {
pConn.start = len(pConn.rs)
}()
n = copy(b, pConn.rs[pConn.start:])
if !pConn.readMore {
return
}
}
var n2 = 0
n2, err = pConn.Conn.Read(b[n:])
n = n + n2
return
}
func (pConn *PortConn) Write(b []byte) (n int, err error) {
return pConn.Conn.Write(b)
}
func (pConn *PortConn) Close() error {
return pConn.Conn.Close()
}
func (pConn *PortConn) LocalAddr() net.Addr {
return pConn.Conn.LocalAddr()
}
func (pConn *PortConn) RemoteAddr() net.Addr {
return pConn.Conn.RemoteAddr()
}
func (pConn *PortConn) SetDeadline(t time.Time) error {
return pConn.Conn.SetDeadline(t)
}
func (pConn *PortConn) SetReadDeadline(t time.Time) error {
return pConn.Conn.SetReadDeadline(t)
}
func (pConn *PortConn) SetWriteDeadline(t time.Time) error {
return pConn.Conn.SetWriteDeadline(t)
}
================================================
FILE: lib/pmux/plistener.go
================================================
package pmux
import (
"errors"
"net"
)
type PortListener struct {
net.Listener
connCh chan *PortConn
addr net.Addr
isClose bool
}
func NewPortListener(connCh chan *PortConn, addr net.Addr) *PortListener {
return &PortListener{
connCh: connCh,
addr: addr,
}
}
func (pListener *PortListener) Accept() (net.Conn, error) {
if pListener.isClose {
return nil, errors.New("the listener has closed")
}
conn := <-pListener.connCh
if conn != nil {
return conn, nil
}
return nil, errors.New("the listener has closed")
}
func (pListener *PortListener) Close() error {
//close
if pListener.isClose {
return errors.New("the listener has closed")
}
pListener.isClose = true
return nil
}
func (pListener *PortListener) Addr() net.Addr {
return pListener.addr
}
================================================
FILE: lib/pmux/pmux.go
================================================
// This module is used for port reuse
// Distinguish client, web manager , HTTP and HTTPS according to the difference of protocol
package pmux
import (
"bufio"
"bytes"
"io"
"net"
"os"
"strconv"
"strings"
"time"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego/logs"
"github.com/pkg/errors"
)
const (
HTTP_GET = 716984
HTTP_POST = 807983
HTTP_HEAD = 726965
HTTP_PUT = 808585
HTTP_DELETE = 686976
HTTP_CONNECT = 677978
HTTP_OPTIONS = 798084
HTTP_TRACE = 848265
CLIENT = 848384
ACCEPT_TIME_OUT = 10
)
type PortMux struct {
net.Listener
port int
isClose bool
managerHost string
clientConn chan *PortConn
httpConn chan *PortConn
httpsConn chan *PortConn
managerConn chan *PortConn
}
func NewPortMux(port int, managerHost string) *PortMux {
pMux := &PortMux{
managerHost: managerHost,
port: port,
clientConn: make(chan *PortConn),
httpConn: make(chan *PortConn),
httpsConn: make(chan *PortConn),
managerConn: make(chan *PortConn),
}
pMux.Start()
return pMux
}
func (pMux *PortMux) Start() error {
// Port multiplexing is based on TCP only
tcpAddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:"+strconv.Itoa(pMux.port))
if err != nil {
return err
}
pMux.Listener, err = net.ListenTCP("tcp", tcpAddr)
if err != nil {
logs.Error(err)
os.Exit(0)
}
go func() {
for {
conn, err := pMux.Listener.Accept()
if err != nil {
logs.Warn(err)
//close
pMux.Close()
}
go pMux.process(conn)
}
}()
return nil
}
func (pMux *PortMux) process(conn net.Conn) {
// Recognition according to different signs
// read 3 byte
buf := make([]byte, 3)
if n, err := io.ReadFull(conn, buf); err != nil || n != 3 {
return
}
var ch chan *PortConn
var rs []byte
var buffer bytes.Buffer
var readMore = false
switch common.BytesToNum(buf) {
case HTTP_CONNECT, HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_OPTIONS, HTTP_POST, HTTP_PUT, HTTP_TRACE: //http and manager
buffer.Reset()
r := bufio.NewReader(conn)
buffer.Write(buf)
for {
b, _, err := r.ReadLine()
if err != nil {
logs.Warn("read line error", err.Error())
conn.Close()
break
}
buffer.Write(b)
buffer.Write([]byte("\r\n"))
if strings.Index(string(b), "Host:") == 0 || strings.Index(string(b), "host:") == 0 {
// Remove host and space effects
str := strings.Replace(string(b), "Host:", "", -1)
str = strings.Replace(str, "host:", "", -1)
str = strings.TrimSpace(str)
// Determine whether it is the same as the manager domain name
if common.GetIpByAddr(str) == pMux.managerHost {
ch = pMux.managerConn
} else {
ch = pMux.httpConn
}
b, _ := r.Peek(r.Buffered())
buffer.Write(b)
rs = buffer.Bytes()
break
}
}
case CLIENT: // client connection
ch = pMux.clientConn
default: // https
readMore = true
ch = pMux.httpsConn
}
if len(rs) == 0 {
rs = buf
}
timer := time.NewTimer(ACCEPT_TIME_OUT)
select {
case <-timer.C:
case ch <- newPortConn(conn, rs, readMore):
}
}
func (pMux *PortMux) Close() error {
if pMux.isClose {
return errors.New("the port pmux has closed")
}
pMux.isClose = true
close(pMux.clientConn)
close(pMux.httpsConn)
close(pMux.httpConn)
close(pMux.managerConn)
return pMux.Listener.Close()
}
func (pMux *PortMux) GetClientListener() net.Listener {
return NewPortListener(pMux.clientConn, pMux.Listener.Addr())
}
func (pMux *PortMux) GetHttpListener() net.Listener {
return NewPortListener(pMux.httpConn, pMux.Listener.Addr())
}
func (pMux *PortMux) GetHttpsListener() net.Listener {
return NewPortListener(pMux.httpsConn, pMux.Listener.Addr())
}
func (pMux *PortMux) GetManagerListener() net.Listener {
return NewPortListener(pMux.managerConn, pMux.Listener.Addr())
}
================================================
FILE: lib/pmux/pmux_test.go
================================================
package pmux
import (
"testing"
"time"
"github.com/astaxie/beego/logs"
)
func TestPortMux_Close(t *testing.T) {
logs.Reset()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
pMux := NewPortMux(8888, "Ds")
go func() {
if pMux.Start() != nil {
logs.Warn("Error")
}
}()
time.Sleep(time.Second * 3)
go func() {
l := pMux.GetHttpListener()
conn, err := l.Accept()
logs.Warn(conn, err)
}()
go func() {
l := pMux.GetHttpListener()
conn, err := l.Accept()
logs.Warn(conn, err)
}()
go func() {
l := pMux.GetHttpListener()
conn, err := l.Accept()
logs.Warn(conn, err)
}()
l := pMux.GetHttpListener()
conn, err := l.Accept()
logs.Warn(conn, err)
}
================================================
FILE: lib/rate/conn.go
================================================
package rate
import (
"io"
)
type rateConn struct {
conn io.ReadWriteCloser
rate *Rate
}
func NewRateConn(conn io.ReadWriteCloser, rate *Rate) io.ReadWriteCloser {
return &rateConn{
conn: conn,
rate: rate,
}
}
func (s *rateConn) Read(b []byte) (n int, err error) {
n, err = s.conn.Read(b)
if s.rate != nil {
s.rate.Get(int64(n))
}
return
}
func (s *rateConn) Write(b []byte) (n int, err error) {
n, err = s.conn.Write(b)
if s.rate != nil {
s.rate.Get(int64(n))
}
return
}
func (s *rateConn) Close() error {
return s.conn.Close()
}
================================================
FILE: lib/rate/rate.go
================================================
package rate
import (
"sync/atomic"
"time"
)
type Rate struct {
bucketSize int64
bucketSurplusSize int64
bucketAddSize int64
stopChan chan bool
NowRate int64
}
func NewRate(addSize int64) *Rate {
return &Rate{
bucketSize: addSize * 2,
bucketSurplusSize: 0,
bucketAddSize: addSize,
stopChan: make(chan bool),
}
}
func (s *Rate) Start() {
go s.session()
}
func (s *Rate) add(size int64) {
if res := s.bucketSize - s.bucketSurplusSize; res < s.bucketAddSize {
atomic.AddInt64(&s.bucketSurplusSize, res)
return
}
atomic.AddInt64(&s.bucketSurplusSize, size)
}
//回桶
func (s *Rate) ReturnBucket(size int64) {
s.add(size)
}
//停止
func (s *Rate) Stop() {
s.stopChan <- true
}
func (s *Rate) Get(size int64) {
if s.bucketSurplusSize >= size {
atomic.AddInt64(&s.bucketSurplusSize, -size)
return
}
ticker := time.NewTicker(time.Millisecond * 100)
for {
select {
case <-ticker.C:
if s.bucketSurplusSize >= size {
atomic.AddInt64(&s.bucketSurplusSize, -size)
ticker.Stop()
return
}
}
}
}
func (s *Rate) session() {
ticker := time.NewTicker(time.Second * 1)
for {
select {
case <-ticker.C:
if rs := s.bucketAddSize - s.bucketSurplusSize; rs > 0 {
s.NowRate = rs
} else {
s.NowRate = s.bucketSize - s.bucketSurplusSize
}
s.add(s.bucketAddSize)
case <-s.stopChan:
ticker.Stop()
return
}
}
}
================================================
FILE: lib/sheap/heap.go
================================================
package sheap
type IntHeap []int64
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
*h = append(*h, x.(int64))
}
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
================================================
FILE: lib/version/version.go
================================================
package version
const VERSION = "0.26.10"
// Compulsory minimum version, Minimum downward compatibility to this version
func GetVersion() string {
return "0.26.0"
}
================================================
FILE: server/connection/connection.go
================================================
package connection
import (
"net"
"os"
"strconv"
"ehang.io/nps/lib/pmux"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
)
var pMux *pmux.PortMux
var bridgePort string
var httpsPort string
var httpPort string
var webPort string
func InitConnectionService() {
bridgePort = beego.AppConfig.String("bridge_port")
httpsPort = beego.AppConfig.String("https_proxy_port")
httpPort = beego.AppConfig.String("http_proxy_port")
webPort = beego.AppConfig.String("web_port")
if httpPort == bridgePort || httpsPort == bridgePort || webPort == bridgePort {
port, err := strconv.Atoi(bridgePort)
if err != nil {
logs.Error(err)
os.Exit(0)
}
pMux = pmux.NewPortMux(port, beego.AppConfig.String("web_host"))
}
}
func GetBridgeListener(tp string) (net.Listener, error) {
logs.Info("server start, the bridge type is %s, the bridge port is %s", tp, bridgePort)
var p int
var err error
if p, err = strconv.Atoi(bridgePort); err != nil {
return nil, err
}
if pMux != nil {
return pMux.GetClientListener(), nil
}
return net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(beego.AppConfig.String("bridge_ip")), p, ""})
}
func GetHttpListener() (net.Listener, error) {
if pMux != nil && httpPort == bridgePort {
logs.Info("start http listener, port is", bridgePort)
return pMux.GetHttpListener(), nil
}
logs.Info("start http listener, port is", httpPort)
return getTcpListener(beego.AppConfig.String("http_proxy_ip"), httpPort)
}
func GetHttpsListener() (net.Listener, error) {
if pMux != nil && httpsPort == bridgePort {
logs.Info("start https listener, port is", bridgePort)
return pMux.GetHttpsListener(), nil
}
logs.Info("start https listener, port is", httpsPort)
return getTcpListener(beego.AppConfig.String("http_proxy_ip"), httpsPort)
}
func GetWebManagerListener() (net.Listener, error) {
if pMux != nil && webPort == bridgePort {
logs.Info("Web management start, access port is", bridgePort)
return pMux.GetManagerListener(), nil
}
logs.Info("web management start, access port is", webPort)
return getTcpListener(beego.AppConfig.String("web_ip"), webPort)
}
func getTcpListener(ip, p string) (net.Listener, error) {
port, err := strconv.Atoi(p)
if err != nil {
logs.Error(err)
os.Exit(0)
}
if ip == "" {
ip = "0.0.0.0"
}
return net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(ip), port, ""})
}
================================================
FILE: server/proxy/base.go
================================================
package proxy
import (
"errors"
"net"
"net/http"
"sync"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego/logs"
)
type Service interface {
Start() error
Close() error
}
type NetBridge interface {
SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error)
}
//BaseServer struct
type BaseServer struct {
id int
bridge NetBridge
task *file.Tunnel
errorContent []byte
sync.Mutex
}
func NewBaseServer(bridge *bridge.Bridge, task *file.Tunnel) *BaseServer {
return &BaseServer{
bridge: bridge,
task: task,
errorContent: nil,
Mutex: sync.Mutex{},
}
}
//add the flow
func (s *BaseServer) FlowAdd(in, out int64) {
s.Lock()
defer s.Unlock()
s.task.Flow.ExportFlow += out
s.task.Flow.InletFlow += in
}
//change the flow
func (s *BaseServer) FlowAddHost(host *file.Host, in, out int64) {
s.Lock()
defer s.Unlock()
host.Flow.ExportFlow += out
host.Flow.InletFlow += in
}
//write fail bytes to the connection
func (s *BaseServer) writeConnFail(c net.Conn) {
c.Write([]byte(common.ConnectionFailBytes))
c.Write(s.errorContent)
}
//auth check
func (s *BaseServer) auth(r *http.Request, c *conn.Conn, u, p string) error {
if u != "" && p != "" && !common.CheckAuth(r, u, p) {
c.Write([]byte(common.UnauthorizedBytes))
c.Close()
return errors.New("401 Unauthorized")
}
return nil
}
//check flow limit of the client ,and decrease the allow num of client
func (s *BaseServer) CheckFlowAndConnNum(client *file.Client) error {
if client.Flow.FlowLimit > 0 && (client.Flow.FlowLimit<<20) < (client.Flow.ExportFlow+client.Flow.InletFlow) {
return errors.New("Traffic exceeded")
}
if !client.GetConn() {
return errors.New("Connections exceed the current client limit")
}
return nil
}
//create a new connection and start bytes copying
func (s *BaseServer) DealClient(c *conn.Conn, client *file.Client, addr string, rb []byte, tp string, f func(), flow *file.Flow, localProxy bool) error {
link := conn.NewLink(tp, addr, client.Cnf.Crypt, client.Cnf.Compress, c.Conn.RemoteAddr().String(), localProxy)
if target, err := s.bridge.SendLinkInfo(client.Id, link, s.task); err != nil {
logs.Warn("get connection from client id %d error %s", client.Id, err.Error())
c.Close()
return err
} else {
if f != nil {
f()
}
conn.CopyWaitGroup(target, c.Conn, link.Crypt, link.Compress, client.Rate, flow, true, rb)
}
return nil
}
================================================
FILE: server/proxy/http.go
================================================
package proxy
import (
"bufio"
"crypto/tls"
"io"
"net"
"net/http"
"net/http/httputil"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/cache"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"ehang.io/nps/server/connection"
"github.com/astaxie/beego/logs"
)
type httpServer struct {
BaseServer
httpPort int
httpsPort int
httpServer *http.Server
httpsServer *http.Server
httpsListener net.Listener
useCache bool
addOrigin bool
cache *cache.Cache
cacheLen int
}
func NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, useCache bool, cacheLen int, addOrigin bool) *httpServer {
httpServer := &httpServer{
BaseServer: BaseServer{
task: c,
bridge: bridge,
Mutex: sync.Mutex{},
},
httpPort: httpPort,
httpsPort: httpsPort,
useCache: useCache,
cacheLen: cacheLen,
addOrigin: addOrigin,
}
if useCache {
httpServer.cache = cache.New(cacheLen)
}
return httpServer
}
func (s *httpServer) Start() error {
var err error
if s.errorContent, err = common.ReadAllFromFile(filepath.Join(common.GetRunPath(), "web", "static", "page", "error.html")); err != nil {
s.errorContent = []byte("nps 404")
}
if s.httpPort > 0 {
s.httpServer = s.NewServer(s.httpPort, "http")
go func() {
l, err := connection.GetHttpListener()
if err != nil {
logs.Error(err)
os.Exit(0)
}
err = s.httpServer.Serve(l)
if err != nil {
logs.Error(err)
os.Exit(0)
}
}()
}
if s.httpsPort > 0 {
s.httpsServer = s.NewServer(s.httpsPort, "https")
go func() {
s.httpsListener, err = connection.GetHttpsListener()
if err != nil {
logs.Error(err)
os.Exit(0)
}
logs.Error(NewHttpsServer(s.httpsListener, s.bridge, s.useCache, s.cacheLen).Start())
}()
}
return nil
}
func (s *httpServer) Close() error {
if s.httpsListener != nil {
s.httpsListener.Close()
}
if s.httpsServer != nil {
s.httpsServer.Close()
}
if s.httpServer != nil {
s.httpServer.Close()
}
return nil
}
func (s *httpServer) handleTunneling(w http.ResponseWriter, r *http.Request) {
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
return
}
c, _, err := hijacker.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
}
s.handleHttp(conn.NewConn(c), r)
}
func (s *httpServer) handleHttp(c *conn.Conn, r *http.Request) {
var (
host *file.Host
target net.Conn
err error
connClient io.ReadWriteCloser
scheme = r.URL.Scheme
lk *conn.Link
targetAddr string
lenConn *conn.LenConn
isReset bool
wg sync.WaitGroup
)
defer func() {
if connClient != nil {
connClient.Close()
} else {
s.writeConnFail(c.Conn)
}
c.Close()
}()
reset:
if isReset {
host.Client.AddConn()
}
if host, err = file.GetDb().GetInfoByHost(r.Host, r); err != nil {
logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI)
return
}
if err := s.CheckFlowAndConnNum(host.Client); err != nil {
logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error())
return
}
if !isReset {
defer host.Client.AddConn()
}
if err = s.auth(r, c, host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
logs.Warn("auth error", err, r.RemoteAddr)
return
}
if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
logs.Warn(err.Error())
return
}
lk = conn.NewLink("http", targetAddr, host.Client.Cnf.Crypt, host.Client.Cnf.Compress, r.RemoteAddr, host.Target.LocalProxy)
if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, nil); err != nil {
logs.Notice("connect to target %s error %s", lk.Host, err)
return
}
connClient = conn.GetConn(target, lk.Crypt, lk.Compress, host.Client.Rate, true)
//read from inc-client
go func() {
wg.Add(1)
isReset = false
defer connClient.Close()
defer func() {
wg.Done()
if !isReset {
c.Close()
}
}()
for {
if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil || resp == nil || r == nil {
// if there got broken pipe, http.ReadResponse will get a nil
return
} else {
//if the cache is start and the response is in the extension,store the response to the cache list
if s.useCache && r.URL != nil && strings.Contains(r.URL.Path, ".") {
b, err := httputil.DumpResponse(resp, true)
if err != nil {
return
}
c.Write(b)
host.Flow.Add(0, int64(len(b)))
s.cache.Add(filepath.Join(host.Host, r.URL.Path), b)
} else {
lenConn := conn.NewLenConn(c)
if err := resp.Write(lenConn); err != nil {
logs.Error(err)
return
}
host.Flow.Add(0, int64(lenConn.Len))
}
}
}
}()
for {
//if the cache start and the request is in the cache list, return the cache
if s.useCache {
if v, ok := s.cache.Get(filepath.Join(host.Host, r.URL.Path)); ok {
n, err := c.Write(v.([]byte))
if err != nil {
break
}
logs.Trace("%s request, method %s, host %s, url %s, remote address %s, return cache", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String())
host.Flow.Add(0, int64(n))
//if return cache and does not create a new conn with client and Connection is not set or close, close the connection.
if strings.ToLower(r.Header.Get("Connection")) == "close" || strings.ToLower(r.Header.Get("Connection")) == "" {
break
}
goto readReq
}
}
//change the host and header and set proxy setting
common.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String(), s.addOrigin)
logs.Trace("%s request, method %s, host %s, url %s, remote address %s, target %s", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String(), lk.Host)
//write
lenConn = conn.NewLenConn(connClient)
if err := r.Write(lenConn); err != nil {
logs.Error(err)
break
}
host.Flow.Add(int64(lenConn.Len), 0)
readReq:
//read req from connection
if r, err = http.ReadRequest(bufio.NewReader(c)); err != nil {
break
}
r.URL.Scheme = scheme
//What happened ,Why one character less???
r.Method = resetReqMethod(r.Method)
if hostTmp, err := file.GetDb().GetInfoByHost(r.Host, r); err != nil {
logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI)
break
} else if host != hostTmp {
host = hostTmp
isReset = true
connClient.Close()
goto reset
}
}
wg.Wait()
}
func resetReqMethod(method string) string {
if method == "ET" {
return "GET"
}
if method == "OST" {
return "POST"
}
return method
}
func (s *httpServer) NewServer(port int, scheme string) *http.Server {
return &http.Server{
Addr: ":" + strconv.Itoa(port),
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.URL.Scheme = scheme
s.handleTunneling(w, r)
}),
// Disable HTTP/2.
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}
}
================================================
FILE: server/proxy/https.go
================================================
package proxy
import (
"net"
"net/http"
"net/url"
"sync"
"ehang.io/nps/lib/cache"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/pkg/errors"
)
type HttpsServer struct {
httpServer
listener net.Listener
httpsListenerMap sync.Map
}
func NewHttpsServer(l net.Listener, bridge NetBridge, useCache bool, cacheLen int) *HttpsServer {
https := &HttpsServer{listener: l}
https.bridge = bridge
https.useCache = useCache
if useCache {
https.cache = cache.New(cacheLen)
}
return https
}
//start https server
func (https *HttpsServer) Start() error {
if b, err := beego.AppConfig.Bool("https_just_proxy"); err == nil && b {
conn.Accept(https.listener, func(c net.Conn) {
https.handleHttps(c)
})
} else {
//start the default listener
certFile := beego.AppConfig.String("https_default_cert_file")
keyFile := beego.AppConfig.String("https_default_key_file")
if common.FileExists(certFile) && common.FileExists(keyFile) {
l := NewHttpsListener(https.listener)
https.NewHttps(l, certFile, keyFile)
https.httpsListenerMap.Store("default", l)
}
conn.Accept(https.listener, func(c net.Conn) {
serverName, rb := GetServerNameFromClientHello(c)
//if the clientHello does not contains sni ,use the default ssl certificate
if serverName == "" {
serverName = "default"
}
var l *HttpsListener
if v, ok := https.httpsListenerMap.Load(serverName); ok {
l = v.(*HttpsListener)
} else {
r := buildHttpsRequest(serverName)
if host, err := file.GetDb().GetInfoByHost(serverName, r); err != nil {
c.Close()
logs.Notice("the url %s can't be parsed!,remote addr %s", serverName, c.RemoteAddr().String())
return
} else {
if !common.FileExists(host.CertFilePath) || !common.FileExists(host.KeyFilePath) {
//if the host cert file or key file is not set ,use the default file
if v, ok := https.httpsListenerMap.Load("default"); ok {
l = v.(*HttpsListener)
} else {
c.Close()
logs.Error("the key %s cert %s file is not exist", host.KeyFilePath, host.CertFilePath)
return
}
} else {
l = NewHttpsListener(https.listener)
https.NewHttps(l, host.CertFilePath, host.KeyFilePath)
https.httpsListenerMap.Store(serverName, l)
}
}
}
acceptConn := conn.NewConn(c)
acceptConn.Rb = rb
l.acceptConn <- acceptConn
})
}
return nil
}
// close
func (https *HttpsServer) Close() error {
return https.listener.Close()
}
// new https server by cert and key file
func (https *HttpsServer) NewHttps(l net.Listener, certFile string, keyFile string) {
go func() {
logs.Error(https.NewServer(0, "https").ServeTLS(l, certFile, keyFile))
}()
}
//handle the https which is just proxy to other client
func (https *HttpsServer) handleHttps(c net.Conn) {
hostName, rb := GetServerNameFromClientHello(c)
var targetAddr string
r := buildHttpsRequest(hostName)
var host *file.Host
var err error
if host, err = file.GetDb().GetInfoByHost(hostName, r); err != nil {
c.Close()
logs.Notice("the url %s can't be parsed!", hostName)
return
}
if err := https.CheckFlowAndConnNum(host.Client); err != nil {
logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error())
c.Close()
return
}
defer host.Client.AddConn()
if err = https.auth(r, conn.NewConn(c), host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
logs.Warn("auth error", err, r.RemoteAddr)
return
}
if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
logs.Warn(err.Error())
}
logs.Trace("new https connection,clientId %d,host %s,remote address %s", host.Client.Id, r.Host, c.RemoteAddr().String())
https.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow, host.Target.LocalProxy)
}
type HttpsListener struct {
acceptConn chan *conn.Conn
parentListener net.Listener
}
// https listener
func NewHttpsListener(l net.Listener) *HttpsListener {
return &HttpsListener{parentListener: l, acceptConn: make(chan *conn.Conn)}
}
// accept
func (httpsListener *HttpsListener) Accept() (net.Conn, error) {
httpsConn := <-httpsListener.acceptConn
if httpsConn == nil {
return nil, errors.New("get connection error")
}
return httpsConn, nil
}
// close
func (httpsListener *HttpsListener) Close() error {
return nil
}
// addr
func (httpsListener *HttpsListener) Addr() net.Addr {
return httpsListener.parentListener.Addr()
}
// get server name from connection by read client hello bytes
func GetServerNameFromClientHello(c net.Conn) (string, []byte) {
buf := make([]byte, 4096)
data := make([]byte, 4096)
n, err := c.Read(buf)
if err != nil {
return "", nil
}
if n < 42 {
return "", nil
}
copy(data, buf[:n])
clientHello := new(crypt.ClientHelloMsg)
clientHello.Unmarshal(data[5:n])
return clientHello.GetServerName(), buf[:n]
}
// build https request
func buildHttpsRequest(hostName string) *http.Request {
r := new(http.Request)
r.RequestURI = "/"
r.URL = new(url.URL)
r.URL.Scheme = "https"
r.Host = hostName
return r
}
================================================
FILE: server/proxy/p2p.go
================================================
package proxy
import (
"net"
"strings"
"time"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego/logs"
)
type P2PServer struct {
BaseServer
p2pPort int
p2p map[string]*p2p
listener *net.UDPConn
}
type p2p struct {
visitorAddr *net.UDPAddr
providerAddr *net.UDPAddr
}
func NewP2PServer(p2pPort int) *P2PServer {
return &P2PServer{
p2pPort: p2pPort,
p2p: make(map[string]*p2p),
}
}
func (s *P2PServer) Start() error {
logs.Info("start p2p server port", s.p2pPort)
var err error
s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), s.p2pPort, ""})
if err != nil {
return err
}
for {
buf := common.BufPoolUdp.Get().([]byte)
n, addr, err := s.listener.ReadFromUDP(buf)
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
break
}
continue
}
go s.handleP2P(addr, string(buf[:n]))
}
return nil
}
func (s *P2PServer) handleP2P(addr *net.UDPAddr, str string) {
var (
v *p2p
ok bool
)
arr := strings.Split(str, common.CONN_DATA_SEQ)
if len(arr) < 2 {
return
}
if v, ok = s.p2p[arr[0]]; !ok {
v = new(p2p)
s.p2p[arr[0]] = v
}
logs.Trace("new p2p connection ,role %s , password %s ,local address %s", arr[1], arr[0], addr.String())
if arr[1] == common.WORK_P2P_VISITOR {
v.visitorAddr = addr
for i := 20; i > 0; i-- {
if v.providerAddr != nil {
s.listener.WriteTo([]byte(v.providerAddr.String()), v.visitorAddr)
s.listener.WriteTo([]byte(v.visitorAddr.String()), v.providerAddr)
break
}
time.Sleep(time.Second)
}
delete(s.p2p, arr[0])
} else {
v.providerAddr = addr
}
}
================================================
FILE: server/proxy/socks5.go
================================================
package proxy
import (
"encoding/binary"
"errors"
"io"
"net"
"strconv"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego/logs"
)
const (
ipV4 = 1
domainName = 3
ipV6 = 4
connectMethod = 1
bindMethod = 2
associateMethod = 3
// The maximum packet size of any udp Associate packet, based on ethernet's max size,
// minus the IP and UDP headers. IPv4 has a 20 byte header, UDP adds an
// additional 4 bytes. This is a total overhead of 24 bytes. Ethernet's
// max packet size is 1500 bytes, 1500 - 24 = 1476.
maxUDPPacketSize = 1476
)
const (
succeeded uint8 = iota
serverFailure
notAllowed
networkUnreachable
hostUnreachable
connectionRefused
ttlExpired
commandNotSupported
addrTypeNotSupported
)
const (
UserPassAuth = uint8(2)
userAuthVersion = uint8(1)
authSuccess = uint8(0)
authFailure = uint8(1)
)
type Sock5ModeServer struct {
BaseServer
listener net.Listener
}
//req
func (s *Sock5ModeServer) handleRequest(c net.Conn) {
/*
The SOCKS request is formed as follows:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
*/
header := make([]byte, 3)
_, err := io.ReadFull(c, header)
if err != nil {
logs.Warn("illegal request", err)
c.Close()
return
}
switch header[1] {
case connectMethod:
s.handleConnect(c)
case bindMethod:
s.handleBind(c)
case associateMethod:
s.handleUDP(c)
default:
s.sendReply(c, commandNotSupported)
c.Close()
}
}
//reply
func (s *Sock5ModeServer) sendReply(c net.Conn, rep uint8) {
reply := []byte{
5,
rep,
0,
1,
}
localAddr := c.LocalAddr().String()
localHost, localPort, _ := net.SplitHostPort(localAddr)
ipBytes := net.ParseIP(localHost).To4()
nPort, _ := strconv.Atoi(localPort)
reply = append(reply, ipBytes...)
portBytes := make([]byte, 2)
binary.BigEndian.PutUint16(portBytes, uint16(nPort))
reply = append(reply, portBytes...)
c.Write(reply)
}
//do conn
func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) {
addrType := make([]byte, 1)
c.Read(addrType)
var host string
switch addrType[0] {
case ipV4:
ipv4 := make(net.IP, net.IPv4len)
c.Read(ipv4)
host = ipv4.String()
case ipV6:
ipv6 := make(net.IP, net.IPv6len)
c.Read(ipv6)
host = ipv6.String()
case domainName:
var domainLen uint8
binary.Read(c, binary.BigEndian, &domainLen)
domain := make([]byte, domainLen)
c.Read(domain)
host = string(domain)
default:
s.sendReply(c, addrTypeNotSupported)
return
}
var port uint16
binary.Read(c, binary.BigEndian, &port)
// connect to host
addr := net.JoinHostPort(host, strconv.Itoa(int(port)))
var ltype string
if command == associateMethod {
ltype = common.CONN_UDP
} else {
ltype = common.CONN_TCP
}
s.DealClient(conn.NewConn(c), s.task.Client, addr, nil, ltype, func() {
s.sendReply(c, succeeded)
}, s.task.Flow, s.task.Target.LocalProxy)
return
}
//conn
func (s *Sock5ModeServer) handleConnect(c net.Conn) {
s.doConnect(c, connectMethod)
}
// passive mode
func (s *Sock5ModeServer) handleBind(c net.Conn) {
}
func (s *Sock5ModeServer) sendUdpReply(writeConn net.Conn, c net.Conn, rep uint8, serverIp string) {
reply := []byte{
5,
rep,
0,
1,
}
localHost, localPort, _ := net.SplitHostPort(c.LocalAddr().String())
localHost = serverIp
ipBytes := net.ParseIP(localHost).To4()
nPort, _ := strconv.Atoi(localPort)
reply = append(reply, ipBytes...)
portBytes := make([]byte, 2)
binary.BigEndian.PutUint16(portBytes, uint16(nPort))
reply = append(reply, portBytes...)
writeConn.Write(reply)
}
func (s *Sock5ModeServer) handleUDP(c net.Conn) {
defer c.Close()
addrType := make([]byte, 1)
c.Read(addrType)
var host string
switch addrType[0] {
case ipV4:
ipv4 := make(net.IP, net.IPv4len)
c.Read(ipv4)
host = ipv4.String()
case ipV6:
ipv6 := make(net.IP, net.IPv6len)
c.Read(ipv6)
host = ipv6.String()
case domainName:
var domainLen uint8
binary.Read(c, binary.BigEndian, &domainLen)
domain := make([]byte, domainLen)
c.Read(domain)
host = string(domain)
default:
s.sendReply(c, addrTypeNotSupported)
return
}
//读取端口
var port uint16
binary.Read(c, binary.BigEndian, &port)
logs.Warn(host, string(port))
replyAddr, err := net.ResolveUDPAddr("udp", s.task.ServerIp+":0")
if err != nil {
logs.Error("build local reply addr error", err)
return
}
reply, err := net.ListenUDP("udp", replyAddr)
if err != nil {
s.sendReply(c, addrTypeNotSupported)
logs.Error("listen local reply udp port error")
return
}
// reply the local addr
s.sendUdpReply(c, reply, succeeded, common.GetServerIpByClientIp(c.RemoteAddr().(*net.TCPAddr).IP))
defer reply.Close()
// new a tunnel to client
link := conn.NewLink("udp5", "", s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.RemoteAddr().String(), false)
target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task)
if err != nil {
logs.Warn("get connection from client id %d error %s", s.task.Client.Id, err.Error())
return
}
var clientAddr net.Addr
// copy buffer
go func() {
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
defer c.Close()
for {
n, laddr, err := reply.ReadFrom(b)
if err != nil {
logs.Error("read data from %s err %s", reply.LocalAddr().String(), err.Error())
return
}
if clientAddr == nil {
clientAddr = laddr
}
if _, err := target.Write(b[:n]); err != nil {
logs.Error("write data to client error", err.Error())
return
}
}
}()
go func() {
var l int32
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
defer c.Close()
for {
if err := binary.Read(target, binary.LittleEndian, &l); err != nil || l >= common.PoolSizeUdp || l <= 0 {
logs.Warn("read len bytes error", err.Error())
return
}
binary.Read(target, binary.LittleEndian, b[:l])
if err != nil {
logs.Warn("read data form client error", err.Error())
return
}
if _, err := reply.WriteTo(b[:l], clientAddr); err != nil {
logs.Warn("write data to user ", err.Error())
return
}
}
}()
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
defer target.Close()
for {
_, err := c.Read(b)
if err != nil {
c.Close()
return
}
}
}
//new conn
func (s *Sock5ModeServer) handleConn(c net.Conn) {
buf := make([]byte, 2)
if _, err := io.ReadFull(c, buf); err != nil {
logs.Warn("negotiation err", err)
c.Close()
return
}
if version := buf[0]; version != 5 {
logs.Warn("only support socks5, request from: ", c.RemoteAddr())
c.Close()
return
}
nMethods := buf[1]
methods := make([]byte, nMethods)
if len, err := c.Read(methods); len != int(nMethods) || err != nil {
logs.Warn("wrong method")
c.Close()
return
}
if (s.task.Client.Cnf.U != "" && s.task.Client.Cnf.P != "") || (s.task.MultiAccount != nil && len(s.task.MultiAccount.AccountMap) > 0) {
buf[1] = UserPassAuth
c.Write(buf)
if err := s.Auth(c); err != nil {
c.Close()
logs.Warn("Validation failed:", err)
return
}
} else {
buf[1] = 0
c.Write(buf)
}
s.handleRequest(c)
}
//socks5 auth
func (s *Sock5ModeServer) Auth(c net.Conn) error {
header := []byte{0, 0}
if _, err := io.ReadAtLeast(c, header, 2); err != nil {
return err
}
if header[0] != userAuthVersion {
return errors.New("验证方式不被支持")
}
userLen := int(header[1])
user := make([]byte, userLen)
if _, err := io.ReadAtLeast(c, user, userLen); err != nil {
return err
}
if _, err := c.Read(header[:1]); err != nil {
return errors.New("密码长度获取错误")
}
passLen := int(header[0])
pass := make([]byte, passLen)
if _, err := io.ReadAtLeast(c, pass, passLen); err != nil {
return err
}
var U, P string
if s.task.MultiAccount != nil {
// enable multi user auth
U = string(user)
var ok bool
P, ok = s.task.MultiAccount.AccountMap[U]
if !ok {
return errors.New("验证不通过")
}
} else {
U = s.task.Client.Cnf.U
P = s.task.Client.Cnf.P
}
if string(user) == U && string(pass) == P {
if _, err := c.Write([]byte{userAuthVersion, authSuccess}); err != nil {
return err
}
return nil
} else {
if _, err := c.Write([]byte{userAuthVersion, authFailure}); err != nil {
return err
}
return errors.New("验证不通过")
}
}
//start
func (s *Sock5ModeServer) Start() error {
return conn.NewTcpListenerAndProcess(s.task.ServerIp+":"+strconv.Itoa(s.task.Port), func(c net.Conn) {
if err := s.CheckFlowAndConnNum(s.task.Client); err != nil {
logs.Warn("client id %d, task id %d, error %s, when socks5 connection", s.task.Client.Id, s.task.Id, err.Error())
c.Close()
return
}
logs.Trace("New socks5 connection,client %d,remote address %s", s.task.Client.Id, c.RemoteAddr())
s.handleConn(c)
s.task.Client.AddConn()
}, &s.listener)
}
//new
func NewSock5ModeServer(bridge NetBridge, task *file.Tunnel) *Sock5ModeServer {
s := new(Sock5ModeServer)
s.bridge = bridge
s.task = task
return s
}
//close
func (s *Sock5ModeServer) Close() error {
return s.listener.Close()
}
================================================
FILE: server/proxy/tcp.go
================================================
package proxy
import (
"errors"
"net"
"net/http"
"path/filepath"
"strconv"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"ehang.io/nps/server/connection"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
)
type TunnelModeServer struct {
BaseServer
process process
listener net.Listener
}
//tcp|http|host
func NewTunnelModeServer(process process, bridge NetBridge, task *file.Tunnel) *TunnelModeServer {
s := new(TunnelModeServer)
s.bridge = bridge
s.process = process
s.task = task
return s
}
//开始
func (s *TunnelModeServer) Start() error {
return conn.NewTcpListenerAndProcess(s.task.ServerIp+":"+strconv.Itoa(s.task.Port), func(c net.Conn) {
if err := s.CheckFlowAndConnNum(s.task.Client); err != nil {
logs.Warn("client id %d, task id %d,error %s, when tcp connection", s.task.Client.Id, s.task.Id, err.Error())
c.Close()
return
}
logs.Trace("new tcp connection,local port %d,client %d,remote address %s", s.task.Port, s.task.Client.Id, c.RemoteAddr())
s.process(conn.NewConn(c), s)
s.task.Client.AddConn()
}, &s.listener)
}
//close
func (s *TunnelModeServer) Close() error {
return s.listener.Close()
}
//web管理方式
type WebServer struct {
BaseServer
}
//开始
func (s *WebServer) Start() error {
p, _ := beego.AppConfig.Int("web_port")
if p == 0 {
stop := make(chan struct{})
<-stop
}
beego.BConfig.WebConfig.Session.SessionOn = true
beego.SetStaticPath(beego.AppConfig.String("web_base_url")+"/static", filepath.Join(common.GetRunPath(), "web", "static"))
beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views"))
err := errors.New("Web management startup failure ")
var l net.Listener
if l, err = connection.GetWebManagerListener(); err == nil {
beego.InitBeforeHTTPRun()
if beego.AppConfig.String("web_open_ssl") == "true" {
keyPath := beego.AppConfig.String("web_key_file")
certPath := beego.AppConfig.String("web_cert_file")
err = http.ServeTLS(l, beego.BeeApp.Handlers, certPath, keyPath)
} else {
err = http.Serve(l, beego.BeeApp.Handlers)
}
} else {
logs.Error(err)
}
return err
}
func (s *WebServer) Close() error {
return nil
}
//new
func NewWebServer(bridge *bridge.Bridge) *WebServer {
s := new(WebServer)
s.bridge = bridge
return s
}
type process func(c *conn.Conn, s *TunnelModeServer) error
//tcp proxy
func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {
targetAddr, err := s.task.Target.GetRandomTarget()
if err != nil {
c.Close()
logs.Warn("tcp port %d ,client id %d,task id %d connect error %s", s.task.Port, s.task.Client.Id, s.task.Id, err.Error())
return err
}
return s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy)
}
//http proxy
func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error {
_, addr, rb, err, r := c.GetHost()
if err != nil {
c.Close()
logs.Info(err)
return err
}
if r.Method == "CONNECT" {
c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
rb = nil
}
if err := s.auth(r, c, s.task.Client.Cnf.U, s.task.Client.Cnf.P); err != nil {
return err
}
return s.DealClient(c, s.task.Client, addr, rb, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy)
}
================================================
FILE: server/proxy/transport.go
================================================
// +build !windows
package proxy
import (
"net"
"strconv"
"syscall"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
)
func HandleTrans(c *conn.Conn, s *TunnelModeServer) error {
if addr, err := getAddress(c.Conn); err != nil {
return err
} else {
return s.DealClient(c, s.task.Client, addr, nil, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy)
}
}
const SO_ORIGINAL_DST = 80
func getAddress(conn net.Conn) (string, error) {
sysrawconn, f := conn.(syscall.Conn)
if !f {
return "", nil
}
rawConn, err := sysrawconn.SyscallConn()
if err != nil {
return "", nil
}
var ip string
var port uint16
err = rawConn.Control(func(fd uintptr) {
addr, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
if err != nil {
return
}
ip = net.IP(addr.Multiaddr[4:8]).String()
port = uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3])
})
return ip + ":" + strconv.Itoa(int(port)), nil
}
================================================
FILE: server/proxy/transport_windows.go
================================================
// +build windows
package proxy
import (
"ehang.io/nps/lib/conn"
)
func HandleTrans(c *conn.Conn, s *TunnelModeServer) error {
return nil
}
================================================
FILE: server/proxy/udp.go
================================================
package proxy
import (
"io"
"net"
"strings"
"sync"
"time"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego/logs"
)
type UdpModeServer struct {
BaseServer
addrMap sync.Map
listener *net.UDPConn
}
func NewUdpModeServer(bridge *bridge.Bridge, task *file.Tunnel) *UdpModeServer {
s := new(UdpModeServer)
s.bridge = bridge
s.task = task
return s
}
//开始
func (s *UdpModeServer) Start() error {
var err error
if s.task.ServerIp == "" {
s.task.ServerIp = "0.0.0.0"
}
s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP(s.task.ServerIp), s.task.Port, ""})
if err != nil {
return err
}
for {
buf := common.BufPoolUdp.Get().([]byte)
n, addr, err := s.listener.ReadFromUDP(buf)
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
break
}
continue
}
logs.Trace("New udp connection,client %d,remote address %s", s.task.Client.Id, addr)
go s.process(addr, buf[:n])
}
return nil
}
func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
if v, ok := s.addrMap.Load(addr.String()); ok {
clientConn, ok := v.(io.ReadWriteCloser)
if ok {
clientConn.Write(data)
s.task.Flow.Add(int64(len(data)), 0)
}
} else {
if err := s.CheckFlowAndConnNum(s.task.Client); err != nil {
logs.Warn("client id %d, task id %d,error %s, when udp connection", s.task.Client.Id, s.task.Id, err.Error())
return
}
defer s.task.Client.AddConn()
link := conn.NewLink(common.CONN_UDP, s.task.Target.TargetStr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String(), s.task.Target.LocalProxy)
if clientConn, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task); err != nil {
return
} else {
target := conn.GetConn(clientConn, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, nil, true)
s.addrMap.Store(addr.String(), target)
defer target.Close()
target.Write(data)
buf := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(buf)
s.task.Flow.Add(int64(len(data)), 0)
for {
clientConn.SetReadDeadline(time.Now().Add(time.Minute * 10))
if n, err := target.Read(buf); err != nil {
s.addrMap.Delete(addr.String())
logs.Warn(err)
return
} else {
s.listener.WriteTo(buf[:n], addr)
s.task.Flow.Add(0, int64(n))
}
}
}
}
}
func (s *UdpModeServer) Close() error {
return s.listener.Close()
}
================================================
FILE: server/server.go
================================================
package server
import (
"ehang.io/nps/lib/version"
"errors"
"math"
"os"
"strconv"
"strings"
"sync"
"time"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"ehang.io/nps/server/proxy"
"ehang.io/nps/server/tool"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
)
var (
Bridge *bridge.Bridge
RunList sync.Map //map[int]interface{}
)
func init() {
RunList = sync.Map{}
}
//init task from db
func InitFromCsv() {
//Add a public password
if vkey := beego.AppConfig.String("public_vkey"); vkey != "" {
c := file.NewClient(vkey, true, true)
file.GetDb().NewClient(c)
RunList.Store(c.Id, nil)
//RunList[c.Id] = nil
}
//Initialize services in server-side files
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
if value.(*file.Tunnel).Status {
AddTask(value.(*file.Tunnel))
}
return true
})
}
//get bridge command
func DealBridgeTask() {
for {
select {
case t := <-Bridge.OpenTask:
AddTask(t)
case t := <-Bridge.CloseTask:
StopServer(t.Id)
case id := <-Bridge.CloseClient:
DelTunnelAndHostByClientId(id, true)
if v, ok := file.GetDb().JsonDb.Clients.Load(id); ok {
if v.(*file.Client).NoStore {
file.GetDb().DelClient(id)
}
}
case tunnel := <-Bridge.OpenTask:
StartTask(tunnel.Id)
case s := <-Bridge.SecretChan:
logs.Trace("New secret connection, addr", s.Conn.Conn.RemoteAddr())
if t := file.GetDb().GetTaskByMd5Password(s.Password); t != nil {
if t.Status {
go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Client, t.Target.TargetStr, nil, common.CONN_TCP, nil, t.Flow, t.Target.LocalProxy)
} else {
s.Conn.Close()
logs.Trace("This key %s cannot be processed,status is close", s.Password)
}
} else {
logs.Trace("This key %s cannot be processed", s.Password)
s.Conn.Close()
}
}
}
}
//start a new server
func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string, bridgeDisconnect int) {
Bridge = bridge.NewTunnel(bridgePort, bridgeType, common.GetBoolByStr(beego.AppConfig.String("ip_limit")), RunList, bridgeDisconnect)
go func() {
if err := Bridge.StartTunnel(); err != nil {
logs.Error("start server bridge error", err)
os.Exit(0)
}
}()
if p, err := beego.AppConfig.Int("p2p_port"); err == nil {
go proxy.NewP2PServer(p).Start()
go proxy.NewP2PServer(p + 1).Start()
go proxy.NewP2PServer(p + 2).Start()
}
go DealBridgeTask()
go dealClientFlow()
if svr := NewMode(Bridge, cnf); svr != nil {
if err := svr.Start(); err != nil {
logs.Error(err)
}
RunList.Store(cnf.Id, svr)
//RunList[cnf.Id] = svr
} else {
logs.Error("Incorrect startup mode %s", cnf.Mode)
}
}
func dealClientFlow() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
dealClientData()
}
}
}
//new a server by mode name
func NewMode(Bridge *bridge.Bridge, c *file.Tunnel) proxy.Service {
var service proxy.Service
switch c.Mode {
case "tcp", "file":
service = proxy.NewTunnelModeServer(proxy.ProcessTunnel, Bridge, c)
case "socks5":
service = proxy.NewSock5ModeServer(Bridge, c)
case "httpProxy":
service = proxy.NewTunnelModeServer(proxy.ProcessHttp, Bridge, c)
case "tcpTrans":
service = proxy.NewTunnelModeServer(proxy.HandleTrans, Bridge, c)
case "udp":
service = proxy.NewUdpModeServer(Bridge, c)
case "webServer":
InitFromCsv()
t := &file.Tunnel{
Port: 0,
Mode: "httpHostServer",
Status: true,
}
AddTask(t)
service = proxy.NewWebServer(Bridge)
case "httpHostServer":
httpPort, _ := beego.AppConfig.Int("http_proxy_port")
httpsPort, _ := beego.AppConfig.Int("https_proxy_port")
useCache, _ := beego.AppConfig.Bool("http_cache")
cacheLen, _ := beego.AppConfig.Int("http_cache_length")
addOrigin, _ := beego.AppConfig.Bool("http_add_origin_header")
service = proxy.NewHttp(Bridge, c, httpPort, httpsPort, useCache, cacheLen, addOrigin)
}
return service
}
//stop server
func StopServer(id int) error {
//if v, ok := RunList[id]; ok {
if v, ok := RunList.Load(id); ok {
if svr, ok := v.(proxy.Service); ok {
if err := svr.Close(); err != nil {
return err
}
logs.Info("stop server id %d", id)
} else {
logs.Warn("stop server id %d error", id)
}
if t, err := file.GetDb().GetTask(id); err != nil {
return err
} else {
t.Status = false
file.GetDb().UpdateTask(t)
}
//delete(RunList, id)
RunList.Delete(id)
return nil
}
return errors.New("task is not running")
}
//add task
func AddTask(t *file.Tunnel) error {
if t.Mode == "secret" || t.Mode == "p2p" {
logs.Info("secret task %s start ", t.Remark)
//RunList[t.Id] = nil
RunList.Store(t.Id, nil)
return nil
}
if b := tool.TestServerPort(t.Port, t.Mode); !b && t.Mode != "httpHostServer" {
logs.Error("taskId %d start error port %d open failed", t.Id, t.Port)
return errors.New("the port open error")
}
if minute, err := beego.AppConfig.Int("flow_store_interval"); err == nil && minute > 0 {
go flowSession(time.Minute * time.Duration(minute))
}
if svr := NewMode(Bridge, t); svr != nil {
logs.Info("tunnel task %s start mode:%s port %d", t.Remark, t.Mode, t.Port)
//RunList[t.Id] = svr
RunList.Store(t.Id, svr)
go func() {
if err := svr.Start(); err != nil {
logs.Error("clientId %d taskId %d start error %s", t.Client.Id, t.Id, err)
//delete(RunList, t.Id)
RunList.Delete(t.Id)
return
}
}()
} else {
return errors.New("the mode is not correct")
}
return nil
}
//start task
func StartTask(id int) error {
if t, err := file.GetDb().GetTask(id); err != nil {
return err
} else {
AddTask(t)
t.Status = true
file.GetDb().UpdateTask(t)
}
return nil
}
//delete task
func DelTask(id int) error {
//if _, ok := RunList[id]; ok {
if _, ok := RunList.Load(id); ok {
if err := StopServer(id); err != nil {
return err
}
}
return file.GetDb().DelTask(id)
}
//get task list by page num
func GetTunnel(start, length int, typeVal string, clientId int, search string) ([]*file.Tunnel, int) {
list := make([]*file.Tunnel, 0)
var cnt int
keys := file.GetMapKeys(file.GetDb().JsonDb.Tasks, false, "", "")
for _, key := range keys {
if value, ok := file.GetDb().JsonDb.Tasks.Load(key); ok {
v := value.(*file.Tunnel)
if (typeVal != "" && v.Mode != typeVal || (clientId != 0 && v.Client.Id != clientId)) || (typeVal == "" && clientId != v.Client.Id) {
continue
}
if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || v.Port == common.GetIntNoErrByStr(search) || strings.Contains(v.Password, search) || strings.Contains(v.Remark, search)) {
continue
}
cnt++
if _, ok := Bridge.Client.Load(v.Client.Id); ok {
v.Client.IsConnect = true
} else {
v.Client.IsConnect = false
}
if start--; start < 0 {
if length--; length >= 0 {
//if _, ok := RunList[v.Id]; ok {
if _, ok := RunList.Load(v.Id); ok {
v.RunStatus = true
} else {
v.RunStatus = false
}
list = append(list, v)
}
}
}
}
return list, cnt
}
//get client list
func GetClientList(start, length int, search, sort, order string, clientId int) (list []*file.Client, cnt int) {
list, cnt = file.GetDb().GetClientList(start, length, search, sort, order, clientId)
dealClientData()
return
}
func dealClientData() {
file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*file.Client)
if vv, ok := Bridge.Client.Load(v.Id); ok {
v.IsConnect = true
v.Version = vv.(*bridge.Client).Version
} else {
v.IsConnect = false
}
v.Flow.InletFlow = 0
v.Flow.ExportFlow = 0
file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
h := value.(*file.Host)
if h.Client.Id == v.Id {
v.Flow.InletFlow += h.Flow.InletFlow
v.Flow.ExportFlow += h.Flow.ExportFlow
}
return true
})
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
t := value.(*file.Tunnel)
if t.Client.Id == v.Id {
v.Flow.InletFlow += t.Flow.InletFlow
v.Flow.ExportFlow += t.Flow.ExportFlow
}
return true
})
return true
})
return
}
//delete all host and tasks by client id
func DelTunnelAndHostByClientId(clientId int, justDelNoStore bool) {
var ids []int
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
v := value.(*file.Tunnel)
if justDelNoStore && !v.NoStore {
return true
}
if v.Client.Id == clientId {
ids = append(ids, v.Id)
}
return true
})
for _, id := range ids {
DelTask(id)
}
ids = ids[:0]
file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
v := value.(*file.Host)
if justDelNoStore && !v.NoStore {
return true
}
if v.Client.Id == clientId {
ids = append(ids, v.Id)
}
return true
})
for _, id := range ids {
file.GetDb().DelHost(id)
}
}
//close the client
func DelClientConnect(clientId int) {
Bridge.DelClient(clientId)
}
func GetDashboardData() map[string]interface{} {
data := make(map[string]interface{})
data["version"] = version.VERSION
data["hostCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Hosts)
data["clientCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Clients)
if beego.AppConfig.String("public_vkey") != "" { //remove public vkey
data["clientCount"] = data["clientCount"].(int) - 1
}
dealClientData()
c := 0
var in, out int64
file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*file.Client)
if v.IsConnect {
c += 1
}
in += v.Flow.InletFlow
out += v.Flow.ExportFlow
return true
})
data["clientOnlineCount"] = c
data["inletFlowCount"] = int(in)
data["exportFlowCount"] = int(out)
var tcp, udp, secret, socks5, p2p, http int
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
switch value.(*file.Tunnel).Mode {
case "tcp":
tcp += 1
case "socks5":
socks5 += 1
case "httpProxy":
http += 1
case "udp":
udp += 1
case "p2p":
p2p += 1
case "secret":
secret += 1
}
return true
})
data["tcpC"] = tcp
data["udpCount"] = udp
data["socks5Count"] = socks5
data["httpProxyCount"] = http
data["secretCount"] = secret
data["p2pCount"] = p2p
data["bridgeType"] = beego.AppConfig.String("bridge_type")
data["httpProxyPort"] = beego.AppConfig.String("http_proxy_port")
data["httpsProxyPort"] = beego.AppConfig.String("https_proxy_port")
data["ipLimit"] = beego.AppConfig.String("ip_limit")
data["flowStoreInterval"] = beego.AppConfig.String("flow_store_interval")
data["serverIp"] = beego.AppConfig.String("p2p_ip")
data["p2pPort"] = beego.AppConfig.String("p2p_port")
data["logLevel"] = beego.AppConfig.String("log_level")
tcpCount := 0
file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {
tcpCount += int(value.(*file.Client).NowConn)
return true
})
data["tcpCount"] = tcpCount
cpuPercet, _ := cpu.Percent(0, true)
var cpuAll float64
for _, v := range cpuPercet {
cpuAll += v
}
loads, _ := load.Avg()
data["load"] = loads.String()
data["cpu"] = math.Round(cpuAll / float64(len(cpuPercet)))
swap, _ := mem.SwapMemory()
data["swap_mem"] = math.Round(swap.UsedPercent)
vir, _ := mem.VirtualMemory()
data["virtual_mem"] = math.Round(vir.UsedPercent)
conn, _ := net.ProtoCounters(nil)
io1, _ := net.IOCounters(false)
time.Sleep(time.Millisecond * 500)
io2, _ := net.IOCounters(false)
if len(io2) > 0 && len(io1) > 0 {
data["io_send"] = (io2[0].BytesSent - io1[0].BytesSent) * 2
data["io_recv"] = (io2[0].BytesRecv - io1[0].BytesRecv) * 2
}
for _, v := range conn {
data[v.Protocol] = v.Stats["CurrEstab"]
}
//chart
var fg int
if len(tool.ServerStatus) >= 10 {
fg = len(tool.ServerStatus) / 10
for i := 0; i <= 9; i++ {
data["sys"+strconv.Itoa(i+1)] = tool.ServerStatus[i*fg]
}
}
return data
}
func flowSession(m time.Duration) {
ticker := time.NewTicker(m)
defer ticker.Stop()
for {
select {
case <-ticker.C:
file.GetDb().JsonDb.StoreHostToJsonFile()
file.GetDb().JsonDb.StoreTasksToJsonFile()
file.GetDb().JsonDb.StoreClientsToJsonFile()
}
}
}
================================================
FILE: server/test/test.go
================================================
package test
import (
"log"
"path/filepath"
"strconv"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego"
)
func TestServerConfig() {
var postTcpArr []int
var postUdpArr []int
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
v := value.(*file.Tunnel)
if v.Mode == "udp" {
isInArr(&postUdpArr, v.Port, v.Remark, "udp")
} else if v.Port != 0 {
isInArr(&postTcpArr, v.Port, v.Remark, "tcp")
}
return true
})
p, err := beego.AppConfig.Int("web_port")
if err != nil {
log.Fatalln("Getting web management port error :", err)
} else {
isInArr(&postTcpArr, p, "Web Management port", "tcp")
}
if p := beego.AppConfig.String("bridge_port"); p != "" {
if port, err := strconv.Atoi(p); err != nil {
log.Fatalln("get Server and client communication portserror:", err)
} else if beego.AppConfig.String("bridge_type") == "kcp" {
isInArr(&postUdpArr, port, "Server and client communication ports", "udp")
} else {
isInArr(&postTcpArr, port, "Server and client communication ports", "tcp")
}
}
if p := beego.AppConfig.String("httpProxyPort"); p != "" {
if port, err := strconv.Atoi(p); err != nil {
log.Fatalln("get http port error:", err)
} else {
isInArr(&postTcpArr, port, "https port", "tcp")
}
}
if p := beego.AppConfig.String("https_proxy_port"); p != "" {
if b, err := beego.AppConfig.Bool("https_just_proxy"); !(err == nil && b) {
if port, err := strconv.Atoi(p); err != nil {
log.Fatalln("get https port error", err)
} else {
if beego.AppConfig.String("pemPath") != "" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("pemPath"))) {
log.Fatalf("ssl certFile %s is not exist", beego.AppConfig.String("pemPath"))
}
if beego.AppConfig.String("keyPath") != "" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("keyPath"))) {
log.Fatalf("ssl keyFile %s is not exist", beego.AppConfig.String("pemPath"))
}
isInArr(&postTcpArr, port, "http port", "tcp")
}
}
}
}
func isInArr(arr *[]int, val int, remark string, tp string) {
for _, v := range *arr {
if v == val {
log.Fatalf("the port %d is reused,remark: %s", val, remark)
}
}
if tp == "tcp" {
if !common.TestTcpPort(val) {
log.Fatalf("open the %d port error ,remark: %s", val, remark)
}
} else {
if !common.TestUdpPort(val) {
log.Fatalf("open the %d port error ,remark: %s", val, remark)
}
}
*arr = append(*arr, val)
return
}
================================================
FILE: server/tool/utils.go
================================================
package tool
import (
"math"
"strconv"
"time"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
)
var (
ports []int
ServerStatus []map[string]interface{}
)
func StartSystemInfo() {
if b, err := beego.AppConfig.Bool("system_info_display"); err == nil && b {
ServerStatus = make([]map[string]interface{}, 0, 1500)
go getSeverStatus()
}
}
func InitAllowPort() {
p := beego.AppConfig.String("allow_ports")
ports = common.GetPorts(p)
}
func TestServerPort(p int, m string) (b bool) {
if m == "p2p" || m == "secret" {
return true
}
if p > 65535 || p < 0 {
return false
}
if len(ports) != 0 {
if !common.InIntArr(ports, p) {
return false
}
}
if m == "udp" {
b = common.TestUdpPort(p)
} else {
b = common.TestTcpPort(p)
}
return
}
func getSeverStatus() {
for {
if len(ServerStatus) < 10 {
time.Sleep(time.Second)
} else {
time.Sleep(time.Minute)
}
cpuPercet, _ := cpu.Percent(0, true)
var cpuAll float64
for _, v := range cpuPercet {
cpuAll += v
}
m := make(map[string]interface{})
loads, _ := load.Avg()
m["load1"] = loads.Load1
m["load5"] = loads.Load5
m["load15"] = loads.Load15
m["cpu"] = math.Round(cpuAll / float64(len(cpuPercet)))
swap, _ := mem.SwapMemory()
m["swap_mem"] = math.Round(swap.UsedPercent)
vir, _ := mem.VirtualMemory()
m["virtual_mem"] = math.Round(vir.UsedPercent)
conn, _ := net.ProtoCounters(nil)
io1, _ := net.IOCounters(false)
time.Sleep(time.Millisecond * 500)
io2, _ := net.IOCounters(false)
if len(io2) > 0 && len(io1) > 0 {
m["io_send"] = (io2[0].BytesSent - io1[0].BytesSent) * 2
m["io_recv"] = (io2[0].BytesRecv - io1[0].BytesRecv) * 2
}
t := time.Now()
m["time"] = strconv.Itoa(t.Hour()) + ":" + strconv.Itoa(t.Minute()) + ":" + strconv.Itoa(t.Second())
for _, v := range conn {
m[v.Protocol] = v.Stats["CurrEstab"]
}
if len(ServerStatus) >= 1440 {
ServerStatus = ServerStatus[1:]
}
ServerStatus = append(ServerStatus, m)
}
}
================================================
FILE: web/controllers/auth.go
================================================
package controllers
import (
"encoding/hex"
"time"
"ehang.io/nps/lib/crypt"
"github.com/astaxie/beego"
)
type AuthController struct {
beego.Controller
}
func (s *AuthController) GetAuthKey() {
m := make(map[string]interface{})
defer func() {
s.Data["json"] = m
s.ServeJSON()
}()
if cryptKey := beego.AppConfig.String("auth_crypt_key"); len(cryptKey) != 16 {
m["status"] = 0
return
} else {
b, err := crypt.AesEncrypt([]byte(beego.AppConfig.String("auth_key")), []byte(cryptKey))
if err != nil {
m["status"] = 0
return
}
m["status"] = 1
m["crypt_auth_key"] = hex.EncodeToString(b)
m["crypt_type"] = "aes cbc"
return
}
}
func (s *AuthController) GetTime() {
m := make(map[string]interface{})
m["time"] = time.Now().Unix()
s.Data["json"] = m
s.ServeJSON()
}
================================================
FILE: web/controllers/base.go
================================================
package controllers
import (
"html"
"math"
"strconv"
"strings"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"ehang.io/nps/server"
"github.com/astaxie/beego"
)
type BaseController struct {
beego.Controller
controllerName string
actionName string
}
//初始化参数
func (s *BaseController) Prepare() {
s.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
controllerName, actionName := s.GetControllerAndAction()
s.controllerName = strings.ToLower(controllerName[0 : len(controllerName)-10])
s.actionName = strings.ToLower(actionName)
// web api verify
// param 1 is md5(authKey+Current timestamp)
// param 2 is timestamp (It's limited to 20 seconds.)
md5Key := s.getEscapeString("auth_key")
timestamp := s.GetIntNoErr("timestamp")
configKey := beego.AppConfig.String("auth_key")
timeNowUnix := time.Now().Unix()
if !(md5Key != "" && (math.Abs(float64(timeNowUnix-int64(timestamp))) <= 20) && (crypt.Md5(configKey+strconv.Itoa(timestamp)) == md5Key)) {
if s.GetSession("auth") != true {
s.Redirect(beego.AppConfig.String("web_base_url")+"/login/index", 302)
}
} else {
s.SetSession("isAdmin", true)
s.Data["isAdmin"] = true
}
if s.GetSession("isAdmin") != nil && !s.GetSession("isAdmin").(bool) {
s.Ctx.Input.SetData("client_id", s.GetSession("clientId").(int))
s.Ctx.Input.SetParam("client_id", strconv.Itoa(s.GetSession("clientId").(int)))
s.Data["isAdmin"] = false
s.Data["username"] = s.GetSession("username")
s.CheckUserAuth()
} else {
s.Data["isAdmin"] = true
}
s.Data["https_just_proxy"], _ = beego.AppConfig.Bool("https_just_proxy")
s.Data["allow_user_login"], _ = beego.AppConfig.Bool("allow_user_login")
s.Data["allow_flow_limit"], _ = beego.AppConfig.Bool("allow_flow_limit")
s.Data["allow_rate_limit"], _ = beego.AppConfig.Bool("allow_rate_limit")
s.Data["allow_connection_num_limit"], _ = beego.AppConfig.Bool("allow_connection_num_limit")
s.Data["allow_multi_ip"], _ = beego.AppConfig.Bool("allow_multi_ip")
s.Data["system_info_display"], _ = beego.AppConfig.Bool("system_info_display")
s.Data["allow_tunnel_num_limit"], _ = beego.AppConfig.Bool("allow_tunnel_num_limit")
s.Data["allow_local_proxy"], _ = beego.AppConfig.Bool("allow_local_proxy")
s.Data["allow_user_change_username"], _ = beego.AppConfig.Bool("allow_user_change_username")
}
//加载模板
func (s *BaseController) display(tpl ...string) {
s.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
var tplname string
if s.Data["menu"] == nil {
s.Data["menu"] = s.actionName
}
if len(tpl) > 0 {
tplname = strings.Join([]string{tpl[0], "html"}, ".")
} else {
tplname = s.controllerName + "/" + s.actionName + ".html"
}
ip := s.Ctx.Request.Host
s.Data["ip"] = common.GetIpByAddr(ip)
s.Data["bridgeType"] = beego.AppConfig.String("bridge_type")
if common.IsWindows() {
s.Data["win"] = ".exe"
}
s.Data["p"] = server.Bridge.TunnelPort
s.Data["proxyPort"] = beego.AppConfig.String("hostPort")
s.Layout = "public/layout.html"
s.TplName = tplname
}
//错误
func (s *BaseController) error() {
s.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
s.Layout = "public/layout.html"
s.TplName = "public/error.html"
}
//getEscapeString
func (s *BaseController) getEscapeString(key string) string {
return html.EscapeString(s.GetString(key))
}
//去掉没有err返回值的int
func (s *BaseController) GetIntNoErr(key string, def ...int) int {
strv := s.Ctx.Input.Query(key)
if len(strv) == 0 && len(def) > 0 {
return def[0]
}
val, _ := strconv.Atoi(strv)
return val
}
//获取去掉错误的bool值
func (s *BaseController) GetBoolNoErr(key string, def ...bool) bool {
strv := s.Ctx.Input.Query(key)
if len(strv) == 0 && len(def) > 0 {
return def[0]
}
val, _ := strconv.ParseBool(strv)
return val
}
//ajax正确返回
func (s *BaseController) AjaxOk(str string) {
s.Data["json"] = ajax(str, 1)
s.ServeJSON()
s.StopRun()
}
//ajax错误返回
func (s *BaseController) AjaxErr(str string) {
s.Data["json"] = ajax(str, 0)
s.ServeJSON()
s.StopRun()
}
//组装ajax
func ajax(str string, status int) map[string]interface{} {
json := make(map[string]interface{})
json["status"] = status
json["msg"] = str
return json
}
//ajax table返回
func (s *BaseController) AjaxTable(list interface{}, cnt int, recordsTotal int, kwargs map[string]interface{}) {
json := make(map[string]interface{})
json["rows"] = list
json["total"] = recordsTotal
if kwargs != nil {
for k, v := range kwargs {
if v != nil {
json[k] = v
}
}
}
s.Data["json"] = json
s.ServeJSON()
s.StopRun()
}
//ajax table参数
func (s *BaseController) GetAjaxParams() (start, limit int) {
return s.GetIntNoErr("offset"), s.GetIntNoErr("limit")
}
func (s *BaseController) SetInfo(name string) {
s.Data["name"] = name
}
func (s *BaseController) SetType(name string) {
s.Data["type"] = name
}
func (s *BaseController) CheckUserAuth() {
if s.controllerName == "client" {
if s.actionName == "add" {
s.StopRun()
return
}
if id := s.GetIntNoErr("id"); id != 0 {
if id != s.GetSession("clientId").(int) {
s.StopRun()
return
}
}
}
if s.controllerName == "index" {
if id := s.GetIntNoErr("id"); id != 0 {
belong := false
if strings.Contains(s.actionName, "h") {
if v, ok := file.GetDb().JsonDb.Hosts.Load(id); ok {
if v.(*file.Host).Client.Id == s.GetSession("clientId").(int) {
belong = true
}
}
} else {
if v, ok := file.GetDb().JsonDb.Tasks.Load(id); ok {
if v.(*file.Tunnel).Client.Id == s.GetSession("clientId").(int) {
belong = true
}
}
}
if !belong {
s.StopRun()
}
}
}
}
================================================
FILE: web/controllers/client.go
================================================
package controllers
import (
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/rate"
"ehang.io/nps/server"
"github.com/astaxie/beego"
)
type ClientController struct {
BaseController
}
func (s *ClientController) List() {
if s.Ctx.Request.Method == "GET" {
s.Data["menu"] = "client"
s.SetInfo("client")
s.display("client/list")
return
}
start, length := s.GetAjaxParams()
clientIdSession := s.GetSession("clientId")
var clientId int
if clientIdSession == nil {
clientId = 0
} else {
clientId = clientIdSession.(int)
}
list, cnt := server.GetClientList(start, length, s.getEscapeString("search"), s.getEscapeString("sort"), s.getEscapeString("order"), clientId)
cmd := make(map[string]interface{})
ip := s.Ctx.Request.Host
cmd["ip"] = common.GetIpByAddr(ip)
cmd["bridgeType"] = beego.AppConfig.String("bridge_type")
cmd["bridgePort"] = server.Bridge.TunnelPort
s.AjaxTable(list, cnt, cnt, cmd)
}
//添加客户端
func (s *ClientController) Add() {
if s.Ctx.Request.Method == "GET" {
s.Data["menu"] = "client"
s.SetInfo("add client")
s.display()
} else {
t := &file.Client{
VerifyKey: s.getEscapeString("vkey"),
Id: int(file.GetDb().JsonDb.GetClientId()),
Status: true,
Remark: s.getEscapeString("remark"),
Cnf: &file.Config{
U: s.getEscapeString("u"),
P: s.getEscapeString("p"),
Compress: common.GetBoolByStr(s.getEscapeString("compress")),
Crypt: s.GetBoolNoErr("crypt"),
},
ConfigConnAllow: s.GetBoolNoErr("config_conn_allow"),
RateLimit: s.GetIntNoErr("rate_limit"),
MaxConn: s.GetIntNoErr("max_conn"),
WebUserName: s.getEscapeString("web_username"),
WebPassword: s.getEscapeString("web_password"),
MaxTunnelNum: s.GetIntNoErr("max_tunnel"),
Flow: &file.Flow{
ExportFlow: 0,
InletFlow: 0,
FlowLimit: int64(s.GetIntNoErr("flow_limit")),
},
}
if err := file.GetDb().NewClient(t); err != nil {
s.AjaxErr(err.Error())
}
s.AjaxOk("add success")
}
}
func (s *ClientController) GetClient() {
if s.Ctx.Request.Method == "POST" {
id := s.GetIntNoErr("id")
data := make(map[string]interface{})
if c, err := file.GetDb().GetClient(id); err != nil {
data["code"] = 0
} else {
data["code"] = 1
data["data"] = c
}
s.Data["json"] = data
s.ServeJSON()
}
}
//修改客户端
func (s *ClientController) Edit() {
id := s.GetIntNoErr("id")
if s.Ctx.Request.Method == "GET" {
s.Data["menu"] = "client"
if c, err := file.GetDb().GetClient(id); err != nil {
s.error()
} else {
s.Data["c"] = c
}
s.SetInfo("edit client")
s.display()
} else {
if c, err := file.GetDb().GetClient(id); err != nil {
s.error()
s.AjaxErr("client ID not found")
return
} else {
if s.getEscapeString("web_username") != "" {
if s.getEscapeString("web_username") == beego.AppConfig.String("web_username") || !file.GetDb().VerifyUserName(s.getEscapeString("web_username"), c.Id) {
s.AjaxErr("web login username duplicate, please reset")
return
}
}
if s.GetSession("isAdmin").(bool) {
if !file.GetDb().VerifyVkey(s.getEscapeString("vkey"), c.Id) {
s.AjaxErr("Vkey duplicate, please reset")
return
}
c.VerifyKey = s.getEscapeString("vkey")
c.Flow.FlowLimit = int64(s.GetIntNoErr("flow_limit"))
c.RateLimit = s.GetIntNoErr("rate_limit")
c.MaxConn = s.GetIntNoErr("max_conn")
c.MaxTunnelNum = s.GetIntNoErr("max_tunnel")
}
c.Remark = s.getEscapeString("remark")
c.Cnf.U = s.getEscapeString("u")
c.Cnf.P = s.getEscapeString("p")
c.Cnf.Compress = common.GetBoolByStr(s.getEscapeString("compress"))
c.Cnf.Crypt = s.GetBoolNoErr("crypt")
b, err := beego.AppConfig.Bool("allow_user_change_username")
if s.GetSession("isAdmin").(bool) || (err == nil && b) {
c.WebUserName = s.getEscapeString("web_username")
}
c.WebPassword = s.getEscapeString("web_password")
c.ConfigConnAllow = s.GetBoolNoErr("config_conn_allow")
if c.Rate != nil {
c.Rate.Stop()
}
if c.RateLimit > 0 {
c.Rate = rate.NewRate(int64(c.RateLimit * 1024))
c.Rate.Start()
} else {
c.Rate = rate.NewRate(int64(2 << 23))
c.Rate.Start()
}
file.GetDb().JsonDb.StoreClientsToJsonFile()
}
s.AjaxOk("save success")
}
}
//更改状态
func (s *ClientController) ChangeStatus() {
id := s.GetIntNoErr("id")
if client, err := file.GetDb().GetClient(id); err == nil {
client.Status = s.GetBoolNoErr("status")
if client.Status == false {
server.DelClientConnect(client.Id)
}
s.AjaxOk("modified success")
}
s.AjaxErr("modified fail")
}
//删除客户端
func (s *ClientController) Del() {
id := s.GetIntNoErr("id")
if err := file.GetDb().DelClient(id); err != nil {
s.AjaxErr("delete error")
}
server.DelTunnelAndHostByClientId(id, false)
server.DelClientConnect(id)
s.AjaxOk("delete success")
}
================================================
FILE: web/controllers/index.go
================================================
package controllers
import (
"ehang.io/nps/lib/file"
"ehang.io/nps/server"
"ehang.io/nps/server/tool"
"github.com/astaxie/beego"
)
type IndexController struct {
BaseController
}
func (s *IndexController) Index() {
s.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
s.Data["data"] = server.GetDashboardData()
s.SetInfo("dashboard")
s.display("index/index")
}
func (s *IndexController) Help() {
s.SetInfo("about")
s.display("index/help")
}
func (s *IndexController) Tcp() {
s.SetInfo("tcp")
s.SetType("tcp")
s.display("index/list")
}
func (s *IndexController) Udp() {
s.SetInfo("udp")
s.SetType("udp")
s.display("index/list")
}
func (s *IndexController) Socks5() {
s.SetInfo("socks5")
s.SetType("socks5")
s.display("index/list")
}
func (s *IndexController) Http() {
s.SetInfo("http proxy")
s.SetType("httpProxy")
s.display("index/list")
}
func (s *IndexController) File() {
s.SetInfo("file server")
s.SetType("file")
s.display("index/list")
}
func (s *IndexController) Secret() {
s.SetInfo("secret")
s.SetType("secret")
s.display("index/list")
}
func (s *IndexController) P2p() {
s.SetInfo("p2p")
s.SetType("p2p")
s.display("index/list")
}
func (s *IndexController) Host() {
s.SetInfo("host")
s.SetType("hostServer")
s.display("index/list")
}
func (s *IndexController) All() {
s.Data["menu"] = "client"
clientId := s.getEscapeString("client_id")
s.Data["client_id"] = clientId
s.SetInfo("client id:" + clientId)
s.display("index/list")
}
func (s *IndexController) GetTunnel() {
start, length := s.GetAjaxParams()
taskType := s.getEscapeString("type")
clientId := s.GetIntNoErr("client_id")
list, cnt := server.GetTunnel(start, length, taskType, clientId, s.getEscapeString("search"))
s.AjaxTable(list, cnt, cnt, nil)
}
func (s *IndexController) Add() {
if s.Ctx.Request.Method == "GET" {
s.Data["type"] = s.getEscapeString("type")
s.Data["client_id"] = s.getEscapeString("client_id")
s.SetInfo("add tunnel")
s.display()
} else {
t := &file.Tunnel{
Port: s.GetIntNoErr("port"),
ServerIp: s.getEscapeString("server_ip"),
Mode: s.getEscapeString("type"),
Target: &file.Target{TargetStr: s.getEscapeString("target"), LocalProxy: s.GetBoolNoErr("local_proxy")},
Id: int(file.GetDb().JsonDb.GetTaskId()),
Status: true,
Remark: s.getEscapeString("remark"),
Password: s.getEscapeString("password"),
LocalPath: s.getEscapeString("local_path"),
StripPre: s.getEscapeString("strip_pre"),
Flow: &file.Flow{},
}
if !tool.TestServerPort(t.Port, t.Mode) {
s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.")
}
var err error
if t.Client, err = file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
s.AjaxErr(err.Error())
}
if t.Client.MaxTunnelNum != 0 && t.Client.GetTunnelNum() >= t.Client.MaxTunnelNum {
s.AjaxErr("The number of tunnels exceeds the limit")
}
if err := file.GetDb().NewTask(t); err != nil {
s.AjaxErr(err.Error())
}
if err := server.AddTask(t); err != nil {
s.AjaxErr(err.Error())
} else {
s.AjaxOk("add success")
}
}
}
func (s *IndexController) GetOneTunnel() {
id := s.GetIntNoErr("id")
data := make(map[string]interface{})
if t, err := file.GetDb().GetTask(id); err != nil {
data["code"] = 0
} else {
data["code"] = 1
data["data"] = t
}
s.Data["json"] = data
s.ServeJSON()
}
func (s *IndexController) Edit() {
id := s.GetIntNoErr("id")
if s.Ctx.Request.Method == "GET" {
if t, err := file.GetDb().GetTask(id); err != nil {
s.error()
} else {
s.Data["t"] = t
}
s.SetInfo("edit tunnel")
s.display()
} else {
if t, err := file.GetDb().GetTask(id); err != nil {
s.error()
} else {
if client, err := file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
s.AjaxErr("modified error,the client is not exist")
return
} else {
t.Client = client
}
if s.GetIntNoErr("port") != t.Port {
if !tool.TestServerPort(s.GetIntNoErr("port"), t.Mode) {
s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.")
return
}
t.Port = s.GetIntNoErr("port")
}
t.ServerIp = s.getEscapeString("server_ip")
t.Mode = s.getEscapeString("type")
t.Target = &file.Target{TargetStr: s.getEscapeString("target")}
t.Password = s.getEscapeString("password")
t.Id = id
t.LocalPath = s.getEscapeString("local_path")
t.StripPre = s.getEscapeString("strip_pre")
t.Remark = s.getEscapeString("remark")
t.Target.LocalProxy = s.GetBoolNoErr("local_proxy")
file.GetDb().UpdateTask(t)
server.StopServer(t.Id)
server.StartTask(t.Id)
}
s.AjaxOk("modified success")
}
}
func (s *IndexController) Stop() {
id := s.GetIntNoErr("id")
if err := server.StopServer(id); err != nil {
s.AjaxErr("stop error")
}
s.AjaxOk("stop success")
}
func (s *IndexController) Del() {
id := s.GetIntNoErr("id")
if err := server.DelTask(id); err != nil {
s.AjaxErr("delete error")
}
s.AjaxOk("delete success")
}
func (s *IndexController) Start() {
id := s.GetIntNoErr("id")
if err := server.StartTask(id); err != nil {
s.AjaxErr("start error")
}
s.AjaxOk("start success")
}
func (s *IndexController) HostList() {
if s.Ctx.Request.Method == "GET" {
s.Data["client_id"] = s.getEscapeString("client_id")
s.Data["menu"] = "host"
s.SetInfo("host list")
s.display("index/hlist")
} else {
start, length := s.GetAjaxParams()
clientId := s.GetIntNoErr("client_id")
list, cnt := file.GetDb().GetHost(start, length, clientId, s.getEscapeString("search"))
s.AjaxTable(list, cnt, cnt, nil)
}
}
func (s *IndexController) GetHost() {
if s.Ctx.Request.Method == "POST" {
data := make(map[string]interface{})
if h, err := file.GetDb().GetHostById(s.GetIntNoErr("id")); err != nil {
data["code"] = 0
} else {
data["data"] = h
data["code"] = 1
}
s.Data["json"] = data
s.ServeJSON()
}
}
func (s *IndexController) DelHost() {
id := s.GetIntNoErr("id")
if err := file.GetDb().DelHost(id); err != nil {
s.AjaxErr("delete error")
}
s.AjaxOk("delete success")
}
func (s *IndexController) AddHost() {
if s.Ctx.Request.Method == "GET" {
s.Data["client_id"] = s.getEscapeString("client_id")
s.Data["menu"] = "host"
s.SetInfo("add host")
s.display("index/hadd")
} else {
h := &file.Host{
Id: int(file.GetDb().JsonDb.GetHostId()),
Host: s.getEscapeString("host"),
Target: &file.Target{TargetStr: s.getEscapeString("target"), LocalProxy: s.GetBoolNoErr("local_proxy")},
HeaderChange: s.getEscapeString("header"),
HostChange: s.getEscapeString("hostchange"),
Remark: s.getEscapeString("remark"),
Location: s.getEscapeString("location"),
Flow: &file.Flow{},
Scheme: s.getEscapeString("scheme"),
KeyFilePath: s.getEscapeString("key_file_path"),
CertFilePath: s.getEscapeString("cert_file_path"),
}
var err error
if h.Client, err = file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
s.AjaxErr("add error the client can not be found")
}
if err := file.GetDb().NewHost(h); err != nil {
s.AjaxErr("add fail" + err.Error())
}
s.AjaxOk("add success")
}
}
func (s *IndexController) EditHost() {
id := s.GetIntNoErr("id")
if s.Ctx.Request.Method == "GET" {
s.Data["menu"] = "host"
if h, err := file.GetDb().GetHostById(id); err != nil {
s.error()
} else {
s.Data["h"] = h
}
s.SetInfo("edit")
s.display("index/hedit")
} else {
if h, err := file.GetDb().GetHostById(id); err != nil {
s.error()
} else {
if h.Host != s.getEscapeString("host") {
tmpHost := new(file.Host)
tmpHost.Host = s.getEscapeString("host")
tmpHost.Location = s.getEscapeString("location")
tmpHost.Scheme = s.getEscapeString("scheme")
if file.GetDb().IsHostExist(tmpHost) {
s.AjaxErr("host has exist")
return
}
}
if client, err := file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
s.AjaxErr("modified error,the client is not exist")
} else {
h.Client = client
}
h.Host = s.getEscapeString("host")
h.Target = &file.Target{TargetStr: s.getEscapeString("target")}
h.HeaderChange = s.getEscapeString("header")
h.HostChange = s.getEscapeString("hostchange")
h.Remark = s.getEscapeString("remark")
h.Location = s.getEscapeString("location")
h.Scheme = s.getEscapeString("scheme")
h.KeyFilePath = s.getEscapeString("key_file_path")
h.CertFilePath = s.getEscapeString("cert_file_path")
h.Target.LocalProxy = s.GetBoolNoErr("local_proxy")
file.GetDb().JsonDb.StoreHostToJsonFile()
}
s.AjaxOk("modified success")
}
}
================================================
FILE: web/controllers/login.go
================================================
package controllers
import (
"math/rand"
"net"
"sync"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"ehang.io/nps/server"
"github.com/astaxie/beego"
)
type LoginController struct {
beego.Controller
}
var ipRecord sync.Map
type record struct {
hasLoginFailTimes int
lastLoginTime time.Time
}
func (self *LoginController) Index() {
// Try login implicitly, will succeed if it's configured as no-auth(empty username&password).
webBaseUrl := beego.AppConfig.String("web_base_url")
if self.doLogin("", "", false) {
self.Redirect(webBaseUrl+"/index/index", 302)
}
self.Data["web_base_url"] = webBaseUrl
self.Data["register_allow"], _ = beego.AppConfig.Bool("allow_user_register")
self.TplName = "login/index.html"
}
func (self *LoginController) Verify() {
username := self.GetString("username")
password := self.GetString("password")
if self.doLogin(username, password, true) {
self.Data["json"] = map[string]interface{}{"status": 1, "msg": "login success"}
} else {
self.Data["json"] = map[string]interface{}{"status": 0, "msg": "username or password incorrect"}
}
self.ServeJSON()
}
func (self *LoginController) doLogin(username, password string, explicit bool) bool {
clearIprecord()
ip, _, _ := net.SplitHostPort(self.Ctx.Request.RemoteAddr)
if v, ok := ipRecord.Load(ip); ok {
vv := v.(*record)
if (time.Now().Unix() - vv.lastLoginTime.Unix()) >= 60 {
vv.hasLoginFailTimes = 0
}
if vv.hasLoginFailTimes >= 10 {
return false
}
}
var auth bool
if password == beego.AppConfig.String("web_password") && username == beego.AppConfig.String("web_username") {
self.SetSession("isAdmin", true)
self.DelSession("clientId")
self.DelSession("username")
auth = true
server.Bridge.Register.Store(common.GetIpByAddr(self.Ctx.Input.IP()), time.Now().Add(time.Hour*time.Duration(2)))
}
b, err := beego.AppConfig.Bool("allow_user_login")
if err == nil && b && !auth {
file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*file.Client)
if !v.Status || v.NoDisplay {
return true
}
if v.WebUserName == "" && v.WebPassword == "" {
if username != "user" || v.VerifyKey != password {
return true
} else {
auth = true
}
}
if !auth && v.WebPassword == password && v.WebUserName == username {
auth = true
}
if auth {
self.SetSession("isAdmin", false)
self.SetSession("clientId", v.Id)
self.SetSession("username", v.WebUserName)
return false
}
return true
})
}
if auth {
self.SetSession("auth", true)
ipRecord.Delete(ip)
return true
}
if v, load := ipRecord.LoadOrStore(ip, &record{hasLoginFailTimes: 1, lastLoginTime: time.Now()}); load && explicit {
vv := v.(*record)
vv.lastLoginTime = time.Now()
vv.hasLoginFailTimes += 1
ipRecord.Store(ip, vv)
}
return false
}
func (self *LoginController) Register() {
if self.Ctx.Request.Method == "GET" {
self.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
self.TplName = "login/register.html"
} else {
if b, err := beego.AppConfig.Bool("allow_user_register"); err != nil || !b {
self.Data["json"] = map[string]interface{}{"status": 0, "msg": "register is not allow"}
self.ServeJSON()
return
}
if self.GetString("username") == "" || self.GetString("password") == "" || self.GetString("username") == beego.AppConfig.String("web_username") {
self.Data["json"] = map[string]interface{}{"status": 0, "msg": "please check your input"}
self.ServeJSON()
return
}
t := &file.Client{
Id: int(file.GetDb().JsonDb.GetClientId()),
Status: true,
Cnf: &file.Config{},
WebUserName: self.GetString("username"),
WebPassword: self.GetString("password"),
Flow: &file.Flow{},
}
if err := file.GetDb().NewClient(t); err != nil {
self.Data["json"] = map[string]interface{}{"status": 0, "msg": err.Error()}
} else {
self.Data["json"] = map[string]interface{}{"status": 1, "msg": "register success"}
}
self.ServeJSON()
}
}
func (self *LoginController) Out() {
self.SetSession("auth", false)
self.Redirect(beego.AppConfig.String("web_base_url")+"/login/index", 302)
}
func clearIprecord() {
rand.Seed(time.Now().UnixNano())
x := rand.Intn(100)
if x == 1 {
ipRecord.Range(func(key, value interface{}) bool {
v := value.(*record)
if time.Now().Unix()-v.lastLoginTime.Unix() >= 60 {
ipRecord.Delete(key)
}
return true
})
}
}
================================================
FILE: web/routers/router.go
================================================
package routers
import (
"ehang.io/nps/web/controllers"
"github.com/astaxie/beego"
)
func Init() {
web_base_url := beego.AppConfig.String("web_base_url")
if len(web_base_url) > 0 {
ns := beego.NewNamespace(web_base_url,
beego.NSRouter("/", &controllers.IndexController{}, "*:Index"),
beego.NSAutoRouter(&controllers.IndexController{}),
beego.NSAutoRouter(&controllers.LoginController{}),
beego.NSAutoRouter(&controllers.ClientController{}),
beego.NSAutoRouter(&controllers.AuthController{}),
)
beego.AddNamespace(ns)
} else {
beego.Router("/", &controllers.IndexController{}, "*:Index")
beego.AutoRouter(&controllers.IndexController{})
beego.AutoRouter(&controllers.LoginController{})
beego.AutoRouter(&controllers.ClientController{})
beego.AutoRouter(&controllers.AuthController{})
}
}
================================================
FILE: web/static/css/datatables.css
================================================
/*
* This combined file was created by the DataTables downloader builder:
* https://datatables.net/download
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#dt/dt-1.10.20
*
* Included libraries:
* DataTables 1.10.20
*/
/*
* Table styles
*/
table.dataTable {
width: 100%;
margin: 0 auto;
clear: both;
border-collapse: separate;
border-spacing: 0;
/*
* Header and footer styles
*/
/*
* Body styles
*/
}
table.dataTable thead th,
table.dataTable tfoot th {
font-weight: bold;
}
table.dataTable thead th,
table.dataTable thead td {
padding: 10px 18px;
border-bottom: 1px solid #111;
}
table.dataTable thead th:active,
table.dataTable thead td:active {
outline: none;
}
table.dataTable tfoot th,
table.dataTable tfoot td {
padding: 10px 18px 6px 18px;
border-top: 1px solid #111;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
cursor: pointer;
*cursor: hand;
background-repeat: no-repeat;
background-position: center right;
}
table.dataTable thead .sorting {
background-image: url("DataTables-1.10.20/images/sort_both.png");
}
table.dataTable thead .sorting_asc {
background-image: url("DataTables-1.10.20/images/sort_asc.png");
}
table.dataTable thead .sorting_desc {
background-image: url("DataTables-1.10.20/images/sort_desc.png");
}
table.dataTable thead .sorting_asc_disabled {
background-image: url("DataTables-1.10.20/images/sort_asc_disabled.png");
}
table.dataTable thead .sorting_desc_disabled {
background-image: url("DataTables-1.10.20/images/sort_desc_disabled.png");
}
table.dataTable tbody tr {
background-color: #ffffff;
}
table.dataTable tbody tr.selected {
background-color: #B0BED9;
}
table.dataTable tbody th,
table.dataTable tbody td {
padding: 8px 10px;
}
table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {
border-top: 1px solid #ddd;
}
table.dataTable.row-border tbody tr:first-child th,
table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,
table.dataTable.display tbody tr:first-child td {
border-top: none;
}
table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {
border-top: 1px solid #ddd;
border-right: 1px solid #ddd;
}
table.dataTable.cell-border tbody tr th:first-child,
table.dataTable.cell-border tbody tr td:first-child {
border-left: 1px solid #ddd;
}
table.dataTable.cell-border tbody tr:first-child th,
table.dataTable.cell-border tbody tr:first-child td {
border-top: none;
}
table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {
background-color: #f9f9f9;
}
table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {
background-color: #acbad4;
}
table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
background-color: #f6f6f6;
}
table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {
background-color: #aab7d1;
}
table.dataTable.order-column tbody tr > .sorting_1,
table.dataTable.order-column tbody tr > .sorting_2,
table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
table.dataTable.display tbody tr > .sorting_2,
table.dataTable.display tbody tr > .sorting_3 {
background-color: #fafafa;
}
table.dataTable.order-column tbody tr.selected > .sorting_1,
table.dataTable.order-column tbody tr.selected > .sorting_2,
table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,
table.dataTable.display tbody tr.selected > .sorting_2,
table.dataTable.display tbody tr.selected > .sorting_3 {
background-color: #acbad5;
}
table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
background-color: #f1f1f1;
}
table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
background-color: #f3f3f3;
}
table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
background-color: whitesmoke;
}
table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
background-color: #a6b4cd;
}
table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
background-color: #a8b5cf;
}
table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
background-color: #a9b7d1;
}
table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
background-color: #fafafa;
}
table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
background-color: #fcfcfc;
}
table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
background-color: #fefefe;
}
table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
background-color: #acbad5;
}
table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
background-color: #aebcd6;
}
table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
background-color: #afbdd8;
}
table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
background-color: #eaeaea;
}
table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
background-color: #ececec;
}
table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
background-color: #efefef;
}
table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
background-color: #a2aec7;
}
table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
background-color: #a3b0c9;
}
table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
background-color: #a5b2cb;
}
table.dataTable.no-footer {
border-bottom: 1px solid #111;
}
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.compact thead th,
table.dataTable.compact thead td {
padding: 4px 17px 4px 4px;
}
table.dataTable.compact tfoot th,
table.dataTable.compact tfoot td {
padding: 4px;
}
table.dataTable.compact tbody th,
table.dataTable.compact tbody td {
padding: 4px;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
}
table.dataTable,
table.dataTable th,
table.dataTable td {
box-sizing: content-box;
}
/*
* Control feature layout
*/
.dataTables_wrapper {
position: relative;
clear: both;
*zoom: 1;
zoom: 1;
}
.dataTables_wrapper .dataTables_length {
float: left;
}
.dataTables_wrapper .dataTables_filter {
float: right;
text-align: right;
}
.dataTables_wrapper .dataTables_filter input {
margin-left: 0.5em;
}
.dataTables_wrapper .dataTables_info {
clear: both;
float: left;
padding-top: 0.755em;
}
.dataTables_wrapper .dataTables_paginate {
float: right;
text-align: right;
padding-top: 0.25em;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
box-sizing: border-box;
display: inline-block;
min-width: 1.5em;
padding: 0.5em 1em;
margin-left: 2px;
text-align: center;
text-decoration: none !important;
cursor: pointer;
*cursor: hand;
color: #333 !important;
border: 1px solid transparent;
border-radius: 2px;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
color: #333 !important;
border: 1px solid #979797;
background-color: white;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);
/* IE10+ */
background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);
/* W3C */
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
cursor: default;
color: #666 !important;
border: 1px solid transparent;
background: transparent;
box-shadow: none;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
color: white !important;
border: 1px solid #111;
background-color: #585858;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, #585858 0%, #111 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, #585858 0%, #111 100%);
/* IE10+ */
background: -o-linear-gradient(top, #585858 0%, #111 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, #585858 0%, #111 100%);
/* W3C */
}
.dataTables_wrapper .dataTables_paginate .paginate_button:active {
outline: none;
background-color: #2b2b2b;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* IE10+ */
background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);
/* W3C */
box-shadow: inset 0 0 3px #111;
}
.dataTables_wrapper .dataTables_paginate .ellipsis {
padding: 0 1em;
}
.dataTables_wrapper .dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 40px;
margin-left: -50%;
margin-top: -25px;
padding-top: 20px;
text-align: center;
font-size: 1.2em;
background-color: white;
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));
background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
}
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_processing,
.dataTables_wrapper .dataTables_paginate {
color: #333;
}
.dataTables_wrapper .dataTables_scroll {
clear: both;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
*margin-top: -1px;
-webkit-overflow-scrolling: touch;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td {
vertical-align: middle;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing {
height: 0;
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
}
.dataTables_wrapper.no-footer .dataTables_scrollBody {
border-bottom: 1px solid #111;
}
.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,
.dataTables_wrapper.no-footer div.dataTables_scrollBody > table {
border-bottom: none;
}
.dataTables_wrapper:after {
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
}
@media screen and (max-width: 767px) {
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate {
float: none;
text-align: center;
}
.dataTables_wrapper .dataTables_paginate {
margin-top: 0.5em;
}
}
@media screen and (max-width: 640px) {
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter {
float: none;
text-align: center;
}
.dataTables_wrapper .dataTables_filter {
margin-top: 0.5em;
}
}
================================================
FILE: web/static/css/style.css
================================================
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700");
@import url("https://fonts.googleapis.com/css?family=Roboto:400,300,500,700");
/*
*
* INSPINIA - Responsive Admin Theme
* version 2.9.3
*
*/
@font-face {
font-family: 'Glyphicons Halflings';
src: url('../fonts/glyphicons-halflings-regular.eot');
src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg') format('svg');
}
.glyphicon {
position: relative;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.glyphicon-asterisk:before {
content: "\002a";
}
.glyphicon-plus:before {
content: "\002b";
}
.glyphicon-euro:before,
.glyphicon-eur:before {
content: "\20ac";
}
.glyphicon-minus:before {
content: "\2212";
}
.glyphicon-cloud:before {
content: "\2601";
}
.glyphicon-envelope:before {
content: "\2709";
}
.glyphicon-pencil:before {
content: "\270f";
}
.glyphicon-glass:before {
content: "\e001";
}
.glyphicon-music:before {
content: "\e002";
}
.glyphicon-search:before {
content: "\e003";
}
.glyphicon-heart:before {
content: "\e005";
}
.glyphicon-star:before {
content: "\e006";
}
.glyphicon-star-empty:before {
content: "\e007";
}
.glyphicon-user:before {
content: "\e008";
}
.glyphicon-film:before {
content: "\e009";
}
.glyphicon-th-large:before {
content: "\e010";
}
.glyphicon-th:before {
content: "\e011";
}
.glyphicon-th-list:before {
content: "\e012";
}
.glyphicon-ok:before {
content: "\e013";
}
.glyphicon-remove:before {
content: "\e014";
}
.glyphicon-zoom-in:before {
content: "\e015";
}
.glyphicon-zoom-out:before {
content: "\e016";
}
.glyphicon-off:before {
content: "\e017";
}
.glyphicon-signal:before {
content: "\e018";
}
.glyphicon-cog:before {
content: "\e019";
}
.glyphicon-trash:before {
content: "\e020";
}
.glyphicon-home:before {
content: "\e021";
}
.glyphicon-file:before {
content: "\e022";
}
.glyphicon-time:before {
content: "\e023";
}
.glyphicon-road:before {
content: "\e024";
}
.glyphicon-download-alt:before {
content: "\e025";
}
.glyphicon-download:before {
content: "\e026";
}
.glyphicon-upload:before {
content: "\e027";
}
.glyphicon-inbox:before {
content: "\e028";
}
.glyphicon-play-circle:before {
content: "\e029";
}
.glyphicon-repeat:before {
content: "\e030";
}
.glyphicon-refresh:before {
content: "\e031";
}
.glyphicon-list-alt:before {
content: "\e032";
}
.glyphicon-lock:before {
content: "\e033";
}
.glyphicon-flag:before {
content: "\e034";
}
.glyphicon-headphones:before {
content: "\e035";
}
.glyphicon-volume-off:before {
content: "\e036";
}
.glyphicon-volume-down:before {
content: "\e037";
}
.glyphicon-volume-up:before {
content: "\e038";
}
.glyphicon-qrcode:before {
content: "\e039";
}
.glyphicon-barcode:before {
content: "\e040";
}
.glyphicon-tag:before {
content: "\e041";
}
.glyphicon-tags:before {
content: "\e042";
}
.glyphicon-book:before {
content: "\e043";
}
.glyphicon-bookmark:before {
content: "\e044";
}
.glyphicon-print:before {
content: "\e045";
}
.glyphicon-camera:before {
content: "\e046";
}
.glyphicon-font:before {
content: "\e047";
}
.glyphicon-bold:before {
content: "\e048";
}
.glyphicon-italic:before {
content: "\e049";
}
.glyphicon-text-height:before {
content: "\e050";
}
.glyphicon-text-width:before {
content: "\e051";
}
.glyphicon-align-left:before {
content: "\e052";
}
.glyphicon-align-center:before {
content: "\e053";
}
.glyphicon-align-right:before {
content: "\e054";
}
.glyphicon-align-justify:before {
content: "\e055";
}
.glyphicon-list:before {
content: "\e056";
}
.glyphicon-indent-left:before {
content: "\e057";
}
.glyphicon-indent-right:before {
content: "\e058";
}
.glyphicon-facetime-video:before {
content: "\e059";
}
.glyphicon-picture:before {
content: "\e060";
}
.glyphicon-map-marker:before {
content: "\e062";
}
.glyphicon-adjust:before {
content: "\e063";
}
.glyphicon-tint:before {
content: "\e064";
}
.glyphicon-edit:before {
content: "\e065";
}
.glyphicon-share:before {
content: "\e066";
}
.glyphicon-check:before {
content: "\e067";
}
.glyphicon-move:before {
content: "\e068";
}
.glyphicon-step-backward:before {
content: "\e069";
}
.glyphicon-fast-backward:before {
content: "\e070";
}
.glyphicon-backward:before {
content: "\e071";
}
.glyphicon-play:before {
content: "\e072";
}
.glyphicon-pause:before {
content: "\e073";
}
.glyphicon-stop:before {
content: "\e074";
}
.glyphicon-forward:before {
content: "\e075";
}
.glyphicon-fast-forward:before {
content: "\e076";
}
.glyphicon-step-forward:before {
content: "\e077";
}
.glyphicon-eject:before {
content: "\e078";
}
.glyphicon-chevron-left:before {
content: "\e079";
}
.glyphicon-chevron-right:before {
content: "\e080";
}
.glyphicon-plus-sign:before {
content: "\e081";
}
.glyphicon-minus-sign:before {
content: "\e082";
}
.glyphicon-remove-sign:before {
content: "\e083";
}
.glyphicon-ok-sign:before {
content: "\e084";
}
.glyphicon-question-sign:before {
content: "\e085";
}
.glyphicon-info-sign:before {
content: "\e086";
}
.glyphicon-screenshot:before {
content: "\e087";
}
.glyphicon-remove-circle:before {
content: "\e088";
}
.glyphicon-ok-circle:before {
content: "\e089";
}
.glyphicon-ban-circle:before {
content: "\e090";
}
.glyphicon-arrow-left:before {
content: "\e091";
}
.glyphicon-arrow-right:before {
content: "\e092";
}
.glyphicon-arrow-up:before {
content: "\e093";
}
.glyphicon-arrow-down:before {
content: "\e094";
}
.glyphicon-share-alt:before {
content: "\e095";
}
.glyphicon-resize-full:before {
content: "\e096";
}
.glyphicon-resize-small:before {
content: "\e097";
}
.glyphicon-exclamation-sign:before {
content: "\e101";
}
.glyphicon-gift:before {
content: "\e102";
}
.glyphicon-leaf:before {
content: "\e103";
}
.glyphicon-fire:before {
content: "\e104";
}
.glyphicon-eye-open:before {
content: "\e105";
}
.glyphicon-eye-close:before {
content: "\e106";
}
.glyphicon-warning-sign:before {
content: "\e107";
}
.glyphicon-plane:before {
content: "\e108";
}
.glyphicon-calendar:before {
content: "\e109";
}
.glyphicon-random:before {
content: "\e110";
}
.glyphicon-comment:before {
content: "\e111";
}
.glyphicon-magnet:before {
content: "\e112";
}
.glyphicon-chevron-up:before {
content: "\e113";
}
.glyphicon-chevron-down:before {
content: "\e114";
}
.glyphicon-retweet:before {
content: "\e115";
}
.glyphicon-shopping-cart:before {
content: "\e116";
}
.glyphicon-folder-close:before {
content: "\e117";
}
.glyphicon-folder-open:before {
content: "\e118";
}
.glyphicon-resize-vertical:before {
content: "\e119";
}
.glyphicon-resize-horizontal:before {
content: "\e120";
}
.glyphicon-hdd:before {
content: "\e121";
}
.glyphicon-bullhorn:before {
content: "\e122";
}
.glyphicon-bell:before {
content: "\e123";
}
.glyphicon-certificate:before {
content: "\e124";
}
.glyphicon-thumbs-up:before {
content: "\e125";
}
.glyphicon-thumbs-down:before {
content: "\e126";
}
.glyphicon-hand-right:before {
content: "\e127";
}
.glyphicon-hand-left:before {
content: "\e128";
}
.glyphicon-hand-up:before {
content: "\e129";
}
.glyphicon-hand-down:before {
content: "\e130";
}
.glyphicon-circle-arrow-right:before {
content: "\e131";
}
.glyphicon-circle-arrow-left:before {
content: "\e132";
}
.glyphicon-circle-arrow-up:before {
content: "\e133";
}
.glyphicon-circle-arrow-down:before {
content: "\e134";
}
.glyphicon-globe:before {
content: "\e135";
}
.glyphicon-wrench:before {
content: "\e136";
}
.glyphicon-tasks:before {
content: "\e137";
}
.glyphicon-filter:before {
content: "\e138";
}
.glyphicon-briefcase:before {
content: "\e139";
}
.glyphicon-fullscreen:before {
content: "\e140";
}
.glyphicon-dashboard:before {
content: "\e141";
}
.glyphicon-paperclip:before {
content: "\e142";
}
.glyphicon-heart-empty:before {
content: "\e143";
}
.glyphicon-link:before {
content: "\e144";
}
.glyphicon-phone:before {
content: "\e145";
}
.glyphicon-pushpin:before {
content: "\e146";
}
.glyphicon-usd:before {
content: "\e148";
}
.glyphicon-gbp:before {
content: "\e149";
}
.glyphicon-sort:before {
content: "\e150";
}
.glyphicon-sort-by-alphabet:before {
content: "\e151";
}
.glyphicon-sort-by-alphabet-alt:before {
content: "\e152";
}
.glyphicon-sort-by-order:before {
content: "\e153";
}
.glyphicon-sort-by-order-alt:before {
content: "\e154";
}
.glyphicon-sort-by-attributes:before {
content: "\e155";
}
.glyphicon-sort-by-attributes-alt:before {
content: "\e156";
}
.glyphicon-unchecked:before {
content: "\e157";
}
.glyphicon-expand:before {
content: "\e158";
}
.glyphicon-collapse-down:before {
content: "\e159";
}
.glyphicon-collapse-up:before {
content: "\e160";
}
.glyphicon-log-in:before {
content: "\e161";
}
.glyphicon-flash:before {
content: "\e162";
}
.glyphicon-log-out:before {
content: "\e163";
}
.glyphicon-new-window:before {
content: "\e164";
}
.glyphicon-record:before {
content: "\e165";
}
.glyphicon-save:before {
content: "\e166";
}
.glyphicon-open:before {
content: "\e167";
}
.glyphicon-saved:before {
content: "\e168";
}
.glyphicon-import:before {
content: "\e169";
}
.glyphicon-export:before {
content: "\e170";
}
.glyphicon-send:before {
content: "\e171";
}
.glyphicon-floppy-disk:before {
content: "\e172";
}
.glyphicon-floppy-saved:before {
content: "\e173";
}
.glyphicon-floppy-remove:before {
content: "\e174";
}
.glyphicon-floppy-save:before {
content: "\e175";
}
.glyphicon-floppy-open:before {
content: "\e176";
}
.glyphicon-credit-card:before {
content: "\e177";
}
.glyphicon-transfer:before {
content: "\e178";
}
.glyphicon-cutlery:before {
content: "\e179";
}
.glyphicon-header:before {
content: "\e180";
}
.glyphicon-compressed:before {
content: "\e181";
}
.glyphicon-earphone:before {
content: "\e182";
}
.glyphicon-phone-alt:before {
content: "\e183";
}
.glyphicon-tower:before {
content: "\e184";
}
.glyphicon-stats:before {
content: "\e185";
}
.glyphicon-sd-video:before {
content: "\e186";
}
.glyphicon-hd-video:before {
content: "\e187";
}
.glyphicon-subtitles:before {
content: "\e188";
}
.glyphicon-sound-stereo:before {
content: "\e189";
}
.glyphicon-sound-dolby:before {
content: "\e190";
}
.glyphicon-sound-5-1:before {
content: "\e191";
}
.glyphicon-sound-6-1:before {
content: "\e192";
}
.glyphicon-sound-7-1:before {
content: "\e193";
}
.glyphicon-copyright-mark:before {
content: "\e194";
}
.glyphicon-registration-mark:before {
content: "\e195";
}
.glyphicon-cloud-download:before {
content: "\e197";
}
.glyphicon-cloud-upload:before {
content: "\e198";
}
.glyphicon-tree-conifer:before {
content: "\e199";
}
.glyphicon-tree-deciduous:before {
content: "\e200";
}
.glyphicon-cd:before {
content: "\e201";
}
.glyphicon-save-file:before {
content: "\e202";
}
.glyphicon-open-file:before {
content: "\e203";
}
.glyphicon-level-up:before {
content: "\e204";
}
.glyphicon-copy:before {
content: "\e205";
}
.glyphicon-paste:before {
content: "\e206";
}
.glyphicon-alert:before {
content: "\e209";
}
.glyphicon-equalizer:before {
content: "\e210";
}
.glyphicon-king:before {
content: "\e211";
}
.glyphicon-queen:before {
content: "\e212";
}
.glyphicon-pawn:before {
content: "\e213";
}
.glyphicon-bishop:before {
content: "\e214";
}
.glyphicon-knight:before {
content: "\e215";
}
.glyphicon-baby-formula:before {
content: "\e216";
}
.glyphicon-tent:before {
content: "\26fa";
}
.glyphicon-blackboard:before {
content: "\e218";
}
.glyphicon-bed:before {
content: "\e219";
}
.glyphicon-apple:before {
content: "\f8ff";
}
.glyphicon-erase:before {
content: "\e221";
}
.glyphicon-hourglass:before {
content: "\231b";
}
.glyphicon-lamp:before {
content: "\e223";
}
.glyphicon-duplicate:before {
content: "\e224";
}
.glyphicon-piggy-bank:before {
content: "\e225";
}
.glyphicon-scissors:before {
content: "\e226";
}
.glyphicon-bitcoin:before {
content: "\e227";
}
.glyphicon-btc:before {
content: "\e227";
}
.glyphicon-xbt:before {
content: "\e227";
}
.glyphicon-yen:before {
content: "\00a5";
}
.glyphicon-jpy:before {
content: "\00a5";
}
.glyphicon-ruble:before {
content: "\20bd";
}
.glyphicon-rub:before {
content: "\20bd";
}
.glyphicon-scale:before {
content: "\e230";
}
.glyphicon-ice-lolly:before {
content: "\e231";
}
.glyphicon-ice-lolly-tasted:before {
content: "\e232";
}
.glyphicon-education:before {
content: "\e233";
}
.glyphicon-option-horizontal:before {
content: "\e234";
}
.glyphicon-option-vertical:before {
content: "\e235";
}
.glyphicon-menu-hamburger:before {
content: "\e236";
}
.glyphicon-modal-window:before {
content: "\e237";
}
.glyphicon-oil:before {
content: "\e238";
}
.glyphicon-grain:before {
content: "\e239";
}
.glyphicon-sunglasses:before {
content: "\e240";
}
.glyphicon-text-size:before {
content: "\e241";
}
.glyphicon-text-color:before {
content: "\e242";
}
.glyphicon-text-background:before {
content: "\e243";
}
.glyphicon-object-align-top:before {
content: "\e244";
}
.glyphicon-object-align-bottom:before {
content: "\e245";
}
.glyphicon-object-align-horizontal:before {
content: "\e246";
}
.glyphicon-object-align-left:before {
content: "\e247";
}
.glyphicon-object-align-vertical:before {
content: "\e248";
}
.glyphicon-object-align-right:before {
content: "\e249";
}
.glyphicon-triangle-right:before {
content: "\e250";
}
.glyphicon-triangle-left:before {
content: "\e251";
}
.glyphicon-triangle-bottom:before {
content: "\e252";
}
.glyphicon-triangle-top:before {
content: "\e253";
}
.glyphicon-console:before {
content: "\e254";
}
.glyphicon-superscript:before {
content: "\e255";
}
.glyphicon-subscript:before {
content: "\e256";
}
.glyphicon-menu-left:before {
content: "\e257";
}
.glyphicon-menu-right:before {
content: "\e258";
}
.glyphicon-menu-down:before {
content: "\e259";
}
.glyphicon-menu-up:before {
content: "\e260";
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 100;
}
.h1,
.h2,
.h3,
h1,
h2,
h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h1 {
font-size: 30px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 16px;
}
h4 {
font-size: 14px;
}
h5 {
font-size: 12px;
}
h6 {
font-size: 10px;
}
h3,
h4,
h5 {
margin-top: 5px;
font-weight: 600;
}
.nav > li > a {
color: #a7b1c2;
font-weight: 600;
padding: 14px 20px 14px 25px;
display: block;
}
.nav.metismenu > li {
display: block;
width: 100%;
position: relative;
}
.nav.metismenu .dropdown-menu > li > a {
padding: 3px 20px;
display: block;
}
.nav.navbar-right > li > a {
color: #999c9e;
}
.nav > li.active > a {
color: #ffffff;
}
.navbar-default .nav > li > a:hover,
.navbar-default .nav > li > a:focus {
background-color: #293846;
color: white;
}
.nav .open > a,
.nav .open > a:hover,
.nav .open > a:focus {
background: #fff;
}
.nav.navbar-top-links > li > a:hover,
.nav.navbar-top-links > li > a:focus {
background-color: transparent;
}
.nav > li > a i {
margin-right: 6px;
}
.navbar {
border: 0;
}
.navbar-default {
background-color: transparent;
border-color: #2f4050;
}
.navbar-top-links li {
display: inline-block;
align-self: center;
}
.body-small .navbar-top-links li:last-child {
margin-right: 0;
}
.navbar-top-links li a {
padding: 20px 10px;
min-height: 50px;
}
.dropdown-menu {
border: medium none;
border-radius: 3px;
box-shadow: 0 0 3px rgba(86, 96, 117, 0.7);
display: none;
float: left;
font-size: 12px;
left: 0;
list-style: none outside none;
padding: 0;
position: absolute;
text-shadow: none;
top: 100%;
z-index: 1000;
}
.dropdown-menu > li > a {
border-radius: 3px;
color: inherit;
line-height: 25px;
margin: 4px;
text-align: left;
font-weight: normal;
display: block;
padding: 3px 20px;
}
.dropdown-menu > li > a:focus,
.dropdown-menu > li > a:hover {
color: #262626;
text-decoration: none;
background-color: #f5f5f5;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:focus,
.dropdown-menu > .active > a:hover {
color: #fff;
text-decoration: none;
background-color: #1ab394;
outline: 0;
}
.dropdown-menu > li > a.font-bold {
font-weight: 600;
}
.navbar-top-links .dropdown-menu li {
display: block;
}
.navbar-top-links .dropdown-menu li:last-child {
margin-right: 0;
}
.navbar-top-links .dropdown-menu li a {
padding: 3px 20px;
min-height: 0;
}
.navbar-top-links .dropdown-menu li a div {
white-space: normal;
}
.navbar-top-links .dropdown-messages,
.navbar-top-links .dropdown-tasks,
.navbar-top-links .dropdown-alerts {
width: 310px;
min-width: 0;
}
.navbar-top-links .dropdown-messages {
margin-left: 5px;
}
.navbar-top-links .dropdown-tasks {
margin-left: -59px;
}
.navbar-top-links .dropdown-alerts {
margin-left: -123px;
}
.navbar-top-links .dropdown-user {
right: 0;
left: auto;
}
.dropdown-messages,
.dropdown-alerts {
padding: 10px 10px 10px 10px;
}
.dropdown-messages li a,
.dropdown-alerts li a {
font-size: 12px;
}
.dropdown-messages li em,
.dropdown-alerts li em {
font-size: 10px;
}
.nav.navbar-top-links .dropdown-alerts a {
font-size: 12px;
}
.nav-header {
padding: 33px 25px;
background-color: #2f4050;
// background-image: url("patterns/header-profile.png");
}
/*.caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: 4px dashed;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
}*/
.profile-element .dropdown-toggle::after {
display: none;
}
.pace-done .nav-header {
transition: all 0.4s;
}
ul.nav-second-level {
background: #293846;
}
.nav > li.active {
border-left: 4px solid #19aa8d;
background: #293846;
}
.nav.nav-second-level > li.active {
border: none;
}
.nav.nav-second-level.collapse[style] {
height: auto !important;
}
.nav-header a {
color: #DFE4ED;
}
.nav-header .text-muted {
color: #8095a8 !important;
}
.minimalize-styl-2 {
padding: 4px 12px;
margin: 14px 5px 5px 20px;
font-size: 14px;
float: left;
}
.navbar-form-custom {
float: left;
height: 50px;
padding: 0;
width: 200px;
display: block;
}
.navbar-form-custom .form-group {
margin-bottom: 0;
}
.nav.navbar-top-links a {
font-size: 14px;
}
.navbar-form-custom .form-control {
background: none repeat scroll 0 0 rgba(0, 0, 0, 0);
border: medium none;
font-size: 14px;
height: 60px;
margin: 0;
z-index: 2000;
}
/*.nav.navbar-top-links .dropdown-toggle::after {
display: none;
}
*/
.navbar.navbar-static-top {
padding: 0;
width: 100%;
align-items: inherit;
}
.navbar-static-top .dropdown-menu {
right: 0;
left: auto;
}
.count-info .label {
line-height: 12px;
padding: 2px 5px;
position: absolute;
right: 6px;
top: 12px;
}
.arrow {
float: right;
}
.fa.arrow:before {
content: "\f104";
}
.active > a > .fa.arrow:before {
content: "\f107";
}
.nav-second-level li,
.nav-third-level li {
border-bottom: none !important;
}
.nav.nav-third-level > li.active {
border: none;
}
.nav-second-level li a {
padding: 7px 10px 7px 10px;
padding-left: 52px;
}
.fixed-sidebar.mini-navbar .nav-second-level.collapsing li a,
.nav-second-level.collapsing li a {
min-width: 220px;
}
.body-small .nav-second-level.collapsing li a,
.mini-navbar .nav-second-level.collapsing li a {
min-width: 140px;
}
.nav-third-level li a,
.fixed-sidebar.mini-navbar .nav-second-level li .nav-third-level li a {
padding-left: 62px;
}
.nav-second-level li:last-child {
padding-bottom: 10px;
}
body:not(.fixed-sidebar):not(.canvas-menu).mini-navbar .nav li:hover > .nav-second-level,
.mini-navbar .nav li:focus > .nav-second-level {
display: block;
border-radius: 0 2px 2px 0;
min-width: 160px;
height: auto;
}
body.mini-navbar .navbar-default .nav > li > .nav-second-level li a {
font-size: 12px;
border-radius: 3px;
}
.fixed-nav .slimScrollDiv #side-menu {
padding-bottom: 60px;
}
.mini-navbar .nav-second-level li a {
padding: 10px 10px 10px 15px;
}
.mini-navbar .nav .nav-second-level {
position: absolute;
left: 70px;
top: 0;
background-color: #2f4050;
padding: 10px 10px 10px 10px;
font-size: 12px;
}
.canvas-menu.mini-navbar .nav-second-level {
background: #293846;
}
.mini-navbar li.active .nav-second-level {
left: 65px;
}
.navbar-default .special_link a {
background: #1ab394;
color: white;
}
.navbar-default .special_link a:hover {
background: #17987e !important;
color: white;
}
.navbar-default .special_link a span.label {
background: #fff;
color: #1ab394;
}
.navbar-default .landing_link a {
background: #1cc09f;
color: white;
}
.navbar-default .landing_link a:hover {
background: #1ab394 !important;
color: white;
}
.navbar-default .landing_link a span.label {
background: #fff;
color: #1cc09f;
}
.logo-element {
text-align: center;
font-size: 18px;
font-weight: 600;
color: white;
display: none;
padding: 18px 0;
}
.navbar-static-side {
transition: width 0s;
}
.footer {
transition: margin 0s;
}
.pace-done .navbar-static-side,
.pace-done .nav-header,
.pace-done li.active,
.pace-done #page-wrapper,
.pace-done .footer {
-webkit-transition: all 0.4s;
-moz-transition: all 0.4s;
-o-transition: all 0.4s;
transition: all 0.4s;
}
.navbar-fixed-top {
background: #fff;
transition-duration: 0.4s;
border-bottom: 1px solid #e7eaec !important;
z-index: 2030;
position: fixed;
right: 0;
left: 0;
padding: 0;
top: 0;
}
.navbar-fixed-top .navbar-form-custom .form-control {
height: 50px;
}
.navbar-fixed-top,
.navbar-static-top {
background: #f3f3f4;
}
.fixed-nav #wrapper {
margin-top: 0;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background: none;
border-color: #dddddd #dddddd rgba(0, 0, 0, 0);
border-bottom: #f3f3f4;
border-image: none;
border-style: solid;
border-width: 1px;
color: #555555;
cursor: default;
}
.nav.nav-tabs li {
background: none;
border: none;
}
body.fixed-nav #wrapper .navbar-static-side,
body.fixed-nav #wrapper #page-wrapper {
margin-top: 60px;
}
body.top-navigation.fixed-nav #wrapper #page-wrapper {
margin-top: 0;
}
body.fixed-nav.fixed-nav-basic .navbar-fixed-top {
left: 220px;
}
body.fixed-nav.fixed-nav-basic.mini-navbar .navbar-fixed-top {
left: 70px;
}
body.fixed-nav.fixed-nav-basic.fixed-sidebar.mini-navbar .navbar-fixed-top {
left: 0;
}
body.fixed-nav.fixed-nav-basic #wrapper .navbar-static-side {
margin-top: 0;
}
body.fixed-nav.fixed-nav-basic.body-small .navbar-fixed-top {
left: 0;
}
body.fixed-nav.fixed-nav-basic.fixed-sidebar.mini-navbar.body-small .navbar-fixed-top {
left: 220px;
}
.fixed-nav .minimalize-styl-2 {
margin: 10px 5px 5px 15px;
}
.body-small .navbar-fixed-top {
margin-left: 0;
}
body.mini-navbar .navbar-static-side {
width: 70px;
}
body.mini-navbar .profile-element,
body.mini-navbar .nav-label,
body.mini-navbar .navbar-default .nav li a span {
display: none;
}
body.canvas-menu .profile-element {
display: block;
}
body:not(.fixed-sidebar):not(.canvas-menu).mini-navbar .nav-second-level {
display: none;
}
body.mini-navbar .navbar-default .nav > li > a {
font-size: 16px;
}
body.mini-navbar .logo-element {
display: block;
}
body.canvas-menu .logo-element {
display: none;
}
body.mini-navbar .nav-header {
padding: 0;
background-color: #1ab394;
}
body.canvas-menu .nav-header {
padding: 33px 25px;
}
body.canvas-menu .sidebar-collapse li {
width: 100%;
}
body.mini-navbar #page-wrapper {
width: calc(100% - 70px);
}
body.canvas-menu.mini-navbar #page-wrapper,
body.canvas-menu.mini-navbar .footer {
margin: 0;
width: 100%;
}
body.fixed-sidebar .navbar-static-side,
body.canvas-menu .navbar-static-side {
width: 220px;
z-index: 2001;
height: 100vh;
position: fixed;
}
body.fixed-sidebar.mini-navbar .navbar-static-side {
width: 0;
}
body.fixed-sidebar #page-wrapper {
margin: 0 0 0 220px;
}
body.fixed-sidebar.body-small #page-wrapper {
margin: 0;
}
body.fixed-sidebar.mini-navbar #page-wrapper {
margin: 0 0 0 0;
width: 100%;
}
body.body-small.fixed-sidebar.mini-navbar #page-wrapper {
margin: 0 0 0 220px;
}
body.body-small.fixed-sidebar.mini-navbar .navbar-static-side {
width: 220px;
}
.fixed-sidebar.mini-navbar .nav li:focus > .nav-second-level,
.canvas-menu.mini-navbar .nav li:focus > .nav-second-level {
display: block;
height: auto;
}
body.fixed-sidebar.mini-navbar .navbar-default .nav > li > .nav-second-level li a {
font-size: 12px;
border-radius: 3px;
}
body.canvas-menu.mini-navbar .navbar-default .nav > li > .nav-second-level li a {
font-size: 13px;
border-radius: 3px;
}
.fixed-sidebar.mini-navbar .nav-second-level li a,
.canvas-menu.mini-navbar .nav-second-level li a {
padding: 10px 10px 10px 15px;
}
.fixed-sidebar.mini-navbar .nav-second-level,
.canvas-menu.mini-navbar .nav-second-level {
position: relative;
padding: 0;
font-size: 13px;
}
.fixed-sidebar.mini-navbar li.active .nav-second-level,
.canvas-menu.mini-navbar li.active .nav-second-level {
left: 0;
}
body.fixed-sidebar.mini-navbar .navbar-default .nav > li > a,
body.canvas-menu.mini-navbar .navbar-default .nav > li > a {
font-size: 13px;
}
body.fixed-sidebar.mini-navbar .nav-label,
body.fixed-sidebar.mini-navbar .navbar-default .nav li a span,
body.canvas-menu.mini-navbar .nav-label,
body.canvas-menu.mini-navbar .navbar-default .nav li a span {
display: inline;
}
body.canvas-menu.mini-navbar .navbar-default .nav li .profile-element a span {
display: block;
}
.canvas-menu.mini-navbar .nav-second-level li a,
.fixed-sidebar.mini-navbar .nav-second-level li a {
padding: 7px 10px 7px 52px;
}
.fixed-sidebar.mini-navbar .nav-second-level,
.canvas-menu.mini-navbar .nav-second-level {
left: 0;
}
body.canvas-menu nav.navbar-static-side {
z-index: 2001;
background: #2f4050;
height: 100%;
position: fixed;
display: none;
}
body.canvas-menu.mini-navbar nav.navbar-static-side {
display: block;
width: 220px;
}
.top-navigation #page-wrapper {
width: 100%;
}
.top-navigation .navbar-nav .dropdown-menu > .active > a {
background: white;
color: #1ab394;
font-weight: bold;
}
.white-bg .navbar-fixed-top,
.white-bg .navbar-static-top {
background: #fff;
}
.top-navigation .navbar {
margin-bottom: 0;
}
.top-navigation .nav > li > a {
padding: 15px 20px;
color: #676a6c;
}
.top-navigation .nav > li a:hover,
.top-navigation .nav > li a:focus {
background: #fff;
color: #1ab394;
}
.top-navigation .navbar .nav > li.active {
background: #fff;
border: none;
}
.top-navigation .nav > li.active > a {
color: #1ab394;
}
.top-navigation .navbar-right {
margin-right: 10px;
}
.top-navigation .navbar-nav .dropdown-menu {
box-shadow: none;
border: 1px solid #e7eaec;
}
.top-navigation .dropdown-menu > li > a {
margin: 0;
padding: 7px 20px;
}
.navbar .dropdown-menu {
margin-top: 0;
}
.top-navigation .navbar-brand {
background: #1ab394;
color: #fff;
padding: 15px 25px;
font-size: 18px;
line-height: 20px;
}
.top-navigation .navbar-top-links li:last-child {
margin-right: 0;
}
.top-navigation.mini-navbar #page-wrapper,
.top-navigation.body-small.fixed-sidebar.mini-navbar #page-wrapper,
.mini-navbar .top-navigation #page-wrapper,
.body-small.fixed-sidebar.mini-navbar .top-navigation #page-wrapper,
.canvas-menu #page-wrapper {
margin: 0;
width: 100%;
}
.top-navigation.fixed-nav #wrapper,
.fixed-nav #wrapper.top-navigation {
margin-top: 50px;
}
.top-navigation .footer.fixed {
margin-left: 0 !important;
}
.top-navigation .wrapper.wrapper-content {
padding: 40px;
}
.top-navigation.body-small .wrapper.wrapper-content,
.body-small .top-navigation .wrapper.wrapper-content {
padding: 40px 0 40px 0;
}
.navbar-toggler {
background-color: #1ab394;
color: #fff;
padding: 6px 12px;
font-size: 14px;
margin: 8px;
}
.top-navigation .navbar-nav .open .dropdown-menu > li > a,
.top-navigation .navbar-nav .open .dropdown-menu .dropdown-header {
padding: 10px 15px 10px 20px;
}
@media (max-width: 768px) {
.top-navigation .navbar-header {
display: block;
float: none;
}
}
.menu-visible-lg,
.menu-visible-md {
display: none !important;
}
@media (min-width: 1200px) {
.menu-visible-lg {
display: block !important;
}
}
@media (min-width: 992px) {
.menu-visible-md {
display: block !important;
}
}
@media (max-width: 767px) {
.menu-visible-md {
display: block !important;
}
.menu-visible-lg {
display: block !important;
}
}
button:focus {
outline: 0 !important;
}
.btn {
border-radius: 3px;
font-size: inherit;
}
.btn:focus {
box-shadow: none;
}
.btn-xs {
font-size: 0.7rem;
padding: 0.2rem 0.4rem;
}
.btn-group-sm > .btn,
.btn-sm {
font-size: .8rem;
}
.float-e-margins .btn {
margin-bottom: 5px;
}
.btn-w-m {
min-width: 120px;
}
.btn-primary.btn-outline {
color: #1ab394;
}
.btn-success.btn-outline {
color: #1c84c6;
}
.btn-info.btn-outline {
color: #23c6c8;
}
.btn-warning.btn-outline {
color: #f8ac59;
}
.btn-danger.btn-outline {
color: #ed5565;
}
.btn-primary.btn-outline:hover,
.btn-success.btn-outline:hover,
.btn-info.btn-outline:hover,
.btn-warning.btn-outline:hover,
.btn-danger.btn-outline:hover {
color: #fff;
}
.btn.active,
.btn:active {
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-primary {
color: #fff;
background-color: #1ab394;
border-color: #1ab394;
}
.btn-primary:hover,
.btn-primary:focus,
.btn-primary.focus {
background-color: #18a689;
border-color: #18a689;
color: #FFFFFF;
}
.btn-primary.disabled,
.btn-primary:disabled {
color: #fff;
background-color: #18a689;
border-color: #18a689;
}
.btn-primary:not(:disabled):not(.disabled):active,
.btn-primary:not(:disabled):not(.disabled).active,
.show > .btn-primary.dropdown-toggle {
color: #fff;
background-color: #18a689;
border-color: #18a689;
}
.btn-primary:not(:disabled):not(.disabled):active:focus,
.btn-primary:not(:disabled):not(.disabled).active:focus,
.show > .btn-primary.dropdown-toggle:focus {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-success {
color: #fff;
background-color: #1c84c6;
border-color: #1c84c6;
}
.btn-success:hover,
.btn-success:focus,
.btn-success.focus {
color: #fff;
background-color: #1a7bb9;
border-color: #1a7bb9;
}
.btn-success.disabled,
.btn-success:disabled {
color: #fff;
background-color: #1a7bb9;
border-color: #1a7bb9;
}
.btn-success:not(:disabled):not(.disabled):active,
.btn-success:not(:disabled):not(.disabled).active,
.show > .btn-success.dropdown-toggle {
color: #fff;
background-color: #1a7bb9;
border-color: #1a7bb9;
}
.btn-success:not(:disabled):not(.disabled):active:focus,
.btn-success:not(:disabled):not(.disabled).active:focus,
.show > .btn-success.dropdown-toggle:focus {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-info {
color: #fff;
background-color: #23c6c8;
border-color: #23c6c8;
}
.btn-info:hover,
.btn-info:focus,
.btn-info.focus {
color: #fff;
background-color: #21b9bb;
border-color: #21b9bb;
}
.btn-info.disabled,
.btn-info:disabled {
color: #fff;
background-color: #21b9bb;
border-color: #21b9bb;
}
.btn-info:not(:disabled):not(.disabled):active,
.btn-info:not(:disabled):not(.disabled).active,
.show > .btn-info.dropdown-toggle {
color: #fff;
background-color: #21b9bb;
border-color: #21b9bb;
}
.btn-info:not(:disabled):not(.disabled):active:focus,
.btn-info:not(:disabled):not(.disabled).active:focus,
.show > .btn-info.dropdown-toggle:focus {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-default {
color: inherit;
background: white;
border: 1px solid #e7eaec;
}
.btn-default:hover,
.btn-default:focus,
.btn-default:active,
.btn-default.active,
.open .dropdown-toggle.btn-default,
.btn-default:active:focus,
.btn-default:active:hover,
.btn-default.active:hover,
.btn-default.active:focus {
color: inherit;
border: 1px solid #d2d2d2;
}
.btn-default:active,
.btn-default.active,
.open .dropdown-toggle.btn-default {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15) inset;
}
.btn-default.disabled,
.btn-default.disabled:hover,
.btn-default.disabled:focus,
.btn-default.disabled:active,
.btn-default.disabled.active,
.btn-default[disabled],
.btn-default[disabled]:hover,
.btn-default[disabled]:focus,
.btn-default[disabled]:active,
.btn-default.active[disabled],
fieldset[disabled] .btn-default,
fieldset[disabled] .btn-default:hover,
fieldset[disabled] .btn-default:focus,
fieldset[disabled] .btn-default:active,
fieldset[disabled] .btn-default.active {
color: #cacaca;
}
.btn-warning {
color: #ffffff;
background-color: #f8ac59;
border-color: #f8ac59;
}
.btn-warning:hover,
.btn-warning:focus,
.btn-warning.focus {
color: #ffffff;
background-color: #f7a54a;
border-color: #f7a54a;
}
.btn-warning.disabled,
.btn-warning:disabled {
color: #ffffff;
background-color: #f7a54a;
border-color: #f7a54a;
}
.btn-warning:not(:disabled):not(.disabled):active,
.btn-warning:not(:disabled):not(.disabled).active,
.show > .btn-warning.dropdown-toggle {
color: #ffffff;
background-color: #f7a54a;
border-color: #f7a54a;
}
.btn-warning:not(:disabled):not(.disabled):active:focus,
.btn-warning:not(:disabled):not(.disabled).active:focus,
.show > .btn-warning.dropdown-toggle:focus {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-danger {
color: #fff;
background-color: #ed5565;
border-color: #ed5565;
}
.btn-danger:hover,
.btn-danger:focus,
.btn-danger.focus {
color: #fff;
background-color: #ec4758;
border-color: #ec4758;
}
.btn-danger.disabled,
.btn-danger:disabled {
color: #fff;
background-color: #ec4758;
border-color: #ec4758;
}
.btn-danger:not(:disabled):not(.disabled):active,
.btn-danger:not(:disabled):not(.disabled).active,
.show > .btn-danger.dropdown-toggle {
color: #fff;
background-color: #ec4758;
border-color: #ec4758;
}
.btn-danger:not(:disabled):not(.disabled):active:focus,
.btn-danger:not(:disabled):not(.disabled).active:focus,
.show > .btn-danger.dropdown-toggle:focus {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-link {
color: inherit;
}
.btn-link:hover,
.btn-link:focus,
.btn-link:active,
.btn-link.active,
.open .dropdown-toggle.btn-link {
color: #1ab394;
text-decoration: none;
}
.btn-link:active,
.btn-link.active,
.open .dropdown-toggle.btn-link {
background-image: none;
box-shadow: none;
}
.btn-link.disabled,
.btn-link.disabled:hover,
.btn-link.disabled:focus,
.btn-link.disabled:active,
.btn-link.disabled.active,
.btn-link[disabled],
.btn-link[disabled]:hover,
.btn-link[disabled]:focus,
.btn-link[disabled]:active,
.btn-link.active[disabled],
fieldset[disabled] .btn-link,
fieldset[disabled] .btn-link:hover,
fieldset[disabled] .btn-link:focus,
fieldset[disabled] .btn-link:active,
fieldset[disabled] .btn-link.active {
color: #cacaca;
}
.btn-white {
color: inherit;
background: white;
border: 1px solid #e7eaec;
}
.btn-white:hover,
.btn-white:focus,
.btn-white:active,
.btn-white.active,
.open .dropdown-toggle.btn-white,
.btn-white:active:focus,
.btn-white:active:hover,
.btn-white.active:hover,
.btn-white.active:focus {
color: inherit;
border: 1px solid #d2d2d2;
}
.btn-white:active,
.btn-white.active {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15) inset;
}
.btn-white:active,
.btn-white.active,
.open .dropdown-toggle.btn-white {
background-image: none;
}
.btn-white.disabled,
.btn-white.disabled:hover,
.btn-white.disabled:focus,
.btn-white.disabled:active,
.btn-white.disabled.active,
.btn-white[disabled],
.btn-white[disabled]:hover,
.btn-white[disabled]:focus,
.btn-white[disabled]:active,
.btn-white.active[disabled],
fieldset[disabled] .btn-white,
fieldset[disabled] .btn-white:hover,
fieldset[disabled] .btn-white:focus,
fieldset[disabled] .btn-white:active,
fieldset[disabled] .btn-white.active {
color: #cacaca;
}
.form-control,
.form-control:focus,
.has-error .form-control:focus,
.has-success .form-control:focus,
.has-warning .form-control:focus,
.navbar-collapse,
.navbar-form,
.navbar-form-custom .form-control:focus,
.navbar-form-custom .form-control:hover,
.open .btn.dropdown-toggle,
.panel,
.popover,
.progress,
.progress-bar {
box-shadow: none;
}
.btn-outline {
color: inherit;
background-color: transparent;
transition: all .5s;
}
.btn-rounded {
border-radius: 50px;
}
.btn-large-dim {
width: 90px;
height: 90px;
font-size: 42px;
}
button.dim {
display: inline-block;
text-decoration: none;
text-transform: uppercase;
text-align: center;
padding-top: 6px;
margin-right: 10px;
position: relative;
cursor: pointer;
border-radius: 5px;
font-weight: 600;
margin-bottom: 20px !important;
}
button.dim:active {
top: 3px;
}
button.btn-primary.dim {
box-shadow: inset 0 0 0 #16987e, 0 5px 0 0 #16987e, 0 10px 5px #999999 !important;
}
button.btn-primary.dim:active {
box-shadow: inset 0 0 0 #16987e, 0 2px 0 0 #16987e, 0 5px 3px #999999 !important;
}
button.btn-default.dim {
box-shadow: inset 0 0 0 #b3b3b3, 0 5px 0 0 #b3b3b3, 0 10px 5px #999999 !important;
}
button.btn-default.dim:active {
box-shadow: inset 0 0 0 #b3b3b3, 0 2px 0 0 #b3b3b3, 0 5px 3px #999999 !important;
}
button.btn-warning.dim {
box-shadow: inset 0 0 0 #f79d3c, 0 5px 0 0 #f79d3c, 0 10px 5px #999999 !important;
}
button.btn-warning.dim:active {
box-shadow: inset 0 0 0 #f79d3c, 0 2px 0 0 #f79d3c, 0 5px 3px #999999 !important;
}
button.btn-info.dim {
box-shadow: inset 0 0 0 #1eacae, 0 5px 0 0 #1eacae, 0 10px 5px #999999 !important;
}
button.btn-info.dim:active {
box-shadow: inset 0 0 0 #1eacae, 0 2px 0 0 #1eacae, 0 5px 3px #999999 !important;
}
button.btn-success.dim {
box-shadow: inset 0 0 0 #1872ab, 0 5px 0 0 #1872ab, 0 10px 5px #999999 !important;
}
button.btn-success.dim:active {
box-shadow: inset 0 0 0 #1872ab, 0 2px 0 0 #1872ab, 0 5px 3px #999999 !important;
}
button.btn-danger.dim {
box-shadow: inset 0 0 0 #ea394c, 0 5px 0 0 #ea394c, 0 10px 5px #999999 !important;
}
button.btn-danger.dim:active {
box-shadow: inset 0 0 0 #ea394c, 0 2px 0 0 #ea394c, 0 5px 3px #999999 !important;
}
button.dim:before {
font-size: 50px;
line-height: 1em;
font-weight: normal;
color: #fff;
display: block;
padding-top: 10px;
}
button.dim:active:before {
top: 7px;
font-size: 50px;
}
.btn:focus {
outline: none !important;
}
.label {
background-color: #d1dade;
color: #5e5e5e;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 600;
padding: 3px 8px;
text-shadow: none;
border-radius: 0.25em;
line-height: 1;
white-space: nowrap;
}
.nav .label,
.ibox .label {
font-size: 10px;
}
.badge {
background-color: #d1dade;
color: #5e5e5e;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 11px;
font-weight: 600;
padding-bottom: 4px;
padding-left: 6px;
padding-right: 6px;
text-shadow: none;
white-space: nowrap;
}
.label-primary,
.badge-primary {
background-color: #1ab394;
color: #FFFFFF;
}
.label-success,
.badge-success {
background-color: #1c84c6;
color: #FFFFFF;
}
.label-warning,
.badge-warning {
background-color: #f8ac59;
color: #FFFFFF;
}
.label-warning-light,
.badge-warning-light {
background-color: #f8ac59;
color: #ffffff;
}
.label-danger,
.badge-danger {
background-color: #ed5565;
color: #FFFFFF;
}
.label-info,
.badge-info {
background-color: #23c6c8;
color: #FFFFFF;
}
.label-inverse,
.badge-inverse {
background-color: #262626;
color: #FFFFFF;
}
.label-white,
.badge-white {
background-color: #FFFFFF;
color: #5E5E5E;
}
.label-white,
.badge-disable {
background-color: #2A2E36;
color: #8B91A0;
}
/* TOOGLE SWICH */
.onoffswitch {
position: relative;
width: 64px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.onoffswitch-checkbox {
display: none;
}
.onoffswitch-label {
display: block;
overflow: hidden;
cursor: pointer;
border: 2px solid #1ab394;
border-radius: 2px;
}
.onoffswitch-inner {
width: 200%;
margin-left: -100%;
-moz-transition: margin 0.3s ease-in 0s;
-webkit-transition: margin 0.3s ease-in 0s;
-o-transition: margin 0.3s ease-in 0s;
transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before,
.onoffswitch-inner:after {
float: left;
width: 50%;
height: 20px;
padding: 0;
line-height: 20px;
font-size: 12px;
color: white;
font-family: Trebuchet, Arial, sans-serif;
font-weight: bold;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.onoffswitch-inner:before {
content: "ON";
padding-left: 10px;
background-color: #1ab394;
color: #FFFFFF;
}
.onoffswitch-inner:after {
content: "OFF";
padding-right: 10px;
background-color: #FFFFFF;
color: #999999;
text-align: right;
}
.onoffswitch-switch {
width: 20px;
margin: 0;
background: #FFFFFF;
border: 2px solid #1ab394;
border-radius: 2px;
position: absolute;
top: 0;
bottom: 0;
right: 44px;
-moz-transition: all 0.3s ease-in 0s;
-webkit-transition: all 0.3s ease-in 0s;
-o-transition: all 0.3s ease-in 0s;
transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
right: 0;
}
.onoffswitch-checkbox:disabled + .onoffswitch-label .onoffswitch-inner:before {
background-color: #919191;
}
.onoffswitch-checkbox:disabled + .onoffswitch-label,
.onoffswitch-checkbox:disabled + .onoffswitch-label .onoffswitch-switch {
border-color: #919191;
}
/* CHOSEN PLUGIN */
.chosen-container-single .chosen-single {
background: #ffffff;
box-shadow: none;
-moz-box-sizing: border-box;
border-radius: 2px;
cursor: text;
height: auto !important;
margin: 0;
min-height: 30px;
overflow: hidden;
padding: 4px 12px;
position: relative;
width: 100%;
}
.chosen-container-multi .chosen-choices li.search-choice {
background: #f1f1f1;
border: 1px solid #e5e6e7;
border-radius: 2px;
box-shadow: none;
color: #333333;
cursor: default;
line-height: 13px;
margin: 3px 0 3px 5px;
padding: 3px 20px 3px 5px;
position: relative;
}
/* Tags Input Plugin */
.bootstrap-tagsinput {
border: 1px solid #e5e6e7;
box-shadow: none;
}
/* PAGINATIN */
.pagination > .active > a,
.pagination > .active > span,
.pagination > .active > a:hover,
.pagination > .active > span:hover,
.pagination > .active > a:focus,
.pagination > .active > span:focus {
border-color: #DDDDDD;
cursor: default;
z-index: 2;
}
.pagination > li > a,
.pagination > li > span {
background-color: #FFFFFF;
border: 1px solid #DDDDDD;
color: inherit;
float: left;
line-height: 1.42857;
margin-left: -1px;
padding: 4px 10px;
position: relative;
text-decoration: none;
}
.page-item.active .page-link {
background-color: #1ab394;
border-color: #1ab394;
}
.page-link:focus {
box-shadow: none;
}
.page-link:hover {
color: #676a6c;
}
.pagination .footable-page.active a {
background-color: #1ab394;
border-color: #1ab394;
color: white;
}
/* TOOLTIPS */
.tooltip-inner {
background-color: #2f4050;
}
.tooltip.top .tooltip-arrow {
border-top-color: #2f4050;
}
.tooltip.right .tooltip-arrow {
border-right-color: #2f4050;
}
.tooltip.bottom .tooltip-arrow {
border-bottom-color: #2f4050;
}
.tooltip.left .tooltip-arrow {
border-left-color: #2f4050;
}
/* EASY PIE CHART*/
.easypiechart {
position: relative;
text-align: center;
}
.easypiechart .h2 {
margin-left: 10px;
margin-top: 10px;
display: inline-block;
}
.easypiechart canvas {
top: 0;
left: 0;
}
.easypiechart .easypie-text {
line-height: 1;
position: absolute;
top: 33px;
width: 100%;
z-index: 1;
}
.easypiechart img {
margin-top: -4px;
}
.jqstooltip {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* FULLCALENDAR */
.fc-state-default {
background-color: #ffffff;
background-image: none;
background-repeat: repeat-x;
box-shadow: none;
color: #333333;
text-shadow: none;
}
.fc-state-default {
border: 1px solid;
}
.fc-button {
color: inherit;
border: 1px solid #e7eaec;
cursor: pointer;
display: inline-block;
height: 1.9em;
line-height: 1.9em;
overflow: hidden;
padding: 0 0.6em;
position: relative;
white-space: nowrap;
}
.fc-state-active {
background-color: #1ab394;
border-color: #1ab394;
color: #ffffff;
}
.fc-header-title h2 {
font-size: 16px;
font-weight: 600;
color: inherit;
}
.fc-content .fc-widget-header,
.fc-content .fc-widget-content {
border-color: #e7eaec;
font-weight: normal;
}
.fc-border-separate tbody {
background-color: #F8F8F8;
}
.fc-state-highlight {
background: none repeat scroll 0 0 #FCF8E3;
}
.external-event {
padding: 5px 10px;
border-radius: 2px;
cursor: pointer;
margin-bottom: 5px;
}
.fc-ltr .fc-event-hori.fc-event-end,
.fc-rtl .fc-event-hori.fc-event-start {
border-radius: 2px;
}
.fc-event,
.fc-agenda .fc-event-time,
.fc-event a {
padding: 4px 6px;
background-color: #1ab394;
/* background color */
border-color: #1ab394;
/* border color */
}
.fc-event-time,
.fc-event-title {
color: #717171;
padding: 0 1px;
}
.ui-calendar .fc-event-time,
.ui-calendar .fc-event-title {
color: #fff;
}
.fc-event-container a.fc-event {
color: #fff;
}
/* Chat */
.chat-activity-list .chat-element {
border-bottom: 1px solid #e7eaec;
}
.chat-element:first-child {
margin-top: 0;
}
.chat-element {
padding-bottom: 15px;
}
.chat-element,
.chat-element .media {
margin-top: 15px;
}
.chat-element,
.media-body {
overflow: hidden;
}
.chat-element .media-body {
display: block;
width: auto;
}
.chat-element > .float-left {
margin-right: 10px;
}
.chat-element img.rounded-circle,
.dropdown-messages-box img.rounded-circle {
width: 38px;
height: 38px;
}
.chat-element .well {
border: 1px solid #e7eaec;
box-shadow: none;
margin-top: 10px;
margin-bottom: 5px;
padding: 10px 20px;
font-size: 11px;
line-height: 16px;
}
.chat-element .actions {
margin-top: 10px;
}
.chat-element .photos {
margin: 10px 0;
}
.right.chat-element > .float-right {
margin-left: 10px;
}
.chat-photo {
max-height: 180px;
border-radius: 4px;
overflow: hidden;
margin-right: 10px;
margin-bottom: 10px;
}
.chat {
margin: 0;
padding: 0;
list-style: none;
}
.chat li {
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px dotted #B3A9A9;
}
.chat li.left .chat-body {
margin-left: 60px;
}
.chat li.right .chat-body {
margin-right: 60px;
}
.chat li .chat-body p {
margin: 0;
color: #777777;
}
.panel .slidedown .glyphicon,
.chat .glyphicon {
margin-right: 5px;
}
.chat-panel .panel-body {
height: 350px;
overflow-y: scroll;
}
/* LIST GROUP */
a.list-group-item.active,
a.list-group-item.active:hover,
a.list-group-item.active:focus {
background-color: #1ab394;
border-color: #1ab394;
color: #FFFFFF;
z-index: 2;
}
.list-group-item-heading {
margin-top: 10px;
}
.list-group-item-text {
margin: 0 0 10px;
color: inherit;
font-size: 12px;
line-height: inherit;
}
.no-padding .list-group-item {
border-left: none;
border-right: none;
border-bottom: none;
}
.no-padding .list-group-item:first-child {
border-left: none;
border-right: none;
border-bottom: none;
border-top: none;
}
.no-padding .list-group {
margin-bottom: 0;
}
.list-group-item {
background-color: inherit;
border: 1px solid #e7eaec;
display: block;
margin-bottom: -1px;
padding: 10px 15px;
position: relative;
}
.elements-list .list-group-item {
border-left: none;
border-right: none;
padding: 0;
}
.elements-list .list-group-item:first-child {
border-left: none;
border-right: none;
border-top: none !important;
}
.elements-list .list-group {
margin-bottom: 0;
}
.elements-list a {
color: inherit;
}
.elements-list .list-group-item a.active,
.elements-list .list-group-item a:hover {
background: #f3f3f4;
color: inherit;
border-color: #e7eaec;
border-radius: 0;
}
.elements-list li.active {
transition: none;
}
.elements-list .nav-link {
padding: 15px 25px;
}
.element-detail-box {
padding: 25px;
}
/* FLOT CHART */
.flot-chart {
display: block;
height: 200px;
}
.widget .flot-chart.dashboard-chart {
display: block;
height: 120px;
margin-top: 40px;
}
.flot-chart.dashboard-chart {
display: block;
height: 180px;
margin-top: 40px;
}
.flot-chart-content {
width: 100%;
height: 100%;
}
.flot-chart-pie-content {
width: 200px;
height: 200px;
margin: auto;
}
.jqstooltip {
position: absolute;
display: block;
left: 0;
top: 0;
visibility: hidden;
background: #2b303a;
background-color: rgba(43, 48, 58, 0.8);
color: white;
text-align: left;
white-space: nowrap;
z-index: 10000;
padding: 5px 5px 5px 5px;
min-height: 22px;
border-radius: 3px;
}
.jqsfield {
color: white;
text-align: left;
}
.fh-150 {
height: 150px;
}
.fh-200 {
height: 200px;
}
.h-150 {
min-height: 150px;
}
.h-200 {
min-height: 200px;
}
.h-300 {
min-height: 300px;
}
.w-150 {
min-width: 150px;
}
.w-200 {
min-width: 200px;
}
.w-300 {
min-width: 300px;
}
.legendLabel {
padding-left: 5px;
}
.stat-list li:first-child {
margin-top: 0;
}
.stat-list {
list-style: none;
padding: 0;
margin: 0;
}
.stat-percent {
float: right;
}
.stat-list li {
margin-top: 15px;
position: relative;
}
/* DATATABLES */
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc:after,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
background: transparent;
}
.dataTables_wrapper {
padding-bottom: 30px;
}
.dataTables_length {
float: left;
}
.dataTables_filter label {
margin-right: 5px;
}
.html5buttons {
float: right;
}
.html5buttons a {
border: 1px solid #e7eaec;
background: #fff;
color: #676a6c;
box-shadow: none;
padding: 6px 8px;
font-size: 12px;
}
.html5buttons a:hover,
.html5buttons a:focus:active {
background-color: #eee;
color: inherit;
border-color: #d2d2d2;
}
div.dt-button-info {
z-index: 100;
}
@media (max-width: 768px) {
.html5buttons {
float: none;
margin-top: 10px;
}
.dataTables_length {
float: none;
}
}
/* CIRCLE */
.img-circle {
border-radius: 50%;
}
.btn-circle {
width: 30px;
height: 30px;
padding: 6px 0;
border-radius: 15px;
text-align: center;
font-size: 12px;
line-height: 1.428571429;
}
.btn-circle.btn-lg {
width: 50px;
height: 50px;
padding: 10px 16px;
border-radius: 25px;
font-size: 18px;
line-height: 1.33;
}
.btn-circle.btn-xl {
width: 70px;
height: 70px;
padding: 10px 16px;
border-radius: 35px;
font-size: 24px;
line-height: 1.33;
}
.show-grid [class^="col-"] {
padding-top: 10px;
padding-bottom: 10px;
border: 1px solid #ddd;
background-color: #eee !important;
}
.show-grid {
margin: 15px 0;
}
/* ANIMATION */
.css-animation-box h1 {
font-size: 44px;
}
.animation-efect-links a {
padding: 4px 6px;
font-size: 12px;
}
#animation_box {
background-color: #f9f8f8;
border-radius: 16px;
width: 80%;
margin: 0 auto;
padding-top: 80px;
}
.animation-text-box {
position: absolute;
margin-top: 40px;
left: 50%;
margin-left: -100px;
width: 200px;
}
.animation-text-info {
position: absolute;
margin-top: -60px;
left: 50%;
margin-left: -100px;
width: 200px;
font-size: 10px;
}
.animation-text-box h2 {
font-size: 54px;
font-weight: 600;
margin-bottom: 5px;
}
.animation-text-box p {
font-size: 12px;
text-transform: uppercase;
}
/* PEACE */
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.pace-inactive {
display: none;
}
.pace .pace-progress {
background: #1ab394;
position: fixed;
z-index: 2040;
top: 0;
right: 100%;
width: 100%;
height: 2px;
}
.pace-inactive {
display: none;
}
/* WIDGETS */
.widget {
border-radius: 5px;
padding: 15px 20px;
margin-bottom: 10px;
margin-top: 10px;
}
.widget.style1 h2 {
font-size: 30px;
}
.widget h2,
.widget h3 {
margin-top: 5px;
margin-bottom: 0;
}
.widget-text-box {
padding: 20px;
border: 1px solid #e7eaec;
background: #ffffff;
}
.widget-head-color-box {
border-radius: 5px 5px 0 0;
margin-top: 10px;
}
.widget .flot-chart {
height: 100px;
}
.vertical-align div {
display: inline-block;
vertical-align: middle;
}
.vertical-align h2,
.vertical-align h3 {
margin: 0;
}
.todo-list {
list-style: none outside none;
margin: 0;
padding: 0;
font-size: 14px;
}
.todo-list.small-list {
font-size: 12px;
}
.todo-list.small-list > li {
background: #f3f3f4;
border-left: none;
border-right: none;
border-radius: 4px;
color: inherit;
margin-bottom: 2px;
padding: 6px 6px 6px 12px;
}
.todo-list.small-list .btn-xs,
.todo-list.small-list .btn-group-xs > .btn {
border-radius: 5px;
font-size: 10px;
line-height: 1.5;
padding: 1px 2px 1px 5px;
}
.todo-list > li {
background: #f3f3f4;
border-left: 6px solid #e7eaec;
border-right: 6px solid #e7eaec;
border-radius: 4px;
color: inherit;
margin-bottom: 2px;
padding: 10px;
}
.todo-list .handle {
cursor: move;
display: inline-block;
font-size: 16px;
margin: 0 5px;
}
.todo-list > li .label {
font-size: 9px;
margin-left: 10px;
}
.check-link {
font-size: 16px;
}
.todo-completed {
text-decoration: line-through;
}
.geo-statistic h1 {
font-size: 36px;
margin-bottom: 0;
}
.glyphicon.fa {
font-family: "FontAwesome";
}
/* INPUTS */
.inline {
display: inline-block !important;
}
.input-s-sm {
width: 120px;
}
.input-s {
width: 200px;
}
.form-control {
font-size: 0.9rem;
}
select.form-control:not([size]):not([multiple]) {
height: 2.05rem;
}
.input-sm,
.form-control-sm {
height: 31px;
}
.input-s-lg {
width: 250px;
}
.i-checks {
padding-left: 0;
}
.form-control,
.single-line {
background-color: #FFFFFF;
background-image: none;
border: 1px solid #e5e6e7;
border-radius: 1px;
color: inherit;
display: block;
padding: 6px 12px;
transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s;
width: 100%;
}
.form-control:focus,
.single-line:focus {
border-color: #1ab394;
}
.has-success .form-control,
.has-success .form-control:focus {
border-color: #1ab394;
}
.has-warning .form-control,
.has-warning .form-control:focus {
border-color: #f8ac59;
}
.has-error .form-control,
.has-error .form-control:focus {
border-color: #ed5565;
}
.has-success .control-label {
color: #1ab394;
}
.has-warning .control-label {
color: #f8ac59;
}
.has-error .control-label {
color: #ed5565;
}
.input-group-addon {
background-color: #fff;
border: 1px solid #E5E6E7;
border-radius: 1px;
color: inherit;
font-size: 14px;
font-weight: 400;
line-height: 1;
padding: 9px 12px 4px 12px;
text-align: center;
}
.input-daterange .input-group-addon {
margin: 0;
}
.input-group.date .input-group-addon {
border-right: 0;
}
.spinner-buttons.input-group-btn .btn-xs {
line-height: 1.13;
}
.spinner-buttons.input-group-btn {
width: 20%;
}
.noUi-connect {
background: none repeat scroll 0 0 #1ab394;
box-shadow: none;
}
.slider_red .noUi-connect {
background: none repeat scroll 0 0 #ed5565;
box-shadow: none;
}
/* UI Sortable */
.ui-sortable .ibox-title {
cursor: move;
}
.ui-sortable-placeholder {
border: 1px dashed #cecece !important;
visibility: visible !important;
background: #e7eaec;
}
.ibox.ui-sortable-placeholder {
margin: 0 0 23px !important;
}
/* SWITCHES */
.onoffswitch {
position: relative;
width: 54px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.onoffswitch-checkbox {
display: none;
}
.onoffswitch-label {
display: block;
overflow: hidden;
cursor: pointer;
border: 2px solid #1AB394;
border-radius: 3px;
}
.onoffswitch-inner {
display: block;
width: 200%;
margin-left: -100%;
-moz-transition: margin 0.3s ease-in 0s;
-webkit-transition: margin 0.3s ease-in 0s;
-o-transition: margin 0.3s ease-in 0s;
transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before,
.onoffswitch-inner:after {
display: block;
float: left;
width: 50%;
height: 16px;
padding: 0;
line-height: 16px;
font-size: 10px;
color: white;
font-family: Trebuchet, Arial, sans-serif;
font-weight: bold;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.onoffswitch-inner:before {
content: "ON";
padding-left: 7px;
background-color: #1AB394;
color: #FFFFFF;
}
.onoffswitch-inner:after {
content: "OFF";
padding-right: 7px;
background-color: #FFFFFF;
color: #919191;
text-align: right;
}
.onoffswitch-switch {
display: block;
width: 18px;
margin: 0;
background: #FFFFFF;
border: 2px solid #1AB394;
border-radius: 3px;
position: absolute;
top: 0;
bottom: 0;
right: 36px;
-moz-transition: all 0.3s ease-in 0s;
-webkit-transition: all 0.3s ease-in 0s;
-o-transition: all 0.3s ease-in 0s;
transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
right: 0;
}
/* jqGrid */
.ui-jqgrid {
-moz-box-sizing: content-box;
}
.ui-jqgrid-btable {
border-collapse: separate;
}
.ui-jqgrid-htable {
border-collapse: separate;
}
.ui-jqgrid-titlebar {
height: 40px;
line-height: 15px;
color: #676a6c;
background-color: #F9F9F9;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
.ui-jqgrid .ui-jqgrid-title {
float: left;
margin: 1.1em 1em 0.2em;
}
.ui-jqgrid .ui-jqgrid-titlebar {
position: relative;
border-left: 0 solid;
border-right: 0 solid;
border-top: 0 solid;
}
.ui-widget-header {
background: none;
background-image: none;
background-color: #f5f5f6;
text-transform: uppercase;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.ui-jqgrid tr.ui-row-ltr td {
border-right-color: inherit;
border-right-style: solid;
border-right-width: 1px;
text-align: left;
border-color: #DDDDDD;
background-color: inherit;
}
.ui-search-toolbar input[type="text"] {
font-size: 12px;
height: 15px;
border: 1px solid #CCCCCC;
border-radius: 0;
}
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default {
background: #F9F9F9;
border: 1px solid #DDDDDD;
line-height: 15px;
font-weight: bold;
color: #676a6c;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
.ui-widget-content {
box-sizing: content-box;
}
.ui-icon-triangle-1-n {
background-position: 1px -16px;
}
.ui-jqgrid tr.ui-search-toolbar th {
border-top-width: 0 !important;
border-top-color: inherit !important;
border-top-style: ridge !important;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus {
background: #f5f5f5;
border-collapse: separate;
}
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight {
background: #f2fbff;
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active {
border: 1px solid #dddddd;
background: #ffffff;
font-weight: normal;
color: #212121;
}
.ui-jqgrid .ui-pg-input {
font-size: inherit;
width: 50px;
border: 1px solid #CCCCCC;
height: 15px;
}
.ui-jqgrid .ui-pg-selbox {
display: block;
font-size: 1em;
height: 25px;
line-height: 18px;
margin: 0;
width: auto;
}
.ui-jqgrid .ui-pager-control {
position: relative;
}
.ui-jqgrid .ui-jqgrid-pager {
height: 32px;
position: relative;
}
.ui-pg-table .navtable .ui-corner-all {
border-radius: 0;
}
.ui-jqgrid .ui-pg-button:hover {
padding: 1px;
border: 0;
}
.ui-jqgrid .loading {
position: absolute;
top: 45%;
left: 45%;
width: auto;
height: auto;
z-index: 101;
padding: 6px;
margin: 5px;
text-align: center;
font-weight: bold;
display: none;
border-width: 2px !important;
font-size: 11px;
}
.ui-jqgrid .form-control {
height: 10px;
width: auto;
display: inline;
padding: 10px 12px;
}
.ui-jqgrid-pager {
height: 32px;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-left,
.ui-corner-tl {
border-top-left-radius: 0;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-right,
.ui-corner-tr {
border-top-right-radius: 0;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-left,
.ui-corner-bl {
border-bottom-left-radius: 0;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-br {
border-bottom-right-radius: 0;
}
.ui-widget-content {
border: 1px solid #ddd;
}
.ui-jqgrid .ui-jqgrid-titlebar {
padding: 0;
}
.ui-jqgrid .ui-jqgrid-titlebar {
border-bottom: 1px solid #ddd;
}
.ui-jqgrid tr.jqgrow td {
padding: 6px;
}
.ui-jqdialog .ui-jqdialog-titlebar {
padding: 10px 10px;
}
.ui-jqdialog .ui-jqdialog-title {
float: none !important;
}
.ui-jqdialog > .ui-resizable-se {
position: absolute;
}
/* Nestable list */
.dd {
position: relative;
display: block;
margin: 0;
padding: 0;
list-style: none;
font-size: 13px;
line-height: 20px;
}
.dd-list {
display: block;
position: relative;
margin: 0;
padding: 0;
list-style: none;
}
.dd-list .dd-list {
padding-left: 30px;
}
.dd-collapsed .dd-list {
display: none;
}
.dd-item,
.dd-empty,
.dd-placeholder {
display: block;
position: relative;
margin: 0;
padding: 0;
min-height: 20px;
font-size: 13px;
line-height: 20px;
}
.dd-handle {
display: block;
margin: 5px 0;
padding: 5px 10px;
color: #333;
text-decoration: none;
border: 1px solid #e7eaec;
background: #f5f5f5;
-webkit-border-radius: 3px;
border-radius: 3px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.dd-handle span {
font-weight: bold;
}
.dd-handle:hover {
background: #f0f0f0;
cursor: pointer;
font-weight: bold;
}
.dd-item > button {
display: block;
position: relative;
cursor: pointer;
float: left;
width: 25px;
height: 20px;
margin: 5px 0;
padding: 0;
text-indent: 100%;
white-space: nowrap;
overflow: hidden;
border: 0;
background: transparent;
font-size: 12px;
line-height: 1;
text-align: center;
font-weight: bold;
}
.dd-item > button:before {
content: '+';
display: block;
position: absolute;
width: 100%;
text-align: center;
text-indent: 0;
}
.dd-item > button[data-action="collapse"]:before {
content: '-';
}
#nestable2 .dd-item > button {
font-family: FontAwesome;
height: 34px;
width: 33px;
color: #c1c1c1;
}
#nestable2 .dd-item > button:before {
content: "\f067";
}
#nestable2 .dd-item > button[data-action="collapse"]:before {
content: "\f068";
}
.dd-placeholder,
.dd-empty {
margin: 5px 0;
padding: 0;
min-height: 30px;
background: #f2fbff;
border: 1px dashed #b6bcbf;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.dd-empty {
border: 1px dashed #bbb;
min-height: 100px;
background-color: #e5e5e5;
background-image: -webkit-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), -webkit-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);
background-image: -moz-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), -moz-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);
background-image: linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);
background-size: 60px 60px;
background-position: 0 0, 30px 30px;
}
.dd-dragel {
position: absolute;
z-index: 9999;
pointer-events: none;
}
.dd-dragel > .dd-item .dd-handle {
margin-top: 0;
}
.dd-dragel .dd-handle {
-webkit-box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1);
box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1);
}
/**
* Nestable Extras
*/
.nestable-lists {
display: block;
clear: both;
padding: 30px 0;
width: 100%;
border: 0;
border-top: 2px solid #ddd;
border-bottom: 2px solid #ddd;
}
#nestable-menu {
padding: 0;
margin: 10px 0 20px 0;
}
#nestable-output,
#nestable2-output {
width: 100%;
font-size: 0.75em;
line-height: 1.333333em;
font-family: open sans, lucida grande, lucida sans unicode, helvetica, arial, sans-serif;
padding: 5px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
#nestable2 .dd-handle {
color: inherit;
border: 1px dashed #e7eaec;
background: #f3f3f4;
padding: 10px;
}
#nestable2 span.label {
margin-right: 10px;
}
#nestable-output,
#nestable2-output {
font-size: 12px;
padding: 25px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
/* CodeMirror */
.CodeMirror {
border: 1px solid #eee;
height: auto;
}
.CodeMirror-scroll {
overflow-y: hidden;
overflow-x: auto;
}
/* Google Maps */
.google-map {
height: 300px;
}
/* Validation */
label.error {
color: #cc5965;
display: inline-block;
margin-left: 5px;
}
.form-control.error {
border: 1px dotted #cc5965;
}
/* ngGrid */
.gridStyle {
border: 1px solid #d4d4d4;
width: 100%;
height: 400px;
}
.gridStyle2 {
border: 1px solid #d4d4d4;
width: 500px;
height: 300px;
}
.ngH eaderCell {
border-right: none;
border-bottom: 1px solid #e7eaec;
}
.ngCell {
border-right: none;
}
.ngTopPanel {
background: #F5F5F6;
}
.ngRow.even {
background: #f9f9f9;
}
.ngRow.selected {
background: #EBF2F1;
}
.ngRow {
border-bottom: 1px solid #e7eaec;
}
.ngCell {
background-color: transparent;
}
.ngHeaderCell {
border-right: none;
}
/* Toastr custom style */
#toast-container > div {
-moz-box-shadow: 0 0 3px #999;
-webkit-box-shadow: 0 0 3px #999;
box-shadow: 0 0 3px #999;
opacity: .9;
-ms-filter: alpha(opacity=90);
filter: alpha(opacity=90);
}
#toast-container > :hover {
-moz-box-shadow: 0 0 4px #999;
-webkit-box-shadow: 0 0 4px #999;
box-shadow: 0 0 4px #999;
opacity: 1;
-ms-filter: alpha(opacity=100);
filter: alpha(opacity=100);
cursor: pointer;
}
.toast {
background-color: #1ab394;
border-color: #e7eaec;
}
.toast-success {
background-color: #1ab394;
}
.toast-error {
background-color: #ed5565;
}
.toast-info {
background-color: #23c6c8;
}
.toast-warning {
background-color: #f8ac59;
}
.toast-top-full-width {
margin-top: 20px;
}
.toast-bottom-full-width {
margin-bottom: 20px;
}
.toast {
z-index: 3000;
}
.toast.toast-bootstrap {
background-color: white;
}
.toast.toast-bootstrap .toast-body {
background-color: #fbfbfb;
font-size: .775rem;
}
/* Notifie */
.cg-notify-message.inspinia-notify {
background: #fff;
padding: 0;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.2);
border: none;
margin-top: 30px;
color: inherit;
}
.inspinia-notify.alert-warning {
border-left: 6px solid #f8ac59;
}
.inspinia-notify.alert-success {
border-left: 6px solid #1c84c6;
}
.inspinia-notify.alert-danger {
border-left: 6px solid #ed5565;
}
.inspinia-notify.alert-info {
border-left: 6px solid #1ab394;
}
/* Image cropper style */
.img-container,
.img-preview {
overflow: hidden;
text-align: center;
width: 100%;
}
.img-preview-sm {
height: 130px;
width: 200px;
}
/* Forum styles */
.forum-post-container .media {
margin: 10px 10px 10px 10px;
padding: 20px 10px 20px 10px;
border-bottom: 1px solid #f1f1f1;
}
.forum-avatar {
float: left;
margin-right: 20px;
text-align: center;
width: 110px;
}
.forum-avatar .rounded-circle {
height: 48px;
width: 48px;
}
.author-info {
color: #676a6c;
font-size: 11px;
margin-top: 5px;
text-align: center;
}
.forum-post-info {
padding: 9px 12px 6px 12px;
background: #f9f9f9;
border: 1px solid #f1f1f1;
}
.media-body > .media {
background: #f9f9f9;
border-radius: 3px;
border: 1px solid #f1f1f1;
}
.forum-post-container .media-body .photos {
margin: 10px 0;
}
.forum-photo {
max-width: 140px;
border-radius: 3px;
}
.media-body > .media .forum-avatar {
width: 70px;
margin-right: 10px;
}
.media-body > .media .forum-avatar .rounded-circle {
height: 38px;
width: 38px;
}
.mid-icon {
font-size: 66px;
}
.forum-item {
margin: 10px 0;
padding: 10px 0 20px;
border-bottom: 1px solid #f1f1f1;
}
.views-number {
font-size: 24px;
line-height: 18px;
font-weight: 400;
}
.forum-container,
.forum-post-container {
padding: 30px !important;
}
.forum-item small {
color: #999;
}
.forum-item .forum-sub-title {
color: #999;
margin-left: 50px;
}
.forum-title {
margin: 15px 0 15px 0;
}
.forum-info {
text-align: center;
}
.forum-desc {
color: #999;
}
.forum-icon {
float: left;
width: 30px;
margin-right: 20px;
text-align: center;
}
a.forum-item-title {
color: inherit;
display: block;
font-size: 18px;
font-weight: 600;
}
a.forum-item-title:hover {
color: inherit;
}
.forum-icon .fa {
font-size: 30px;
margin-top: 8px;
color: #9b9b9b;
}
.forum-item.active .fa {
color: #1ab394;
}
.forum-item.active a.forum-item-title {
color: #1ab394;
}
@media (max-width: 992px) {
.forum-info {
margin: 15px 0 10px 0;
/* Comment this is you want to show forum info in small devices */
display: none;
}
.forum-desc {
float: none !important;
}
}
/* New Timeline style */
.vertical-container {
/* this class is used to give a max-width to the element it is applied to, and center it horizontally when it reaches that max-width */
width: 90%;
max-width: 1170px;
margin: 0 auto;
}
.vertical-container::after {
/* clearfix */
content: '';
display: table;
clear: both;
}
#vertical-timeline {
position: relative;
padding: 0;
margin-top: 2em;
margin-bottom: 2em;
}
#vertical-timeline::before {
content: '';
position: absolute;
top: 0;
left: 18px;
height: 100%;
width: 4px;
background: #f1f1f1;
}
.vertical-timeline-content .btn {
float: right;
}
#vertical-timeline.light-timeline:before {
background: #e7eaec;
}
.dark-timeline .vertical-timeline-content:before {
border-color: transparent #f5f5f5 transparent transparent;
}
.dark-timeline.center-orientation .vertical-timeline-content:before {
border-color: transparent transparent transparent #f5f5f5;
}
.dark-timeline .vertical-timeline-block:nth-child(2n) .vertical-timeline-content:before,
.dark-timeline.center-orientation .vertical-timeline-block:nth-child(2n) .vertical-timeline-content:before {
border-color: transparent #f5f5f5 transparent transparent;
}
.dark-timeline .vertical-timeline-content,
.dark-timeline.center-orientation .vertical-timeline-content {
background: #f5f5f5;
}
@media only screen and (min-width: 1170px) {
#vertical-timeline.center-orientation {
margin-top: 3em;
margin-bottom: 3em;
}
#vertical-timeline.center-orientation:before {
left: 50%;
margin-left: -2px;
}
}
@media only screen and (max-width: 1170px) {
.center-orientation.dark-timeline .vertical-timeline-content:before {
border-color: transparent #f5f5f5 transparent transparent;
}
}
.vertical-timeline-block {
position: relative;
margin: 2em 0;
}
.vertical-timeline-block:after {
content: "";
display: table;
clear: both;
}
.vertical-timeline-block:first-child {
margin-top: 0;
}
.vertical-timeline-block:last-child {
margin-bottom: 0;
}
@media only screen and (min-width: 1170px) {
.center-orientation .vertical-timeline-block {
margin: 4em 0;
}
.center-orientation .vertical-timeline-block:first-child {
margin-top: 0;
}
.center-orientation .vertical-timeline-block:last-child {
margin-bottom: 0;
}
}
.vertical-timeline-icon {
position: absolute;
top: 0;
left: 0;
width: 40px;
height: 40px;
border-radius: 50%;
font-size: 16px;
border: 3px solid #f1f1f1;
text-align: center;
}
.vertical-timeline-icon i {
display: block;
width: 24px;
height: 24px;
position: relative;
left: 50%;
top: 50%;
margin-left: -12px;
margin-top: -9px;
}
@media only screen and (min-width: 1170px) {
.center-orientation .vertical-timeline-icon {
width: 50px;
height: 50px;
left: 50%;
margin-left: -25px;
-webkit-transform: translateZ(0);
-webkit-backface-visibility: hidden;
font-size: 19px;
}
.center-orientation .vertical-timeline-icon i {
margin-left: -12px;
margin-top: -10px;
}
.center-orientation .cssanimations .vertical-timeline-icon.is-hidden {
visibility: hidden;
}
}
.vertical-timeline-content {
position: relative;
margin-left: 60px;
background: white;
border-radius: 0.25em;
padding: 1em;
}
.vertical-timeline-content:after {
content: "";
display: table;
clear: both;
}
.vertical-timeline-content h2 {
font-weight: 400;
margin-top: 4px;
}
.vertical-timeline-content p {
margin: 1em 0;
line-height: 1.6;
}
.vertical-timeline-content .vertical-date {
float: left;
font-weight: 500;
}
.vertical-date small {
color: #1ab394;
font-weight: 400;
}
.vertical-timeline-content::before {
content: '';
position: absolute;
top: 16px;
right: 100%;
height: 0;
width: 0;
border: 7px solid transparent;
border-right: 7px solid white;
}
@media only screen and (min-width: 768px) {
.vertical-timeline-content h2 {
font-size: 18px;
}
.vertical-timeline-content p {
font-size: 13px;
}
}
@media only screen and (min-width: 1170px) {
.center-orientation .vertical-timeline-content {
margin-left: 0;
padding: 1.6em;
width: 45%;
}
.center-orientation .vertical-timeline-content::before {
top: 24px;
left: 100%;
border-color: transparent;
border-left-color: white;
}
.center-orientation .vertical-timeline-content .btn {
float: left;
}
.center-orientation .vertical-timeline-content .vertical-date {
position: absolute;
width: 100%;
left: 122%;
top: 2px;
font-size: 14px;
}
.center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content {
float: right;
}
.center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content::before {
top: 24px;
left: auto;
right: 100%;
border-color: transparent;
border-right-color: white;
}
.center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content .btn {
float: right;
}
.center-orientation .vertical-timeline-block:nth-child(even) .vertical-timeline-content .vertical-date {
left: auto;
right: 122%;
text-align: right;
}
.center-orientation .cssanimations .vertical-timeline-content.is-hidden {
visibility: hidden;
}
}
/* Tabs */
.tabs-container .panel-body {
background: #fff;
border: 1px solid #e7eaec;
border-radius: 2px;
padding: 20px;
position: relative;
}
.tabs-container .nav-tabs > li.active > a,
.tabs-container .nav-tabs > li.active > a:hover,
.tabs-container .nav-tabs > li.active > a:focus {
border: 1px solid #e7eaec;
border-bottom-color: transparent;
background-color: #fff;
}
.tabs-container .nav-tabs > li {
float: left;
margin-bottom: -1px;
}
.tabs-container .tab-pane .panel-body {
border-top: none;
}
.tabs-container .nav-tabs > li.active > a,
.tabs-container .nav-tabs > li.active > a:hover,
.tabs-container .nav-tabs > li.active > a:focus {
border: 1px solid #e7eaec;
border-bottom-color: transparent;
}
.tabs-container .nav-tabs {
border-bottom: 1px solid #e7eaec;
}
.tabs-container .tab-pane .panel-body {
border-top: none;
}
.tabs-container .tabs-left .tab-pane .panel-body,
.tabs-container .tabs-right .tab-pane .panel-body {
border-top: 1px solid #e7eaec;
}
.tabs-container .tabs-below > .nav-tabs,
.tabs-container .tabs-right > .nav-tabs,
.tabs-container .tabs-left > .nav-tabs {
border-bottom: 0;
}
.tabs-container .tabs-left .panel-body {
position: static;
}
.tabs-container .tabs-left > .nav-tabs,
.tabs-container .tabs-right > .nav-tabs {
width: 20%;
}
.tabs-container .tabs-left .panel-body {
width: 80%;
margin-left: 20%;
}
.tabs-container .tabs-right .panel-body {
width: 80%;
margin-right: 20%;
}
.tabs-container .tab-content > .tab-pane,
.tabs-container .pill-content > .pill-pane {
display: none;
}
.tabs-container .tab-content > .active,
.tabs-container .pill-content > .active {
display: block;
}
.tabs-container .tabs-below > .nav-tabs {
border-top: 1px solid #e7eaec;
}
.tabs-container .tabs-below > .nav-tabs > li {
margin-top: -1px;
margin-bottom: 0;
}
.tabs-container .tabs-below > .nav-tabs > li > a {
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
border-radius: 0 0 4px 4px;
}
.tabs-container .tabs-below > .nav-tabs > li > a:hover,
.tabs-container .tabs-below > .nav-tabs > li > a:focus {
border-top-color: #e7eaec;
border-bottom-color: transparent;
}
.tabs-container .tabs-left > .nav-tabs > li,
.tabs-container .tabs-right > .nav-tabs > li {
float: none;
word-break: break-word;
width: 100%;
}
.tabs-container .tabs-left > .nav-tabs > li > a,
.tabs-container .tabs-right > .nav-tabs > li > a {
margin-right: 0;
margin-bottom: 3px;
}
.tabs-container .tabs-left > .nav-tabs {
float: left;
margin-right: 19px;
}
.tabs-container .tabs-left > .nav-tabs > li > a {
margin-right: -1px;
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
.tabs-container .tabs-left > .nav-tabs a.active,
.tabs-container .tabs-left > .nav-tabs a.active:hover,
.tabs-container .tabs-left > .nav-tabs a.active:focus {
border-color: #e7eaec transparent #e7eaec #e7eaec;
}
.tabs-container .tabs-right > .nav-tabs {
float: right;
margin-left: 19px;
}
.tabs-container .tabs-right > .nav-tabs > li > a {
margin-left: -1px;
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0;
}
.tabs-container .tabs-right > .nav-tabs a.active,
.tabs-container .tabs-right > .nav-tabs a.active:hover,
.tabs-container .tabs-right > .nav-tabs a.active:focus {
border-color: #e7eaec #e7eaec #e7eaec transparent;
z-index: 1;
}
.tabs-container .tabs-right > .nav-tabs li {
z-index: 1;
}
.nav-tabs .nav-link:not(.active):focus,
.nav-tabs .nav-link:not(.active):hover {
border-color: transparent;
}
@media (max-width: 767px) {
.tabs-container .nav-tabs > li {
float: none !important;
}
.tabs-container .nav-tabs > li.active > a {
border-bottom: 1px solid #e7eaec !important;
margin: 0;
}
}
/* jsvectormap */
.jvectormap-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.jvectormap-tip {
position: absolute;
display: none;
border: solid 1px #CDCDCD;
border-radius: 3px;
background: #292929;
color: white;
font-family: sans-serif, Verdana;
font-size: smaller;
padding: 5px;
}
.jvectormap-zoomin,
.jvectormap-zoomout,
.jvectormap-goback {
position: absolute;
left: 10px;
border-radius: 3px;
background: #1ab394;
padding: 3px;
color: white;
cursor: pointer;
line-height: 10px;
text-align: center;
box-sizing: content-box;
}
.jvectormap-zoomin,
.jvectormap-zoomout {
width: 10px;
height: 10px;
}
.jvectormap-zoomin {
top: 10px;
}
.jvectormap-zoomout {
top: 30px;
}
.jvectormap-goback {
bottom: 10px;
z-index: 1000;
padding: 6px;
}
.jvectormap-spinner {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: center no-repeat url(data:image/gif;base64,R0lGODlhIAAgAPMAAP///wAAAMbGxoSEhLa2tpqamjY2NlZWVtjY2OTk5Ly8vB4eHgQEBAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHRLYKhKP1oZmADdEAAAh+QQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY/CZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB+A4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6+Ho7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq+B6QDtuetcaBPnW6+O7wDHpIiK9SaVK5GgV543tzjgGcghAgAh+QQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK++G+w48edZPK+M6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE+G+cD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm+FNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk+aV+oJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0/VNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc+XiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30/iI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE/jiuL04RGEBgwWhShRgQExHBAAh+QQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR+ipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY+Yip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd+MFCN6HAAIKgNggY0KtEBAAh+QQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1+vsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d+jYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg+ygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0+bm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h+Kr0SJ8MFihpNbx+4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX+BP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA==);
}
.jvectormap-legend-title {
font-weight: bold;
font-size: 14px;
text-align: center;
}
.jvectormap-legend-cnt {
position: absolute;
}
.jvectormap-legend-cnt-h {
bottom: 0;
right: 0;
}
.jvectormap-legend-cnt-v {
top: 0;
right: 0;
}
.jvectormap-legend {
background: black;
color: white;
border-radius: 3px;
}
.jvectormap-legend-cnt-h .jvectormap-legend {
float: left;
margin: 0 10px 10px 0;
padding: 3px 3px 1px 3px;
}
.jvectormap-legend-cnt-h .jvectormap-legend .jvectormap-legend-tick {
float: left;
}
.jvectormap-legend-cnt-v .jvectormap-legend {
margin: 10px 10px 0 0;
padding: 3px;
}
.jvectormap-legend-cnt-h .jvectormap-legend-tick {
width: 40px;
}
.jvectormap-legend-cnt-h .jvectormap-legend-tick-sample {
height: 15px;
}
.jvectormap-legend-cnt-v .jvectormap-legend-tick-sample {
height: 20px;
width: 20px;
display: inline-block;
vertical-align: middle;
}
.jvectormap-legend-tick-text {
font-size: 12px;
}
.jvectormap-legend-cnt-h .jvectormap-legend-tick-text {
text-align: center;
}
.jvectormap-legend-cnt-v .jvectormap-legend-tick-text {
display: inline-block;
vertical-align: middle;
line-height: 20px;
padding-left: 3px;
}
/*Slick Carousel */
.slick-prev:before,
.slick-next:before {
color: #1ab394 !important;
}
/* Payments */
.payment-card {
background: #ffffff;
padding: 20px;
margin-bottom: 25px;
border: 1px solid #e7eaec;
}
.payment-icon-big {
font-size: 60px;
color: #d1dade;
}
.payments-method.panel-group .panel + .panel {
margin-top: -1px;
}
.payments-method .panel-heading {
padding: 15px;
background-color: #f3f3f4;
}
.payments-method .panel-default {
border: 1px solid #e7eaec;
}
.payments-method .panel {
border-radius: 0;
}
.payments-method .panel-heading h5 {
margin-bottom: 5px;
}
.payments-method .panel-heading i {
font-size: 26px;
}
/* Select2 custom styles */
.select2-container--bootstrap4 .select2-results__option--highlighted,
.select2-container--bootstrap4 .select2-results__option--highlighted.select2-results__option[aria-selected=true] {
background-color: #1ab394;
}
.select2-container--bootstrap4 .select2-selection,
.select2-container--bootstrap4 .select2-dropdown.select2-dropdown--above,
.select2-container--bootstrap4 .select2-dropdown {
border-color: #e7eaec;
}
.select2-container :focus {
outline: none;
}
.select2-container--bootstrap4.select2-container--focus .select2-selection {
box-shadow: none;
border-color: #1ab394;
}
.select2-container--bootstrap4 .select2-selection__clear {
margin-top: 0.9em;
}
/* Tour */
.tour-tour .btn.btn-default {
background-color: #ffffff;
border: 1px solid #d2d2d2;
color: inherit;
}
.tour-step-backdrop {
z-index: 2101;
}
.tour-backdrop {
z-index: 2100;
opacity: .7;
}
.popover[class*=tour-] {
z-index: 2100;
}
.popover-header {
margin-top: 0;
}
body.tour-open .animated {
animation-fill-mode: initial;
}
.tour-tour .btn.btn-secondary {
background-color: #ffffff;
border: 1px solid #d2d2d2;
color: inherit;
}
/* Resizable */
.resizable-panels .ibox {
clear: none;
margin: 10px;
float: left;
overflow: hidden;
min-height: 150px;
min-width: 150px;
}
.resizable-panels .ibox .ibox-content {
height: calc(100% - 49px);
}
.ui-resizable-helper {
background: rgba(211, 211, 211, 0.4);
}
/* Wizard step fix */
.wizard > .content > .body {
position: relative;
}
/* PDF js style */
.pdf-toolbar {
max-width: 600px;
margin: 0 auto;
}
.pdf-toolbar .input-group {
width: 100px;
}
/* Dropzone */
.dropzone {
min-height: 140px;
border: 1px dashed #1ab394;
background: white;
padding: 20px 20px;
}
.dropzone .dz-message {
font-size: 16px;
}
/* Activity stream */
.stream {
position: relative;
padding: 10px 0;
}
.stream:first-child .stream-badge:before {
top: 10px;
}
.stream:last-child .stream-badge:before {
height: 30px;
}
.stream .stream-badge {
width: 50px;
}
.stream .stream-badge i {
border: 1px solid #e7eaec;
border-radius: 50%;
padding: 6px;
color: #808486;
position: absolute;
background-color: #ffffff;
left: 8px;
}
.stream .stream-badge i.fa-circle {
color: #ced0d1;
}
.stream .stream-badge i.bg-success {
color: #ffffff;
background-color: #1c84c6;
border-color: #1c84c6;
}
.stream .stream-badge i.bg-primary {
color: #ffffff;
background-color: #1ab394;
border-color: #1ab394;
}
.stream .stream-badge i.bg-warning {
color: #ffffff;
background-color: #f8ac59;
border-color: #f8ac59;
}
.stream .stream-badge i.bg-info {
color: #ffffff;
background-color: #23c6c8;
border-color: #23c6c8;
}
.stream .stream-badge i.bg-danger {
color: #ffffff;
background-color: #ed5565;
border-color: #ed5565;
}
.stream .stream-badge:before {
content: '';
width: 1px;
background-color: #e7eaec;
position: absolute;
top: 0;
bottom: 0;
left: 20px;
}
.stream .stream-info {
font-size: 12px;
margin-bottom: 5px;
}
.stream .stream-info img {
border-radius: 50%;
width: 18px;
height: 18px;
margin-right: 2px;
margin-top: -4px;
}
.stream .stream-info .date {
color: #9a9d9f;
font-size: 80%;
}
.stream .stream-panel {
margin-left: 55px;
}
.stream-small {
margin: 10px 0;
}
.stream-small .label {
padding: 2px 6px;
margin-right: 2px;
}
/* Touch Spin */
.bootstrap-touchspin-postfix.input-group-addon {
padding: inherit;
}
.bootstrap-touchspin-postfix .input-group-text {
background-color: inherit;
line-height: 1;
border: none;
}
/* Code */
pre {
display: block;
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
line-height: 1.42857143;
color: #333;
word-break: break-all;
word-wrap: break-word;
background-color: #eff2f3;
border: 1px solid #d1dade;
border-radius: 2px;
}
code,
kbd,
pre,
samp {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}
.sidebar-panel {
width: 220px;
background: #ebebed;
padding: 10px 20px;
position: absolute;
right: 0;
height: calc(100% - 62px);
}
.sidebar-panel .feed-element img.rounded-circle {
width: 32px;
height: 32px;
}
.sidebar-panel .feed-element,
.media-body,
.sidebar-panel p {
font-size: 12px;
}
.sidebar-panel .feed-element {
margin-top: 20px;
padding-bottom: 0;
}
.sidebar-panel .list-group {
margin-bottom: 10px;
}
.sidebar-panel .list-group .list-group-item {
padding: 5px 0;
font-size: 12px;
border: 0;
}
.sidebar-content .wrapper,
.wrapper.sidebar-content {
padding-right: 230px !important;
}
.body-small .sidebar-content .wrapper,
.body-small .wrapper.sidebar-content {
padding-right: 20px !important;
}
#right-sidebar {
background-color: #fff;
border-left: 1px solid #e7eaec;
border-top: 1px solid #e7eaec;
overflow: hidden;
position: fixed;
top: 60px;
width: 260px !important;
z-index: 1009;
bottom: 0;
right: -260px;
}
#right-sidebar.sidebar-open {
right: 0;
}
#right-sidebar.sidebar-open.sidebar-top {
top: 0;
border-top: none;
}
.sidebar-container ul.nav-tabs {
border: none;
}
.sidebar-container ul.nav-tabs.navs-4 li {
width: 25%;
}
.sidebar-container ul.nav-tabs.navs-3 li {
width: 33.3333%;
}
.sidebar-container ul.nav-tabs.navs-2 li {
width: 50%;
}
.sidebar-container ul.nav-tabs li {
border: none;
}
.sidebar-container ul.nav-tabs li a {
border: none;
padding: 12px 10px;
margin: 0;
border-radius: 0;
background: #2f4050;
color: #fff;
text-align: center;
border-right: 1px solid #334556;
}
.sidebar-container ul.nav-tabs li.active a {
border: none;
background: #f9f9f9;
color: #676a6c;
font-weight: bold;
}
.sidebar-container .nav-tabs > li.active > a:hover,
.sidebar-container .nav-tabs > li.active > a:focus {
border: none;
}
.sidebar-container ul.sidebar-list {
margin: 0;
padding: 0;
}
.sidebar-container ul.sidebar-list li {
border-bottom: 1px solid #e7eaec;
padding: 15px 20px;
list-style: none;
font-size: 12px;
}
.sidebar-container .sidebar-message:nth-child(2n+2) {
background: #f9f9f9;
}
.sidebar-container ul.sidebar-list li a {
text-decoration: none;
color: inherit;
}
.sidebar-container .sidebar-content {
padding: 15px 20px;
font-size: 12px;
}
.sidebar-container .sidebar-title {
background: #f9f9f9;
padding: 20px;
border-bottom: 1px solid #e7eaec;
}
.sidebar-container .sidebar-title h3 {
margin-bottom: 3px;
padding-left: 2px;
}
.sidebar-container .tab-content h4 {
margin-bottom: 5px;
}
.sidebar-container .sidebar-message > a > .float-left {
margin-right: 10px;
}
.sidebar-container .sidebar-message > a {
text-decoration: none;
color: inherit;
}
.sidebar-container .sidebar-message {
padding: 15px 20px;
}
.sidebar-container .sidebar-message .media-body {
display: block;
width: auto;
}
.sidebar-container .sidebar-message .message-avatar {
height: 38px;
width: 38px;
border-radius: 50%;
}
.sidebar-container .setings-item {
padding: 15px 20px;
border-bottom: 1px solid #e7eaec;
}
body {
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
background-color: #2f4050;
font-size: 13px;
color: #676a6c;
overflow-x: hidden;
}
html,
body {
height: 100%;
}
body.full-height-layout #wrapper,
body.full-height-layout #page-wrapper {
height: 100%;
}
#page-wrapper {
min-height: 100vh;
}
body.boxed-layout {
background: url('patterns/shattered.png');
}
body.boxed-layout #wrapper {
background-color: #2f4050;
max-width: 1200px;
margin: 0 auto;
-webkit-box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
-moz-box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
}
.top-navigation.boxed-layout #wrapper,
.boxed-layout #wrapper.top-navigation {
max-width: 1300px !important;
}
.block {
display: block;
}
.clear {
display: block;
overflow: hidden;
}
a {
cursor: pointer;
}
a:hover,
a:focus {
text-decoration: none;
}
.border-bottom {
border-bottom: 1px solid #e7eaec !important;
}
.font-bold {
font-weight: 600;
}
.font-normal {
font-weight: 400;
}
.text-uppercase {
text-transform: uppercase;
}
.font-italic {
font-style: italic;
}
.b-r {
border-right: 1px solid #e7eaec;
}
.hr-line-dashed {
border-top: 1px dashed #e7eaec;
color: #ffffff;
background-color: #ffffff;
height: 1px;
margin: 20px 0;
}
.hr-line-solid {
border-bottom: 1px solid #e7eaec;
background-color: rgba(0, 0, 0, 0);
border-style: solid !important;
margin-top: 15px;
margin-bottom: 15px;
}
video {
width: 100% !important;
height: auto !important;
}
/* GALLERY */
.gallery > .row > div {
margin-bottom: 15px;
}
.fancybox img {
margin-bottom: 5px;
/* Only for demo */
width: 24%;
}
/* Summernote text editor */
.note-editor {
height: auto !important;
}
.note-editor.fullscreen {
z-index: 2050;
}
.note-editor.note-frame.fullscreen {
z-index: 2020;
}
.note-editor.note-frame .note-editing-area .note-editable {
color: #676a6c;
padding: 15px;
}
.note-editor.note-frame {
border: none;
}
.note-editor.panel {
margin-bottom: 0;
}
/* MODAL */
.modal-content {
background-clip: padding-box;
background-color: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0);
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
outline: 0 none;
position: relative;
}
.modal-dialog {
z-index: 2200;
}
.modal-body {
padding: 20px 30px 30px 30px;
}
.inmodal .modal-body {
background: #f8fafb;
}
.inmodal .modal-header {
padding: 30px 15px;
text-align: center;
display: block;
}
.animated.modal.fade .modal-dialog {
-webkit-transform: none;
-ms-transform: none;
-o-transform: none;
transform: none;
}
.inmodal .modal-title {
font-size: 26px;
}
.inmodal .modal-icon {
font-size: 84px;
color: #e2e3e3;
}
.modal-footer {
margin-top: 0;
}
/* WRAPPERS */
#wrapper {
width: 100%;
overflow-x: hidden;
display: -ms-flex;
display: -webkit-flex;
display: flex;
}
.wrapper {
padding: 0 20px;
}
.wrapper-content {
padding: 20px 10px 40px;
}
#page-wrapper {
padding: 0 15px;
position: relative !important;
flex-shrink: 1;
width: calc(100% - 220px);
}
@media (min-width: 768px) {
#page-wrapper {
position: inherit;
}
}
.title-action {
text-align: right;
padding-top: 30px;
}
.ibox-content h1,
.ibox-content h2,
.ibox-content h3,
.ibox-content h4,
.ibox-content h5,
.ibox-title h1,
.ibox-title h2,
.ibox-title h3,
.ibox-title h4,
.ibox-title h5 {
margin-top: 5px;
}
ul.unstyled,
ol.unstyled {
list-style: none outside none;
margin-left: 0;
}
.big-icon {
font-size: 160px !important;
color: #e5e6e7;
}
/* FOOTER */
.footer {
background: none repeat scroll 0 0 white;
border-top: 1px solid #e7eaec;
bottom: 0;
left: 0;
padding: 10px 20px;
position: absolute;
right: 0;
}
.footer.fixed_full {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
padding: 10px 20px;
background: white;
border-top: 1px solid #e7eaec;
}
.footer.fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
padding: 10px 20px;
background: white;
border-top: 1px solid #e7eaec;
margin-left: 220px;
}
body.mini-navbar .footer.fixed,
body.body-small.mini-navbar .footer.fixed {
margin: 0 0 0 70px;
}
body.mini-navbar.fixed-sidebar .footer.fixed {
margin: 0;
}
body.mini-navbar.canvas-menu .footer.fixed,
body.canvas-menu .footer.fixed {
margin: 0 !important;
}
body.fixed-sidebar.body-small.mini-navbar .footer.fixed {
margin: 0 0 0 220px;
}
body.body-small .footer.fixed {
margin-left: 0;
}
/* PANELS */
.panel-title > .small,
.panel-title > .small > a,
.panel-title > a,
.panel-title > small,
.panel-title > small > a {
color: inherit;
}
.page-heading {
border-top: 0;
padding: 0 10px 20px 10px;
}
.panel-heading h1,
.panel-heading h2 {
margin-bottom: 5px;
}
.panel-body {
padding: 15px;
}
/* Bootstrap 3.3.x panels */
.panel {
margin-bottom: 20px;
background-color: #fff;
border: 1px solid transparent;
border-radius: 4px;
}
.panel-heading {
color: white;
padding: 10px 15px;
border-bottom: 1px solid transparent;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.panel-footer {
padding: 10px 15px;
border-top: 1px solid #e7eaec;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
.panel-default > .panel-heading {
color: #333;
background-color: #f5f5f5;
border-color: #e7eaec;
}
.panel-default {
border-color: #e7eaec;
}
.panel-group .panel + .panel {
margin-top: 5px;
}
.panel-group .panel {
margin-bottom: 0;
border-radius: 4px;
}
/* TABLES */
.table > caption + thead > tr:first-child > td,
.table > caption + thead > tr:first-child > th,
.table > colgroup + thead > tr:first-child > td,
.table > colgroup + thead > tr:first-child > th,
.table > thead:first-child > tr:first-child > td,
.table > thead:first-child > tr:first-child > th {
border-top: 0;
}
.table-bordered {
border: 1px solid #EBEBEB;
}
.table-bordered > thead > tr > th,
.table-bordered > thead > tr > td {
background-color: #F5F5F6;
border-bottom-width: 1px;
}
.table-bordered > thead > tr > th,
.table-bordered > tbody > tr > th,
.table-bordered > tfoot > tr > th,
.table-bordered > thead > tr > td,
.table-bordered > tbody > tr > td,
.table-bordered > tfoot > tr > td {
border: 1px solid #e7e7e7;
}
.table > thead > tr > th {
border-bottom: 1px solid #DDDDDD;
vertical-align: bottom;
}
.table > thead > tr > th,
.table > tbody > tr > th,
.table > tfoot > tr > th,
.table > thead > tr > td,
.table > tbody > tr > td,
.table > tfoot > tr > td {
border-top: 1px solid #e7eaec;
line-height: 1.42857;
padding: 8px;
vertical-align: top;
}
/* PANELS */
.panel.blank-panel {
background: none;
margin: 0;
}
.blank-panel .panel-heading {
padding-bottom: 0;
}
.nav-tabs > li > a {
color: #A7B1C2;
font-weight: 600;
padding: 10px 20px 10px 25px;
}
.nav-tabs > li > a:hover,
.nav-tabs > li > a:focus {
color: #676a6c;
}
.ui-tab .tab-content {
padding: 20px 0;
}
/* GLOBAL */
.no-padding {
padding: 0 !important;
}
.no-borders {
border: none !important;
}
.no-margins {
margin: 0 !important;
}
.no-top-border {
border-top: 0 !important;
}
.ibox-content.text-box {
padding-bottom: 0;
padding-top: 15px;
}
.border-left-right {
border-left: 1px solid #e7eaec;
border-right: 1px solid #e7eaec;
}
.border-top-bottom {
border-top: 1px solid #e7eaec;
border-bottom: 1px solid #e7eaec;
}
.border-left {
border-left: 1px solid #e7eaec;
}
.border-right {
border-right: 1px solid #e7eaec;
}
.border-top {
border-top: 1px solid #e7eaec;
}
.border-bottom {
border-bottom: 1px solid #e7eaec;
}
.border-size-sm {
border-width: 3px;
}
.border-size-md {
border-width: 6px;
}
.border-size-lg {
border-width: 9px;
}
.border-size-xl {
border-width: 12px;
}
.full-width {
width: 100% !important;
}
.link-block {
font-size: 12px;
padding: 10px;
}
.nav.navbar-top-links .link-block a {
font-size: 12px;
}
.navbar-top-links {
text-align: right;
}
.link-block a {
font-size: 10px;
color: inherit;
}
body.mini-navbar .branding {
display: none;
}
img.circle-border {
border: 6px solid #FFFFFF;
border-radius: 50%;
}
.branding {
float: left;
color: #FFFFFF;
font-size: 18px;
font-weight: 600;
padding: 17px 20px;
text-align: center;
background-color: #1ab394;
}
.login-panel {
margin-top: 25%;
}
.icons-box h3 {
margin-top: 10px;
margin-bottom: 10px;
}
.icons-box .infont a i {
font-size: 25px;
display: block;
color: #676a6c;
}
.icons-box .infont a {
color: #a6a8a9;
}
.icons-box .infont a {
padding: 10px;
margin: 1px;
display: block;
}
.ui-draggable .ibox-title {
cursor: move;
}
.breadcrumb {
background-color: #ffffff;
padding: 0;
margin-bottom: 0;
}
.breadcrumb > li a {
color: inherit;
}
.breadcrumb > .active {
color: inherit;
}
code {
background-color: #F9F2F4;
border-radius: 4px;
color: #ca4440;
font-size: 90%;
padding: 2px 4px;
white-space: nowrap;
}
.ibox {
clear: both;
margin-bottom: 25px;
margin-top: 0;
padding: 0;
}
.ibox.collapsed .ibox-content {
display: none;
}
.ibox.collapsed .fa.fa-chevron-up:before {
content: "\f078";
}
.ibox.collapsed .fa.fa-chevron-down:before {
content: "\f077";
}
.ibox:after,
.ibox:before {
display: table;
}
.ibox-title {
background-color: #ffffff;
border-color: #e7eaec;
border-image: none;
border-style: solid solid none;
border-width: 1px;
color: inherit;
margin-bottom: 0;
padding: 15px 90px 8px 15px;
min-height: 48px;
position: relative;
clear: both;
-webkit-border-radius: 3px 3px 0 0;
-moz-border-radius: 3px 3px 0 0;
border-radius: 2px 2px 0 0;
}
.ibox-content {
background-color: #ffffff;
color: inherit;
padding: 15px 20px 20px 20px;
border-color: #e7eaec;
border-image: none;
border-style: solid;
border-width: 1px;
}
.ibox-footer {
color: inherit;
border-top: 1px solid #e7eaec;
font-size: 90%;
background: #ffffff;
padding: 10px 15px;
}
table.table-mail tr td {
padding: 12px;
}
.table-mail .check-mail {
padding-left: 20px;
}
.table-mail .mail-date {
padding-right: 20px;
}
.star-mail,
.check-mail {
width: 40px;
}
.unread td a,
.unread td {
font-weight: 600;
color: inherit;
}
.read td a,
.read td {
font-weight: normal;
color: inherit;
}
.unread td {
background-color: #f9f8f8;
}
.ibox-content {
clear: both;
}
.ibox-heading {
background-color: #f3f6fb;
border-bottom: none;
}
.ibox-heading h3 {
font-weight: 200;
font-size: 24px;
}
.ibox-title h5 {
display: inline-block;
font-size: 14px;
margin: 0 0 7px;
padding: 0;
text-overflow: ellipsis;
float: none;
}
.ibox-title .label {
margin-left: 4px;
}
.ibox-title .pull-right {
position: absolute;
right: 15px;
top: 15px;
}
.ibox-tools {
display: block;
float: none;
margin-top: 0;
position: absolute;
top: 15px;
right: 15px;
padding: 0;
text-align: right;
}
.ibox-tools a {
cursor: pointer;
margin-left: 5px;
color: #c4c4c4 !important;
}
.ibox-tools a.btn-primary {
color: #fff !important;
}
.ibox-tools .dropdown-menu > li > a {
padding: 4px 10px;
font-size: 12px;
color: #676a6c !important;
}
.ibox .ibox-tools.open > .dropdown-menu {
left: auto;
right: 0;
}
.ibox-tools .dropdown-toggle::after {
display: none;
}
.dropdown-item {
width: auto;
}
.dropdown-item.active,
.dropdown-item:active {
background-color: inherit;
color: inherit;
}
/* BACKGROUNDS */
.gray-bg,
.bg-muted {
background-color: #f3f3f4;
}
.white-bg {
background-color: #ffffff;
}
.blue-bg,
.bg-success {
background-color: #1c84c6 !important;
color: #ffffff;
}
.navy-bg,
.bg-primary {
background-color: #1ab394 !important;
color: #ffffff;
}
.lazur-bg,
.bg-info {
background-color: #23c6c8 !important;
color: #ffffff;
}
.yellow-bg,
.bg-warning {
background-color: #f8ac59 !important;
color: #ffffff;
}
.red-bg,
.bg-danger {
background-color: #ed5565 !important;
color: #ffffff;
}
.black-bg {
background-color: #262626;
}
.panel-primary {
border-color: #1ab394;
}
.panel-primary > .panel-heading {
background-color: #1ab394;
border-color: #1ab394;
}
.panel-success {
border-color: #1c84c6;
}
.panel-success > .panel-heading {
background-color: #1c84c6;
border-color: #1c84c6;
color: #ffffff;
}
.panel-info {
border-color: #23c6c8;
}
.panel-info > .panel-heading {
background-color: #23c6c8;
border-color: #23c6c8;
color: #ffffff;
}
.panel-warning {
border-color: #f8ac59;
}
.panel-warning > .panel-heading {
background-color: #f8ac59;
border-color: #f8ac59;
color: #ffffff;
}
.panel-danger {
border-color: #ed5565;
}
.panel-danger > .panel-heading {
background-color: #ed5565;
border-color: #ed5565;
color: #ffffff;
}
.progress-bar {
background-color: #1ab394;
}
.progress-small,
.progress-small .progress-bar {
height: 10px;
}
.progress-small,
.progress-mini {
margin-top: 5px;
}
.progress-mini,
.progress-mini .progress-bar {
height: 5px;
margin-bottom: 0;
}
.progress-bar-navy-light {
background-color: #3dc7ab;
}
.progress-bar-success {
background-color: #1c84c6;
}
.progress-bar-info {
background-color: #23c6c8;
}
.progress-bar-warning {
background-color: #f8ac59;
}
.progress-bar-danger {
background-color: #ed5565;
}
.panel-title {
font-size: inherit;
}
.jumbotron {
border-radius: 6px;
padding: 40px;
}
.jumbotron h1 {
margin-top: 0;
}
/* COLORS */
.text-navy {
color: #1ab394 !important;
}
.text-primary {
color: inherit !important;
}
.text-success {
color: #1c84c6 !important;
}
.text-info {
color: #23c6c8 !important;
}
.text-warning {
color: #f8ac59 !important;
}
.text-danger {
color: #ed5565 !important;
}
.text-muted {
color: #888888 !important;
}
.text-white {
color: #ffffff;
}
.simple_tag {
background-color: #f3f3f4;
border: 1px solid #e7eaec;
border-radius: 2px;
color: inherit;
font-size: 10px;
margin-right: 5px;
margin-top: 5px;
padding: 5px 12px;
display: inline-block;
}
.img-shadow {
-webkit-box-shadow: 0 0 3px 0 #919191;
-moz-box-shadow: 0 0 3px 0 #919191;
box-shadow: 0 0 3px 0 #919191;
}
/* For handle diferent bg color in AngularJS version */
.dashboards\.dashboard_2 nav.navbar,
.dashboards\.dashboard_3 nav.navbar,
.mailbox\.inbox nav.navbar,
.mailbox\.email_view nav.navbar,
.mailbox\.email_compose nav.navbar,
.dashboards\.dashboard_4_1 nav.navbar,
.metrics nav.navbar,
.metrics\.index nav.navbar,
.dashboards\.dashboard_5 nav.navbar {
background: #fff;
}
/* For handle diferent bg color in MVC version */
.Dashboard_2 .navbar.navbar-static-top,
.Dashboard_3 .navbar.navbar-static-top,
.Dashboard_4_1 .navbar.navbar-static-top,
.ComposeEmail .navbar.navbar-static-top,
.EmailView .navbar.navbar-static-top,
.Inbox .navbar.navbar-static-top,
.Metrics .navbar.navbar-static-top,
.Dashboard_5 .navbar.navbar-static-top {
background: #fff;
}
a.close-canvas-menu {
position: absolute;
top: 10px;
right: 15px;
z-index: 1011;
color: #a7b1c2;
}
a.close-canvas-menu:hover {
color: #fff;
}
.close-canvas-menu {
display: none;
}
.canvas-menu .close-canvas-menu {
display: block;
}
.light-navbar .navbar.navbar-static-top {
background-color: #ffffff;
}
/* FULL HEIGHT */
.full-height {
height: 100%;
}
.fh-breadcrumb {
height: calc(100% - 196px);
margin: 0 -15px;
position: relative;
}
.fh-no-breadcrumb {
height: calc(100% - 99px);
margin: 0 -15px;
position: relative;
}
.fh-column {
background: #fff;
height: 100%;
width: 240px;
float: left;
}
.modal-backdrop {
z-index: 2040 !important;
}
.modal {
z-index: 2050 !important;
}
.spiner-example {
height: 200px;
padding-top: 70px;
}
legend {
font-size: 1rem;
}
/* MARGINS & PADDINGS */
.p-xxs {
padding: 5px;
}
.p-xs {
padding: 10px;
}
.p-sm {
padding: 15px;
}
.p-m {
padding: 20px;
}
.p-md {
padding: 25px;
}
.p-lg {
padding: 30px;
}
.p-xl {
padding: 40px;
}
.p-w-xs {
padding: 0 10px;
}
.p-w-sm {
padding: 0 15px;
}
.p-w-m {
padding: 0 20px;
}
.p-w-md {
padding: 0 25px;
}
.p-w-lg {
padding: 0 30px;
}
.p-w-xl {
padding: 0 40px;
}
.p-h-xs {
padding: 10px 0;
}
.p-h-sm {
padding: 15px 0;
}
.p-h-m {
padding: 20px 0;
}
.p-h-md {
padding: 25px 0;
}
.p-h-lg {
padding: 30px 0;
}
.p-h-xl {
padding: 40px 0;
}
.m-xxs {
margin: 2px 4px;
}
.m {
margin: 15px;
}
.m-xs {
margin: 5px;
}
.m-sm {
margin: 10px;
}
.m-md {
margin: 20px;
}
.m-lg {
margin: 30px;
}
.m-xl {
margin: 50px;
}
.m-n {
margin: 0 !important;
}
.m-l-none {
margin-left: 0;
}
.m-l-xs {
margin-left: 5px;
}
.m-l-sm {
margin-left: 10px;
}
.m-l {
margin-left: 15px;
}
.m-l-md {
margin-left: 20px;
}
.m-l-lg {
margin-left: 30px;
}
.m-l-xl {
margin-left: 40px;
}
.m-l-n-xxs {
margin-left: -1px;
}
.m-l-n-xs {
margin-left: -5px;
}
.m-l-n-sm {
margin-left: -10px;
}
.m-l-n {
margin-left: -15px;
}
.m-l-n-md {
margin-left: -20px;
}
.m-l-n-lg {
margin-left: -30px;
}
.m-l-n-xl {
margin-left: -40px;
}
.m-t-none {
margin-top: 0;
}
.m-t-xxs {
margin-top: 1px;
}
.m-t-xs {
margin-top: 5px;
}
.m-t-sm {
margin-top: 10px;
}
.m-t {
margin-top: 15px;
}
.m-t-md {
margin-top: 20px;
}
.m-t-lg {
margin-top: 30px;
}
.m-t-xl {
margin-top: 40px;
}
.m-t-n-xxs {
margin-top: -1px;
}
.m-t-n-xs {
margin-top: -5px;
}
.m-t-n-sm {
margin-top: -10px;
}
.m-t-n {
margin-top: -15px;
}
.m-t-n-md {
margin-top: -20px;
}
.m-t-n-lg {
margin-top: -30px;
}
.m-t-n-xl {
margin-top: -40px;
}
.m-r-none {
margin-right: 0;
}
.m-r-xxs {
margin-right: 1px;
}
.m-r-xs {
margin-right: 5px;
}
.m-r-sm {
margin-right: 10px;
}
.m-r {
margin-right: 15px;
}
.m-r-md {
margin-right: 20px;
}
.m-r-lg {
margin-right: 30px;
}
.m-r-xl {
margin-right: 40px;
}
.m-r-n-xxs {
margin-right: -1px;
}
.m-r-n-xs {
margin-right: -5px;
}
.m-r-n-sm {
margin-right: -10px;
}
.m-r-n {
margin-right: -15px;
}
.m-r-n-md {
margin-right: -20px;
}
.m-r-n-lg {
margin-right: -30px;
}
.m-r-n-xl {
margin-right: -40px;
}
.m-b-none {
margin-bottom: 0;
}
.m-b-xxs {
margin-bottom: 1px;
}
.m-b-xs {
margin-bottom: 5px;
}
.m-b-sm {
margin-bottom: 10px;
}
.m-b {
margin-bottom: 15px;
}
.m-b-md {
margin-bottom: 20px;
}
.m-b-lg {
margin-bottom: 30px;
}
.m-b-xl {
margin-bottom: 40px;
}
.m-b-n-xxs {
margin-bottom: -1px;
}
.m-b-n-xs {
margin-bottom: -5px;
}
.m-b-n-sm {
margin-bottom: -10px;
}
.m-b-n {
margin-bottom: -15px;
}
.m-b-n-md {
margin-bottom: -20px;
}
.m-b-n-lg {
margin-bottom: -30px;
}
.m-b-n-xl {
margin-bottom: -40px;
}
.space-15 {
margin: 15px 0;
}
.space-20 {
margin: 20px 0;
}
.space-25 {
margin: 25px 0;
}
.space-30 {
margin: 30px 0;
}
.img-sm {
width: 32px;
height: 32px;
}
.img-md {
width: 64px;
height: 64px;
}
.img-lg {
width: 96px;
height: 96px;
}
.b-r-xs {
-webkit-border-radius: 1px;
-moz-border-radius: 1px;
border-radius: 1px;
}
.b-r-sm {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.b-r-md {
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
.b-r-lg {
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
border-radius: 12px;
}
.b-r-xl {
-webkit-border-radius: 24px;
-moz-border-radius: 24px;
border-radius: 24px;
}
.fullscreen-ibox-mode .animated {
animation: none;
}
body.fullscreen-ibox-mode {
overflow-y: hidden;
}
.ibox.fullscreen {
z-index: 2030;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: auto;
margin-bottom: 0;
}
.ibox.fullscreen .collapse-link {
display: none;
}
.ibox.fullscreen .ibox-content {
min-height: calc(100% - 48px);
}
body.modal-open {
padding-right: inherit !important;
}
_::-webkit-full-page-media,
_:future,
:root body.modal-open .wrapper-content.animated {
-webkit-animation: none;
-ms-animation-nam: none;
animation: none;
}
body.modal-open .animated {
animation-fill-mode: initial;
z-index: inherit;
}
/* Show profile dropdown on fixed sidebar */
body.mini-navbar.fixed-sidebar .profile-element,
.block {
display: block !important;
}
body.mini-navbar.fixed-sidebar .nav-header {
padding: 33px 25px;
}
body.mini-navbar.fixed-sidebar .logo-element {
display: none;
}
.fullscreen-video .animated {
animation: none;
}
.list-inline > li {
display: inline-block;
}
.custom-file-label {
padding: .5rem .75rem;
}
.custom-file-label::after {
padding: .5rem .75rem;
}
/* SEARCH PAGE */
.search-form {
margin-top: 10px;
}
.search-result h3 {
margin-bottom: 0;
color: #1E0FBE;
}
.search-result .search-link {
color: #006621;
}
.search-result p {
font-size: 12px;
margin-top: 5px;
}
/* CONTACTS */
.contact-box {
background-color: #ffffff;
border: 1px solid #e7eaec;
padding: 20px;
margin-bottom: 20px;
}
.contact-box > a {
color: inherit;
}
.contact-box.center-version {
border: 1px solid #e7eaec;
padding: 0;
}
.contact-box.center-version > a {
display: block;
background-color: #ffffff;
padding: 20px;
text-align: center;
}
.contact-box.center-version > a img {
width: 80px;
height: 80px;
margin-top: 10px;
margin-bottom: 10px;
}
.contact-box.center-version address {
margin-bottom: 0;
}
.contact-box .contact-box-footer {
text-align: center;
background-color: #ffffff;
border-top: 1px solid #e7eaec;
padding: 15px 20px;
}
/* INVOICE */
.invoice-table tbody > tr > td:last-child,
.invoice-table tbody > tr > td:nth-child(4),
.invoice-table tbody > tr > td:nth-child(3),
.invoice-table tbody > tr > td:nth-child(2) {
text-align: right;
}
.invoice-table thead > tr > th:last-child,
.invoice-table thead > tr > th:nth-child(4),
.invoice-table thead > tr > th:nth-child(3),
.invoice-table thead > tr > th:nth-child(2) {
text-align: right;
}
.invoice-total > tbody > tr > td:first-child {
text-align: right;
}
.invoice-total > tbody > tr > td {
border: 0 none;
}
.invoice-total > tbody > tr > td:last-child {
border-bottom: 1px solid #DDDDDD;
text-align: right;
width: 15%;
}
/* ERROR & LOGIN & LOCKSCREEN*/
.middle-box {
max-width: 400px;
z-index: 100;
margin: 0 auto;
padding-top: 40px;
}
.lockscreen.middle-box {
width: 200px;
padding-top: 110px;
}
.loginscreen.middle-box {
width: 300px;
}
.loginColumns {
max-width: 800px;
margin: 0 auto;
}
.passwordBox {
max-width: 460px;
margin: 0 auto;
padding: 100px 20px 20px 20px;
}
.logo-name {
color: #e6e6e6;
font-size: 180px;
font-weight: 800;
letter-spacing: -10px;
margin-bottom: 0;
}
.middle-box h1 {
font-size: 170px;
}
.wrapper .middle-box {
margin-top: 140px;
}
.lock-word {
z-index: 10;
position: absolute;
top: 110px;
left: 50%;
margin-left: -470px;
}
.lock-word span {
font-size: 100px;
font-weight: 600;
color: #e9e9e9;
display: inline-block;
}
.lock-word .first-word {
margin-right: 160px;
}
/* DASBOARD */
.dashboard-header {
border-top: 0;
padding: 20px 20px 20px 20px;
}
.dashboard-header h2 {
margin-top: 10px;
font-size: 26px;
}
.fist-item {
border-top: none !important;
}
.statistic-box {
margin-top: 40px;
}
.dashboard-header .list-group-item span.label {
margin-right: 10px;
}
.list-group.clear-list .list-group-item {
border-top: 1px solid #e7eaec;
border-bottom: 0;
border-right: 0;
border-left: 0;
padding: 10px 0;
}
ul.clear-list:first-child {
border-top: none !important;
}
/* Intimeline */
.timeline-item .date i {
position: absolute;
top: 0;
right: 0;
padding: 5px;
width: 30px;
text-align: center;
border-top: 1px solid #e7eaec;
border-bottom: 1px solid #e7eaec;
border-left: 1px solid #e7eaec;
background: #f8f8f8;
}
.timeline-item .date {
text-align: right;
width: 110px;
position: relative;
padding-top: 30px;
}
.timeline-item .content {
border-left: 1px solid #e7eaec;
border-top: 1px solid #e7eaec;
padding-top: 10px;
min-height: 100px;
}
.timeline-item .content:hover {
background: #f6f6f6;
}
/* PIN BOARD */
ul.notes li,
ul.tag-list li {
list-style: none;
}
ul.notes li h4 {
margin-top: 20px;
font-size: 16px;
}
ul.notes li div {
text-decoration: none;
color: #000;
background: #ffc;
display: block;
height: 140px;
width: 140px;
padding: 1em;
position: relative;
}
ul.notes li div small {
position: absolute;
top: 5px;
right: 5px;
font-size: 10px;
}
ul.notes li div a {
position: absolute;
right: 10px;
bottom: 10px;
color: inherit;
}
ul.notes li {
margin: 10px 40px 50px 0;
float: left;
}
ul.notes li div p {
font-size: 12px;
}
ul.notes li div {
text-decoration: none;
color: #000;
background: #ffc;
display: block;
height: 140px;
width: 140px;
padding: 1em;
/* Firefox */
-moz-box-shadow: 5px 5px 2px #212121;
/* Safari+Chrome */
-webkit-box-shadow: 5px 5px 2px rgba(33, 33, 33, 0.7);
/* Opera */
box-shadow: 5px 5px 2px rgba(33, 33, 33, 0.7);
}
ul.notes li div {
-webkit-transform: rotate(-6deg);
-o-transform: rotate(-6deg);
-moz-transform: rotate(-6deg);
-ms-transform: rotate(-6deg);
}
ul.notes li:nth-child(even) div {
-o-transform: rotate(4deg);
-webkit-transform: rotate(4deg);
-moz-transform: rotate(4deg);
-ms-transform: rotate(4deg);
position: relative;
top: 5px;
}
ul.notes li:nth-child(3n) div {
-o-transform: rotate(-3deg);
-webkit-transform: rotate(-3deg);
-moz-transform: rotate(-3deg);
-ms-transform: rotate(-3deg);
position: relative;
top: -5px;
}
ul.notes li:nth-child(5n) div {
-o-transform: rotate(5deg);
-webkit-transform: rotate(5deg);
-moz-transform: rotate(5deg);
-ms-transform: rotate(5deg);
position: relative;
top: -10px;
}
ul.notes li div:hover,
ul.notes li div:focus {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-o-transform: scale(1.1);
-ms-transform: scale(1.1);
position: relative;
z-index: 5;
}
ul.notes li div {
text-decoration: none;
color: #000;
background: #ffc;
display: block;
height: 210px;
width: 210px;
padding: 1em;
-moz-box-shadow: 5px 5px 7px #212121;
-webkit-box-shadow: 5px 5px 7px rgba(33, 33, 33, 0.7);
box-shadow: 5px 5px 7px rgba(33, 33, 33, 0.7);
-moz-transition: -moz-transform 0.15s linear;
-o-transition: -o-transform 0.15s linear;
-webkit-transition: -webkit-transform 0.15s linear;
}
/* FILE MANAGER */
.file-box {
float: left;
width: 220px;
}
.file-manager h5 {
text-transform: uppercase;
}
.file-manager {
list-style: none outside none;
margin: 0;
padding: 0;
}
.folder-list li a {
color: #666666;
display: block;
padding: 5px 0;
}
.folder-list li {
border-bottom: 1px solid #e7eaec;
display: block;
}
.folder-list li i {
margin-right: 8px;
color: #3d4d5d;
}
.category-list li a {
color: #666666;
display: block;
padding: 5px 0;
}
.category-list li {
display: block;
}
.category-list li i {
margin-right: 8px;
color: #3d4d5d;
}
.category-list li a .text-navy {
color: #1ab394;
}
.category-list li a .text-primary {
color: #1c84c6;
}
.category-list li a .text-info {
color: #23c6c8;
}
.category-list li a .text-danger {
color: #EF5352;
}
.category-list li a .text-warning {
color: #F8AC59;
}
.file-manager h5.tag-title {
margin-top: 20px;
}
.tag-list li {
float: left;
}
.tag-list li a {
font-size: 10px;
background-color: #f3f3f4;
padding: 5px 12px;
color: inherit;
border-radius: 2px;
border: 1px solid #e7eaec;
margin-right: 5px;
margin-top: 5px;
display: block;
}
.file {
border: 1px solid #e7eaec;
padding: 0;
background-color: #ffffff;
position: relative;
margin-bottom: 20px;
margin-right: 20px;
}
.file-manager .hr-line-dashed {
margin: 15px 0;
}
.file .icon,
.file .image {
height: 100px;
overflow: hidden;
}
.file .icon {
padding: 15px 10px;
text-align: center;
}
.file-control {
color: inherit;
font-size: 11px;
margin-right: 10px;
}
.file-control.active {
text-decoration: underline;
}
.file .icon i {
font-size: 70px;
color: #dadada;
}
.file .file-name {
padding: 10px;
background-color: #f8f8f8;
border-top: 1px solid #e7eaec;
}
.file-name small {
color: #676a6c;
}
.corner {
position: absolute;
display: inline-block;
width: 0;
height: 0;
line-height: 0;
border: 0.6em solid transparent;
border-right: 0.6em solid #f1f1f1;
border-bottom: 0.6em solid #f1f1f1;
right: 0em;
bottom: 0em;
}
a.compose-mail {
padding: 8px 10px;
}
.mail-search {
max-width: 300px;
}
/* PROFILE */
.profile-content {
border-top: none !important;
}
.profile-stats {
margin-right: 10px;
}
.profile-image {
width: 120px;
float: left;
}
.profile-image img {
width: 96px;
height: 96px;
}
.profile-info {
margin-left: 120px;
}
.feed-activity-list .feed-element {
border-bottom: 1px solid #e7eaec;
}
.feed-element:first-child {
margin-top: 0;
}
.feed-element {
padding-bottom: 15px;
}
.feed-element,
.feed-element .media {
margin-top: 15px;
}
.feed-element,
.media-body {
overflow: hidden;
}
.feed-element > a img {
margin-right: 10px;
}
.feed-element img.rounded-circle,
.dropdown-messages-box img.rounded-circle {
width: 38px;
height: 38px;
}
.feed-element .well {
border: 1px solid #e7eaec;
box-shadow: none;
margin-top: 10px;
margin-bottom: 5px;
padding: 10px 20px;
font-size: 11px;
line-height: 16px;
}
.feed-element .actions {
margin-top: 10px;
}
.feed-element .photos {
margin: 10px 0;
}
.dropdown-messages-box .dropdown-item:focus,
.dropdown-messages-box .dropdown-item:hover {
background-color: inherit;
}
.feed-photo {
max-height: 180px;
border-radius: 4px;
overflow: hidden;
margin-right: 10px;
margin-bottom: 10px;
}
.file-list li {
padding: 5px 10px;
font-size: 11px;
border-radius: 2px;
border: 1px solid #e7eaec;
margin-bottom: 5px;
}
.file-list li a {
color: inherit;
}
.file-list li a:hover {
color: #1ab394;
}
.user-friends img {
width: 42px;
height: 42px;
margin-bottom: 5px;
margin-right: 5px;
}
/* MAILBOX */
.mail-box {
background-color: #ffffff;
border: 1px solid #e7eaec;
border-top: 0;
padding: 0;
margin-bottom: 20px;
}
.mail-box-header {
background-color: #ffffff;
border: 1px solid #e7eaec;
border-bottom: 0;
padding: 30px 20px 20px 20px;
}
.mail-box-header h2 {
margin-top: 0;
}
.mailbox-content .tag-list li a {
background: #ffffff;
}
.mail-body {
border-top: 1px solid #e7eaec;
padding: 20px;
}
.mail-text {
border-top: 1px solid #e7eaec;
}
.mail-text .note-toolbar {
padding: 10px 15px;
}
.mail-body .form-group {
margin-bottom: 5px;
}
.mail-text .note-editor .note-toolbar {
background-color: #F9F8F8;
}
.mail-attachment {
border-top: 1px solid #e7eaec;
padding: 20px;
font-size: 12px;
}
.mailbox-content {
background: none;
border: none;
padding: 10px;
}
.mail-ontact {
width: 23%;
}
/* PROJECTS */
.project-people,
.project-actions {
text-align: right;
vertical-align: middle;
}
dd.project-people {
text-align: left;
margin-top: 5px;
}
.project-people img {
width: 32px;
height: 32px;
}
.project-title a {
font-size: 14px;
color: #676a6c;
font-weight: 600;
}
.project-list table tr td {
border-top: none;
border-bottom: 1px solid #e7eaec;
padding: 15px 10px;
vertical-align: middle;
}
.project-manager .tag-list li a {
font-size: 10px;
background-color: white;
padding: 5px 12px;
color: inherit;
border-radius: 2px;
border: 1px solid #e7eaec;
margin-right: 5px;
margin-top: 5px;
display: block;
}
.project-files li a {
font-size: 11px;
color: #676a6c;
margin-left: 10px;
line-height: 22px;
}
/* FAQ */
.faq-item {
padding: 20px;
margin-bottom: 2px;
background: #fff;
}
.faq-question {
font-size: 18px;
font-weight: 600;
color: #1ab394;
display: block;
}
.faq-question:hover {
color: #179d82;
}
.faq-answer {
margin-top: 10px;
background: #f3f3f4;
border: 1px solid #e7eaec;
border-radius: 3px;
padding: 15px;
}
.faq-item .tag-item {
background: #f3f3f4;
padding: 2px 6px;
font-size: 10px;
text-transform: uppercase;
}
/* Chat view */
.message-input {
height: 90px !important;
}
.chat-avatar {
width: 36px;
height: 36px;
float: left;
margin-right: 10px;
}
.chat-user-name {
padding: 10px;
}
.chat-user {
padding: 8px 10px;
border-bottom: 1px solid #e7eaec;
}
.chat-user a {
color: inherit;
}
.chat-view {
z-index: 20012;
}
.chat-users,
.chat-statistic {
margin-left: -30px;
}
@media (max-width: 992px) {
.chat-users,
.chat-statistic {
margin-left: 0;
}
}
.chat-view .ibox-content {
padding: 0;
}
.chat-message {
padding: 10px 20px;
}
.message-avatar {
height: 48px;
width: 48px;
border: 1px solid #e7eaec;
border-radius: 4px;
margin-top: 1px;
}
.chat-discussion .chat-message.left .message-avatar {
float: left;
margin-right: 10px;
}
.chat-discussion .chat-message.right .message-avatar {
float: right;
margin-left: 10px;
}
.message {
background-color: #fff;
border: 1px solid #e7eaec;
text-align: left;
display: block;
padding: 10px 20px;
position: relative;
border-radius: 4px;
}
.chat-discussion .chat-message.left .message-date {
float: right;
}
.chat-discussion .chat-message.right .message-date {
float: left;
}
.chat-discussion .chat-message.left .message {
text-align: left;
margin-left: 55px;
}
.chat-discussion .chat-message.right .message {
text-align: right;
margin-right: 55px;
}
.message-date {
font-size: 10px;
color: #888888;
}
.message-content {
display: block;
}
.chat-discussion {
background: #eee;
padding: 15px;
height: 400px;
overflow-y: auto;
}
.chat-users {
overflow-y: auto;
height: 400px;
}
.chat-message-form .form-group {
margin-bottom: 0;
}
/* jsTree */
.jstree-open > .jstree-anchor > .fa-folder:before {
content: "\f07c";
}
.jstree-default .jstree-icon.none {
width: 0;
}
/* CLIENTS */
.clients-list {
margin-top: 20px;
}
.clients-list .tab-pane {
position: relative;
height: 600px;
}
.client-detail {
position: relative;
height: 620px;
}
.clients-list table tr td {
height: 46px;
vertical-align: middle;
border: none;
}
.client-link {
font-weight: 600;
color: inherit;
}
.client-link:hover {
color: inherit;
}
.client-avatar {
width: 42px;
}
.client-avatar img {
width: 28px;
height: 28px;
border-radius: 50%;
}
.contact-type {
width: 20px;
color: #c1c3c4;
}
.client-status {
text-align: left;
}
.client-detail .vertical-timeline-content p {
margin: 0;
}
.client-detail .vertical-timeline-icon.gray-bg {
color: #a7aaab;
}
.clients-list .nav-tabs > li.active > a,
.clients-list .nav-tabs > li.active > a:hover,
.clients-list .nav-tabs > li.active > a:focus {
border-bottom: 1px solid #fff;
}
/* BLOG ARTICLE */
.blog h2 {
font-weight: 700;
}
.blog h5 {
margin: 0 0 5px 0;
}
.blog .btn {
margin: 0 0 5px 0;
}
.article h1 {
font-size: 48px;
font-weight: 700;
color: #2f4050;
}
.article p {
font-size: 15px;
line-height: 26px;
}
.article-title {
text-align: center;
margin: 40px 0 100px 0;
}
.article .ibox-content {
padding: 40px;
}
/* ISSUE TRACKER */
.issue-tracker .btn-link {
color: #1ab394;
}
table.issue-tracker tbody tr td {
vertical-align: middle;
height: 50px;
}
.issue-info {
width: 50%;
}
.issue-info a {
font-weight: 600;
color: #676a6c;
}
.issue-info small {
display: block;
}
/* TEAMS */
.team-members {
margin: 10px 0;
}
.team-members img.rounded-circle {
width: 42px;
height: 42px;
margin-bottom: 5px;
}
/* AGILE BOARD */
.sortable-list {
padding: 10px 0;
}
.agile-list {
list-style: none;
margin: 0;
}
.agile-list li {
background: #FAFAFB;
border: 1px solid #e7eaec;
margin: 0 0 10px 0;
padding: 10px;
border-radius: 2px;
}
.agile-list li:hover {
cursor: pointer;
background: #fff;
}
.agile-list li.warning-element {
border-left: 3px solid #f8ac59;
}
.agile-list li.danger-element {
border-left: 3px solid #ed5565;
}
.agile-list li.info-element {
border-left: 3px solid #1c84c6;
}
.agile-list li.success-element {
border-left: 3px solid #1ab394;
}
.agile-detail {
margin-top: 5px;
font-size: 12px;
}
/* DIFF */
ins {
background-color: #c6ffc6;
text-decoration: none;
}
del {
background-color: #ffc6c6;
}
/* E-commerce */
.product-box {
padding: 0;
border: 1px solid #e7eaec;
}
.product-box:hover,
.product-box.active {
border: 1px solid transparent;
-webkit-box-shadow: 0 3px 7px 0 #a8a8a8;
-moz-box-shadow: 0 3px 7px 0 #a8a8a8;
box-shadow: 0 3px 7px 0 #a8a8a8;
}
.product-imitation {
text-align: center;
padding: 90px 0;
background-color: #f8f8f9;
color: #bebec3;
font-weight: 600;
}
.cart-product-imitation {
text-align: center;
padding-top: 30px;
height: 80px;
width: 80px;
background-color: #f8f8f9;
}
.product-imitation.xl {
padding: 120px 0;
}
.product-desc {
padding: 20px;
position: relative;
}
.ecommerce .tag-list {
padding: 0;
}
.ecommerce .fa-star {
color: #d1dade;
}
.ecommerce .fa-star.active {
color: #f8ac59;
}
.ecommerce .note-editor {
border: 1px solid #e7eaec;
}
table.shoping-cart-table {
margin-bottom: 0;
}
table.shoping-cart-table tr td {
border: none;
text-align: right;
}
table.shoping-cart-table tr td.desc,
table.shoping-cart-table tr td:first-child {
text-align: left;
}
table.shoping-cart-table tr td:last-child {
width: 80px;
}
.product-name {
font-size: 16px;
font-weight: 600;
color: #676a6c;
display: block;
margin: 2px 0 5px 0;
}
.product-name:hover,
.product-name:focus {
color: #1ab394;
}
.product-price {
font-size: 14px;
font-weight: 600;
color: #ffffff;
background-color: #1ab394;
padding: 6px 12px;
position: absolute;
top: -32px;
right: 0;
}
.product-detail .ibox-content {
padding: 30px 30px 50px 30px;
}
.image-imitation {
background-color: #f8f8f9;
text-align: center;
padding: 200px 0;
}
.product-main-price small {
font-size: 10px;
}
.product-images {
margin: 0 20px;
}
/* Social feed */
.social-feed-separated .social-feed-box {
margin-left: 62px;
}
.social-feed-separated .social-avatar {
float: left;
padding: 0;
}
.social-feed-separated .social-avatar img {
width: 52px;
height: 52px;
border: 1px solid #e7eaec;
}
.social-feed-separated .social-feed-box .social-avatar {
padding: 15px 15px 0 15px;
float: none;
}
.social-feed-box {
/*padding: 15px;*/
border: 1px solid #e7eaec;
background: #fff;
margin-bottom: 15px;
}
.article .social-feed-box {
margin-bottom: 0;
border-bottom: none;
}
.article .social-feed-box:last-child {
margin-bottom: 0;
border-bottom: 1px solid #e7eaec;
}
.article .social-feed-box p {
font-size: 13px;
line-height: 18px;
}
.social-action {
margin: 15px;
}
.social-action .dropdown-toggle::after {
margin-left: auto;
}
.social-avatar {
padding: 15px 15px 0 15px;
}
.social-comment .social-comment {
margin-left: 45px;
}
.social-avatar img {
height: 40px;
width: 40px;
margin-right: 10px;
}
.social-avatar .media-body a {
font-size: 14px;
display: block;
}
.social-body {
padding: 15px;
}
.social-body img {
margin-bottom: 10px;
}
.social-footer {
border-top: 1px solid #e7eaec;
padding: 10px 15px;
background: #f9f9f9;
}
.social-footer .social-comment img {
width: 32px;
margin-right: 10px;
}
.social-comment:first-child {
margin-top: 0;
}
.social-comment {
margin-top: 15px;
}
.social-comment textarea {
font-size: 12px;
}
/* Vote list */
.vote-item {
padding: 20px 25px;
background: #ffffff;
border-top: 1px solid #e7eaec;
}
.vote-item:last-child {
border-bottom: 1px solid #e7eaec;
}
.vote-item:hover {
background: #fbfbfb;
}
.vote-actions {
float: left;
width: 30px;
margin-right: 15px;
text-align: center;
}
.vote-actions a {
color: #1ab394;
font-weight: 600;
}
.vote-actions {
font-weight: 600;
}
.vote-title {
display: block;
color: inherit;
font-size: 18px;
font-weight: 600;
margin-top: 5px;
margin-bottom: 2px;
}
.vote-title:hover,
.vote-title:focus {
color: inherit;
}
.vote-info,
.vote-title {
margin-left: 45px;
}
.vote-info,
.vote-info a {
color: #b4b6b8;
font-size: 12px;
}
.vote-info a {
margin-right: 10px;
}
.vote-info a:hover {
color: #1ab394;
}
.vote-icon {
text-align: right;
font-size: 38px;
display: block;
color: #e8e9ea;
}
.vote-icon.active {
color: #1ab394;
}
body.body-small .vote-icon {
display: none;
}
.lightBoxGallery {
text-align: center;
}
.lightBoxGallery img {
margin: 5px;
}
#small-chat {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
}
#small-chat .badge {
position: absolute;
top: -3px;
right: -4px;
}
.open-small-chat {
height: 38px;
width: 38px;
display: block;
background: #1ab394;
padding: 9px 8px;
text-align: center;
color: #fff;
border-radius: 50%;
}
.open-small-chat:hover {
color: white;
background: #1ab394;
}
.small-chat-box {
display: none;
position: fixed;
bottom: 20px;
right: 75px;
background: #fff;
border: 1px solid #e7eaec;
width: 230px;
height: 320px;
border-radius: 4px;
}
.small-chat-box.ng-small-chat {
display: block;
}
.body-small .small-chat-box {
bottom: 70px;
right: 20px;
}
.small-chat-box.active {
display: block;
}
.small-chat-box {
z-index: 1001;
}
.small-chat-box .heading {
background: #2f4050;
padding: 8px 15px;
font-weight: bold;
color: #fff;
}
.small-chat-box .chat-date {
opacity: 0.6;
font-size: 10px;
font-weight: normal;
}
.small-chat-box .content {
padding: 15px 15px;
}
.small-chat-box .content .author-name {
font-weight: bold;
margin-bottom: 3px;
font-size: 11px;
}
.small-chat-box .content > div {
padding-bottom: 20px;
}
.small-chat-box .content .chat-message {
padding: 5px 10px;
border-radius: 6px;
font-size: 11px;
line-height: 14px;
max-width: 80%;
background: #f3f3f4;
margin-bottom: 10px;
}
.small-chat-box .content .chat-message.active {
background: #1ab394;
color: #fff;
}
.small-chat-box .content .left {
text-align: left;
clear: both;
}
.small-chat-box .content .left .chat-message {
float: left;
}
.small-chat-box .content .right {
text-align: right;
clear: both;
}
.small-chat-box .content .right .chat-message {
float: right;
}
.small-chat-box .form-chat {
padding: 10px 10px;
}
/*
* metismenu - v2.0.2
* A jQuery menu plugin
* https://github.com/onokumus/metisMenu
*
* Made by Osman Nuri Okumus
* Under MIT License
*/
.metismenu .plus-minus,
.metismenu .plus-times {
float: right;
}
.metismenu .arrow {
float: right;
line-height: 1.42857;
}
.metismenu .glyphicon.arrow:before {
content: "\e079";
}
.metismenu .active > a > .glyphicon.arrow:before {
content: "\e114";
}
.metismenu .fa.arrow:before {
content: "\f104";
}
.metismenu .active > a > .fa.arrow:before {
content: "\f107";
}
.metismenu .ion.arrow:before {
content: "\f3d2";
}
.metismenu .active > a > .ion.arrow:before {
content: "\f3d0";
}
.metismenu .fa.plus-minus:before,
.metismenu .fa.plus-times:before {
content: "\f067";
}
.metismenu .active > a > .fa.plus-times {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.metismenu .active > a > .fa.plus-minus:before {
content: "\f068";
}
.metismenu .collapse {
display: none;
}
.metismenu .collapse.in {
display: block;
}
.metismenu .collapsing {
position: relative;
height: 0;
overflow: hidden;
-webkit-transition-timing-function: ease;
transition-timing-function: ease;
-webkit-transition-duration: .35s;
transition-duration: .35s;
-webkit-transition-property: height, visibility;
transition-property: height, visibility;
}
.mini-navbar .metismenu .collapse {
opacity: 0;
}
.mini-navbar .metismenu .collapse.in {
opacity: 1;
}
.mini-navbar .metismenu .collapse a {
display: none;
}
.mini-navbar .metismenu .collapse.in a {
display: block;
}
/*
* Usage:
*
*
*
*/
.sk-spinner-rotating-plane.sk-spinner {
width: 30px;
height: 30px;
background-color: #1ab394;
margin: 0 auto;
-webkit-animation: sk-rotatePlane 1.2s infinite ease-in-out;
animation: sk-rotatePlane 1.2s infinite ease-in-out;
}
@-webkit-keyframes sk-rotatePlane {
0% {
-webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg);
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
}
50% {
-webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
}
100% {
-webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
}
}
@keyframes sk-rotatePlane {
0% {
-webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg);
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
}
50% {
-webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
}
100% {
-webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
}
}
/*
* Usage:
*
*
' + languages['menu'][key] +'适用范围: 小程序开发、微信公众号开发、产品演示
假设场景:
使用步骤:
./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,如需使用https请在配置文件中将https端口设置为443,和将对应的证书文件路径添加到配置文件中
适用范围: ssh、远程桌面等tcp连接场景
假设场景: 想通过访问公网服务器{{.ip}}的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接
使用步骤:
./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
适用范围: 内网dns解析等udp连接场景
假设场景: 内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为{{.ip}}
使用步骤:
./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
适用范围: 在外网环境下如同使用vpn一样访问内网设备或者资源
假设场景: 想将公网服务器{{.ip}}的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果
使用步骤:
./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
适用范围: 在外网环境下访问内网站点
假设场景: 想将公网服务器{{.ip}}的8004端口作为http代理,访问内网网站
使用步骤:
./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
单个客户端可以添加多条隧道或者域名解析