dashboard 备注 客户端管理优化 多客户端支持 流量显示支持 热更新支持 404错误页支持

pull/1219/head
刘河 2019-01-25 12:10:12 +08:00
parent c533436c78
commit c34e5e1a7d
37 changed files with 5415 additions and 732 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/cnlh/easyProxy/utils"
"log"
"net"
"strconv"
"sync"
"time"
)
@ -31,20 +32,20 @@ func newList() *list {
}
type Tunnel struct {
TunnelPort int //通信隧道端口
listener *net.TCPListener //server端监听
SignalList map[string]*list //通信
TunnelList map[string]*list //隧道
RunList map[string]interface{} //运行中的任务
TunnelPort int //通信隧道端口
listener *net.TCPListener //server端监听
SignalList map[int]*list //通信
TunnelList map[int]*list //隧道
RunList map[int]interface{} //运行中的任务
lock sync.Mutex
tunnelLock sync.Mutex
}
func NewTunnel(tunnelPort int, runList map[string]interface{}) *Tunnel {
func NewTunnel(tunnelPort int, runList map[int]interface{}) *Tunnel {
t := new(Tunnel)
t.TunnelPort = tunnelPort
t.SignalList = make(map[string]*list)
t.TunnelList = make(map[string]*list)
t.SignalList = make(map[int]*list)
t.TunnelList = make(map[int]*list)
t.RunList = runList
return t
}
@ -87,7 +88,8 @@ func (s *Tunnel) cliProcess(c *utils.Conn) error {
c.Conn.Close()
return err
}
if !s.verify(string(vval)) {
id, err := utils.GetCsvDb().GetIdByVerifyKey(string(vval),c.Conn.RemoteAddr().String())
if err != nil {
log.Println("当前客户端连接校验错误,关闭此客户端:", c.Conn.RemoteAddr())
s.verifyError(c)
return errors.New("验证错误")
@ -97,18 +99,18 @@ func (s *Tunnel) cliProcess(c *utils.Conn) error {
if flag, err := c.ReadFlag(); err != nil {
return err
} else {
return s.typeDeal(flag, c, string(vval))
return s.typeDeal(flag, c, id)
}
}
//tcp连接类型区分
func (s *Tunnel) typeDeal(typeVal string, c *utils.Conn, cFlag string) error {
func (s *Tunnel) typeDeal(typeVal string, c *utils.Conn, id int) error {
switch typeVal {
case utils.WORK_MAIN:
log.Println("客户端连接成功", c.Conn.RemoteAddr())
s.addList(s.SignalList, c, cFlag)
s.addList(s.SignalList, c, id)
case utils.WORK_CHAN:
s.addList(s.TunnelList, c, cFlag)
s.addList(s.TunnelList, c, id)
default:
return errors.New("无法识别")
}
@ -117,41 +119,38 @@ func (s *Tunnel) typeDeal(typeVal string, c *utils.Conn, cFlag string) error {
}
//加到对应的list中
func (s *Tunnel) addList(m map[string]*list, c *utils.Conn, cFlag string) {
func (s *Tunnel) addList(m map[int]*list, c *utils.Conn, id int) {
s.lock.Lock()
if v, ok := m[cFlag]; ok {
if v, ok := m[id]; ok {
v.Add(c)
} else {
l := newList()
l.Add(c)
m[cFlag] = l
m[id] = l
}
s.lock.Unlock()
}
//新建隧道
func (s *Tunnel) newChan(cFlag string) error {
func (s *Tunnel) newChan(id int) error {
var connPass *utils.Conn
var err error
retry:
if connPass, err = s.waitAndPop(s.SignalList, cFlag); err != nil {
if connPass, err = s.waitAndPop(s.SignalList, id); err != nil {
return err
}
if _, err = connPass.Conn.Write([]byte("chan")); err != nil {
goto retry
}
s.SignalList[cFlag].Add(connPass)
s.SignalList[id].Add(connPass)
return nil
}
//得到一个tcp隧道
//TODO 超时问题 锁机制问题 对单个客户端加锁
func (s *Tunnel) GetTunnel(cFlag string, en, de int, crypt, mux bool) (c *utils.Conn, err error) {
if v, ok := s.TunnelList[cFlag]; !ok || v.Len() < 3 { //新建通道
go s.newChan(cFlag)
}
func (s *Tunnel) GetTunnel(id int, en, de int, crypt, mux bool) (c *utils.Conn, err error) {
retry:
if c, err = s.waitAndPop(s.TunnelList, cFlag); err != nil {
if c, err = s.waitAndPop(s.TunnelList, id); err != nil {
return
}
if _, err = c.WriteTest(); err != nil {
@ -163,61 +162,61 @@ retry:
}
//得到一个通信通道
func (s *Tunnel) GetSignal(cFlag string) (err error, conn *utils.Conn) {
if v, ok := s.SignalList[cFlag]; !ok || v.Len() == 0 {
func (s *Tunnel) GetSignal(id int) (err error, conn *utils.Conn) {
if v, ok := s.SignalList[id]; !ok || v.Len() == 0 {
err = errors.New("客户端未连接")
return
}
conn = s.SignalList[cFlag].Pop()
conn = s.SignalList[id].Pop()
return
}
//重回slice 复用
func (s *Tunnel) ReturnSignal(conn *utils.Conn, cFlag string) {
if v, ok := s.SignalList[cFlag]; ok {
func (s *Tunnel) ReturnSignal(conn *utils.Conn, id int) {
if v, ok := s.SignalList[id]; ok {
v.Add(conn)
}
}
//重回slice 复用
func (s *Tunnel) ReturnTunnel(conn *utils.Conn, cFlag string) {
if v, ok := s.TunnelList[cFlag]; ok {
func (s *Tunnel) ReturnTunnel(conn *utils.Conn, id int) {
if v, ok := s.TunnelList[id]; ok {
utils.FlushConn(conn.Conn)
v.Add(conn)
}
}
//删除通信通道
func (s *Tunnel) DelClientSignal(cFlag string) {
s.delClient(cFlag, s.SignalList)
func (s *Tunnel) DelClientSignal(id int) {
s.delClient(id, s.SignalList)
}
//删除隧道
func (s *Tunnel) DelClientTunnel(cFlag string) {
s.delClient(cFlag, s.TunnelList)
func (s *Tunnel) DelClientTunnel(id int) {
s.delClient(id, s.TunnelList)
}
func (s *Tunnel) delClient(cFlag string, l map[string]*list) {
if t := l[utils.Getverifyval(cFlag)]; t != nil {
func (s *Tunnel) delClient(id int, l map[int]*list) {
if t := l[id]; t != nil {
for {
if t.Len() <= 0 {
break
}
t.Pop().Close()
}
delete(l, utils.Getverifyval(cFlag))
delete(l, id)
}
}
//等待
func (s *Tunnel) waitAndPop(m map[string]*list, cFlag string) (c *utils.Conn, err error) {
func (s *Tunnel) waitAndPop(m map[int]*list, id int) (c *utils.Conn, err error) {
ticker := time.NewTicker(time.Millisecond * 100)
stop := time.After(time.Second * 10)
stop := time.After(time.Second * 3)
for {
select {
case <-ticker.C:
s.lock.Lock()
if v, ok := m[cFlag]; ok && v.Len() > 0 {
if v, ok := m[id]; ok && v.Len() > 0 {
c = v.Pop()
ticker.Stop()
s.lock.Unlock()
@ -225,16 +224,16 @@ func (s *Tunnel) waitAndPop(m map[string]*list, cFlag string) (c *utils.Conn, er
}
s.lock.Unlock()
case <-stop:
err = errors.New("client key: " + cFlag + ",err: get client conn timeout")
err = errors.New("client id: " + strconv.Itoa(id) + ",err: get client conn timeout")
return
}
}
return
}
func (s *Tunnel) verify(vKeyMd5 string) bool {
func (s *Tunnel) verify(id int) bool {
for k := range s.RunList {
if utils.Getverifyval(k) == vKeyMd5 {
if k == id {
return true
}
}

View File

@ -5,12 +5,16 @@ import (
"log"
"net"
"sync"
"sync/atomic"
"time"
)
type TRPClient struct {
svrAddr string
tcpNum int
svrAddr string
tcpNum int
tunnelNum int64
tunnel chan bool
serverStatus bool
sync.Mutex
vKey string
}
@ -21,6 +25,7 @@ func NewRPClient(svraddr string, tcpNum int, vKey string) *TRPClient {
c.svrAddr = svraddr
c.tcpNum = tcpNum
c.vKey = vKey
c.tunnel = make(chan bool)
return c
}
@ -29,12 +34,17 @@ func (s *TRPClient) Start() error {
for i := 0; i < s.tcpNum; i++ {
go s.NewConn()
}
for i := 0; i < 5; i++ {
go s.dealChan()
}
go s.session()
return nil
}
//新建
func (s *TRPClient) NewConn() error {
s.Lock()
s.serverStatus = false
conn, err := net.Dial("tcp", s.svrAddr)
if err != nil {
log.Println("连接服务端失败,五秒后将重连")
@ -44,11 +54,12 @@ func (s *TRPClient) NewConn() error {
return err
}
s.Unlock()
return s.process(utils.NewConn(conn))
return s.processor(utils.NewConn(conn))
}
//处理
func (s *TRPClient) process(c *utils.Conn) error {
func (s *TRPClient) processor(c *utils.Conn) error {
s.serverStatus = true
c.SetAlive()
if _, err := c.Write([]byte(utils.Getverifyval(s.vKey))); err != nil {
return err
@ -58,7 +69,6 @@ func (s *TRPClient) process(c *utils.Conn) error {
flags, err := c.ReadFlag()
if err != nil {
log.Println("服务端断开,五秒后将重连", err)
time.Sleep(5 * time.Second)
go s.NewConn()
break
}
@ -66,9 +76,6 @@ func (s *TRPClient) process(c *utils.Conn) error {
case utils.VERIFY_EER:
log.Fatalln("vkey:", s.vKey, "不正确,服务端拒绝连接,请检查")
case utils.WORK_CHAN: //隧道模式每次开启10个加快连接速度
for i := 0; i < 5; i++ {
go s.dealChan()
}
case utils.RES_MSG:
log.Println("服务端返回错误。")
default:
@ -98,13 +105,16 @@ func (s *TRPClient) dealChan() {
//写标志
c.WriteChan()
re:
atomic.AddInt64(&s.tunnelNum, 1)
//获取连接的host type(tcp or udp)
typeStr, host, en, de, crypt, mux, err := c.GetHostFromConn()
s.tunnel <- true
atomic.AddInt64(&s.tunnelNum, -1)
if err != nil {
c.Close()
return
}
Process(c, typeStr, host, en, de, crypt, mux)
s.ConnectAndCopy(c, typeStr, host, en, de, crypt, mux)
if mux {
utils.FlushConn(conn)
goto re
@ -113,7 +123,20 @@ re:
}
}
func Process(c *utils.Conn, typeStr, host string, en, de int, crypt, mux bool) {
func (s *TRPClient) session() {
t := time.NewTicker(time.Millisecond * 1000)
for {
select {
case <-s.tunnel:
case <-t.C:
}
if s.serverStatus && s.tunnelNum < 5 {
go s.dealChan()
}
}
}
func (s *TRPClient) ConnectAndCopy(c *utils.Conn, typeStr, host string, en, de int, crypt, mux bool) {
//与目标建立连接,超时时间为3
server, err := net.DialTimeout(typeStr, host, time.Second*3)
if err != nil {

View File

@ -25,7 +25,7 @@ var (
func main() {
flag.Parse()
server.VerifyKey = *VerifyKey
cnf := server.ServerConfig{
cnf := &utils.ServerConfig{
TcpPort: *httpPort,
Mode: *rpMode,
Target: *tunnelTarget,
@ -51,5 +51,5 @@ func main() {
}
log.Println("服务端启动监听tcp服务端端口", *TcpPort)
cnf.CompressDecode, cnf.CompressEncode = utils.GetCompressType(cnf.Compress)
server.StartNewServer(*TcpPort, &cnf)
server.StartNewServer(*TcpPort, cnf)
}

2
conf/clients.csv Normal file
View File

@ -0,0 +1,2 @@
2,zl4p3da659qa9rh3,127.0.0.1:58000,测试2,true,,,0,0,
1,rfd0tl1anega0d0g,127.0.0.1:53603,测试,true,1,1,1,1,snappy
1 2 zl4p3da659qa9rh3 127.0.0.1:58000 测试2 true 0 0
2 1 rfd0tl1anega0d0g 127.0.0.1:53603 测试 true 1 1 1 1 snappy

View File

@ -1,2 +1,2 @@
a.o.com,127.0.0.1:8082,7hiixust68kbz33a,,www.baidu.com
b.o.com,,7hiixust68kbz33a,,ab
a.o.com,127.0.0.1:8080,1,,,测试2
b.o.com,127.0.0.1:8082,2,,,测试

1 a.o.com 127.0.0.1:8082 127.0.0.1:8080 7hiixust68kbz33a 1 www.baidu.com 测试2
2 b.o.com 127.0.0.1:8082 7hiixust68kbz33a 2 ab 测试

View File

@ -1,5 +1,4 @@
8001,tunnelServer,127.0.0.1:88,jq5i7n0sjs1h0jje,aaa,bbb,,1,0,0,0,1
0,hostServer,,7n7bxc2bm1fyjfab,ab,b,,1,1,1,0,1
0,hostServer,,ts08z6vk5nc72fs8,aaa,bbb,snappy,1,0,1,2,3
8002,tunnelServer,127.0.0.1:88,2nxo93wvotb9g75s,,,,1,0,0,0,1
8025,socks5Server,,2p3qs71oym3zx52w,,,,1,0,0,0,1
9001,tunnelServer,127.0.0.1:8080,,,,1,0,0,0,0,1,1,true,test
53,udpServer,114.114.114.114:53,,,,1,0,0,0,0,2,2,true,udp测试
8024,socks5Server,,,,,1,0,0,0,0,3,2,true,socks5测试
8025,httpProxyServer,,,,,1,0,0,0,0,4,2,true,http测试

1 8001 9001 tunnelServer 127.0.0.1:88 127.0.0.1:8080 jq5i7n0sjs1h0jje aaa bbb 1 0 0 0 0 1 1 true test
2 0 53 hostServer udpServer 114.114.114.114:53 7n7bxc2bm1fyjfab ab b 1 1 0 1 0 0 0 1 2 2 true udp测试
3 0 8024 hostServer socks5Server ts08z6vk5nc72fs8 snappy aaa bbb 1 0 1 0 2 0 0 3 2 true socks5测试
4 8002 8025 tunnelServer httpProxyServer 127.0.0.1:88 2nxo93wvotb9g75s 1 0 0 0 0 1 4 2 true http测试
8025 socks5Server 2p3qs71oym3zx52w 1 0 0 0 1

76
server/base.go Normal file
View File

@ -0,0 +1,76 @@
package server
import (
"github.com/cnlh/easyProxy/bridge"
"github.com/cnlh/easyProxy/utils"
"sync"
)
//server base struct
type server struct {
bridge *bridge.Tunnel
config *utils.ServerConfig
sync.Mutex
}
func (s *server) GetTunnelAndWriteHost(connType string, cnf *utils.ServerConfig, addr string) (*utils.Conn, error) {
var err error
link, err := s.bridge.GetTunnel(cnf.ClientId, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
if err != nil {
return nil, err
}
if _, err = link.WriteHost(connType, addr); err != nil {
link.Close()
return nil, err
}
return link, nil
}
func (s *server) FlowAdd(in, out int64) {
s.Lock()
defer s.Unlock()
if s.config.Flow == nil {
s.config.Flow = new(utils.Flow)
}
s.config.Flow.ExportFlow += out
s.config.Flow.InletFlow += in
}
func (s *server) FlowAddHost(host *utils.HostList, in, out int64) {
s.Lock()
defer s.Unlock()
if s.config.Flow == nil {
s.config.Flow = new(utils.Flow)
}
host.Flow.ExportFlow += out
host.Flow.InletFlow += in
}
//热更新配置
func (s *server) ResetConfig() {
task, err := CsvDb.GetTask(s.config.Id)
if err != nil {
return
}
s.config.UseClientCnf = task.UseClientCnf
if s.config.UseClientCnf {
client, err := CsvDb.GetClient(s.config.ClientId)
if err == nil {
s.config.U = client.Cnf.U
s.config.P = client.Cnf.P
s.config.Compress = client.Cnf.Compress
s.config.Mux = client.Cnf.Mux
s.config.Crypt = client.Cnf.Crypt
}
s.config.CompressDecode, s.config.CompressEncode = utils.GetCompressType(client.Cnf.Compress)
} else {
if err == nil {
s.config.U = task.U
s.config.P = task.P
s.config.Compress = task.Compress
s.config.Mux = task.Mux
s.config.Crypt = task.Crypt
}
s.config.CompressDecode, s.config.CompressEncode = utils.GetCompressType(task.Compress)
}
}

View File

@ -1,271 +0,0 @@
package server
import (
"encoding/csv"
"errors"
"github.com/astaxie/beego"
"github.com/cnlh/easyProxy/utils"
"log"
"os"
"strconv"
)
type ServerConfig struct {
TcpPort int //服务端与客户端通信端口
Mode string //启动方式
Target string //目标
VerifyKey string //flag
U string //socks5验证用户名
P string //socks5验证密码
Compress string //压缩方式
Start int //是否开启
IsRun int //是否在运行
ClientStatus int //客s户端状态
Crypt bool //是否加密
Mux bool //是否加密
CompressEncode int //加密方式
CompressDecode int //解密方式
}
type HostList struct {
Vkey string //服务端与客户端通信端口
Host string //启动方式
Target string //目标
HeaderChange string //host修改
HostChange string //host修改
}
func NewCsv(runList map[string]interface{}) *Csv {
c := new(Csv)
c.RunList = runList
return c
}
type Csv struct {
Tasks []*ServerConfig
Path string
RunList map[string]interface{}
Hosts []*HostList //域名列表
}
func (s *Csv) Init() {
s.LoadTaskFromCsv()
s.LoadHostFromCsv()
}
func (s *Csv) StoreTasksToCsv() {
// 创建文件
csvFile, err := os.Create(beego.AppPath + "/conf/tasks.csv")
if err != nil {
log.Fatalf(err.Error())
}
defer csvFile.Close()
writer := csv.NewWriter(csvFile)
for _, task := range s.Tasks {
record := []string{
strconv.Itoa(task.TcpPort),
task.Mode,
task.Target,
task.VerifyKey,
task.U,
task.P,
task.Compress,
strconv.Itoa(task.Start),
utils.GetStrByBool(task.Crypt),
utils.GetStrByBool(task.Mux),
strconv.Itoa(task.CompressEncode),
strconv.Itoa(task.CompressDecode),
}
err := writer.Write(record)
if err != nil {
log.Fatalf(err.Error())
}
}
writer.Flush()
}
func (s *Csv) openFile(path string) ([][]string, error) {
// 打开文件
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
// 获取csv的reader
reader := csv.NewReader(file)
// 设置FieldsPerRecord为-1
reader.FieldsPerRecord = -1
// 读取文件中所有行保存到slice中
return reader.ReadAll()
}
func (s *Csv) LoadTaskFromCsv() {
path := beego.AppPath + "/conf/tasks.csv"
records, err := s.openFile(path)
if err != nil {
log.Fatal("配置文件打开错误:", path)
}
var tasks []*ServerConfig
// 将每一行数据保存到内存slice中
for _, item := range records {
post := &ServerConfig{
TcpPort: utils.GetIntNoErrByStr(item[0]),
Mode: item[1],
Target: item[2],
VerifyKey: item[3],
U: item[4],
P: item[5],
Compress: item[6],
Start: utils.GetIntNoErrByStr(item[7]),
Crypt: utils.GetBoolByStr(item[8]),
Mux: utils.GetBoolByStr(item[9]),
CompressEncode: utils.GetIntNoErrByStr(item[10]),
CompressDecode: utils.GetIntNoErrByStr(item[11]),
}
tasks = append(tasks, post)
}
s.Tasks = tasks
}
func (s *Csv) NewTask(t *ServerConfig) {
s.Tasks = append(s.Tasks, t)
s.StoreTasksToCsv()
}
func (s *Csv) UpdateTask(t *ServerConfig) error {
for k, v := range s.Tasks {
if v.VerifyKey == t.VerifyKey {
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
s.Tasks = append(s.Tasks, t)
s.StoreTasksToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) UpdateHost(t *HostList) error {
for k, v := range s.Hosts {
if v.Host == t.Host {
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
s.Hosts = append(s.Hosts, t)
s.StoreHostToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) AddRunList(vKey string, svr interface{}) {
s.RunList[vKey] = svr
}
func (s *Csv) DelRunList(vKey string) {
delete(s.RunList, vKey)
}
func (s *Csv) DelTask(vKey string) error {
for k, v := range s.Tasks {
if v.VerifyKey == vKey {
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
s.StoreTasksToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) GetTask(vKey string) (v *ServerConfig, err error) {
for _, v = range s.Tasks {
if v.VerifyKey == vKey {
return
}
}
err = errors.New("未找到")
return
}
func (s *Csv) StoreHostToCsv() {
// 创建文件
csvFile, err := os.Create(beego.AppPath + "/conf/hosts.csv")
if err != nil {
panic(err)
}
defer csvFile.Close()
// 获取csv的Writer
writer := csv.NewWriter(csvFile)
// 将map中的Post转换成slice因为csv的Write需要slice参数
// 并写入csv文件
for _, host := range s.Hosts {
record := []string{
host.Host,
host.Target,
host.Vkey,
host.HeaderChange,
host.HostChange,
}
err1 := writer.Write(record)
if err1 != nil {
panic(err1)
}
}
// 确保所有内存数据刷到csv文件
writer.Flush()
}
func (s *Csv) LoadHostFromCsv() {
path := beego.AppPath + "/conf/hosts.csv"
records, err := s.openFile(path)
if err != nil {
log.Fatal("配置文件打开错误:", path)
}
var hosts []*HostList
// 将每一行数据保存到内存slice中
for _, item := range records {
post := &HostList{
Vkey: item[2],
Host: item[0],
Target: item[1],
HeaderChange: item[3],
HostChange: item[4],
}
hosts = append(hosts, post)
}
s.Hosts = hosts
}
func (s *Csv) DelHost(host string) error {
for k, v := range s.Hosts {
if v.Host == host {
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
s.StoreHostToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) NewHost(t *HostList) {
s.Hosts = append(s.Hosts, t)
s.StoreHostToCsv()
}
func (s *Csv) GetHostList(start, length int, vKey string) ([]*HostList, int) {
list := make([]*HostList, 0)
var cnt int
for _, v := range s.Hosts {
if v.Vkey == vKey {
cnt++
if start--; start < 0 {
if length--; length > 0 {
list = append(list, v)
}
}
}
}
return list, cnt
}

View File

@ -41,8 +41,8 @@ func ProcessHost(c *utils.Conn, s *TunnelModeServer) error {
var (
isConn = true
link *utils.Conn
cnf *ServerConfig
host *HostList
client *utils.Client
host *utils.HostList
wg sync.WaitGroup
)
for {
@ -52,48 +52,56 @@ func ProcessHost(c *utils.Conn, s *TunnelModeServer) error {
}
//首次获取conn
if isConn {
isConn = false
if host, cnf, err = GetKeyByHost(r.Host); err != nil {
if host, client, err = GetKeyByHost(r.Host); err != nil {
log.Printf("the host %s is not found !", r.Host)
break
}
if err = s.auth(r, c, cnf.U, cnf.P); err != nil {
client.Cnf.ClientId = host.ClientId
client.Cnf.CompressDecode, client.Cnf.CompressEncode = utils.GetCompressType(client.Cnf.Compress)
if err = s.auth(r, c, client.Cnf.U, client.Cnf.P); err != nil {
break
}
if link, err = s.GetTunnelAndWriteHost(utils.CONN_TCP, cnf, host.Target); err != nil {
if link, err = s.GetTunnelAndWriteHost(utils.CONN_TCP, client.Cnf, host.Target); err != nil {
log.Println("get bridge tunnel error: ", err)
break
}
if flag, err := link.ReadFlag(); err != nil || flag == utils.CONN_ERROR {
log.Printf("the host %s connection to %s error", r.Host, host.Target)
break
} else {
wg.Add(1)
go func() {
utils.Relay(c.Conn, link.Conn, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
out, _ := utils.Relay(c.Conn, link.Conn, client.Cnf.CompressDecode, client.Cnf.Crypt, client.Cnf.Mux)
wg.Done()
s.FlowAddHost(host, 0, out)
}()
}
isConn = false
}
utils.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String())
b, err := httputil.DumpRequest(r, true)
s.FlowAddHost(host, int64(len(b)), 0)
if err != nil {
break
}
if _, err := link.WriteTo(b, cnf.CompressEncode, cnf.Crypt); err != nil {
if _, err := link.WriteTo(b, client.Cnf.CompressEncode, client.Cnf.Crypt); err != nil {
break
}
}
wg.Wait()
if cnf != nil && cnf.Mux && link != nil {
link.WriteTo([]byte(utils.IO_EOF), cnf.CompressEncode, cnf.Crypt)
s.bridge.ReturnTunnel(link, getverifyval(cnf.VerifyKey))
if client != nil && client.Cnf != nil && client.Cnf.Mux && link != nil {
link.WriteTo([]byte(utils.IO_EOF), client.Cnf.CompressEncode, client.Cnf.Crypt)
s.bridge.ReturnTunnel(link, client.Id)
} else if link != nil {
link.Close()
}
if isConn {
s.writeConnFail(c.Conn)
}
c.Close()
return nil
}

View File

@ -11,35 +11,43 @@ import (
"sync"
)
type RunServer struct {
flag int //标志
ExportFlow int64 //出口流量
InletFlow int64 //入口流量
service interface{}
sync.Mutex
}
var (
Bridge *bridge.Tunnel
RunList map[string]interface{} //运行中的任务
CsvDb *Csv
RunList map[int]interface{} //运行中的任务
CsvDb = utils.GetCsvDb()
VerifyKey string
)
func init() {
RunList = make(map[string]interface{})
RunList = make(map[int]interface{})
}
//从csv文件中恢复任务
func InitFromCsv() {
for _, v := range CsvDb.Tasks {
if v.Start == 1 {
log.Println("启动模式:", v.Mode, "监听端口:", v.TcpPort, "客户端令牌:", v.VerifyKey)
log.Println("启动模式:", v.Mode, "监听端口:", v.TcpPort)
AddTask(v)
}
}
}
//start a new server
func StartNewServer(bridgePort int, cnf *ServerConfig) {
func StartNewServer(bridgePort int, cnf *utils.ServerConfig) {
Bridge = bridge.NewTunnel(bridgePort, RunList)
if err := Bridge.StartTunnel(); err != nil {
log.Fatalln("服务端开启失败", err)
}
if svr := NewMode(Bridge, cnf); svr != nil {
RunList[cnf.VerifyKey] = svr
RunList[cnf.Id] = svr
err := reflect.ValueOf(svr).MethodByName("Start").Call(nil)[0]
if err.Interface() != nil {
log.Println(err)
@ -50,7 +58,8 @@ func StartNewServer(bridgePort int, cnf *ServerConfig) {
}
//new a server by mode name
func NewMode(Bridge *bridge.Tunnel, config *ServerConfig) interface{} {
func NewMode(Bridge *bridge.Tunnel, c *utils.ServerConfig) interface{} {
config := utils.DeepCopyConfig(c)
switch config.Mode {
case "tunnelServer":
return NewTunnelModeServer(ProcessTunnel, Bridge, config)
@ -61,14 +70,12 @@ func NewMode(Bridge *bridge.Tunnel, config *ServerConfig) interface{} {
case "udpServer":
return NewUdpModeServer(Bridge, config)
case "webServer":
InitCsvDb()
InitFromCsv()
p, _ := beego.AppConfig.Int("hostPort")
t := &ServerConfig{
t := &utils.ServerConfig{
TcpPort: p,
Mode: "httpHostServer",
Target: "",
VerifyKey: "",
U: "",
P: "",
Compress: "",
@ -87,15 +94,10 @@ func NewMode(Bridge *bridge.Tunnel, config *ServerConfig) interface{} {
}
//stop server
func StopServer(cFlag string) error {
if v, ok := RunList[cFlag]; ok {
func StopServer(id int) error {
if v, ok := RunList[id]; ok {
reflect.ValueOf(v).MethodByName("Close").Call(nil)
delete(RunList, cFlag)
if VerifyKey == "" { //多客户端模式关闭相关隧道
Bridge.DelClientSignal(cFlag)
Bridge.DelClientTunnel(cFlag)
}
if t, err := CsvDb.GetTask(cFlag); err != nil {
if t, err := CsvDb.GetTask(id); err != nil {
return err
} else {
t.Start = 0
@ -107,15 +109,14 @@ func StopServer(cFlag string) error {
}
//add task
func AddTask(t *ServerConfig) error {
t.CompressDecode, t.CompressEncode = utils.GetCompressType(t.Compress)
func AddTask(t *utils.ServerConfig) error {
if svr := NewMode(Bridge, t); svr != nil {
RunList[t.VerifyKey] = svr
RunList[t.Id] = svr
go func() {
err := reflect.ValueOf(svr).MethodByName("Start").Call(nil)[0]
if err.Interface() != nil {
log.Println("客户端", t.VerifyKey, "启动失败,错误:", err)
delete(RunList, t.VerifyKey)
log.Println("客户端", t.Id, "启动失败,错误:", err)
delete(RunList, t.Id)
}
}()
} else {
@ -125,8 +126,8 @@ func AddTask(t *ServerConfig) error {
}
//start task
func StartTask(vKey string) error {
if t, err := CsvDb.GetTask(vKey); err != nil {
func StartTask(id int) error {
if t, err := CsvDb.GetTask(id); err != nil {
return err
} else {
AddTask(t)
@ -137,35 +138,20 @@ func StartTask(vKey string) error {
}
//delete task
func DelTask(vKey string) error {
if err := StopServer(vKey); err != nil {
func DelTask(id int) error {
if err := StopServer(id); err != nil {
return err
}
for _, v := range CsvDb.Hosts {
if v.Vkey == vKey {
CsvDb.DelHost(v.Host)
}
}
return CsvDb.DelTask(vKey)
}
//init csv from file
func InitCsvDb() *Csv {
var once sync.Once
once.Do(func() {
CsvDb = NewCsv(RunList)
CsvDb.Init()
})
return CsvDb
return CsvDb.DelTask(id)
}
//get key by host from x
func GetKeyByHost(host string) (h *HostList, t *ServerConfig, err error) {
func GetKeyByHost(host string) (h *utils.HostList, t *utils.Client, err error) {
for _, v := range CsvDb.Hosts {
s := strings.Split(host, ":")
if s[0] == v.Host {
h = v
t, err = CsvDb.GetTask(v.Vkey)
t, err = CsvDb.GetClient(v.ClientId)
return
}
}
@ -174,22 +160,32 @@ func GetKeyByHost(host string) (h *HostList, t *ServerConfig, err error) {
}
//get task list by page num
func GetServerConfig(start, length int, typeVal string) ([]*ServerConfig, int) {
list := make([]*ServerConfig, 0)
func GetServerConfig(start, length int, typeVal string, clientId int) ([]*utils.ServerConfig, int) {
list := make([]*utils.ServerConfig, 0)
var cnt int
for _, v := range CsvDb.Tasks {
if v.Mode != typeVal {
if (typeVal != "" && v.Mode != typeVal) || (typeVal == "" && clientId != v.ClientId) {
continue
}
if v.UseClientCnf {
v = utils.DeepCopyConfig(v)
if c, err := CsvDb.GetClient(v.ClientId); err == nil {
v.Compress = c.Cnf.Compress
v.Mux = c.Cnf.Mux
v.Crypt = c.Cnf.Crypt
v.U = c.Cnf.U
v.P = c.Cnf.P
}
}
cnt++
if start--; start < 0 {
if length--; length > 0 {
if _, ok := RunList[v.VerifyKey]; ok {
if _, ok := RunList[v.Id]; ok {
v.IsRun = 1
} else {
v.IsRun = 0
}
if s, ok := Bridge.SignalList[getverifyval(v.VerifyKey)]; ok {
if s, ok := Bridge.SignalList[v.ClientId]; ok {
if s.Len() > 0 {
v.ClientStatus = 1
} else {
@ -201,16 +197,91 @@ func GetServerConfig(start, length int, typeVal string) ([]*ServerConfig, int) {
list = append(list, v)
}
}
}
return list, cnt
}
//get verify value
//when mode is webServer and vKey is not none
func getverifyval(vkey string) string {
if VerifyKey != "" {
return utils.Md5(VerifyKey)
}
return utils.Md5(vkey)
//获取客户端列表
func GetClientList(start, length int) (list []*utils.Client, cnt int) {
list, cnt = CsvDb.GetClientList(start, length)
dealClientData(list)
return
}
func dealClientData(list []*utils.Client) {
for _, v := range list {
if _, ok := Bridge.SignalList[v.Id]; ok {
v.IsConnect = true
} else {
v.IsConnect = false
}
v.Flow.InletFlow = 0
v.Flow.ExportFlow = 0
for _, h := range CsvDb.Hosts {
if h.ClientId == v.Id {
v.Flow.InletFlow += h.Flow.InletFlow
v.Flow.ExportFlow += h.Flow.ExportFlow
}
}
for _, t := range CsvDb.Tasks {
if t.ClientId == v.Id {
v.Flow.InletFlow += t.Flow.InletFlow
v.Flow.ExportFlow += t.Flow.ExportFlow
}
}
}
return
}
//根据客户端id删除其所属的所有隧道和域名
func DelTunnelAndHostByClientId(clientId int) {
for _, v := range CsvDb.Tasks {
if v.ClientId == clientId {
DelTask(v.Id)
}
}
for _, v := range CsvDb.Hosts {
if v.ClientId == clientId {
CsvDb.DelHost(v.Host)
}
}
}
//关闭客户端连接
func DelClientConnect(clientId int) {
Bridge.DelClientTunnel(clientId)
Bridge.DelClientSignal(clientId)
}
func GetDashboardData() map[string]int {
data := make(map[string]int)
data["hostCount"] = len(CsvDb.Hosts)
data["clientCount"] = len(CsvDb.Clients)
list := CsvDb.Clients
dealClientData(list)
c := 0
var in, out int64
for _, v := range list {
if v.IsConnect {
c += 1
}
in += v.Flow.InletFlow
out += v.Flow.ExportFlow
}
data["clientOnlineCount"] = c
data["inletFlowCount"] = int(in)
data["exportFlowCount"] = int(out)
for _, v := range CsvDb.Tasks {
switch v.Mode {
case "tunnelServer":
data["tunnelServerCount"] += 1
case "socks5Server":
data["socks5ServerCount"] += 1
case "httpProxyServer":
data["httpProxyServerCount"] += 1
case "udpServer":
data["udpServerCount"] += 1
}
}
return data
}

View File

@ -160,13 +160,14 @@ func (s *Sock5ModeServer) handleConnect(c net.Conn) {
proxyConn, err := s.doConnect(c, connectMethod)
defer func() {
if s.config.Mux && proxyConn != nil {
s.bridge.ReturnTunnel(proxyConn, getverifyval(s.config.VerifyKey))
s.bridge.ReturnTunnel(proxyConn, s.config.ClientId)
}
}()
if err != nil {
c.Close()
} else {
utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
out, in := utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
s.FlowAdd(in, out)
}
}
@ -197,13 +198,14 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) {
proxyConn, err := s.doConnect(c, associateMethod)
defer func() {
if s.config.Mux && proxyConn != nil {
s.bridge.ReturnTunnel(proxyConn, getverifyval(s.config.VerifyKey))
s.bridge.ReturnTunnel(proxyConn, s.config.ClientId)
}
}()
if err != nil {
c.Close()
} else {
utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
out, in := utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
s.FlowAdd(in, out)
}
}
@ -295,6 +297,7 @@ func (s *Sock5ModeServer) Start() error {
}
log.Fatal("accept error: ", err)
}
s.ResetConfig()
go s.handleConn(conn)
}
return nil
@ -306,7 +309,7 @@ func (s *Sock5ModeServer) Close() error {
}
//new
func NewSock5ModeServer(bridge *bridge.Tunnel, cnf *ServerConfig) *Sock5ModeServer {
func NewSock5ModeServer(bridge *bridge.Tunnel, cnf *utils.ServerConfig) *Sock5ModeServer {
s := new(Sock5ModeServer)
s.bridge = bridge
s.config = cnf

View File

@ -12,33 +12,15 @@ import (
"strings"
)
//server base struct
type server struct {
bridge *bridge.Tunnel
config *ServerConfig
}
func (s *server) GetTunnelAndWriteHost(connType string, cnf *ServerConfig, addr string) (*utils.Conn, error) {
var err error
link, err := s.bridge.GetTunnel(getverifyval(cnf.VerifyKey), cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
if err != nil {
return nil, err
}
if _, err = link.WriteHost(connType, addr); err != nil {
link.Close()
return nil, err
}
return link, nil
}
type TunnelModeServer struct {
server
process process
listener *net.TCPListener
errorContent []byte
process process
listener *net.TCPListener
}
//tcp|http|host
func NewTunnelModeServer(process process, bridge *bridge.Tunnel, cnf *ServerConfig) *TunnelModeServer {
func NewTunnelModeServer(process process, bridge *bridge.Tunnel, cnf *utils.ServerConfig) *TunnelModeServer {
s := new(TunnelModeServer)
s.bridge = bridge
s.process = process
@ -49,6 +31,9 @@ func NewTunnelModeServer(process process, bridge *bridge.Tunnel, cnf *ServerConf
//开始
func (s *TunnelModeServer) Start() error {
var err error
if s.errorContent, err = utils.ReadAllFromFile(beego.AppPath + "/web/static/page/error.html"); err != nil {
s.errorContent = []byte("easyProxy 404")
}
s.listener, err = net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), s.config.TcpPort, ""})
if err != nil {
return err
@ -62,6 +47,7 @@ func (s *TunnelModeServer) Start() error {
log.Println(err)
continue
}
s.ResetConfig()
go s.process(utils.NewConn(conn), s)
}
return nil
@ -70,20 +56,25 @@ func (s *TunnelModeServer) Start() error {
//权限认证
func (s *TunnelModeServer) auth(r *http.Request, c *utils.Conn, u, p string) error {
if u != "" && p != "" && !utils.CheckAuth(r, u, p) {
c.Write([]byte(utils.Unauthorized_BYTES))
c.Write([]byte(utils.UnauthorizedBytes))
c.Close()
return errors.New("401 Unauthorized")
}
return nil
}
func (s *TunnelModeServer) writeConnFail(c net.Conn) {
c.Write([]byte(utils.ConnectionFailBytes))
c.Write(s.errorContent)
}
//与客户端建立通道
func (s *TunnelModeServer) dealClient(c *utils.Conn, cnf *ServerConfig, addr string, method string, rb []byte) error {
func (s *TunnelModeServer) dealClient(c *utils.Conn, cnf *utils.ServerConfig, addr string, method string, rb []byte) error {
var link *utils.Conn
var err error
defer func() {
if cnf.Mux && link != nil {
s.bridge.ReturnTunnel(link, getverifyval(cnf.VerifyKey))
s.bridge.ReturnTunnel(link, cnf.ClientId)
}
}()
if link, err = s.GetTunnelAndWriteHost(utils.CONN_TCP, cnf, addr); err != nil {
@ -97,7 +88,8 @@ func (s *TunnelModeServer) dealClient(c *utils.Conn, cnf *ServerConfig, addr str
} else if rb != nil {
link.WriteTo(rb, cnf.CompressEncode, cnf.Crypt)
}
utils.ReplayWaitGroup(link.Conn, c.Conn, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
out, in := utils.ReplayWaitGroup(link.Conn, c.Conn, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
s.FlowAdd(in, out)
}
}
return nil
@ -139,7 +131,7 @@ func (s *HostServer) Start() error {
return nil
}
func NewHostServer(cnf *ServerConfig) *HostServer {
func NewHostServer(cnf *utils.ServerConfig) *HostServer {
s := new(HostServer)
s.config = cnf
return s

View File

@ -10,13 +10,12 @@ import (
)
type UdpModeServer struct {
bridge *bridge.Tunnel
server
listener *net.UDPConn
udpMap map[string]*utils.Conn
config *ServerConfig
}
func NewUdpModeServer(bridge *bridge.Tunnel, cnf *ServerConfig) *UdpModeServer {
func NewUdpModeServer(bridge *bridge.Tunnel, cnf *utils.ServerConfig) *UdpModeServer {
s := new(UdpModeServer)
s.bridge = bridge
s.udpMap = make(map[string]*utils.Conn)
@ -40,6 +39,7 @@ func (s *UdpModeServer) Start() error {
}
continue
}
s.ResetConfig()
go s.process(addr, data[:n])
}
return nil
@ -47,7 +47,7 @@ func (s *UdpModeServer) Start() error {
//TODO:效率问题有待解决
func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
conn, err := s.bridge.GetTunnel(getverifyval(s.config.VerifyKey), s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
conn, err := s.bridge.GetTunnel(s.config.ClientId, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
if err != nil {
log.Println(err)
return
@ -60,21 +60,22 @@ func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
defer func() {
if conn != nil && s.config.Mux {
conn.WriteTo([]byte(utils.IO_EOF), s.config.CompressEncode, s.config.Crypt)
s.bridge.ReturnTunnel(conn, getverifyval(s.config.VerifyKey))
s.bridge.ReturnTunnel(conn, s.config.ClientId)
} else {
conn.Close()
}
}()
if flag == utils.CONN_SUCCESS {
conn.WriteTo(data, s.config.CompressEncode, s.config.Crypt)
buf := make([]byte, 1024)
//conn.conn.SetReadDeadline(time.Now().Add(time.Duration(time.Second * 3)))
n, err := conn.ReadFrom(buf, s.config.CompressDecode, s.config.Crypt)
in, _ := conn.WriteTo(data, s.config.CompressEncode, s.config.Crypt)
buf := utils.BufPoolUdp.Get().([]byte)
out, err := conn.ReadFrom(buf, s.config.CompressDecode, s.config.Crypt)
if err != nil || err == io.EOF {
log.Println("revieve error:", err)
return
}
s.listener.WriteToUDP(buf[:n], addr)
s.listener.WriteToUDP(buf[:out], addr)
s.FlowAdd(int64(in), int64(out))
utils.BufPoolUdp.Put(buf)
}
}
}

View File

@ -107,14 +107,14 @@ func (s *SnappyConn) Write(b []byte) (n int, err error) {
//snappy压缩读 包含解密
func (s *SnappyConn) Read(b []byte) (n int, err error) {
buf := bufPool.Get().([]byte)
defer func() {
if err == nil && n == len(IO_EOF) && string(b[:n]) == IO_EOF {
err = io.EOF
n = 0
}
bufPool.Put(buf)
}()
buf := bufPool.Get().([]byte)
defer bufPool.Put(buf)
if n, err = s.r.Read(buf); err != nil {
return
}
@ -153,8 +153,8 @@ func (s *Conn) ReadLen(cLen int) ([]byte, error) {
buf = bufPoolSmall.Get().([]byte)[:cLen]
defer bufPoolSmall.Put(buf)
} else {
buf = bufPool.Get().([]byte)[:cLen]
defer bufPool.Put(buf)
buf = bufPoolMax.Get().([]byte)[:cLen]
defer bufPoolMax.Put(buf)
}
if n, err := io.ReadFull(s, buf); err != nil || n != cLen {
return buf, errors.New("读取指定长度错误" + err.Error())

487
utils/file.go Normal file
View File

@ -0,0 +1,487 @@
package utils
import (
"easyProxy/utils"
"encoding/csv"
"errors"
"github.com/astaxie/beego"
"log"
"os"
"strconv"
"sync"
)
var (
CsvDb *Csv
once sync.Once
)
type Flow struct {
ExportFlow int64 //出口流量
InletFlow int64 //入口流量
}
type Client struct {
Cnf *ServerConfig
Id int //id
VerifyKey string //验证密钥
Addr string //客户端ip地址
Remark string //备注
Status bool //是否开启
IsConnect bool //是否连接
Flow *Flow
}
type ServerConfig struct {
TcpPort int //服务端与客户端通信端口
VerifyKey string
Mode string //启动方式
Target string //目标
U string //socks5验证用户名
P string //socks5验证密码
Compress string //压缩方式
Start int //是否开启
IsRun int //是否在运行
ClientStatus int //客s户端状态
Crypt bool //是否加密
Mux bool //是否加密
CompressEncode int //加密方式
CompressDecode int //解密方式
Id int //Id
ClientId int //所属客户端id
UseClientCnf bool //是否继承客户端配置
Flow *Flow
Remark string //备注
}
type HostList struct {
ClientId int //服务端与客户端通信端口
Host string //启动方式
Target string //目标
HeaderChange string //host修改
HostChange string //host修改
Flow *Flow
Remark string //备注
}
func NewCsv() *Csv {
c := new(Csv)
return c
}
type Csv struct {
Tasks []*ServerConfig
Path string
Hosts []*HostList //域名列表
Clients []*Client //客户端
ClientIncreaseId int //客户端id
TaskIncreaseId int //任务自增ID
sync.Mutex
}
func (s *Csv) Init() {
s.LoadTaskFromCsv()
s.LoadHostFromCsv()
s.LoadClientFromCsv()
}
func (s *Csv) StoreTasksToCsv() {
// 创建文件
csvFile, err := os.Create(beego.AppPath + "/conf/tasks.csv")
if err != nil {
log.Fatalf(err.Error())
}
defer csvFile.Close()
writer := csv.NewWriter(csvFile)
for _, task := range s.Tasks {
record := []string{
strconv.Itoa(task.TcpPort),
task.Mode,
task.Target,
task.U,
task.P,
task.Compress,
strconv.Itoa(task.Start),
GetStrByBool(task.Crypt),
GetStrByBool(task.Mux),
strconv.Itoa(task.CompressEncode),
strconv.Itoa(task.CompressDecode),
strconv.Itoa(task.Id),
strconv.Itoa(task.ClientId),
strconv.FormatBool(task.UseClientCnf),
task.Remark,
}
err := writer.Write(record)
if err != nil {
log.Fatalf(err.Error())
}
}
writer.Flush()
}
func (s *Csv) openFile(path string) ([][]string, error) {
// 打开文件
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
// 获取csv的reader
reader := csv.NewReader(file)
// 设置FieldsPerRecord为-1
reader.FieldsPerRecord = -1
// 读取文件中所有行保存到slice中
return reader.ReadAll()
}
func (s *Csv) LoadTaskFromCsv() {
path := beego.AppPath + "/conf/tasks.csv"
records, err := s.openFile(path)
if err != nil {
log.Fatal("配置文件打开错误:", path)
}
var tasks []*ServerConfig
// 将每一行数据保存到内存slice中
for _, item := range records {
post := &ServerConfig{
TcpPort: GetIntNoErrByStr(item[0]),
Mode: item[1],
Target: item[2],
U: item[3],
P: item[4],
Compress: item[5],
Start: GetIntNoErrByStr(item[6]),
Crypt: GetBoolByStr(item[7]),
Mux: GetBoolByStr(item[8]),
CompressEncode: GetIntNoErrByStr(item[9]),
CompressDecode: GetIntNoErrByStr(item[10]),
Id: GetIntNoErrByStr(item[11]),
ClientId: GetIntNoErrByStr(item[12]),
UseClientCnf: GetBoolByStr(item[13]),
Remark: item[14],
}
post.Flow = new(Flow)
tasks = append(tasks, post)
if post.Id > s.TaskIncreaseId {
s.TaskIncreaseId = post.Id
}
}
s.Tasks = tasks
}
func (s *Csv) GetTaskId() int {
s.Lock()
defer s.Unlock()
s.TaskIncreaseId++
return s.TaskIncreaseId
}
func (s *Csv) GetIdByVerifyKey(vKey string, addr string) (int, error) {
s.Lock()
defer s.Unlock()
for _, v := range s.Clients {
if utils.Getverifyval(v.VerifyKey) == vKey && v.Status {
v.Addr = addr
return v.Id, nil
}
}
return 0, errors.New("not found")
}
func (s *Csv) NewTask(t *ServerConfig) {
t.Flow = new(Flow)
s.Tasks = append(s.Tasks, t)
s.StoreTasksToCsv()
}
func (s *Csv) UpdateTask(t *ServerConfig) error {
for k, v := range s.Tasks {
if v.Id == t.Id {
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
s.Tasks = append(s.Tasks, t)
s.StoreTasksToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) DelTask(id int) error {
for k, v := range s.Tasks {
if v.Id == id {
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
s.StoreTasksToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) GetTask(id int) (v *ServerConfig, err error) {
for _, v = range s.Tasks {
if v.Id == id {
return
}
}
err = errors.New("未找到")
return
}
func (s *Csv) StoreHostToCsv() {
// 创建文件
csvFile, err := os.Create(beego.AppPath + "/conf/hosts.csv")
if err != nil {
panic(err)
}
defer csvFile.Close()
// 获取csv的Writer
writer := csv.NewWriter(csvFile)
// 将map中的Post转换成slice因为csv的Write需要slice参数
// 并写入csv文件
for _, host := range s.Hosts {
record := []string{
host.Host,
host.Target,
strconv.Itoa(host.ClientId),
host.HeaderChange,
host.HostChange,
host.Remark,
}
err1 := writer.Write(record)
if err1 != nil {
panic(err1)
}
}
// 确保所有内存数据刷到csv文件
writer.Flush()
}
func (s *Csv) LoadClientFromCsv() {
path := beego.AppPath + "/conf/clients.csv"
records, err := s.openFile(path)
if err != nil {
log.Fatal("配置文件打开错误:", path)
}
var clients []*Client
// 将每一行数据保存到内存slice中
for _, item := range records {
post := &Client{
Id: GetIntNoErrByStr(item[0]),
VerifyKey: item[1],
Addr: item[2],
Remark: item[3],
Status: GetBoolByStr(item[4]),
Cnf: &ServerConfig{
U: item[5],
P: item[6],
Crypt: GetBoolByStr(item[7]),
Mux: GetBoolByStr(item[8]),
Compress: item[9],
},
}
if post.Id > s.ClientIncreaseId {
s.ClientIncreaseId = post.Id
}
post.Flow = new(Flow)
clients = append(clients, post)
}
s.Clients = clients
}
func (s *Csv) LoadHostFromCsv() {
path := beego.AppPath + "/conf/hosts.csv"
records, err := s.openFile(path)
if err != nil {
log.Fatal("配置文件打开错误:", path)
}
var hosts []*HostList
// 将每一行数据保存到内存slice中
for _, item := range records {
post := &HostList{
ClientId: GetIntNoErrByStr(item[2]),
Host: item[0],
Target: item[1],
HeaderChange: item[3],
HostChange: item[4],
Remark: item[5],
}
post.Flow = new(Flow)
hosts = append(hosts, post)
}
s.Hosts = hosts
}
func (s *Csv) DelHost(host string) error {
for k, v := range s.Hosts {
if v.Host == host {
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
s.StoreHostToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) NewHost(t *HostList) {
t.Flow = new(Flow)
s.Hosts = append(s.Hosts, t)
s.StoreHostToCsv()
}
func (s *Csv) UpdateHost(t *HostList) error {
for k, v := range s.Hosts {
if v.Host == t.Host {
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
s.Hosts = append(s.Hosts, t)
s.StoreHostToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) GetHostList(start, length int, id int) ([]*HostList, int) {
list := make([]*HostList, 0)
var cnt int
for _, v := range s.Hosts {
if id == 0 || v.ClientId == id {
cnt++
if start--; start < 0 {
if length--; length > 0 {
list = append(list, v)
}
}
}
}
return list, cnt
}
func (s *Csv) DelClient(id int) error {
for k, v := range s.Clients {
if v.Id == id {
s.Clients = append(s.Clients[:k], s.Clients[k+1:]...)
s.StoreClientsToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) NewClient(c *Client) {
s.Lock()
defer s.Unlock()
c.Flow = new(Flow)
s.Clients = append(s.Clients, c)
s.StoreClientsToCsv()
}
func (s *Csv) GetClientId() int {
s.Lock()
defer s.Unlock()
s.ClientIncreaseId++
return s.ClientIncreaseId
}
func (s *Csv) UpdateClient(t *Client) error {
s.Lock()
defer s.Unlock()
for k, v := range s.Clients {
if v.Id == t.Id {
s.Clients = append(s.Clients[:k], s.Clients[k+1:]...)
s.Clients = append(s.Clients, t)
s.StoreClientsToCsv()
return nil
}
}
return errors.New("不存在")
}
func (s *Csv) GetClientList(start, length int) ([]*Client, int) {
list := make([]*Client, 0)
var cnt int
for _, v := range s.Clients {
cnt++
if start--; start < 0 {
if length--; length > 0 {
list = append(list, v)
}
}
}
return list, cnt
}
func (s *Csv) GetClient(id int) (v *Client, err error) {
for _, v = range s.Clients {
if v.Id == id {
return
}
}
err = errors.New("未找到")
return
}
func (s *Csv) StoreClientsToCsv() {
// 创建文件
csvFile, err := os.Create(beego.AppPath + "/conf/clients.csv")
if err != nil {
log.Fatalf(err.Error())
}
defer csvFile.Close()
writer := csv.NewWriter(csvFile)
for _, client := range s.Clients {
record := []string{
strconv.Itoa(client.Id),
client.VerifyKey,
client.Addr,
client.Remark,
strconv.FormatBool(client.Status),
client.Cnf.U,
client.Cnf.P,
utils.GetStrByBool(client.Cnf.Crypt),
utils.GetStrByBool(client.Cnf.Mux),
client.Cnf.Compress,
}
err := writer.Write(record)
if err != nil {
log.Fatalf(err.Error())
}
}
writer.Flush()
}
//init csv from file
func GetCsvDb() *Csv {
once.Do(func() {
CsvDb = NewCsv()
CsvDb.Init()
})
return CsvDb
}
//深拷贝serverConfig
func DeepCopyConfig(c *ServerConfig) *ServerConfig {
return &ServerConfig{
TcpPort: c.TcpPort,
VerifyKey: c.VerifyKey,
Mode: c.Mode,
Target: c.Target,
U: c.U,
P: c.P,
Compress: c.Compress,
Start: c.Start,
IsRun: c.IsRun,
ClientStatus: c.ClientStatus,
Crypt: c.Crypt,
Mux: c.Mux,
CompressEncode: c.CompressEncode,
CompressDecode: c.CompressDecode,
Id: c.Id,
ClientId: c.ClientId,
UseClientCnf: c.UseClientCnf,
Flow: c.Flow,
Remark: c.Remark,
}
}

View File

@ -3,7 +3,8 @@ package utils
import "sync"
const poolSize = 64 * 1024
const poolSizeSmall = 10
const poolSizeSmall = 100
const poolSizeUdp = 1472
const poolSizeCopy = 32 * 1024
var bufPool = sync.Pool{
@ -11,6 +12,16 @@ var bufPool = sync.Pool{
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)

View File

@ -3,9 +3,11 @@ package utils
import (
"encoding/base64"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"regexp"
"strconv"
"strings"
@ -18,48 +20,52 @@ const (
COMPRESS_NONE_DECODE
COMPRESS_SNAPY_ENCODE
COMPRESS_SNAPY_DECODE
VERIFY_EER = "vkey"
WORK_MAIN = "main"
WORK_CHAN = "chan"
RES_SIGN = "sign"
RES_MSG = "msg0"
CONN_SUCCESS = "sucs"
CONN_ERROR = "fail"
TEST_FLAG = "tst"
CONN_TCP = "tcp"
CONN_UDP = "udp"
Unauthorized_BYTES = `HTTP/1.1 401 Unauthorized
VERIFY_EER = "vkey"
WORK_MAIN = "main"
WORK_CHAN = "chan"
RES_SIGN = "sign"
RES_MSG = "msg0"
CONN_SUCCESS = "sucs"
CONN_ERROR = "fail"
TEST_FLAG = "tst"
CONN_TCP = "tcp"
CONN_UDP = "udp"
UnauthorizedBytes = `HTTP/1.1 401 Unauthorized
Content-Type: text/plain; charset=utf-8
WWW-Authenticate: Basic realm="easyProxy"
401 Unauthorized`
IO_EOF = "PROXYEOF"
IO_EOF = "PROXYEOF"
ConnectionFailBytes = `HTTP/1.1 404 Not Found
`
)
//copy
func Relay(in, out net.Conn, compressType int, crypt, mux bool) {
func Relay(in, out net.Conn, compressType int, crypt, mux bool) (n int64, err error) {
switch compressType {
case COMPRESS_SNAPY_ENCODE:
copyBuffer(NewSnappyConn(in, crypt), out)
n, err = copyBuffer(NewSnappyConn(in, crypt), out)
out.Close()
NewSnappyConn(in, crypt).Write([]byte(IO_EOF))
case COMPRESS_SNAPY_DECODE:
copyBuffer(in, NewSnappyConn(out, crypt))
n, err = copyBuffer(in, NewSnappyConn(out, crypt))
in.Close()
if !mux {
out.Close()
}
case COMPRESS_NONE_ENCODE:
copyBuffer(NewCryptConn(in, crypt), out)
n, err = copyBuffer(NewCryptConn(in, crypt), out)
out.Close()
NewCryptConn(in, crypt).Write([]byte(IO_EOF))
case COMPRESS_NONE_DECODE:
copyBuffer(in, NewCryptConn(out, crypt))
n, err = copyBuffer(in, NewCryptConn(out, crypt))
in.Close()
if !mux {
out.Close()
}
}
return
}
//判断压缩方式
@ -145,7 +151,6 @@ func GetIntNoErrByStr(str string) int {
return i
}
// io.copy的优化版读取buffer长度原为32*1024与snappy不同导致读取出的内容存在差异不利于解密
//内存优化 用到pool快速回收
func copyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
@ -167,7 +172,7 @@ func copyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
err = io.ErrShortWrite
break
}
}else {
} else {
bufPoolCopy.Put(buf)
}
if er != nil {
@ -199,15 +204,17 @@ func Getverifyval(vkey string) string {
}
//wait replay group
func ReplayWaitGroup(conn1 net.Conn, conn2 net.Conn, compressEncode, compressDecode int, crypt, mux bool) {
//conn1 网桥 conn2
func ReplayWaitGroup(conn1 net.Conn, conn2 net.Conn, compressEncode, compressDecode int, crypt, mux bool) (out int64, in int64) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
Relay(conn1, conn2, compressEncode, crypt, mux)
in, _ = Relay(conn1, conn2, compressEncode, crypt, mux)
wg.Done()
}()
Relay(conn2, conn1, compressDecode, crypt, mux)
out, _ = Relay(conn2, conn1, compressDecode, crypt, mux)
wg.Wait()
return
}
func ChangeHostAndHeader(r *http.Request, host string, header string, addr string) {
@ -227,3 +234,12 @@ func ChangeHostAndHeader(r *http.Request, host string, header string, addr strin
r.Header.Set("X-Forwarded-For", addr)
r.Header.Set("X-Real-IP", addr)
}
func ReadAllFromFile(filePth string) ([]byte, error) {
f, err := os.Open(filePth)
if err != nil {
return nil, err
}
return ioutil.ReadAll(f)
}

View File

@ -27,14 +27,18 @@ func (s *BaseController) Prepare() {
//加载模板
func (s *BaseController) display(tpl ...string) {
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"
}
s.Data["menu"] = s.actionName
ip := s.Ctx.Request.Host
s.Data["ip"] = utils.GetHostByName(ip[0:strings.LastIndex(ip, ":")])
if strings.LastIndex(ip, ":") > 0 {
s.Data["ip"] = utils.GetHostByName(ip[0:])
}
s.Data["p"] = server.Bridge.TunnelPort
s.Data["proxyPort"] = beego.AppConfig.String("hostPort")
s.Layout = "public/layout.html"

100
web/controllers/client.go Normal file
View File

@ -0,0 +1,100 @@
package controllers
import (
"github.com/cnlh/easyProxy/server"
"github.com/cnlh/easyProxy/utils"
)
type ClientController struct {
BaseController
}
func (s *ClientController) Client() {
if s.Ctx.Request.Method == "GET" {
s.Data["menu"] = "client"
s.SetInfo("客户端管理")
s.display("client/list")
return
}
start, length := s.GetAjaxParams()
list, cnt := server.GetClientList(start, length)
s.AjaxTable(list, cnt, cnt)
}
//添加客户端
func (s *ClientController) Add() {
if s.Ctx.Request.Method == "GET" {
s.Data["menu"] = "client"
s.SetInfo("新增")
s.display()
} else {
t := &utils.Client{
VerifyKey: utils.GetRandomString(16),
Id: server.CsvDb.GetClientId(),
Status: true,
Remark: s.GetString("Remark"),
Cnf: &utils.ServerConfig{
U: s.GetString("u"),
P: s.GetString("p"),
Compress: s.GetString("compress"),
Crypt: s.GetBoolNoErr("crypt"),
Mux: s.GetBoolNoErr("mux"),
},
}
server.CsvDb.NewClient(t)
s.AjaxOk("添加成功")
}
}
//修改客户端
func (s *ClientController) Edit() {
if s.Ctx.Request.Method == "GET" {
s.Data["menu"] = "client"
id := s.GetIntNoErr("id")
if c, err := server.CsvDb.GetClient(id); err != nil {
s.error()
} else {
s.Data["c"] = c
}
s.SetInfo("修改")
s.display()
} else {
id := s.GetIntNoErr("Id")
if c, err := server.CsvDb.GetClient(id); err != nil {
s.error()
} else {
c.Remark = s.GetString("Remark")
c.Cnf.U = s.GetString("u")
c.Cnf.P = s.GetString("p")
c.Cnf.Compress = s.GetString("compress")
c.Cnf.Crypt = s.GetBoolNoErr("crypt")
c.Cnf.Mux = s.GetBoolNoErr("mux")
server.CsvDb.UpdateClient(c)
}
s.AjaxOk("修改成功")
}
}
//更改状态
func (s *ClientController) ChangeStatus() {
id := s.GetIntNoErr("id")
if client, err := server.CsvDb.GetClient(id); err == nil {
client.Status = s.GetBoolNoErr("status")
if client.Status == false {
server.DelClientConnect(client.Id)
}
s.AjaxOk("修改成功")
}
s.AjaxErr("修改失败")
}
//删除客户端
func (s *ClientController) Del() {
id := s.GetIntNoErr("id")
if err := server.CsvDb.DelClient(id); err != nil {
s.AjaxErr("删除失败")
}
server.DelTunnelAndHostByClientId(id)
server.DelClientConnect(id)
s.AjaxOk("删除成功")
}

View File

@ -10,9 +10,14 @@ type IndexController struct {
}
func (s *IndexController) Index() {
s.SetInfo("使用说明")
s.Data["data"] = server.GetDashboardData()
s.SetInfo("dashboard")
s.display("index/index")
}
func (s *IndexController) Help() {
s.SetInfo("使用说明")
s.display("index/help")
}
func (s *IndexController) Tcp() {
s.SetInfo("tcp隧道管理")
@ -44,30 +49,44 @@ func (s *IndexController) Host() {
s.display("index/list")
}
func (s *IndexController) All() {
s.Data["menu"] = "client"
clientId := s.GetString("client_id")
s.Data["client_id"] = clientId
s.SetInfo("客户端" + clientId + "的所有隧道")
s.display("index/list")
}
func (s *IndexController) GetServerConfig() {
start, length := s.GetAjaxParams()
taskType := s.GetString("type")
list, cnt := server.GetServerConfig(start, length, taskType)
clientId := s.GetIntNoErr("client_id")
list, cnt := server.GetServerConfig(start, length, taskType, clientId)
s.AjaxTable(list, cnt, cnt)
}
func (s *IndexController) Add() {
if s.Ctx.Request.Method == "GET" {
s.Data["type"] = s.GetString("type")
s.Data["client_id"] = s.GetString("client_id")
s.SetInfo("新增")
s.display()
} else {
t := &server.ServerConfig{
TcpPort: s.GetIntNoErr("port"),
Mode: s.GetString("type"),
Target: s.GetString("target"),
VerifyKey: utils.GetRandomString(16),
U: s.GetString("u"),
P: s.GetString("p"),
Compress: s.GetString("compress"),
Crypt: utils.GetBoolByStr(s.GetString("crypt")),
Mux: utils.GetBoolByStr(s.GetString("mux")),
IsRun: 0,
t := &utils.ServerConfig{
TcpPort: s.GetIntNoErr("port"),
Mode: s.GetString("type"),
Target: s.GetString("target"),
U: s.GetString("u"),
P: s.GetString("p"),
Compress: s.GetString("compress"),
Crypt: s.GetBoolNoErr("crypt"),
Mux: s.GetBoolNoErr("mux"),
IsRun: 0,
Id: server.CsvDb.GetTaskId(),
ClientId: s.GetIntNoErr("client_id"),
UseClientCnf: s.GetBoolNoErr("use_client"),
Start: 1,
Remark: s.GetString("remark"),
}
server.CsvDb.NewTask(t)
if err := server.AddTask(t); err != nil {
@ -79,9 +98,9 @@ func (s *IndexController) Add() {
}
func (s *IndexController) Edit() {
id := s.GetIntNoErr("id")
if s.Ctx.Request.Method == "GET" {
vKey := s.GetString("vKey")
if t, err := server.CsvDb.GetTask(vKey); err != nil {
if t, err := server.CsvDb.GetTask(id); err != nil {
s.error()
} else {
s.Data["t"] = t
@ -89,44 +108,46 @@ func (s *IndexController) Edit() {
s.SetInfo("修改")
s.display()
} else {
vKey := s.GetString("vKey")
if t, err := server.CsvDb.GetTask(vKey); err != nil {
if t, err := server.CsvDb.GetTask(id); err != nil {
s.error()
} else {
t.TcpPort = s.GetIntNoErr("port")
t.Mode = s.GetString("type")
t.Target = s.GetString("target")
t.Id = id
t.ClientId = s.GetIntNoErr("client_id")
t.U = s.GetString("u")
t.P = s.GetString("p")
t.Compress = s.GetString("compress")
t.Crypt = utils.GetBoolByStr(s.GetString("crypt"))
t.Mux = utils.GetBoolByStr(s.GetString("mux"))
t.Crypt = s.GetBoolNoErr("crypt")
t.Mux = s.GetBoolNoErr("mux")
t.UseClientCnf = s.GetBoolNoErr("use_client")
t.Remark = s.GetString("remark")
server.CsvDb.UpdateTask(t)
server.StopServer(t.VerifyKey)
server.StartTask(t.VerifyKey)
}
s.AjaxOk("修改成功")
}
}
func (s *IndexController) Stop() {
vKey := s.GetString("vKey")
if err := server.StopServer(vKey); err != nil {
id := s.GetIntNoErr("id")
if err := server.StopServer(id); err != nil {
s.AjaxErr("停止失败")
}
s.AjaxOk("停止成功")
}
func (s *IndexController) Del() {
vKey := s.GetString("vKey")
if err := server.DelTask(vKey); err != nil {
id := s.GetIntNoErr("id")
if err := server.DelTask(id); err != nil {
s.AjaxErr("删除失败")
}
s.AjaxOk("删除成功")
}
func (s *IndexController) Start() {
vKey := s.GetString("vKey")
if err := server.StartTask(vKey); err != nil {
id := s.GetIntNoErr("id")
if err := server.StartTask(id); err != nil {
s.AjaxErr("开启失败")
}
s.AjaxOk("开启成功")
@ -134,13 +155,14 @@ func (s *IndexController) Start() {
func (s *IndexController) HostList() {
if s.Ctx.Request.Method == "GET" {
s.Data["vkey"] = s.GetString("vkey")
s.Data["client_id"] = s.GetString("client_id")
s.Data["menu"] = "host"
s.SetInfo("域名列表")
s.display("index/hlist")
} else {
start, length := s.GetAjaxParams()
vkey := s.GetString("vkey")
list, cnt := server.CsvDb.GetHostList(start, length, vkey)
clientId := s.GetIntNoErr("client_id")
list, cnt := server.CsvDb.GetHostList(start, length, clientId)
s.AjaxTable(list, cnt, cnt)
}
}
@ -155,16 +177,18 @@ func (s *IndexController) DelHost() {
func (s *IndexController) AddHost() {
if s.Ctx.Request.Method == "GET" {
s.Data["vkey"] = s.GetString("vkey")
s.Data["client_id"] = s.GetString("client_id")
s.Data["menu"] = "host"
s.SetInfo("新增")
s.display("index/hadd")
} else {
h := &server.HostList{
Vkey: s.GetString("vkey"),
h := &utils.HostList{
ClientId: s.GetIntNoErr("client_id"),
Host: s.GetString("host"),
Target: s.GetString("target"),
HeaderChange: s.GetString("header"),
HostChange: s.GetString("hostchange"),
Remark: s.GetString("remark"),
}
server.CsvDb.NewHost(h)
s.AjaxOk("添加成功")
@ -172,26 +196,26 @@ func (s *IndexController) AddHost() {
}
func (s *IndexController) EditHost() {
host := s.GetString("host")
if s.Ctx.Request.Method == "GET" {
host := s.GetString("host")
if h, t, err := server.GetKeyByHost(host); err != nil {
s.Data["menu"] = "host"
if h, _, err := server.GetKeyByHost(host); err != nil {
s.error()
} else {
s.Data["t"] = t
s.Data["h"] = h
}
s.SetInfo("修改")
s.display("index/hedit")
} else {
host := s.GetString("host")
if h, _, err := server.GetKeyByHost(host); err != nil {
s.error()
} else {
h.Vkey = s.GetString("vkey")
h.ClientId = s.GetIntNoErr("client_id")
h.Host = s.GetString("nhost")
h.Target = s.GetString("target")
h.HeaderChange = s.GetString("header")
h.HostChange = s.GetString("hostchange")
h.Remark = s.GetString("remark")
server.CsvDb.UpdateHost(h)
}
s.AjaxOk("修改成功")

View File

@ -9,4 +9,5 @@ func init() {
beego.Router("/", &controllers.IndexController{}, "*:Index")
beego.AutoRouter(&controllers.IndexController{})
beego.AutoRouter(&controllers.LoginController{})
beego.AutoRouter(&controllers.ClientController{})
}

3477
web/static/js/chart.js Executable file

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,10 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs4/jq-3.3.1/dt-1.10.18
* https://datatables.net/download/#bs4/jq-3.3.1/dt-1.10.18/r-2.2.2
*
* Included libraries:
* jQuery 3 3.3.1, DataTables 1.10.18
* jQuery 3 3.3.1, DataTables 1.10.18, Responsive 2.2.2
*/
/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
@ -164,7 +164,7 @@ Z(n.defaults.column);n.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender
aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",
iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:
this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};n.ext=x={buttons:{},
classes:{},build:"bs4/jq-3.3.1/dt-1.10.18",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:n.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:n.version};h.extend(x,{afnFiltering:x.search,aTypes:x.type.detect,ofnSearch:x.type.search,oSort:x.type.order,afnSortData:x.order,aoFeatures:x.feature,oApi:x.internal,oStdClasses:x.classes,oPagination:x.pager});
classes:{},build:"bs4/jq-3.3.1/dt-1.10.18/r-2.2.2",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:n.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:n.version};h.extend(x,{afnFiltering:x.search,aTypes:x.type.detect,ofnSearch:x.type.search,oSort:x.type.order,afnSortData:x.order,aoFeatures:x.feature,oApi:x.internal,oStdClasses:x.classes,oPagination:x.pager});
h.extend(n.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",
sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",
sJUIHeader:"",sJUIFooter:""});var Kb=n.ext.pager;h.extend(Kb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ia(a,b)]},simple_numbers:function(a,b){return["previous",ia(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ia(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",ia(a,b),"last"]},_numbers:ia,numbers_length:7});h.extend(!0,n.ext.renderer,{pageButton:{_:function(a,b,c,d,e,
@ -192,3 +192,43 @@ renderer:"bootstrap"});b.extend(f.ext.classes,{sWrapper:"dataTables_wrapper dt-b
{"class":t.sPageButton+" "+g,id:0===r&&"string"===typeof c?a.sTableId+"_"+c:null}).append(b("<a>",{href:"#","aria-controls":a.sTableId,"aria-label":u[c],"data-dt-idx":p,tabindex:a.iTabIndex,"class":"page-link"}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(v){}q(b(h).empty().html('<ul class="pagination"/>').children("ul"),s);i!==m&&b(h).find("[data-dt-idx="+i+"]").focus()};return f});
/*!
Responsive 2.2.2
2014-2018 SpryMedia Ltd - datatables.net/license
*/
(function(d){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(l){return d(l,window,document)}):"object"===typeof exports?module.exports=function(l,j){l||(l=window);if(!j||!j.fn.dataTable)j=require("datatables.net")(l,j).$;return d(j,l,l.document)}:d(jQuery,window,document)})(function(d,l,j,q){function t(a,b,c){var e=b+"-"+c;if(n[e])return n[e];for(var d=[],a=a.cell(b,c).node().childNodes,b=0,c=a.length;b<c;b++)d.push(a[b]);return n[e]=d}function r(a,b,d){var e=b+
"-"+d;if(n[e]){for(var a=a.cell(b,d).node(),d=n[e][0].parentNode.childNodes,b=[],f=0,g=d.length;f<g;f++)b.push(d[f]);d=0;for(f=b.length;d<f;d++)a.appendChild(b[d]);n[e]=q}}var o=d.fn.dataTable,i=function(a,b){if(!o.versionCheck||!o.versionCheck("1.10.10"))throw"DataTables Responsive requires DataTables 1.10.10 or newer";this.s={dt:new o.Api(a),columns:[],current:[]};this.s.dt.settings()[0].responsive||(b&&"string"===typeof b.details?b.details={type:b.details}:b&&!1===b.details?b.details={type:!1}:
b&&!0===b.details&&(b.details={type:"inline"}),this.c=d.extend(!0,{},i.defaults,o.defaults.responsive,b),a.responsive=this,this._constructor())};d.extend(i.prototype,{_constructor:function(){var a=this,b=this.s.dt,c=b.settings()[0],e=d(l).width();b.settings()[0]._responsive=this;d(l).on("resize.dtr orientationchange.dtr",o.util.throttle(function(){var b=d(l).width();b!==e&&(a._resize(),e=b)}));c.oApi._fnCallbackReg(c,"aoRowCreatedCallback",function(e){-1!==d.inArray(!1,a.s.current)&&d(">td, >th",
e).each(function(e){e=b.column.index("toData",e);!1===a.s.current[e]&&d(this).css("display","none")})});b.on("destroy.dtr",function(){b.off(".dtr");d(b.table().body()).off(".dtr");d(l).off("resize.dtr orientationchange.dtr");d.each(a.s.current,function(b,e){!1===e&&a._setColumnVis(b,!0)})});this.c.breakpoints.sort(function(a,b){return a.width<b.width?1:a.width>b.width?-1:0});this._classLogic();this._resizeAuto();c=this.c.details;!1!==c.type&&(a._detailsInit(),b.on("column-visibility.dtr",function(){a._timer&&
clearTimeout(a._timer);a._timer=setTimeout(function(){a._timer=null;a._classLogic();a._resizeAuto();a._resize();a._redrawChildren()},100)}),b.on("draw.dtr",function(){a._redrawChildren()}),d(b.table().node()).addClass("dtr-"+c.type));b.on("column-reorder.dtr",function(){a._classLogic();a._resizeAuto();a._resize()});b.on("column-sizing.dtr",function(){a._resizeAuto();a._resize()});b.on("preXhr.dtr",function(){var e=[];b.rows().every(function(){this.child.isShown()&&e.push(this.id(true))});b.one("draw.dtr",
function(){a._resizeAuto();a._resize();b.rows(e).every(function(){a._detailsDisplay(this,false)})})});b.on("init.dtr",function(){a._resizeAuto();a._resize();d.inArray(false,a.s.current)&&b.columns.adjust()});this._resize()},_columnsVisiblity:function(a){var b=this.s.dt,c=this.s.columns,e,f,g=c.map(function(a,b){return{columnIdx:b,priority:a.priority}}).sort(function(a,b){return a.priority!==b.priority?a.priority-b.priority:a.columnIdx-b.columnIdx}),h=d.map(c,function(e,c){return!1===b.column(c).visible()?
"not-visible":e.auto&&null===e.minWidth?!1:!0===e.auto?"-":-1!==d.inArray(a,e.includeIn)}),m=0;e=0;for(f=h.length;e<f;e++)!0===h[e]&&(m+=c[e].minWidth);e=b.settings()[0].oScroll;e=e.sY||e.sX?e.iBarWidth:0;m=b.table().container().offsetWidth-e-m;e=0;for(f=h.length;e<f;e++)c[e].control&&(m-=c[e].minWidth);var s=!1;e=0;for(f=g.length;e<f;e++){var k=g[e].columnIdx;"-"===h[k]&&(!c[k].control&&c[k].minWidth)&&(s||0>m-c[k].minWidth?(s=!0,h[k]=!1):h[k]=!0,m-=c[k].minWidth)}g=!1;e=0;for(f=c.length;e<f;e++)if(!c[e].control&&
!c[e].never&&!1===h[e]){g=!0;break}e=0;for(f=c.length;e<f;e++)c[e].control&&(h[e]=g),"not-visible"===h[e]&&(h[e]=!1);-1===d.inArray(!0,h)&&(h[0]=!0);return h},_classLogic:function(){var a=this,b=this.c.breakpoints,c=this.s.dt,e=c.columns().eq(0).map(function(a){var b=this.column(a),e=b.header().className,a=c.settings()[0].aoColumns[a].responsivePriority;a===q&&(b=d(b.header()).data("priority"),a=b!==q?1*b:1E4);return{className:e,includeIn:[],auto:!1,control:!1,never:e.match(/\bnever\b/)?!0:!1,priority:a}}),
f=function(a,b){var c=e[a].includeIn;-1===d.inArray(b,c)&&c.push(b)},g=function(d,c,g,k){if(g)if("max-"===g){k=a._find(c).width;c=0;for(g=b.length;c<g;c++)b[c].width<=k&&f(d,b[c].name)}else if("min-"===g){k=a._find(c).width;c=0;for(g=b.length;c<g;c++)b[c].width>=k&&f(d,b[c].name)}else{if("not-"===g){c=0;for(g=b.length;c<g;c++)-1===b[c].name.indexOf(k)&&f(d,b[c].name)}}else e[d].includeIn.push(c)};e.each(function(a,e){for(var c=a.className.split(" "),f=!1,i=0,l=c.length;i<l;i++){var j=d.trim(c[i]);
if("all"===j){f=!0;a.includeIn=d.map(b,function(a){return a.name});return}if("none"===j||a.never){f=!0;return}if("control"===j){f=!0;a.control=!0;return}d.each(b,function(a,b){var d=b.name.split("-"),c=j.match(RegExp("(min\\-|max\\-|not\\-)?("+d[0]+")(\\-[_a-zA-Z0-9])?"));c&&(f=!0,c[2]===d[0]&&c[3]==="-"+d[1]?g(e,b.name,c[1],c[2]+c[3]):c[2]===d[0]&&!c[3]&&g(e,b.name,c[1],c[2]))})}f||(a.auto=!0)});this.s.columns=e},_detailsDisplay:function(a,b){var c=this,e=this.s.dt,f=this.c.details;if(f&&!1!==f.type){var g=
f.display(a,b,function(){return f.renderer(e,a[0],c._detailsObj(a[0]))});(!0===g||!1===g)&&d(e.table().node()).triggerHandler("responsive-display.dt",[e,a,g,b])}},_detailsInit:function(){var a=this,b=this.s.dt,c=this.c.details;"inline"===c.type&&(c.target="td:first-child, th:first-child");b.on("draw.dtr",function(){a._tabIndexes()});a._tabIndexes();d(b.table().body()).on("keyup.dtr","td, th",function(a){a.keyCode===13&&d(this).data("dtr-keyboard")&&d(this).click()});var e=c.target;d(b.table().body()).on("click.dtr mousedown.dtr mouseup.dtr",
"string"===typeof e?e:"td, th",function(c){if(d(b.table().node()).hasClass("collapsed")&&d.inArray(d(this).closest("tr").get(0),b.rows().nodes().toArray())!==-1){if(typeof e==="number"){var g=e<0?b.columns().eq(0).length+e:e;if(b.cell(this).index().column!==g)return}g=b.row(d(this).closest("tr"));c.type==="click"?a._detailsDisplay(g,false):c.type==="mousedown"?d(this).css("outline","none"):c.type==="mouseup"&&d(this).blur().css("outline","")}})},_detailsObj:function(a){var b=this,c=this.s.dt;return d.map(this.s.columns,
function(e,d){if(!e.never&&!e.control)return{title:c.settings()[0].aoColumns[d].sTitle,data:c.cell(a,d).render(b.c.orthogonal),hidden:c.column(d).visible()&&!b.s.current[d],columnIndex:d,rowIndex:a}})},_find:function(a){for(var b=this.c.breakpoints,c=0,e=b.length;c<e;c++)if(b[c].name===a)return b[c]},_redrawChildren:function(){var a=this,b=this.s.dt;b.rows({page:"current"}).iterator("row",function(c,e){b.row(e);a._detailsDisplay(b.row(e),!0)})},_resize:function(){var a=this,b=this.s.dt,c=d(l).width(),
e=this.c.breakpoints,f=e[0].name,g=this.s.columns,h,m=this.s.current.slice();for(h=e.length-1;0<=h;h--)if(c<=e[h].width){f=e[h].name;break}var i=this._columnsVisiblity(f);this.s.current=i;e=!1;h=0;for(c=g.length;h<c;h++)if(!1===i[h]&&!g[h].never&&!g[h].control&&!1===!b.column(h).visible()){e=!0;break}d(b.table().node()).toggleClass("collapsed",e);var k=!1,j=0;b.columns().eq(0).each(function(b,c){!0===i[c]&&j++;i[c]!==m[c]&&(k=!0,a._setColumnVis(b,i[c]))});k&&(this._redrawChildren(),d(b.table().node()).trigger("responsive-resize.dt",
[b,this.s.current]),0===b.page.info().recordsDisplay&&d("td",b.table().body()).eq(0).attr("colspan",j))},_resizeAuto:function(){var a=this.s.dt,b=this.s.columns;if(this.c.auto&&-1!==d.inArray(!0,d.map(b,function(a){return a.auto}))){d.isEmptyObject(n)||d.each(n,function(b){b=b.split("-");r(a,1*b[0],1*b[1])});a.table().node();var c=a.table().node().cloneNode(!1),e=d(a.table().header().cloneNode(!1)).appendTo(c),f=d(a.table().body()).clone(!1,!1).empty().appendTo(c),g=a.columns().header().filter(function(b){return a.column(b).visible()}).to$().clone(!1).css("display",
"table-cell").css("min-width",0);d(f).append(d(a.rows({page:"current"}).nodes()).clone(!1)).find("th, td").css("display","");if(f=a.table().footer()){var f=d(f.cloneNode(!1)).appendTo(c),h=a.columns().footer().filter(function(b){return a.column(b).visible()}).to$().clone(!1).css("display","table-cell");d("<tr/>").append(h).appendTo(f)}d("<tr/>").append(g).appendTo(e);"inline"===this.c.details.type&&d(c).addClass("dtr-inline collapsed");d(c).find("[name]").removeAttr("name");d(c).css("position","relative");
c=d("<div/>").css({width:1,height:1,overflow:"hidden",clear:"both"}).append(c);c.insertBefore(a.table().node());g.each(function(c){c=a.column.index("fromVisible",c);b[c].minWidth=this.offsetWidth||0});c.remove()}},_setColumnVis:function(a,b){var c=this.s.dt,e=b?"":"none";d(c.column(a).header()).css("display",e);d(c.column(a).footer()).css("display",e);c.column(a).nodes().to$().css("display",e);d.isEmptyObject(n)||c.cells(null,a).indexes().each(function(a){r(c,a.row,a.column)})},_tabIndexes:function(){var a=
this.s.dt,b=a.cells({page:"current"}).nodes().to$(),c=a.settings()[0],e=this.c.details.target;b.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]");"number"===typeof e?a.cells(null,e,{page:"current"}).nodes().to$().attr("tabIndex",c.iTabIndex).data("dtr-keyboard",1):("td:first-child, th:first-child"===e&&(e=">td:first-child, >th:first-child"),d(e,a.rows({page:"current"}).nodes()).attr("tabIndex",c.iTabIndex).data("dtr-keyboard",1))}});i.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",
width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];i.display={childRow:function(a,b,c){if(b){if(d(a.node()).hasClass("parent"))return a.child(c(),"child").show(),!0}else{if(a.child.isShown())return a.child(!1),d(a.node()).removeClass("parent"),!1;a.child(c(),"child").show();d(a.node()).addClass("parent");return!0}},childRowImmediate:function(a,b,c){if(!b&&a.child.isShown()||!a.responsive.hasHidden())return a.child(!1),d(a.node()).removeClass("parent"),
!1;a.child(c(),"child").show();d(a.node()).addClass("parent");return!0},modal:function(a){return function(b,c,e){if(c)d("div.dtr-modal-content").empty().append(e());else{var f=function(){g.remove();d(j).off("keypress.dtr")},g=d('<div class="dtr-modal"/>').append(d('<div class="dtr-modal-display"/>').append(d('<div class="dtr-modal-content"/>').append(e())).append(d('<div class="dtr-modal-close">&times;</div>').click(function(){f()}))).append(d('<div class="dtr-modal-background"/>').click(function(){f()})).appendTo("body");
d(j).on("keyup.dtr",function(a){27===a.keyCode&&(a.stopPropagation(),f())})}a&&a.header&&d("div.dtr-modal-content").prepend("<h2>"+a.header(b)+"</h2>")}}};var n={};i.renderer={listHiddenNodes:function(){return function(a,b,c){var e=d('<ul data-dtr-index="'+b+'" class="dtr-details"/>'),f=!1;d.each(c,function(b,c){c.hidden&&(d('<li data-dtr-index="'+c.columnIndex+'" data-dt-row="'+c.rowIndex+'" data-dt-column="'+c.columnIndex+'"><span class="dtr-title">'+c.title+"</span> </li>").append(d('<span class="dtr-data"/>').append(t(a,
c.rowIndex,c.columnIndex))).appendTo(e),f=!0)});return f?e:!1}},listHidden:function(){return function(a,b,c){return(a=d.map(c,function(a){return a.hidden?'<li data-dtr-index="'+a.columnIndex+'" data-dt-row="'+a.rowIndex+'" data-dt-column="'+a.columnIndex+'"><span class="dtr-title">'+a.title+'</span> <span class="dtr-data">'+a.data+"</span></li>":""}).join(""))?d('<ul data-dtr-index="'+b+'" class="dtr-details"/>').append(a):!1}},tableAll:function(a){a=d.extend({tableClass:""},a);return function(b,
c,e){b=d.map(e,function(a){return'<tr data-dt-row="'+a.rowIndex+'" data-dt-column="'+a.columnIndex+'"><td>'+a.title+":</td> <td>"+a.data+"</td></tr>"}).join("");return d('<table class="'+a.tableClass+' dtr-details" width="100%"/>').append(b)}}};i.defaults={breakpoints:i.breakpoints,auto:!0,details:{display:i.display.childRow,renderer:i.renderer.listHidden(),target:0,type:"inline"},orthogonal:"display"};var p=d.fn.dataTable.Api;p.register("responsive()",function(){return this});p.register("responsive.index()",
function(a){a=d(a);return{column:a.data("dtr-index"),row:a.parent().data("dtr-index")}});p.register("responsive.rebuild()",function(){return this.iterator("table",function(a){a._responsive&&a._responsive._classLogic()})});p.register("responsive.recalc()",function(){return this.iterator("table",function(a){a._responsive&&(a._responsive._resizeAuto(),a._responsive._resize())})});p.register("responsive.hasHidden()",function(){var a=this.context[0];return a._responsive?-1!==d.inArray(!1,a._responsive.s.current):
!1});p.registerPlural("columns().responsiveHidden()","column().responsiveHidden()",function(){return this.iterator("column",function(a,b){return a._responsive?a._responsive.s.current[b]:!1},1)});i.version="2.2.2";d.fn.dataTable.Responsive=i;d.fn.DataTable.Responsive=i;d(j).on("preInit.dt.dtr",function(a,b){if("dt"===a.namespace&&(d(b.nTable).hasClass("responsive")||d(b.nTable).hasClass("dt-responsive")||b.oInit.responsive||o.defaults.responsive)){var c=b.oInit.responsive;!1!==c&&new i(b,d.isPlainObject(c)?
c:{})}});return i});
/*!
Bootstrap 4 integration for DataTables' Responsive
©2016 SpryMedia Ltd - datatables.net/license
*/
(function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net-bs4","datatables.net-responsive"],function(a){return c(a,window,document)}):"object"===typeof exports?module.exports=function(a,b){a||(a=window);if(!b||!b.fn.dataTable)b=require("datatables.net-bs4")(a,b).$;b.fn.dataTable.Responsive||require("datatables.net-responsive")(a,b);return c(b,a,a.document)}:c(jQuery,window,document)})(function(c){var a=c.fn.dataTable,b=a.Responsive.display,g=b.modal,e=c('<div class="modal fade dtr-bs-modal" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"/></div></div></div>');
b.modal=function(a){return function(b,d,f){if(c.fn.modal){if(!d){if(a&&a.header){var d=e.find("div.modal-header"),h=d.find("button").detach();d.empty().append('<h4 class="modal-title">'+a.header(b)+"</h4>").append(h)}e.find("div.modal-body").empty().append(f());e.appendTo("body").modal()}}else g(b,d,f)}};return a.Responsive});

View File

@ -1,27 +1,48 @@
(function () {
"use strict";
"use strict";
var treeviewMenu = $('.app-menu');
var treeviewMenu = $('.app-menu');
// Toggle Sidebar
$('[data-toggle="sidebar"]').click(function(event) {
event.preventDefault();
$('.app').toggleClass('sidenav-toggled');
});
// Toggle Sidebar
$('[data-toggle="sidebar"]').click(function (event) {
event.preventDefault();
$('.app').toggleClass('sidenav-toggled');
});
// Activate sidebar treeview toggle
$("[data-toggle='treeview']").click(function(event) {
event.preventDefault();
if(!$(this).parent().hasClass('is-expanded')) {
treeviewMenu.find("[data-toggle='treeview']").parent().removeClass('is-expanded');
}
$(this).parent().toggleClass('is-expanded');
});
// Activate sidebar treeview toggle
$("[data-toggle='treeview']").click(function (event) {
event.preventDefault();
if (!$(this).parent().hasClass('is-expanded')) {
treeviewMenu.find("[data-toggle='treeview']").parent().removeClass('is-expanded');
}
$(this).parent().toggleClass('is-expanded');
});
// Set initial active toggle
$("[data-toggle='treeview.'].is-expanded").parent().toggleClass('is-expanded');
// Set initial active toggle
$("[data-toggle='treeview.'].is-expanded").parent().toggleClass('is-expanded');
//Activate bootstrip tooltips
// $("[data-toggle='tooltip']").tooltip();
//Activate bootstrip tooltips
// $("[data-toggle='tooltip']").tooltip();
})();
function change(limit) {
var size = "";
if (limit < 0.1 * 1024) {
size = limit.toFixed(2) + "B"
} else if (limit < 0.1 * 1024 * 1024) {
size = (limit / 1024).toFixed(2) + "KB"
} else if (limit < 0.1 * 1024 * 1024 * 1024) {
size = (limit / (1024 * 1024)).toFixed(2) + "MB"
} else {
size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB"
}
var sizeStr = size + "";
var index = sizeStr.indexOf(".");
var dou = sizeStr.substr(index + 1, 2)
if (dou == "00") {
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
}
return size;
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>easyProxy error</title>
</head>
<body>
404 not found,power by easyProxy
</body>
</html>

67
web/views/client/add.html Executable file
View File

@ -0,0 +1,67 @@
<div class="row tile">
<div class="col-md-6 col-md-auto">
<div>
<h3 class="tile-title"></h3>
<div class="tile-body">
<form>
<div class="form-group" id="target">
<label class="control-label"></label>
<input class="form-control" type="text" name="Remark" placeholder="客户端备注">
</div>
<div class="form-group" id="u">
<label class="control-label">(socks5,web穿)</label>
<input class="form-control" type="text" name="u" placeholder="不填则无需验证">
</div>
<div class="form-group" id="p">
<label class="control-label">(socks5,web穿)</label>
<input class="form-control" type="text" name="p" placeholder="不填则无需验证">
</div>
<div class="form-group" id="compress">
<label class="control-label"></label>
<select class="form-control" name="compress">
<option value=""></option>
<option value="snappy">snappy</option>
</select>
</div>
<div class="form-group" id="compress">
<label class="control-label"></label>
<select class="form-control" name="crypt">
<option value="0"></option>
<option value="1"></option>
</select>
</div>
<div class="form-group" id="compress">
<label class="control-label">TCP</label>
<select class="form-control" name="crypt">
<option value="0"></option>
<option value="1"></option>
</select>
</div>
</form>
</div>
<div class="tile-footer">
&nbsp;&nbsp;<button class="btn btn-success" href="#" id="add"><i
class="fa fa-fw fa-lg fa-eye"></i>
</button>
</div>
</div>
</div>
</div>
</main>
<script>
$(function () {
$("#add").on("click", function () {
$.ajax({
type: "POST",
url: "/client/add",
data: $("form").serializeArray(),
success: function (res) {
alert(res.msg)
if (res.status) {
history.back(-1)
}
}
})
})
})
</script>

71
web/views/client/edit.html Executable file
View File

@ -0,0 +1,71 @@
<div class="row tile">
<div class="col-md-6 col-md-auto">
<div>
<h3 class="tile-title"></h3>
<div class="tile-body">
<form>
<input type="hidden" name="Id" value="{{.c.Id}}">
<div class="form-group" id="target">
<label class="control-label"></label>
<input class="form-control" value="{{.c.Remark}}" type="text" name="Remark" placeholder="客户端备注">
</div>
<div class="form-group" id="u">
<label class="control-label">(socks5,web穿)</label>
<input class="form-control" value="{{.c.Cnf.U}}" type="text" name="u"
placeholder="不填则无需验证">
</div>
<div class="form-group" id="p">
<label class="control-label">(socks5,web穿)</label>
<input class="form-control" value="{{.c.Cnf.P}}" type="text" name="p"
placeholder="不填则无需验证">
</div>
<div class="form-group" id="compress">
<label class="control-label">()</label>
<select class="form-control" name="compress">
<option {{if eq "" .c.Cnf.Compress}}selected{{end}} value=""></option>
<option {{if eq "snappy" .c.Cnf.Compress}}selected{{end}} value="snappy">snappy</option>
</select>
</div>
<div class="form-group" id="compress">
<label class="control-label">()</label>
<select class="form-control" name="crypt">
<option {{if eq false .c.Cnf.Crypt}}selected{{end}} value="0"></option>
<option {{if eq true .c.Cnf.Crypt}}selected{{end}} value="1"></option>
</select>
</div>
<div class="form-group" id="compress">
<label class="control-label">TCP()</label>
<select class="form-control" name="mux">
<option {{if eq false .c.Cnf.Mux}}selected{{end}} value="0"></option>
<option {{if eq true .c.Cnf.Mux}}selected{{end}} value="1"></option>
</select>
</div>
</form>
</div>
<div class="tile-footer">
&nbsp;&nbsp;<button class="btn btn-success" href="#" id="add"><i
class="fa fa-fw fa-lg fa-eye"></i>
</button>
</div>
</div>
</div>
</div>
</main>
<script>
$(function () {
$("#add").on("click", function () {
$.ajax({
type: "POST",
url: "/client/edit",
data: $("form").serializeArray(),
success: function (res) {
alert(res.msg)
if (res.status) {
history.back(-1)
}
}
})
})
})
</script>

234
web/views/client/list.html Executable file
View File

@ -0,0 +1,234 @@
<div class="row">
<div class="col-md-12">
<div class="tile">
<div class="tile-body">
<table class="table table-hover table-bordered" id="sampleTable">
<thead>
<tr>
<th>Id</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
<script type="text/javascript">
function del(id) {
if (confirm("确定要删除数据吗?")) {
$.ajax({
type: "POST",
url: "/client/del",
data: {"id": id},
success: function (res) {
alert(res.msg)
if (res.status) {
document.location.reload();
}
}
})
}
}
function start(id) {
if (confirm("确定要开始任务吗?")) {
$.ajax({
type: "POST",
url: "/client/changestatus",
data: {"id": id, "status": 1},
success: function (res) {
alert(res.msg)
if (res.status) {
document.location.reload();
}
}
})
}
}
function stop(id) {
if (confirm("确定要暂停吗?")) {
$.ajax({
type: "POST",
url: "/client/changestatus",
data: {
"id": id, "status": 0
},
success:
function (res) {
alert(res.msg)
if (res.status) {
document.location.reload();
}
}
})
}
}
function edit(id) {
window.location.href = "/client/edit?id=" + id
}
function add() {
window.location.href = "/client/add"
}
function show_tunnel_list(id) {
window.location.href = "/index/all?client_id=" + id
}
function show_host_list(id) {
window.location.href = "/index/hostlist?client_id=" + id
}
$(document)
.ready(function () {
var table = $('#sampleTable').DataTable({
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.childRowImmediate
}
},
dom: 'Bfrtip',
processing: true,
serverSide: true,
autoWidth: false,
ordering: false,
ajax: {
url: '/client/client',
type: 'POST'
},
dom: '<"top"fl><"toolbar">rt<"bottom"ip><"clear">',
columns: [ //这个是显示到界面上的个数据 格式为 {data:'显示的字段名'}
{data: 'Id'},
{data: 'VerifyKey'},
{data: "Addr"},
{data: "Remark"},
{data: "Addr"},
{data: "Addr"},
{data: "Addr"},
{data: "Addr"},
{data: "Remark"},
{data: "Status"},
{data: "IsConnect"},
{data: "Addr"},
{data: "Addr"},
{data: ""},
],
bFilter: false,
columnDefs: [{
targets: -1,
render: function (data, type, row, meta) {
if (row.Status == true) {
btn = "<button onclick=\"stop('" + row.Id + "')\" class=\"btn btn-secondary btn-sm\" type=\"button\">关闭</button>"
} else {
btn = "<button onclick=\"start('" + row.Id + "')\" class=\"btn btn-success btn-sm\" type=\"button\">打开</button>"
}
btn_del = '<button onclick="del(\'' + row.Id + '\')" class="btn btn-danger btn-sm"></button> '
btn_edit = '<button onclick="edit(\'' + row.Id + '\')" class="btn btn-primary btn-sm"></button> '
btn_list = '<button onclick="show_tunnel_list(\'' + row.Id + '\')" class="btn btn-info btn-sm"></button> '
btn_host = '<button onclick="show_host_list(\'' + row.Id + '\')" class="btn btn-info btn-sm"></button> '
return '<div class="btn-group" role="group" aria-label="..."> ' + btn + btn_del + btn_edit + btn_host + btn_list + '</div>'
}
},
{
targets: -5,
render: function (data, type, row, meta) {
if (data == false) {
return "<span class=\"badge badge-pill badge-secondary\">暂停</span>"
} else {
return "<span class=\"badge badge-pill badge-success\">正常</span>"
}
}
},
{
targets: -4,
render: function (data, type, row, meta) {
if (data == false) {
return "<span class=\"badge badge-pill badge-secondary\">未连接</span>"
} else {
return "<span class=\"badge badge-pill badge-success\">已连接</span>"
}
}
},
{
targets: -2,
render: function (data, type, row, meta) {
return change(row.Flow.InletFlow)
}
},
{
targets: -3,
render: function (data, type, row, meta) {
return change(row.Flow.ExportFlow)
}
},
{
targets: -6,
render: function (data, type, row, meta) {
return row.Cnf.P
}
},
{
targets: -7,
render: function (data, type, row, meta) {
return row.Cnf.U
}
},
{
targets: -8,
render: function (data, type, row, meta) {
if (row.Cnf.Mux == "0") {
return "不启用"
} else {
return "启用"
}
}
}
,
{
targets: -9,
render: function (data, type, row, meta) {
if (row.Cnf.Crypt == "0") {
return "不加密"
} else {
return "加密"
}
}
}
,
{
targets: -10,
render: function (data, type, row, meta) {
return row.Cnf.Compress
}
}
],
buttons: []
});
$("#sampleTable_length").html('<button class="btn btn-primary" onclick="add()" type="button"></button>')
})
;
</script>

View File

@ -19,9 +19,12 @@
<option {{if eq "udpServer" .type}}selected{{end}} value="udpServer">udp</option>
<option {{if eq "socks5Server" .type}}selected{{end}} value="socks5Server">socks5</option>
<option {{if eq "httpProxyServer" .type}}selected{{end}} value="httpProxyServer">http
<option {{if eq "hostServer" .type}}selected{{end}} value="hostServer">host</option>
</select>
</div>
<div class="form-group">
<label class="control-label"></label>
<input class="form-control" type="text" name="remark" placeholder="备注">
</div>
<div class="form-group" id="port">
<label class="control-label"></label>
<input class="form-control" type="text" name="port" placeholder="公网服务器对外访问端口例如8024">
@ -30,6 +33,18 @@
<label class="control-label">(ip:)</label>
<input class="form-control" type="text" name="target" placeholder="内网代理地址例如10.1.50.203:22">
</div>
<div class="form-group" id="client_id">
<label class="control-label">ID</label>
<input value="{{.client_id}}" class="form-control" type="text" name="client_id"
placeholder="客户端id">
</div>
<div class="form-group" id="use_client">
<label class="control-label">使</label>
<select class="form-control" name="use_client">
<option value="1"></option>
<option value="0"></option>
</select>
</div>
<div class="form-group" id="compress">
<label class="control-label"></label>
<select class="form-control" name="compress">
@ -37,16 +52,16 @@
<option value="snappy">snappy</option>
</select>
</div>
<div class="form-group" id="compress">
<div class="form-group" id="crypt">
<label class="control-label"></label>
<select class="form-control" name="crypt">
<option value="0"></option>
<option value="1"></option>
</select>
</div>
<div class="form-group" id="compress">
<div class="form-group" id="mux">
<label class="control-label">TCP</label>
<select class="form-control" name="crypt">
<select class="form-control" name="mux">
<option value="0"></option>
<option value="1"></option>
</select>
@ -77,7 +92,7 @@
arr["udpServer"] = ["type", "port", "target", "compress", "udp隧道模式提供一条udp隧道适用于dns、内网dns访问等添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后访问公网服务器的设定端口则相当于访问内网目标地址的udp目标端口"]
arr["socks5Server"] = ["type", "port", "compress", "u", "p", "socks5代理模式内网socks5代理配合proxifer可如同使用vpn一样访问内网设备或资源添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后在外网环境下本机配置socks5代理即访问内网设备或者资源 "]
arr["httpProxyServer"] = ["type", "port", "compress", "u", "p", " http代理模式内网http代理可访问内网网站添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后在外网环境下本机配置http代理即访问内网站点"]
arr["hostServer"] = ["type", "compress", "u", "p", "域名分发模式使用域名代理内网服务适用于小程序开发、公众号开发、站点演示等添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后使用nginx将请求反向代理到本程序再进行域名配置即可解析"]
arrClientHide = ["compress", "u", "p", "crypt", "mux"]
function resetForm() {
for (var i = 0; i < arr["all"].length; i++) {
@ -90,10 +105,28 @@
$("#info").html(arr[o][arr[o].length - 1])
}
function resetClientCnf() {
for (var i = 0; i < arrClientHide.length; i++) {
$("#" + arrClientHide[i]).css("display", "block")
}
op = $("#use_client option:selected").val()
if (op == 1) {
for (var i = 0; i < arrClientHide.length; i++) {
$("#" + arrClientHide[i]).css("display", "none")
}
}
}
$(function () {
resetForm()
resetClientCnf()
$("#type").on("change", function () {
resetForm()
resetClientCnf()
})
$("#use_client").on("change", function () {
resetForm()
resetClientCnf()
})
$("#add").on("click", function () {
$.ajax({

View File

@ -4,17 +4,21 @@
<h3 class="tile-title"></h3>
<div class="tile-body">
<form>
<input type="hidden" name="vKey" value="{{.t.VerifyKey}}">
<input type="hidden" name="id" value="{{.t.Id}}">
<div class="form-group">
<label class="control-label"></label>
<select class="form-control" name="type" id="type">
<option {{if eq "tunnelServer" .t.Mode}}selected{{end}} value="tunnelServer">tcp</option>
<option {{if eq "udpServer" .t.Mode}}selected{{end}} value="udpServer">udp</option>
<option {{if eq "socks5Server" .t.Mode}}selected{{end}} value="socks5Server">socks5</option>
<option {{if eq "socks5Server" .t.Mode}}selected{{end}} value="socks5Server">socks5
</option>
<option {{if eq "httpProxyServer" .t.Mode}}selected{{end}} value="httpProxyServer">http
<option {{if eq "hostServer" .t.Mode}}selected{{end}} value="hostServer">host</option>
</select>
</div>
<div class="form-group">
<label class="control-label"></label>
<input class="form-control" value="{{.t.Remark}}" type="text" name="remark" placeholder="备注">
</div>
<div class="form-group" id="port">
<label class="control-label"></label>
<input class="form-control" value="{{.t.TcpPort}}" type="text" name="port"
@ -25,6 +29,18 @@
<input class="form-control" value="{{.t.Target}}" type="text" name="target"
placeholder="内网隧道目标例如10.1.50.203:22">
</div>
<div class="form-group" id="client_id">
<label class="control-label">ID</label>
<input class="form-control" value="{{.t.ClientId}}" type="text" name="client_id"
placeholder="客户端id">
</div>
<div class="form-group" id="use_client">
<label class="control-label">使</label>
<select class="form-control" name="use_client">
<option {{if eq false .t.UseClientCnf}}selected{{end}} value="0"></option>
<option {{if eq true .t.UseClientCnf}}selected{{end}} value="1"></option>
</select>
</div>
<div class="form-group" id="compress">
<label class="control-label">()</label>
<select class="form-control" name="compress">
@ -32,14 +48,14 @@
<option {{if eq "snappy" .t.Compress}}selected{{end}} value="snappy">snappy</option>
</select>
</div>
<div class="form-group" id="compress">
<div class="form-group" id="crypt">
<label class="control-label">()</label>
<select class="form-control" name="crypt">
<option {{if eq false .t.Crypt}}selected{{end}} value="0"></option>
<option {{if eq true .t.Crypt}}selected{{end}} value="1"></option>
</select>
</div>
<div class="form-group" id="compress">
<div class="form-group" id="mux">
<label class="control-label">TCP()</label>
<select class="form-control" name="mux">
<option {{if eq false .t.Mux}}selected{{end}} value="0"></option>
@ -74,7 +90,7 @@
arr["udpServer"] = ["type", "port", "target", "compress"]
arr["socks5Server"] = ["type", "port", "compress", "u", "p"]
arr["httpProxyServer"] = ["type", "port", "compress", "u", "p"]
arr["hostServer"] = ["type", "compress", "u", "p"]
arrClientHide = ["compress", "u", "p", "crypt", "mux"]
function resetForm() {
for (var i = 0; i < arr["all"].length; i++) {
@ -86,10 +102,28 @@
}
}
function resetClientCnf() {
for (var i = 0; i < arrClientHide.length; i++) {
$("#" + arrClientHide[i]).css("display", "block")
}
op = $("#use_client option:selected").val()
if (op == 1) {
for (var i = 0; i < arrClientHide.length; i++) {
$("#" + arrClientHide[i]).css("display", "none")
}
}
}
$(function () {
resetForm()
resetClientCnf()
$("#type").on("change", function () {
resetForm()
resetClientCnf()
})
$("#use_client").on("change", function () {
resetForm()
resetClientCnf()
})
$("#add").on("click", function () {
$.ajax({

View File

@ -4,18 +4,26 @@
<h3 class="tile-title"></h3>
<div class="tile-body">
<form>
<input type="hidden" name="vkey" value="{{.vkey}}">
<div class="form-group">
<label class="control-label"></label>
<input class="form-control" type="text" name="remark" placeholder="备注">
</div>
<div class="form-group">
<label class="control-label"></label>
<input class="form-control" type="text" name="host" placeholder="域名">
</div>
<div class="form-group">
<label class="control-label">id</label>
<input value="{{.client_id}}" class="form-control" type="text" name="client_id" placeholder="客户端id">
</div>
<div class="form-group">
<label class="control-label"></label>
<input class="form-control" type="text" name="target" placeholder="内网隧道目标例如10.1.50.203:22">
</div>
<div class="form-group" id="header">
<label class="control-label">header()</label>
<textarea class="form-control" rows="4" type="text" name="header" placeholder="Cache-Control: no-cache"></textarea>
<textarea class="form-control" rows="4" type="text" name="header"
placeholder="Cache-Control: no-cache"></textarea>
</div>
<div class="form-group" id="hostchange">
<label class="control-label">host</label>

View File

@ -4,12 +4,20 @@
<h3 class="tile-title"></h3>
<div class="tile-body">
<form>
<input type="hidden" name="vkey" value="{{.t.VerifyKey}}">
<div class="form-group">
<label class="control-label"></label>
<input class="form-control" value="{{.h.Host}}" type="hidden" name="host" placeholder="域名">
<input class="form-control" value="{{.h.Host}}" type="text" name="nhost" placeholder="域名">
</div>
<div class="form-group">
<label class="control-label"></label>
<input class="form-control" value="{{.h.Remark}}" type="text" name="remark" placeholder="备注">
</div>
<div class="form-group">
<label class="control-label">id</label>
<input class="form-control" value="{{.h.ClientId}}" type="text" name="client_id"
placeholder="客户端id">
</div>
<div class="form-group">
<label class="control-label"></label>
<input class="form-control" value="{{.h.Target}}" type="text" name="target"

146
web/views/index/help.html Normal file
View File

@ -0,0 +1,146 @@
<div class="row">
<div class="col-md-12">
<div class="tile">
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=star&count=true&size=large"
frameborder="0" scrolling="0" width="160px" height="30px"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=watch&count=true&size=large&v=2"
frameborder="0" scrolling="0" width="160px" height="30px"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=fork&count=true&size=large"
frameborder="0" scrolling="0" width="158px" height="30px"></iframe>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="tile">
<h3 class="tile-title"></h3>
<p>
<b></b>
</p>
<p>
<b></b>
<li>proxy.comip{{.ip}}</li>
<li>127.0.0.1:81127.0.0.1:82</li>
<li>a.proxy.com访127.0.0.1:81b.proxy.com访127.0.0.1:82</li>
</p>
<p><b>使</b></p>
<ul>
<li>a.proxy.comb.proxy.com{{.ip}}</li>
<li>使nginxssl</li>
<li>nginxhttp80<br>
<pre><code>
server {
listen 80;
server_name a.proxy.com b.proxy.com;#*.proxy.com
#ssl
<b>location / {
proxy_pass http://127.0.0.1:{{.proxyPort}};
}</b>
}
</code></pre>
</li>
<li> <a href="/index/add?type=hostServer"></a></li>
{{/*<li>在域名代理管理中找到新加的客户端(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
<li>1a.proxy.com127.0.0.1:812b.proxy.com127.0.0.1:82</li>
<li>访a.proxy.comb.proxy.com</li>
</ul>
<p>ip{{.ip}}</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">tcp</h3>
<p>
<b></b> sshtcp
</p>
<p>
<b></b> 访{{.ip}}800110.1.50.10122ssh
</p>
<p><b>使</b></p>
<ul>
<li>tcp8001ip10.1.50.101:22 <a
href="/index/add?type=tunnelServer"></a></li>
{{/*<li>在tcp管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
<li>访ip{{.ip}}:(8001)访ip(10.1.50.101):(22)ssh -p 8001 root@{{.ip}}</li>
</ul>
<p>ip{{.ip}}</p>
</div>
</div>
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">udp</h3>
<p>
<b></b> dnsudp
</p>
<p>
<b></b> dns10.1.50.102:53使dns{{.ip}}
</p>
<p><b>使</b></p>
<ul>
<li>udp8002ip10.1.50.102:53 <a
href="/index/add?type=udpServer"></a></li>
{{/*<li>在udp管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
<li>dns{{.ip}}使10.1.50.202dns</li>
</ul>
<p>ip{{.ip}}</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">socks5</h3>
<p>
<b></b> 使vpn访
</p>
<p>
<b></b> {{.ip}}8003socks5访
</p>
<p><b>使</b></p>
<ul>
<li>socks58003proxifer <a
href="/index/add?type=sock5Server"></a></li>
{{/*<li>在socks5代理管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
<li>socks5ipip{{.ip}}(8003)</li>
</ul>
<p>ip{{.ip}}</p>
</div>
</div>
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">http</h3>
<p>
<b></b> 访
</p>
<p>
<b></b> {{.ip}}8004http访
</p>
<p><b>使</b></p>
<ul>
<li>http8004 <a
href="/index/add?type=httpProxyServer"></a></li>
<li>http(vkey)</li>
<li>httpipip{{.ip}}(8004)访</li>
</ul>
<p>ip{{.ip}}</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="tile">
<p>
<b></b>
<li>./proxy_server</li>
<li>./proxy_client -server={{.ip}}:{{.p}} -vkey=xxx</li>
</p>
<p><b>./proxy_client -server={{.ip}}:{{.p}}
-vkey=ccc./proxy_client
-server={{.ip}}:{{.p}} -vkey=ccc,dddvkey</b></p>
</div>
</div>
</div>
</main>

View File

@ -5,9 +5,13 @@
<table class="table table-hover table-bordered" id="sampleTable">
<thead>
<tr>
<th>key</th>
<th>id</th>
<th></th>
<th>host</th>
<th></th>
<th>host</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
@ -39,7 +43,7 @@
}
function add() {
window.location.href = "/index/addhost?vkey={{.vkey}}"
window.location.href = "/index/addhost?vkey={{.task_id}}&client_id={{.client_id}}"
}
function edit(host) {
@ -48,6 +52,11 @@
$(document).ready(function () {
var table = $('#sampleTable').DataTable({
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.childRowImmediate
}
},
dom: 'Bfrtip',
processing: true,
serverSide: true,
@ -59,9 +68,13 @@
},
dom: '<"top"fl><"toolbar">rt<"bottom"ip><"clear">',
columns: [ //这个是显示到界面上的个数据 格式为 {data:'显示的字段名'}
{data: 'Vkey'},
{data: 'ClientId'},
{data: 'Remark'},
{data: 'Host'},
{data: 'Target'},
{data: 'HostChange'},
{data: 'HostChange'},
{data: 'HostChange'},
{data: 'Target'},
],
bFilter: false,
@ -74,7 +87,19 @@
'<button onclick="edit(\'' + row.Host + '\')" type="button" class="btn btn-primary btn-sm"></button> '
+ ' </div>'
}
}
},
{
targets: -2,
render: function (data, type, row, meta) {
return change(row.Flow.InletFlow)
}
},
{
targets: -3,
render: function (data, type, row, meta) {
return change(row.Flow.ExportFlow)
}
}
],
buttons: []
});

View File

@ -1,147 +1,104 @@
<div class="row">
<div class="col-md-12">
<div class="tile">
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=star&count=true&size=large"
frameborder="0" scrolling="0" width="160px" height="30px"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=watch&count=true&size=large&v=2"
frameborder="0" scrolling="0" width="160px" height="30px"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=cnlh&repo=easyProxy&type=fork&count=true&size=large"
frameborder="0" scrolling="0" width="158px" height="30px"></iframe>
<div class="col-md-3">
<div class="widget-small warning coloured-icon"><i class="icon fa fa-html5 fa-3x"></i>
<div class="info">
<h4>HTTP</h4>
<p><b>{{.p}}</b></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="tile">
<h3 class="tile-title"></h3>
<p>
<b></b>
</p>
<p>
<b></b>
<li>proxy.comip{{.ip}}</li>
<li>127.0.0.1:81127.0.0.1:82</li>
<li>a.proxy.com访127.0.0.1:81b.proxy.com访127.0.0.1:82</li>
</p>
<p><b>使</b></p>
<ul>
<li>a.proxy.comb.proxy.com{{.ip}}</li>
<li>使nginxssl</li>
<li>nginxhttp80<br>
<pre><code>
server {
listen 80;
server_name a.proxy.com b.proxy.com;#*.proxy.com
#ssl
<b>location / {
proxy_pass http://127.0.0.1:{{.proxyPort}};
}</b>
}
</code></pre>
</li>
<li> <a href="/index/add?type=hostServer"></a></li>
{{/*<li>在域名代理管理中找到新加的客户端(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
<li>1a.proxy.com127.0.0.1:812b.proxy.com127.0.0.1:82</li>
<li>访a.proxy.comb.proxy.com</li>
</ul>
<p>ip{{.ip}}</p>
<div class="col-md-3">
<div class="widget-small danger coloured-icon"><i class="icon fa fa-home fa-3x"></i>
<div class="info">
<h4></h4>
<p><b>{{.data.hostCount}}</b></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="widget-small primary coloured-icon"><i class="icon fa fa-users fa-3x"></i>
<div class="info">
<h4></h4>
<p><b>{{.data.clientCount}}</b></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="widget-small info coloured-icon"><i class="icon fa fa-user-secret fa-3x"></i>
<div class="info">
<h4>线</h4>
<p><b>{{.data.clientOnlineCount}}</b></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">tcp</h3>
<p>
<b></b> sshtcp
</p>
<p>
<b></b> 访{{.ip}}800110.1.50.10122ssh
</p>
<p><b>使</b></p>
<ul>
<li>tcp8001ip10.1.50.101:22 <a
href="/index/add?type=tunnelServer"></a></li>
{{/*<li>在tcp管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
<li>访ip{{.ip}}:(8001)访ip(10.1.50.101):(22)ssh -p 8001 root@{{.ip}}</li>
</ul>
<p>ip{{.ip}}</p>
<h3 class="tile-title"></h3>
<div class="embed-responsive embed-responsive-16by9">
<canvas class="embed-responsive-item" id="flow"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">udp</h3>
<p>
<b></b> dnsudp
</p>
<p>
<b></b> dns10.1.50.102:53使dns{{.ip}}
</p>
<p><b>使</b></p>
<ul>
<li>udp8002ip10.1.50.102:53 <a
href="/index/add?type=udpServer"></a></li>
{{/*<li>在udp管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
<li>dns{{.ip}}使10.1.50.202dns</li>
</ul>
<p>ip{{.ip}}</p>
<h3 class="tile-title"></h3>
<div class="embed-responsive embed-responsive-16by9">
<canvas class="embed-responsive-item" id="types"></canvas>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">socks5</h3>
<p>
<b></b> 使vpn访
</p>
<p>
<b></b> {{.ip}}8003socks5访
</p>
<p><b>使</b></p>
<ul>
<li>socks58003proxifer <a
href="/index/add?type=sock5Server"></a></li>
{{/*<li>在socks5代理管理列表中找到新加的隧道(查看其vkey或者客户端命令),任意内网机器执行客户端命令</li>*/}}
<li>socks5ipip{{.ip}}(8003)</li>
</ul>
<p>ip{{.ip}}</p>
</div>
</div>
<div class="col-md-6">
<div class="tile">
<h3 class="tile-title">http</h3>
<p>
<b></b> 访
</p>
<p>
<b></b> {{.ip}}8004http访
</p>
<p><b>使</b></p>
<ul>
<li>http8004 <a
href="/index/add?type=httpProxyServer"></a></li>
<li>http(vkey)</li>
<li>httpipip{{.ip}}(8004)访</li>
</ul>
<p>ip{{.ip}}</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="tile">
<p>
<b></b>
<li>./proxy_server</li>
<li>./proxy_client -server={{.ip}}:{{.p}} -vkey=xxx</li>
</p>
<p><b>./proxy_client -server={{.ip}}:{{.p}} -vkey=ccc./proxy_client
-server={{.ip}}:{{.p}} -vkey=ccc,dddvkey</b></p>
</div>
</div>
</div>
</main>
<script>
var pdataFlow = [
{
value: {{.data.inletFlowCount}},
color: "#46BFBD",
highlight: "#5AD3D1",
label: "入口流量"
},
{
value: {{.data.exportFlowCount}},
color: "#FDB45C",
highlight: "#FFC870",
label: "出口流量"
}
]
var pdataTypes = [
{
value: {{.data.tunnelServerCount}},
color: "#46BFBD",
highlight: "#5AD3D1",
label: "tcp隧道"
},
{
value: {{.data.socks5ServerCount}},
color: "#85FEAA",
highlight: "#85FEAA",
label: "socks5隧道"
},
{
value: {{.data.httpProxyServerCount}},
color: "#4B653C",
highlight: "#4B653C",
label: "http代理"
},
{
value: {{.data.udpServerCount}},
color: "#90653C",
highlight: "#90653C",
label: "udp代理"
},
{
value: {{.data.hostCount}},
color: "#FDB45C",
highlight: "#FDB45C",
label: "域名解析"
}
]
var ctxp = $("#flow").get(0).getContext("2d");
var pieChart = new Chart(ctxp).Pie(pdataFlow);
var ctxd = $("#types").get(0).getContext("2d");
var doughnutChart = new Chart(ctxd).Doughnut(pdataTypes);
</script>

View File

@ -5,10 +5,11 @@
<table class="table table-hover table-bordered" id="sampleTable">
<thead>
<tr>
{{/*<th>模式</th>*/}}
<th>Id</th>
<th></th>
<th>Id</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th>TCP</th>
@ -16,11 +17,12 @@
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
@ -30,12 +32,12 @@
</main>
<script type="text/javascript">
function del(vKey) {
function del(id) {
if (confirm("确定要删除数据吗?")) {
$.ajax({
type: "POST",
url: "/index/del",
data: {"vKey": vKey},
data: {"id": id},
success: function (res) {
alert(res.msg)
if (res.status) {
@ -46,12 +48,12 @@
}
}
function start(vKey) {
function start(id) {
if (confirm("确定要开始任务吗?")) {
$.ajax({
type: "POST",
url: "/index/start",
data: {"vKey": vKey},
data: {"id": id},
success: function (res) {
alert(res.msg)
if (res.status) {
@ -62,12 +64,12 @@
}
}
function stop(vKey) {
function stop(id) {
if (confirm("确定要暂停吗?")) {
$.ajax({
type: "POST",
url: "/index/stop",
data: {"vKey": vKey},
data: {"id": id},
success: function (res) {
alert(res.msg)
if (res.status) {
@ -78,35 +80,38 @@
}
}
function edit(vKey) {
window.location.href = "/index/edit?vKey=" + vKey
function edit(id) {
window.location.href = "/index/edit?id=" + id
}
function add() {
window.location.href = "/index/add?type=" +{{.type}}
window.location.href = "/index/add?type={{.type}}" + "&client_id={{.client_id}}"
}
function hostList(vkey) {
window.location.href = "/index/hostlist?vkey=" + vkey
}
$(document).ready(function () {
var table = $('#sampleTable').DataTable({
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.childRowImmediate
}
},
dom: 'Bfrtip',
processing: true,
serverSide: true,
autoWidth: false,
ordering: false,
ajax: {
url: '/index/getserverconfig?type={{.type}}',
url: '/index/getserverconfig?type={{.type}}' + "&client_id=" +{{.client_id}},
type: 'POST'
},
dom: '<"top"fl><"toolbar">rt<"bottom"ip><"clear">',
columns: [ //这个是显示到界面上的个数据 格式为 {data:'显示的字段名'}
// {data: 'Mode'},
{data: 'Id'},
{data: 'Remark'},
{data: 'ClientId'},
{data: 'TcpPort'},
{data: 'Target'},
{data: 'VerifyKey'},
{data: 'Compress'},
{data: 'Crypt'},
{data: 'Mux'},
@ -114,6 +119,8 @@
{data: 'P'},
{data: 'ClientStatus'},
{data: 'IsRun'},
{data: 'ExportFlow'},
{data: 'InletFlow'},
{data: "Id"}
],
bFilter: false,
@ -121,27 +128,18 @@
targets: -1,
render: function (data, type, row, meta) {
if (row.IsRun == 1) {
btn = "<button onclick=\"stop('" + row.VerifyKey + "')\" class=\"btn btn-secondary btn-sm\" type=\"button\">关闭</button>"
btn = "<button onclick=\"stop('" + row.Id + "')\" class=\"btn btn-secondary btn-sm\" type=\"button\">关闭</button>"
} else {
btn = "<button onclick=\"start('" + row.VerifyKey + "')\" class=\"btn btn-success btn-sm\" type=\"button\">打开</button>"
}
btn_edit = '<button onclick="edit(\'' + row.VerifyKey + '\')" type="button" class="btn btn-primary btn-sm"></button> '
if ({{.type}} == "hostServer"
)
{
btn_host = '<button onclick="hostList(\'' + row.VerifyKey + '\')" type="button" class="btn btn-info btn-sm"></button> '
}
else
{
btn_host = ""
btn = "<button onclick=\"start('" + row.Id + "')\" class=\"btn btn-success btn-sm\" type=\"button\">打开</button>"
}
btn_edit = '<button onclick="edit(\'' + row.Id + '\')" type="button" class="btn btn-primary btn-sm"></button> '
return '<div class="btn-group" role="group" aria-label="..."> ' +
'<button onclick="del(\'' + row.VerifyKey + '\')" type="button" class="btn btn-danger btn-sm"></button>' +
btn_edit + btn + btn_host + ' </div>'
'<button onclick="del(\'' + row.Id + '\')" type="button" class="btn btn-danger btn-sm"></button>' +
btn_edit + btn + ' </div>'
}
},
{
targets: -2,
targets: -4,
render: function (data, type, row, meta) {
if (data == 0) {
return "<span class=\"badge badge-pill badge-secondary\">暂停</span>"
@ -151,7 +149,7 @@
}
},
{
targets: -7,
targets: -9,
render: function (data, type, row, meta) {
if (data == "0") {
return "不加密"
@ -161,7 +159,7 @@
}
},
{
targets: -6,
targets: -8,
render: function (data, type, row, meta) {
if (data == "0") {
return "不启用"
@ -169,17 +167,9 @@
return "启用"
}
}
}
,
{
targets: 2,
render: function (data, type, row, meta) {
return "./proxy_client -server={{.ip}}:{{.p}} -vkey=" + data
// return data
}
},
{
targets: -3,
targets: -5,
render: function (data, type, row, meta) {
if (data == 0) {
return "<span class=\"badge badge-pill badge-secondary\">离线</span>"
@ -187,6 +177,18 @@
return "<span class=\"badge badge-pill badge-success\">在线</span>"
}
}
},
{
targets: -2,
render: function (data, type, row, meta) {
return change(row.Flow.InletFlow)
}
},
{
targets: -3,
render: function (data, type, row, meta) {
return change(row.Flow.ExportFlow)
}
}
],
buttons: []

View File

@ -41,8 +41,10 @@
</div>
<ul class="app-menu">
<li><a class="app-menu__item {{if eq "index" .menu}}active{{end}}" href="/"><i
class="app-menu__icon fa fa-dashboard"></i><span class="app-menu__label">使</span></a></li>
<li><a class="app-menu__item {{if eq "host" .menu}}active{{end}}" href="/index/host"><i
class="app-menu__icon fa fa-dashboard"></i><span class="app-menu__label">dashboard</span></a></li>
<li><a class="app-menu__item {{if eq "client" .menu}}active{{end}}" href="/client/client"><i
class="app-menu__icon fa fa-dot-circle-o"></i><span class="app-menu__label"></span></a></li>
<li><a class="app-menu__item {{if eq "host" .menu}}active{{end}}" href="/index/hostlist"><i
class="app-menu__icon fa fa-lemon-o"></i><span class="app-menu__label"></span></a></li>
<li><a class="app-menu__item {{if eq "tcp" .menu}}active{{end}}" href="/index/tcp"><i
class="app-menu__icon fa fa-life-buoy"></i><span class="app-menu__label">tcp</span></a></li>
@ -52,12 +54,14 @@
class="app-menu__icon fa fa-lightbulb-o"></i><span class="app-menu__label">socks5</span></a></li>
<li><a class="app-menu__item {{if eq "http" .menu}}active{{end}}" href="/index/http"><i
class="app-menu__icon fa fa-magic"></i><span class="app-menu__label">http</span></a></li>
<li><a class="app-menu__item {{if eq "help" .menu}}active{{end}}" href="/index/help"><i
class="app-menu__icon fa fa-question-circle"></i><span class="app-menu__label">使</span></a></li>
</ul>
</aside>
<script type="text/javascript" src="/static/js/datatables.min.js"></script>
<script src="/static/js/main.js"></script>
<script src="/static/js/chart.js"></script>
<main class="app-content">
<div class="app-title">
<div>