mirror of
https://github.com/bjdgyc/anylink.git
synced 2025-09-29 00:19:36 +08:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ddba116fbf | ||
|
dd1eae5d32 | ||
|
879b9114ac | ||
|
e5c4a47a37 | ||
|
a0669e1e32 | ||
|
ea7d27b4f0 | ||
|
9f2e9de49a | ||
|
8709dbaba1 | ||
|
4928ad5f62 | ||
|
0f91c779e3 | ||
|
3464d1d10e | ||
|
1579e92ba1 | ||
|
48327fe8d3 | ||
|
ef7723b03b | ||
|
0baab68bb2 | ||
|
665732fc03 | ||
|
edb0fe2dc9 | ||
|
1c6572f5e3 | ||
|
103329c3d0 | ||
|
d40b753871 | ||
|
fa5a58e98d | ||
|
62f30c05ff | ||
|
c02ffc27c0 | ||
|
a4e09e7719 | ||
|
631e49bd41 | ||
|
ef95b1f927 | ||
|
9e0da33c6a | ||
|
3bb771971c | ||
|
dd83b330eb | ||
|
73d1edd62f |
5
.codecov.yml
Normal file
5
.codecov.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ignore:
|
||||||
|
- "screenshot"
|
||||||
|
- "web"
|
||||||
|
- "server/conf"
|
||||||
|
- "server/files"
|
43
.github/workflows/go.yml
vendored
43
.github/workflows/go.yml
vendored
@@ -2,9 +2,9 @@ name: Go
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@@ -13,21 +13,32 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.15
|
go-version: 1.15
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: |
|
||||||
go get -v -t -d ./...
|
cd server
|
||||||
|
go get -v -t -d ./...
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v .
|
run: |
|
||||||
|
cd server
|
||||||
|
go build -v -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`"
|
||||||
|
./anylink -rev
|
||||||
|
|
||||||
- name: Test
|
- name: Test coverage
|
||||||
run: go test -v .
|
run: |
|
||||||
|
cd server
|
||||||
|
go test -race -coverprofile=coverage.txt -covermode=atomic -v ./...
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
run: |
|
||||||
|
cd server
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
|
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,19 +1,5 @@
|
|||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
vendor/
|
|
||||||
|
|
||||||
ui/
|
|
||||||
.idea/
|
.idea/
|
||||||
anylink
|
anylink-deploy
|
||||||
|
ui
|
||||||
|
|
||||||
|
56
README.md
56
README.md
@@ -1,6 +1,9 @@
|
|||||||
# AnyLink
|
# AnyLink
|
||||||
|
|
||||||
|
[](https://github.com/bjdgyc/anylink/actions)
|
||||||
[](https://pkg.go.dev/github.com/bjdgyc/anylink)
|
[](https://pkg.go.dev/github.com/bjdgyc/anylink)
|
||||||
|
[](https://goreportcard.com/report/github.com/bjdgyc/anylink)
|
||||||
|
[](https://codecov.io/gh/bjdgyc/anylink)
|
||||||
|
|
||||||
AnyLink 是一个企业级远程办公ssl vpn软件,可以支持多人同时在线使用。
|
AnyLink 是一个企业级远程办公ssl vpn软件,可以支持多人同时在线使用。
|
||||||
|
|
||||||
@@ -17,11 +20,11 @@ AnyLink 基于 [ietf-openconnect](https://tools.ietf.org/html/draft-mavrogiannop
|
|||||||
|
|
||||||
AnyLink 使用TLS/DTLS进行数据加密,因此需要RSA或ECC证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。
|
AnyLink 使用TLS/DTLS进行数据加密,因此需要RSA或ECC证书,可以通过 Let's Encrypt 和 TrustAsia 申请免费的SSL证书。
|
||||||
|
|
||||||
AnyLink 服务端仅在CentOS7测试通过,如需要安装在其他系统,需要服务端支持tun/tap功能、ip设置命令。
|
AnyLink 服务端仅在CentOS 7、Ubuntu 18.04测试通过,如需要安装在其他系统,需要服务端支持tun/tap功能、ip设置命令。
|
||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -31,11 +34,16 @@ AnyLink 服务端仅在CentOS7测试通过,如需要安装在其他系统,
|
|||||||
git clone https://github.com/bjdgyc/anylink.git
|
git clone https://github.com/bjdgyc/anylink.git
|
||||||
|
|
||||||
cd anylink
|
cd anylink
|
||||||
sh deploy.sh
|
sh -x build.sh
|
||||||
|
|
||||||
#注意使用root权限运行
|
# 注意使用root权限运行
|
||||||
cd anylink-deploy
|
cd anylink-deploy
|
||||||
sudo ./anylink -conf="conf/server.toml"
|
sudo ./anylink -conf="conf/server.toml"
|
||||||
|
|
||||||
|
# 默认管理后台访问地址
|
||||||
|
# http://host:8800
|
||||||
|
# 默认日志文件
|
||||||
|
# log/anylink.log
|
||||||
```
|
```
|
||||||
|
|
||||||
## Feature
|
## Feature
|
||||||
@@ -49,10 +57,11 @@ sudo ./anylink -conf="conf/server.toml"
|
|||||||
- [x] 用户组支持
|
- [x] 用户组支持
|
||||||
- [x] 多用户支持
|
- [x] 多用户支持
|
||||||
- [x] TOTP令牌支持
|
- [x] TOTP令牌支持
|
||||||
|
- [x] TOTP令牌开关
|
||||||
- [x] 流量控制
|
- [x] 流量控制
|
||||||
- [x] 后台管理界面
|
- [x] 后台管理界面
|
||||||
|
- [x] 访问权限管理
|
||||||
|
|
||||||
- [ ] 访问权限管理
|
|
||||||
- [ ] DTLS-UDP通道
|
- [ ] DTLS-UDP通道
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
@@ -67,7 +76,7 @@ sudo ./anylink -conf="conf/server.toml"
|
|||||||
./anylink -secret
|
./anylink -secret
|
||||||
```
|
```
|
||||||
|
|
||||||
[conf/server.toml](https://github.com/bjdgyc/anylink/blob/master/conf/server.toml)
|
[conf/server.toml](server/conf/server.toml)
|
||||||
|
|
||||||
## Setting
|
## Setting
|
||||||
|
|
||||||
@@ -122,23 +131,40 @@ sh bridge-init.sh
|
|||||||
|
|
||||||
## Soft
|
## Soft
|
||||||
|
|
||||||
相关软件下载: https://gitee.com/bjdgyc/anylink-soft
|
相关软件下载: QQ群共享文件: 567510628
|
||||||
|
|
||||||
|
## Discussion
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
添加QQ群: 567510628
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
欢迎提交 PR、Issues,感谢为AnyLink做出贡献
|
||||||
|
|
||||||
## Other Screenshot
|
## Other Screenshot
|
||||||
|
|
||||||

|
<details>
|
||||||

|
<summary>展开查看</summary>
|
||||||

|
|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
|
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 LICENSE 文件中。
|
||||||
|
|
||||||
|
## Thank
|
||||||
|
|
||||||
|
<a href="https://www.jetbrains.com">
|
||||||
|
<img src="screenshot/jetbrains.png" width="200" height="200" alt="jetbrains.png" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
90
base/log.go
90
base/log.go
@@ -1,90 +0,0 @@
|
|||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
_Debug = iota
|
|
||||||
_Info
|
|
||||||
_Warn
|
|
||||||
_Error
|
|
||||||
_Fatal
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
baseLog *log.Logger
|
|
||||||
baseLevel int
|
|
||||||
levels map[int]string
|
|
||||||
)
|
|
||||||
|
|
||||||
func initLog() {
|
|
||||||
baseLog = log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile)
|
|
||||||
baseLevel = logLevel2Int(Cfg.LogLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logLevel2Int(l string) int {
|
|
||||||
levels = map[int]string{
|
|
||||||
_Debug: "Debug",
|
|
||||||
_Info: "Info",
|
|
||||||
_Warn: "Warn",
|
|
||||||
_Error: "Error",
|
|
||||||
_Fatal: "Fatal",
|
|
||||||
}
|
|
||||||
lvl := _Info
|
|
||||||
for k, v := range levels {
|
|
||||||
if strings.ToLower(l) == strings.ToLower(v) {
|
|
||||||
lvl = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lvl
|
|
||||||
}
|
|
||||||
|
|
||||||
func output(l int, s ...interface{}) {
|
|
||||||
lvl := fmt.Sprintf("[%s] ", levels[l])
|
|
||||||
baseLog.Output(3, lvl+fmt.Sprintln(s...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debug(v ...interface{}) {
|
|
||||||
l := _Debug
|
|
||||||
if baseLevel > l {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output(l, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Info(v ...interface{}) {
|
|
||||||
l := _Info
|
|
||||||
if baseLevel > l {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output(l, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Warn(v ...interface{}) {
|
|
||||||
l := _Warn
|
|
||||||
if baseLevel > l {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output(l, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Error(v ...interface{}) {
|
|
||||||
l := _Error
|
|
||||||
if baseLevel > l {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output(l, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Fatal(v ...interface{}) {
|
|
||||||
l := _Fatal
|
|
||||||
if baseLevel > l {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output(l, v...)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
35
build.sh
Normal file
35
build.sh
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#当前目录
|
||||||
|
cpath=$(pwd)
|
||||||
|
|
||||||
|
echo "编译二进制文件"
|
||||||
|
cd $cpath/server
|
||||||
|
go build -o anylink -ldflags "-X main.COMMIT_ID=$(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
echo "编译前端项目"
|
||||||
|
cd $cpath/web
|
||||||
|
#国内可替换源加快速度
|
||||||
|
npm install --registry=https://registry.npm.taobao.org
|
||||||
|
npm run build --registry=https://registry.npm.taobao.org
|
||||||
|
#npm install
|
||||||
|
#npm run build
|
||||||
|
|
||||||
|
cd $cpath
|
||||||
|
|
||||||
|
echo "整理部署文件"
|
||||||
|
deploy="anylink-deploy"
|
||||||
|
rm -rf $deploy
|
||||||
|
mkdir $deploy
|
||||||
|
mkdir $deploy/log
|
||||||
|
|
||||||
|
cp -r server/anylink $deploy
|
||||||
|
cp -r server/conf $deploy
|
||||||
|
cp -r server/files $deploy
|
||||||
|
cp -r server/bridge-init.sh $deploy
|
||||||
|
|
||||||
|
cp -r web/ui $deploy
|
||||||
|
|
||||||
|
#注意使用root权限运行
|
||||||
|
#cd anylink-deploy
|
||||||
|
#sudo ./anylink -conf="conf/server.toml"
|
28
deploy.sh
28
deploy.sh
@@ -1,28 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
#编译二进制文件
|
|
||||||
go build -o anylink -ldflags "-X main.COMMIT_ID=`git rev-parse HEAD`"
|
|
||||||
|
|
||||||
#编译前端项目
|
|
||||||
git clone https://github.com/bjdgyc/anylink-web.git
|
|
||||||
cd anylink-web
|
|
||||||
#国内可替换源加快速度
|
|
||||||
#npm install --registry=https://registry.npm.taobao.org
|
|
||||||
#npm run build --registry=https://registry.npm.taobao.org
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
cd ../
|
|
||||||
|
|
||||||
#整理部署文件
|
|
||||||
mkdir anylink-deploy
|
|
||||||
|
|
||||||
cp -r anylink anylink-deploy
|
|
||||||
cp -r anylink-web/ui anylink-deploy
|
|
||||||
cp -r conf anylink-deploy
|
|
||||||
cp -r down_files anylink-deploy
|
|
||||||
|
|
||||||
#注意使用root权限运行
|
|
||||||
#cd anylink-deploy
|
|
||||||
#sudo ./anylink -conf="conf/server.toml"
|
|
||||||
|
|
@@ -1,10 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Title</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,47 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import "github.com/bjdgyc/anylink/sessdata"
|
|
||||||
|
|
||||||
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
|
|
||||||
payload := &sessdata.Payload{
|
|
||||||
LType: lType,
|
|
||||||
PType: pType,
|
|
||||||
Data: data,
|
|
||||||
}
|
|
||||||
|
|
||||||
return payloadInData(cSess, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
|
|
||||||
closed := false
|
|
||||||
|
|
||||||
select {
|
|
||||||
case cSess.PayloadIn <- payload:
|
|
||||||
case <-cSess.CloseChan:
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return closed
|
|
||||||
}
|
|
||||||
|
|
||||||
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
|
|
||||||
payload := &sessdata.Payload{
|
|
||||||
LType: lType,
|
|
||||||
PType: pType,
|
|
||||||
Data: data,
|
|
||||||
}
|
|
||||||
|
|
||||||
return payloadOutData(cSess, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func payloadOutData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
|
|
||||||
closed := false
|
|
||||||
|
|
||||||
select {
|
|
||||||
case cSess.PayloadOut <- payload:
|
|
||||||
case <-cSess.CloseChan:
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return closed
|
|
||||||
}
|
|
BIN
screenshot/jetbrains.png
Normal file
BIN
screenshot/jetbrains.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
BIN
screenshot/qq.png
Normal file
BIN
screenshot/qq.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
5
server/.codecov.yml
Normal file
5
server/.codecov.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ignore:
|
||||||
|
- "screenshot"
|
||||||
|
- "web"
|
||||||
|
- "server/conf"
|
||||||
|
- "server/files"
|
19
server/.gitignore
vendored
Normal file
19
server/.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
ui/
|
||||||
|
.idea/
|
||||||
|
anylink
|
@@ -10,26 +10,26 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 登陆接口
|
// Login 登陆接口
|
||||||
func Login(w http.ResponseWriter, r *http.Request) {
|
func Login(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO 调试信息输出
|
// TODO 调试信息输出
|
||||||
// hd, _ := httputil.DumpRequest(r, true)
|
// hd, _ := httputil.DumpRequest(r, true)
|
||||||
// fmt.Println("DumpRequest: ", string(hd))
|
// fmt.Println("DumpRequest: ", string(hd))
|
||||||
|
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
admin_user := r.PostFormValue("admin_user")
|
adminUser := r.PostFormValue("admin_user")
|
||||||
admin_pass := r.PostFormValue("admin_pass")
|
adminPass := r.PostFormValue("admin_pass")
|
||||||
|
|
||||||
// 认证错误
|
// 认证错误
|
||||||
if !(admin_user == base.Cfg.AdminUser &&
|
if !(adminUser == base.Cfg.AdminUser &&
|
||||||
utils.PasswordVerify(admin_pass, base.Cfg.AdminPass)) {
|
utils.PasswordVerify(adminPass, base.Cfg.AdminPass)) {
|
||||||
RespError(w, RespUserOrPassErr)
|
RespError(w, RespUserOrPassErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// token有效期
|
// token有效期
|
||||||
expiresAt := time.Now().Unix() + 3600*3
|
expiresAt := time.Now().Unix() + 3600*3
|
||||||
jwtData := map[string]interface{}{"admin_user": admin_user}
|
jwtData := map[string]interface{}{"admin_user": adminUser}
|
||||||
tokenString, err := SetJwtData(jwtData, expiresAt)
|
tokenString, err := SetJwtData(jwtData, expiresAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespError(w, 1, err)
|
RespError(w, 1, err)
|
||||||
@@ -38,7 +38,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
data := make(map[string]interface{})
|
data := make(map[string]interface{})
|
||||||
data["token"] = tokenString
|
data["token"] = tokenString
|
||||||
data["admin_user"] = admin_user
|
data["admin_user"] = adminUser
|
||||||
data["expires_at"] = expiresAt
|
data["expires_at"] = expiresAt
|
||||||
|
|
||||||
RespSucess(w, data)
|
RespSucess(w, data)
|
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func GroupList(w http.ResponseWriter, r *http.Request) {
|
func GroupList(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
pageS := r.FormValue("page")
|
pageS := r.FormValue("page")
|
||||||
page, _ := strconv.Atoi(pageS)
|
page, _ := strconv.Atoi(pageS)
|
||||||
if page < 1 {
|
if page < 1 {
|
||||||
@@ -48,7 +48,7 @@ func GroupNames(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GroupDetail(w http.ResponseWriter, r *http.Request) {
|
func GroupDetail(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
idS := r.FormValue("id")
|
idS := r.FormValue("id")
|
||||||
id, _ := strconv.Atoi(idS)
|
id, _ := strconv.Atoi(idS)
|
||||||
if id < 1 {
|
if id < 1 {
|
||||||
@@ -90,7 +90,7 @@ func GroupSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GroupDel(w http.ResponseWriter, r *http.Request) {
|
func GroupDel(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
idS := r.FormValue("id")
|
idS := r.FormValue("id")
|
||||||
id, _ := strconv.Atoi(idS)
|
id, _ := strconv.Atoi(idS)
|
||||||
if id < 1 {
|
if id < 1 {
|
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func UserIpMapList(w http.ResponseWriter, r *http.Request) {
|
func UserIpMapList(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
pageS := r.FormValue("page")
|
pageS := r.FormValue("page")
|
||||||
page, _ := strconv.Atoi(pageS)
|
page, _ := strconv.Atoi(pageS)
|
||||||
if page < 1 {
|
if page < 1 {
|
||||||
@@ -39,7 +39,7 @@ func UserIpMapList(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
|
func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
idS := r.FormValue("id")
|
idS := r.FormValue("id")
|
||||||
id, _ := strconv.Atoi(idS)
|
id, _ := strconv.Atoi(idS)
|
||||||
if id < 1 {
|
if id < 1 {
|
||||||
@@ -58,7 +58,7 @@ func UserIpMapDetail(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
|
func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -92,7 +92,7 @@ func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserIpMapDel(w http.ResponseWriter, r *http.Request) {
|
func UserIpMapDel(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
idS := r.FormValue("id")
|
idS := r.FormValue("id")
|
||||||
id, _ := strconv.Atoi(idS)
|
id, _ := strconv.Atoi(idS)
|
||||||
|
|
@@ -1,7 +1,6 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -84,9 +83,8 @@ func SetSystem(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetSoft(w http.ResponseWriter, r *http.Request) {
|
func SetSoft(w http.ResponseWriter, r *http.Request) {
|
||||||
datas := base.ServerCfg2Slice()
|
data := base.ServerCfg2Slice()
|
||||||
b, _ := json.Marshal(datas)
|
RespSucess(w, data)
|
||||||
w.Write(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func decimal(f float64) float64 {
|
func decimal(f float64) float64 {
|
@@ -19,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func UserList(w http.ResponseWriter, r *http.Request) {
|
func UserList(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
prefix := r.FormValue("prefix")
|
prefix := r.FormValue("prefix")
|
||||||
pageS := r.FormValue("page")
|
pageS := r.FormValue("page")
|
||||||
page, _ := strconv.Atoi(pageS)
|
page, _ := strconv.Atoi(pageS)
|
||||||
@@ -58,7 +58,7 @@ func UserList(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserDetail(w http.ResponseWriter, r *http.Request) {
|
func UserDetail(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
idS := r.FormValue("id")
|
idS := r.FormValue("id")
|
||||||
id, _ := strconv.Atoi(idS)
|
id, _ := strconv.Atoi(idS)
|
||||||
if id < 1 {
|
if id < 1 {
|
||||||
@@ -77,7 +77,7 @@ func UserDetail(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserSet(w http.ResponseWriter, r *http.Request) {
|
func UserSet(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -100,14 +100,18 @@ func UserSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// 发送邮件
|
// 发送邮件
|
||||||
if data.SendEmail {
|
if data.SendEmail {
|
||||||
userAccountMail(data)
|
err = userAccountMail(data)
|
||||||
|
if err != nil {
|
||||||
|
RespError(w, RespInternalErr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RespSucess(w, nil)
|
RespSucess(w, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserDel(w http.ResponseWriter, r *http.Request) {
|
func UserDel(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
idS := r.FormValue("id")
|
idS := r.FormValue("id")
|
||||||
id, _ := strconv.Atoi(idS)
|
id, _ := strconv.Atoi(idS)
|
||||||
|
|
||||||
@@ -126,7 +130,7 @@ func UserDel(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserOtpQr(w http.ResponseWriter, r *http.Request) {
|
func UserOtpQr(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
b64 := r.FormValue("b64")
|
b64 := r.FormValue("b64")
|
||||||
idS := r.FormValue("id")
|
idS := r.FormValue("id")
|
||||||
id, _ := strconv.Atoi(idS)
|
id, _ := strconv.Atoi(idS)
|
||||||
@@ -144,11 +148,16 @@ func UserOtpQr(w http.ResponseWriter, r *http.Request) {
|
|||||||
if b64 == "1" {
|
if b64 == "1" {
|
||||||
data, _ := qr.PNG(300)
|
data, _ := qr.PNG(300)
|
||||||
s := base64.StdEncoding.EncodeToString(data)
|
s := base64.StdEncoding.EncodeToString(data)
|
||||||
fmt.Fprint(w, s)
|
_, err = fmt.Fprint(w, s)
|
||||||
} else {
|
if err != nil {
|
||||||
qr.Write(300, w)
|
base.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = qr.Write(300, w)
|
||||||
|
if err != nil {
|
||||||
|
base.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在线用户
|
// 在线用户
|
||||||
@@ -165,12 +174,19 @@ func UserOnline(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UserOffline(w http.ResponseWriter, r *http.Request) {
|
func UserOffline(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
token := r.FormValue("token")
|
token := r.FormValue("token")
|
||||||
sessdata.CloseSess(token)
|
sessdata.CloseSess(token)
|
||||||
RespSucess(w, nil)
|
RespSucess(w, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserReline(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_ = r.ParseForm()
|
||||||
|
token := r.FormValue("token")
|
||||||
|
sessdata.CloseCSess(token)
|
||||||
|
RespSucess(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
type userAccountMailData struct {
|
type userAccountMailData struct {
|
||||||
Issuer string
|
Issuer string
|
||||||
LinkAddr string
|
LinkAddr string
|
||||||
@@ -220,7 +236,10 @@ func userAccountMail(user *dbdata.User) error {
|
|||||||
}
|
}
|
||||||
w := bytes.NewBufferString("")
|
w := bytes.NewBufferString("")
|
||||||
t, _ := template.New("auth_complete").Parse(htmlBody)
|
t, _ := template.New("auth_complete").Parse(htmlBody)
|
||||||
t.Execute(w, data)
|
err = t.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// fmt.Println(w.String())
|
// fmt.Println(w.String())
|
||||||
return SendMail(base.Cfg.Issuer+"平台通知", user.Email, w.String())
|
return SendMail(base.Cfg.Issuer+"平台通知", user.Email, w.String())
|
||||||
}
|
}
|
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
"github.com/bjdgyc/anylink/dbdata"
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
"github.com/mojocn/base64Captcha"
|
|
||||||
mail "github.com/xhit/go-simple-mail/v2"
|
mail "github.com/xhit/go-simple-mail/v2"
|
||||||
|
// "github.com/mojocn/base64Captcha"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetJwtData(data map[string]interface{}, expiresAt int64) (string, error) {
|
func SetJwtData(data map[string]interface{}, expiresAt int64) (string, error) {
|
||||||
@@ -43,15 +43,6 @@ func GetJwtData(jwtToken string) (map[string]interface{}, error) {
|
|||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createCaptcha() {
|
|
||||||
var store = base64Captcha.DefaultMemStore
|
|
||||||
var driver base64Captcha.Driver
|
|
||||||
driverString := &base64Captcha.DriverString{}
|
|
||||||
driver = driverString.ConvertFonts()
|
|
||||||
c := base64Captcha.NewCaptcha(driver, store)
|
|
||||||
_ = c
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendMail(subject, to, htmlBody string) error {
|
func SendMail(subject, to, htmlBody string) error {
|
||||||
|
|
||||||
dataSmtp := &dbdata.SettingSmtp{}
|
dataSmtp := &dbdata.SettingSmtp{}
|
||||||
@@ -68,7 +59,9 @@ func SendMail(subject, to, htmlBody string) error {
|
|||||||
server.Port = dataSmtp.Port
|
server.Port = dataSmtp.Port
|
||||||
server.Username = dataSmtp.Username
|
server.Username = dataSmtp.Username
|
||||||
server.Password = dataSmtp.Password
|
server.Password = dataSmtp.Password
|
||||||
// server.Encryption = mail.EncryptionTLS
|
if dataSmtp.UseSSl {
|
||||||
|
server.Encryption = mail.EncryptionSSL
|
||||||
|
}
|
||||||
|
|
||||||
// Since v2.3.0 you can specified authentication type:
|
// Since v2.3.0 you can specified authentication type:
|
||||||
// - PLAIN (default)
|
// - PLAIN (default)
|
23
server/admin/common_test.go
Normal file
23
server/admin/common_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bjdgyc/anylink/base"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJwtData(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
base.Cfg.JwtSecret = "dsfasfdfsadfasdfasd3sdaf"
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
}
|
||||||
|
expiresAt := time.Now().Add(time.Minute).Unix()
|
||||||
|
token, err := SetJwtData(data, expiresAt)
|
||||||
|
assert.Nil(err)
|
||||||
|
dataN, err := GetJwtData(token)
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(dataN["key"], "value")
|
||||||
|
}
|
@@ -43,8 +43,10 @@ func respHttp(w http.ResponseWriter, respCode int, data interface{}, errS ...int
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(b)
|
_, err = w.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
base.Error(err)
|
||||||
|
}
|
||||||
// 记录返回数据
|
// 记录返回数据
|
||||||
// logger.Category("response").Debug(string(b))
|
// logger.Category("response").Debug(string(b))
|
||||||
}
|
}
|
39
server/admin/resp_test.go
Normal file
39
server/admin/resp_test.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRespSucess(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
RespSucess(w, "data")
|
||||||
|
// fmt.Println(w)
|
||||||
|
assert.Equal(w.Code, 200)
|
||||||
|
body, _ := ioutil.ReadAll(w.Body)
|
||||||
|
res := Resp{}
|
||||||
|
err := json.Unmarshal(body, &res)
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(res.Code, 0)
|
||||||
|
assert.Equal(res.Data, "data")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRespError(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
RespError(w, 10, "err-msg")
|
||||||
|
// fmt.Println(w)
|
||||||
|
assert.Equal(w.Code, 200)
|
||||||
|
body, _ := ioutil.ReadAll(w.Body)
|
||||||
|
res := Resp{}
|
||||||
|
err := json.Unmarshal(body, &res)
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(res.Code, 10)
|
||||||
|
assert.Equal(res.Msg, "err-msg")
|
||||||
|
}
|
@@ -35,6 +35,7 @@ func StartAdmin() {
|
|||||||
r.HandleFunc("/user/del", UserDel)
|
r.HandleFunc("/user/del", UserDel)
|
||||||
r.HandleFunc("/user/online", UserOnline)
|
r.HandleFunc("/user/online", UserOnline)
|
||||||
r.HandleFunc("/user/offline", UserOffline)
|
r.HandleFunc("/user/offline", UserOffline)
|
||||||
|
r.HandleFunc("/user/reline", UserReline)
|
||||||
r.HandleFunc("/user/otp_qr", UserOtpQr)
|
r.HandleFunc("/user/otp_qr", UserOtpQr)
|
||||||
r.HandleFunc("/user/ip_map/list", UserIpMapList)
|
r.HandleFunc("/user/ip_map/list", UserIpMapList)
|
||||||
r.HandleFunc("/user/ip_map/detail", UserIpMapDetail)
|
r.HandleFunc("/user/ip_map/detail", UserIpMapDetail)
|
@@ -2,5 +2,5 @@ package base
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
APP_NAME = "AnyLink"
|
APP_NAME = "AnyLink"
|
||||||
APP_VER = "0.0.8"
|
APP_VER = "0.1.7"
|
||||||
)
|
)
|
@@ -40,16 +40,16 @@ type ServerConfig struct {
|
|||||||
CertFile string `toml:"cert_file" info:"证书文件"`
|
CertFile string `toml:"cert_file" info:"证书文件"`
|
||||||
CertKey string `toml:"cert_key" info:"证书密钥"`
|
CertKey string `toml:"cert_key" info:"证书密钥"`
|
||||||
UiPath string `toml:"ui_path" info:"ui文件路径"`
|
UiPath string `toml:"ui_path" info:"ui文件路径"`
|
||||||
DownFilesPath string `toml:"down_files_path" info:"外部下载文件路径"`
|
FilesPath string `toml:"files_path" info:"外部下载文件路径"`
|
||||||
|
LogPath string `toml:"log_path" info:"日志文件路径"`
|
||||||
LogLevel string `toml:"log_level" info:"日志等级"`
|
LogLevel string `toml:"log_level" info:"日志等级"`
|
||||||
Issuer string `toml:"issuer" info:"系统名称"`
|
Issuer string `toml:"issuer" info:"系统名称"`
|
||||||
AdminUser string `toml:"admin_user" info:"管理用户名"`
|
AdminUser string `toml:"admin_user" info:"管理用户名"`
|
||||||
AdminPass string `toml:"admin_pass" info:"管理用户密码"`
|
AdminPass string `toml:"admin_pass" info:"管理用户密码"`
|
||||||
JwtSecret string `toml:"jwt_secret" info:"JWT密钥"`
|
JwtSecret string `toml:"jwt_secret" info:"JWT密钥"`
|
||||||
|
|
||||||
LinkMode string `toml:"link_mode" info:"虚拟网络类型"` // tun tap
|
LinkMode string `toml:"link_mode" info:"虚拟网络类型"` // tun tap
|
||||||
Ipv4Network string `toml:"ipv4_network" info:"ipv4_network"` // 192.168.1.0
|
Ipv4CIDR string `toml:"ipv4_cidr" info:"ip地址网段"` // 192.168.1.0/24
|
||||||
Ipv4Netmask string `toml:"ipv4_netmask" info:"ipv4_netmask"` // 255.255.255.0
|
|
||||||
Ipv4Gateway string `toml:"ipv4_gateway" info:"ipv4_gateway"`
|
Ipv4Gateway string `toml:"ipv4_gateway" info:"ipv4_gateway"`
|
||||||
Ipv4Pool []string `toml:"ipv4_pool" info:"IPV4起止地址池"` // Pool[0]=192.168.1.100 Pool[1]=192.168.1.200
|
Ipv4Pool []string `toml:"ipv4_pool" info:"IPV4起止地址池"` // Pool[0]=192.168.1.100 Pool[1]=192.168.1.200
|
||||||
IpLease int `toml:"ip_lease" info:"IP租期(秒)"`
|
IpLease int `toml:"ip_lease" info:"IP租期(秒)"`
|
||||||
@@ -84,7 +84,8 @@ func initServerCfg() {
|
|||||||
Cfg.CertFile = getAbsPath(base, Cfg.CertFile)
|
Cfg.CertFile = getAbsPath(base, Cfg.CertFile)
|
||||||
Cfg.CertKey = getAbsPath(base, Cfg.CertKey)
|
Cfg.CertKey = getAbsPath(base, Cfg.CertKey)
|
||||||
Cfg.UiPath = getAbsPath(base, Cfg.UiPath)
|
Cfg.UiPath = getAbsPath(base, Cfg.UiPath)
|
||||||
Cfg.DownFilesPath = getAbsPath(base, Cfg.DownFilesPath)
|
Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath)
|
||||||
|
Cfg.LogPath = getAbsPath(base, Cfg.LogPath)
|
||||||
|
|
||||||
if len(Cfg.JwtSecret) < 20 {
|
if len(Cfg.JwtSecret) < 20 {
|
||||||
fmt.Println("请设置 jwt_secret 长度20位以上")
|
fmt.Println("请设置 jwt_secret 长度20位以上")
|
||||||
@@ -95,6 +96,10 @@ func initServerCfg() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAbsPath(base, cfile string) string {
|
func getAbsPath(base, cfile string) string {
|
||||||
|
if cfile == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
abs := filepath.IsAbs(cfile)
|
abs := filepath.IsAbs(cfile)
|
||||||
if abs {
|
if abs {
|
||||||
return cfile
|
return cfile
|
||||||
@@ -102,16 +107,17 @@ func getAbsPath(base, cfile string) string {
|
|||||||
return filepath.Join(base, cfile)
|
return filepath.Join(base, cfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServerCfg2Slice() interface{} {
|
type SCfg struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServerCfg2Slice() []SCfg {
|
||||||
ref := reflect.ValueOf(Cfg)
|
ref := reflect.ValueOf(Cfg)
|
||||||
s := ref.Elem()
|
s := ref.Elem()
|
||||||
|
|
||||||
type cfg struct {
|
var datas []SCfg
|
||||||
Name string `json:"name"`
|
|
||||||
Info string `json:"info"`
|
|
||||||
Data interface{} `json:"data"`
|
|
||||||
}
|
|
||||||
var datas []cfg
|
|
||||||
|
|
||||||
typ := s.Type()
|
typ := s.Type()
|
||||||
numFields := s.NumField()
|
numFields := s.NumField()
|
||||||
@@ -122,7 +128,7 @@ func ServerCfg2Slice() interface{} {
|
|||||||
tags := strings.Split(tag, ",")
|
tags := strings.Split(tag, ",")
|
||||||
info := field.Tag.Get("info")
|
info := field.Tag.Get("info")
|
||||||
|
|
||||||
datas = append(datas, cfg{Name: tags[0], Info: info, Data: value.Interface()})
|
datas = append(datas, SCfg{Name: tags[0], Info: info, Data: value.Interface()})
|
||||||
}
|
}
|
||||||
|
|
||||||
return datas
|
return datas
|
@@ -26,7 +26,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initFlag() {
|
func initFlag() {
|
||||||
flag.StringVar(&serverFile, "conf", "./conf/server.toml", "server config file path")
|
flag.StringVar(&serverFile, "conf", "./conf/server.toml", "server config files path")
|
||||||
flag.StringVar(&passwd, "passwd", "", "convert the password plaintext")
|
flag.StringVar(&passwd, "passwd", "", "convert the password plaintext")
|
||||||
flag.BoolVar(&secret, "secret", false, "generate a random jwt secret")
|
flag.BoolVar(&secret, "secret", false, "generate a random jwt secret")
|
||||||
flag.BoolVar(&rev, "rev", false, "display version info")
|
flag.BoolVar(&rev, "rev", false, "display version info")
|
146
server/base/log.go
Normal file
146
server/base/log.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_Debug = iota
|
||||||
|
_Info
|
||||||
|
_Warn
|
||||||
|
_Error
|
||||||
|
_Fatal
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseLog *log.Logger
|
||||||
|
baseLevel int
|
||||||
|
levels map[int]string
|
||||||
|
|
||||||
|
dateFormat = "2006-01-02"
|
||||||
|
logName = "anylink.log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 实现 os.Writer 接口
|
||||||
|
type logWriter struct {
|
||||||
|
UseStdout bool
|
||||||
|
FileName string
|
||||||
|
File *os.File
|
||||||
|
NowDate string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现日志文件的切割
|
||||||
|
func (lw *logWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if !lw.UseStdout {
|
||||||
|
return lw.File.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
date := time.Now().Format(dateFormat)
|
||||||
|
if lw.NowDate != date {
|
||||||
|
_ = lw.File.Close()
|
||||||
|
_ = os.Rename(lw.FileName, lw.FileName+"."+lw.NowDate)
|
||||||
|
lw.NowDate = date
|
||||||
|
lw.newFile()
|
||||||
|
}
|
||||||
|
return lw.File.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新文件
|
||||||
|
func (lw *logWriter) newFile() {
|
||||||
|
if lw.UseStdout {
|
||||||
|
lw.File = os.Stdout
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(lw.FileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
lw.File = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func initLog() {
|
||||||
|
// 初始化 baseLog
|
||||||
|
baseLw := &logWriter{
|
||||||
|
UseStdout: Cfg.LogPath == "",
|
||||||
|
FileName: path.Join(Cfg.LogPath, logName),
|
||||||
|
NowDate: time.Now().Format(dateFormat),
|
||||||
|
}
|
||||||
|
|
||||||
|
baseLw.newFile()
|
||||||
|
baseLevel = logLevel2Int(Cfg.LogLevel)
|
||||||
|
baseLog = log.New(baseLw, "", log.LstdFlags|log.Lshortfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 log.Logger
|
||||||
|
func GetBaseLog() *log.Logger {
|
||||||
|
return baseLog
|
||||||
|
}
|
||||||
|
|
||||||
|
func logLevel2Int(l string) int {
|
||||||
|
levels = map[int]string{
|
||||||
|
_Debug: "Debug",
|
||||||
|
_Info: "Info",
|
||||||
|
_Warn: "Warn",
|
||||||
|
_Error: "Error",
|
||||||
|
_Fatal: "Fatal",
|
||||||
|
}
|
||||||
|
lvl := _Info
|
||||||
|
for k, v := range levels {
|
||||||
|
if strings.EqualFold(strings.ToLower(l), strings.ToLower(v)) {
|
||||||
|
lvl = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lvl
|
||||||
|
}
|
||||||
|
|
||||||
|
func output(l int, s ...interface{}) {
|
||||||
|
lvl := fmt.Sprintf("[%s] ", levels[l])
|
||||||
|
_ = baseLog.Output(3, lvl+fmt.Sprintln(s...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
l := _Debug
|
||||||
|
if baseLevel > l {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
output(l, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
l := _Info
|
||||||
|
if baseLevel > l {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
output(l, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(v ...interface{}) {
|
||||||
|
l := _Warn
|
||||||
|
if baseLevel > l {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
output(l, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(v ...interface{}) {
|
||||||
|
l := _Error
|
||||||
|
if baseLevel > l {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
output(l, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(v ...interface{}) {
|
||||||
|
l := _Fatal
|
||||||
|
if baseLevel > l {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
output(l, v...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
@@ -5,3 +5,7 @@ func Start() {
|
|||||||
initServerCfg()
|
initServerCfg()
|
||||||
initLog()
|
initLog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test() {
|
||||||
|
initLog()
|
||||||
|
}
|
@@ -8,10 +8,6 @@
|
|||||||
# Define Bridge Interface
|
# Define Bridge Interface
|
||||||
br="anylink0"
|
br="anylink0"
|
||||||
|
|
||||||
# Define list of TAP interfaces to be bridged,
|
|
||||||
# for example tap="tap0 tap1 tap2".
|
|
||||||
tap="tap0"
|
|
||||||
|
|
||||||
# Define physical ethernet interface to be bridged
|
# Define physical ethernet interface to be bridged
|
||||||
# with TAP interface(s) above.
|
# with TAP interface(s) above.
|
||||||
|
|
@@ -9,8 +9,9 @@ db_file = "./data.db"
|
|||||||
cert_file = "./vpn_cert.pem"
|
cert_file = "./vpn_cert.pem"
|
||||||
cert_key = "./vpn_cert.key"
|
cert_key = "./vpn_cert.key"
|
||||||
ui_path = "../ui"
|
ui_path = "../ui"
|
||||||
down_files_path = "../down_files"
|
files_path = "../files"
|
||||||
|
#日志目录,为空写入标准输出
|
||||||
|
log_path = "../log"
|
||||||
log_level = "info"
|
log_level = "info"
|
||||||
|
|
||||||
#系统名称
|
#系统名称
|
||||||
@@ -35,8 +36,7 @@ proxy_protocol = false
|
|||||||
link_mode = "tun"
|
link_mode = "tun"
|
||||||
|
|
||||||
#客户端分配的ip地址池
|
#客户端分配的ip地址池
|
||||||
ipv4_network = "192.168.10.0"
|
ipv4_cidr = "192.168.10.0/24"
|
||||||
ipv4_netmask = "255.255.255.0"
|
|
||||||
ipv4_gateway = "192.168.10.1"
|
ipv4_gateway = "192.168.10.1"
|
||||||
ipv4_pool = ["192.168.10.100", "192.168.10.200"]
|
ipv4_pool = ["192.168.10.100", "192.168.10.200"]
|
||||||
|
|
@@ -43,28 +43,27 @@ func initData() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer Set(SettingBucket, Installed, true)
|
defer func() {
|
||||||
|
_ = Set(SettingBucket, Installed, true)
|
||||||
|
}()
|
||||||
|
|
||||||
smtp := &SettingSmtp{
|
smtp := &SettingSmtp{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 25,
|
Port: 25,
|
||||||
From: "vpn@xx.com",
|
From: "vpn@xx.com",
|
||||||
}
|
}
|
||||||
SettingSet(smtp)
|
_ = SettingSet(smtp)
|
||||||
|
|
||||||
other := &SettingOther{
|
other := &SettingOther{
|
||||||
Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为!",
|
Banner: "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为!",
|
||||||
AccountMail: accountMail,
|
AccountMail: accountMail,
|
||||||
}
|
}
|
||||||
SettingSet(other)
|
_ = SettingSet(other)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckErrNotFound(err error) bool {
|
func CheckErrNotFound(err error) bool {
|
||||||
if err == storm.ErrNotFound {
|
return err == storm.ErrNotFound
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountMail = `<p>您好:</p>
|
const accountMail = `<p>您好:</p>
|
@@ -27,7 +27,7 @@ func TestDb(t *testing.T) {
|
|||||||
defer closeIpdata()
|
defer closeIpdata()
|
||||||
|
|
||||||
u := User{Username: "a"}
|
u := User{Username: "a"}
|
||||||
Save(&u)
|
_ = Save(&u)
|
||||||
|
|
||||||
assert.Equal(u.Id, 1)
|
assert.Equal(u.Id, 1)
|
||||||
}
|
}
|
@@ -18,13 +18,15 @@ type GroupLinkAcl struct {
|
|||||||
// 自上而下匹配 默认 allow * *
|
// 自上而下匹配 默认 allow * *
|
||||||
Action string `json:"action"` // allow、deny
|
Action string `json:"action"` // allow、deny
|
||||||
Val string `json:"val"`
|
Val string `json:"val"`
|
||||||
Port uint8 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
IpNet *net.IPNet `json:"ip_net"`
|
IpNet *net.IPNet `json:"ip_net"`
|
||||||
|
Note string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValData struct {
|
type ValData struct {
|
||||||
Val string `json:"val"`
|
Val string `json:"val"`
|
||||||
IpMask string `json:"ip_mask"`
|
IpMask string `json:"ip_mask"`
|
||||||
|
Note string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
@@ -81,8 +83,9 @@ func SetGroup(g *Group) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("RouteInclude 错误" + err.Error())
|
return errors.New("RouteInclude 错误" + err.Error())
|
||||||
}
|
}
|
||||||
vn := ValData{Val: v.Val, IpMask: ipMask}
|
|
||||||
routeInclude = append(routeInclude, vn)
|
v.IpMask = ipMask
|
||||||
|
routeInclude = append(routeInclude, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.RouteInclude = routeInclude
|
g.RouteInclude = routeInclude
|
||||||
@@ -93,8 +96,8 @@ func SetGroup(g *Group) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("RouteExclude 错误" + err.Error())
|
return errors.New("RouteExclude 错误" + err.Error())
|
||||||
}
|
}
|
||||||
vn := ValData{Val: v.Val, IpMask: ipMask}
|
v.IpMask = ipMask
|
||||||
routeExclude = append(routeExclude, vn)
|
routeExclude = append(routeExclude, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.RouteExclude = routeExclude
|
g.RouteExclude = routeExclude
|
||||||
@@ -106,9 +109,8 @@ func SetGroup(g *Group) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("GroupLinkAcl 错误" + err.Error())
|
return errors.New("GroupLinkAcl 错误" + err.Error())
|
||||||
}
|
}
|
||||||
vn := v
|
v.IpNet = ipNet
|
||||||
vn.IpNet = ipNet
|
linkAcl = append(linkAcl, v)
|
||||||
linkAcl = append(linkAcl, vn)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.LinkAcl = linkAcl
|
g.LinkAcl = linkAcl
|
@@ -38,6 +38,7 @@ type SettingSmtp struct {
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
From string `json:"from"`
|
From string `json:"from"`
|
||||||
|
UseSSl bool `json:"use_ssl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingOther struct {
|
type SettingOther struct {
|
@@ -16,13 +16,14 @@ type User struct {
|
|||||||
Nickname string `json:"nickname"`
|
Nickname string `json:"nickname"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
// Password string `json:"password"`
|
// Password string `json:"password"`
|
||||||
PinCode string `json:"pin_code"`
|
PinCode string `json:"pin_code"`
|
||||||
OtpSecret string `json:"otp_secret"`
|
OtpSecret string `json:"otp_secret"`
|
||||||
Groups []string `json:"groups"`
|
DisableOtp bool `json:"disable_otp"` // 禁用otp
|
||||||
Status int8 `json:"status"` // 1正常
|
Groups []string `json:"groups"`
|
||||||
SendEmail bool `json:"send_email"`
|
Status int8 `json:"status"` // 1正常
|
||||||
CreatedAt time.Time `json:"created_at"`
|
SendEmail bool `json:"send_email"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetUser(v *User) error {
|
func SetUser(v *User) error {
|
||||||
@@ -39,7 +40,7 @@ func SetUser(v *User) error {
|
|||||||
v.PinCode = planPass
|
v.PinCode = planPass
|
||||||
|
|
||||||
if v.OtpSecret == "" {
|
if v.OtpSecret == "" {
|
||||||
v.OtpSecret = gotp.RandomSecret(24)
|
v.OtpSecret = gotp.RandomSecret(32)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断组是否有效
|
// 判断组是否有效
|
||||||
@@ -85,18 +86,16 @@ func CheckUser(name, pwd, group string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 判断otp信息
|
// 判断otp信息
|
||||||
otp := pwd[pl-6:]
|
pinCode := pwd
|
||||||
if !checkOtp(name, otp) {
|
if !v.DisableOtp {
|
||||||
return fmt.Errorf("%s %s", name, "动态码错误")
|
pinCode = pwd[:pl-6]
|
||||||
}
|
otp := pwd[pl-6:]
|
||||||
totp := gotp.NewDefaultTOTP(v.OtpSecret)
|
if !checkOtp(name, otp, v.OtpSecret) {
|
||||||
unix := time.Now().Unix()
|
return fmt.Errorf("%s %s", name, "动态码错误")
|
||||||
verify := totp.Verify(otp, int(unix))
|
}
|
||||||
if !verify {
|
|
||||||
return fmt.Errorf("%s %s", name, "动态码错误")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pinCode := pwd[:pl-6]
|
// 判断用户密码
|
||||||
if pinCode != v.PinCode {
|
if pinCode != v.PinCode {
|
||||||
return fmt.Errorf("%s %s", name, "密码错误")
|
return fmt.Errorf("%s %s", name, "密码错误")
|
||||||
}
|
}
|
||||||
@@ -126,18 +125,23 @@ func init() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 令牌只能使用一次
|
// 判断令牌信息
|
||||||
func checkOtp(username, otp string) bool {
|
func checkOtp(name, otp, secret string) bool {
|
||||||
key := fmt.Sprintf("%s:%s", username, otp)
|
key := fmt.Sprintf("%s:%s", name, otp)
|
||||||
|
|
||||||
userOtpMux.Lock()
|
userOtpMux.Lock()
|
||||||
defer userOtpMux.Unlock()
|
defer userOtpMux.Unlock()
|
||||||
|
|
||||||
|
// 令牌只能使用一次
|
||||||
if _, ok := userOtp[key]; ok {
|
if _, ok := userOtp[key]; ok {
|
||||||
// 已经存在
|
// 已经存在
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
userOtp[key] = time.Now()
|
userOtp[key] = time.Now()
|
||||||
return true
|
|
||||||
|
totp := gotp.NewDefaultTOTP(secret)
|
||||||
|
unix := time.Now().Unix()
|
||||||
|
verify := totp.Verify(otp, int(unix))
|
||||||
|
|
||||||
|
return verify
|
||||||
}
|
}
|
0
server/files/index.html
Normal file
0
server/files/index.html
Normal file
@@ -6,21 +6,19 @@ require (
|
|||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||||
github.com/asdine/storm/v3 v3.2.1
|
github.com/asdine/storm/v3 v3.2.1
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/mojocn/base64Captcha v1.3.1
|
|
||||||
github.com/pelletier/go-toml v1.8.1
|
github.com/pelletier/go-toml v1.8.1
|
||||||
github.com/shirou/gopsutil v3.20.11+incompatible
|
github.com/shirou/gopsutil v3.21.1+incompatible
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
|
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
|
||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/xhit/go-simple-mail/v2 v2.6.0
|
github.com/xhit/go-simple-mail/v2 v2.8.0
|
||||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||||
go.etcd.io/bbolt v1.3.5
|
go.etcd.io/bbolt v1.3.5
|
||||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11
|
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
|
||||||
)
|
)
|
@@ -11,10 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@@ -29,14 +27,12 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mojocn/base64Captcha v1.3.1 h1:2Wbkt8Oc8qjmNJ5GyOfSo4tgVQPsbKMftqASnq8GlT0=
|
|
||||||
github.com/mojocn/base64Captcha v1.3.1/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY=
|
|
||||||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto=
|
github.com/shirou/gopsutil v3.21.1+incompatible h1:2LwXWdbjXwyDgq26Yy/OT4xozlpmssQfy/rtfhWb0bY=
|
||||||
github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v3.21.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w=
|
github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w=
|
||||||
@@ -45,12 +41,12 @@ github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D
|
|||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||||
github.com/xhit/go-simple-mail/v2 v2.6.0 h1:pvPmpDUUWy07cnTgwxwEe5fjdyYtETnxcvdGPQxtv/k=
|
github.com/xhit/go-simple-mail/v2 v2.8.0 h1:w6ZDXvRk0EO+r78LRlQl14ngP2tiRDRRHhr9UaVJ0p4=
|
||||||
github.com/xhit/go-simple-mail/v2 v2.6.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
|
github.com/xhit/go-simple-mail/v2 v2.8.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
|
||||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
|
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
|
||||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
|
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
|
||||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
@@ -58,21 +54,20 @@ go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
|||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ=
|
|
||||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
|
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||||
@@ -82,13 +77,11 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
@@ -2,4 +2,5 @@ package handler
|
|||||||
|
|
||||||
// 暂时没有实现
|
// 暂时没有实现
|
||||||
func startDtls() {
|
func startDtls() {
|
||||||
|
|
||||||
}
|
}
|
@@ -17,10 +17,10 @@ import (
|
|||||||
func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
// 判断anyconnect客户端
|
// 判断anyconnect客户端
|
||||||
userAgent := strings.ToLower(r.UserAgent())
|
userAgent := strings.ToLower(r.UserAgent())
|
||||||
x_Aggregate_Auth := r.Header.Get("X-Aggregate-Auth")
|
xAggregateAuth := r.Header.Get("X-Aggregate-Auth")
|
||||||
x_Transcend_Version := r.Header.Get("X-Transcend-Version")
|
xTranscendVersion := r.Header.Get("X-Transcend-Version")
|
||||||
if !(strings.Contains(userAgent, "anyconnect") &&
|
if !(strings.Contains(userAgent, "anyconnect") &&
|
||||||
x_Aggregate_Auth == "1" && x_Transcend_Version == "1") {
|
xAggregateAuth == "1" && xTranscendVersion == "1") {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
fmt.Fprintf(w, "error request")
|
fmt.Fprintf(w, "error request")
|
||||||
return
|
return
|
||||||
@@ -67,7 +67,7 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
// TODO 用户密码校验
|
// TODO 用户密码校验
|
||||||
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect)
|
err = dbdata.CheckUser(cr.Auth.Username, cr.Auth.Password, cr.GroupSelect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Info(err)
|
base.Warn(err)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"}
|
data := RequestData{Group: cr.GroupSelect, Groups: dbdata.GetGroupNames(), Error: "用户名或密码错误"}
|
||||||
tplRequest(tpl_request, w, data)
|
tplRequest(tpl_request, w, data)
|
||||||
@@ -87,11 +87,12 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
|
sess.MacAddr = strings.ToLower(cr.MacAddressList.MacAddress)
|
||||||
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
|
sess.UniqueIdGlobal = cr.DeviceId.UniqueIdGlobal
|
||||||
other := &dbdata.SettingOther{}
|
other := &dbdata.SettingOther{}
|
||||||
dbdata.SettingGet(other)
|
_ = dbdata.SettingGet(other)
|
||||||
rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token,
|
rd := RequestData{SessionId: sess.Sid, SessionToken: sess.Sid + "@" + sess.Token,
|
||||||
Banner: other.Banner}
|
Banner: other.Banner}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
tplRequest(tpl_complete, w, rd)
|
tplRequest(tpl_complete, w, rd)
|
||||||
|
base.Debug("login", cr.Auth.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -102,7 +103,7 @@ const (
|
|||||||
func tplRequest(typ int, w io.Writer, data RequestData) {
|
func tplRequest(typ int, w io.Writer, data RequestData) {
|
||||||
if typ == tpl_request {
|
if typ == tpl_request {
|
||||||
t, _ := template.New("auth_request").Parse(auth_request)
|
t, _ := template.New("auth_request").Parse(auth_request)
|
||||||
t.Execute(w, data)
|
_ = t.Execute(w, data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ func tplRequest(typ int, w io.Writer, data RequestData) {
|
|||||||
data.Banner = strings.ReplaceAll(data.Banner, "\n", "
")
|
data.Banner = strings.ReplaceAll(data.Banner, "\n", "
")
|
||||||
}
|
}
|
||||||
t, _ := template.New("auth_complete").Parse(auth_complete)
|
t, _ := template.New("auth_complete").Parse(auth_complete)
|
||||||
t.Execute(w, data)
|
_ = t.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置输出信息
|
// 设置输出信息
|
@@ -2,11 +2,9 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const BufferSize = 2048
|
const BufferSize = 2048
|
||||||
@@ -43,27 +41,6 @@ type macAddressList struct {
|
|||||||
MacAddress string `xml:"mac-address"`
|
MacAddress string `xml:"mac-address"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断anyconnect客户端
|
|
||||||
func checkLinkClient(h http.HandlerFunc) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// TODO 调试信息输出
|
|
||||||
// hd, _ := httputil.DumpRequest(r, true)
|
|
||||||
// fmt.Println("DumpRequest: ", string(hd))
|
|
||||||
// fmt.Println(r.RemoteAddr)
|
|
||||||
|
|
||||||
userAgent := strings.ToLower(r.UserAgent())
|
|
||||||
x_Aggregate_Auth := r.Header.Get("X-Aggregate-Auth")
|
|
||||||
x_Transcend_Version := r.Header.Get("X-Transcend-Version")
|
|
||||||
if strings.Contains(userAgent, "anyconnect") &&
|
|
||||||
x_Aggregate_Auth == "1" && x_Transcend_Version == "1" {
|
|
||||||
h(w, r)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
fmt.Fprintf(w, "error request")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCommonHeader(w http.ResponseWriter) {
|
func setCommonHeader(w http.ResponseWriter) {
|
||||||
// Content-Length Date 默认已经存在
|
// Content-Length Date 默认已经存在
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
@@ -11,8 +11,8 @@ import (
|
|||||||
|
|
||||||
func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
|
func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
// log.Println("LinkCstp return")
|
base.Debug("LinkCstp return", cSess.IpAddr)
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
cSess.Close()
|
cSess.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -72,8 +72,8 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
|
|||||||
|
|
||||||
func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
|
func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
// log.Println("cstpWrite return")
|
base.Debug("cstpWrite return", cSess.IpAddr)
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
cSess.Close()
|
cSess.Close()
|
||||||
}()
|
}()
|
||||||
|
|
@@ -26,7 +26,7 @@ func LinkHome(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LinkOtpQr(w http.ResponseWriter, r *http.Request) {
|
func LinkOtpQr(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
_ = r.ParseForm()
|
||||||
idS := r.FormValue("id")
|
idS := r.FormValue("id")
|
||||||
jwtToken := r.FormValue("jwt")
|
jwtToken := r.FormValue("jwt")
|
||||||
data, err := admin.GetJwtData(jwtToken)
|
data, err := admin.GetJwtData(jwtToken)
|
@@ -29,6 +29,9 @@ func checkTap() {
|
|||||||
bridgeHw = brFace.HardwareAddr
|
bridgeHw = brFace.HardwareAddr
|
||||||
|
|
||||||
addrs, err := brFace.Addrs()
|
addrs, err := brFace.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
base.Fatal("testTap err: ", err)
|
||||||
|
}
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
ip, _, err := net.ParseCIDR(addr.String())
|
ip, _, err := net.ParseCIDR(addr.String())
|
||||||
if err != nil || ip.To4() == nil {
|
if err != nil || ip.To4() == nil {
|
||||||
@@ -67,7 +70,7 @@ func LinkTap(cSess *sessdata.ConnSession) error {
|
|||||||
err = execCmd(cmdStrs)
|
err = execCmd(cmdStrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
ifce.Close()
|
_ = ifce.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,9 +81,9 @@ func LinkTap(cSess *sessdata.ConnSession) error {
|
|||||||
|
|
||||||
func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
// log.Println("LinkTap return")
|
base.Debug("LinkTap return", cSess.IpAddr)
|
||||||
cSess.Close()
|
cSess.Close()
|
||||||
ifce.Close()
|
_ = ifce.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -150,8 +153,8 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
|
|
||||||
func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
// log.Println("tapRead return")
|
base.Debug("tapRead return", cSess.IpAddr)
|
||||||
ifce.Close()
|
_ = ifce.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
@@ -51,7 +51,7 @@ func LinkTun(cSess *sessdata.ConnSession) error {
|
|||||||
err = execCmd(cmdStrs)
|
err = execCmd(cmdStrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
base.Error(err)
|
base.Error(err)
|
||||||
ifce.Close()
|
_ = ifce.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +62,9 @@ func LinkTun(cSess *sessdata.ConnSession) error {
|
|||||||
|
|
||||||
func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
// log.Println("LinkTun return")
|
base.Debug("LinkTun return", cSess.IpAddr)
|
||||||
cSess.Close()
|
cSess.Close()
|
||||||
ifce.Close()
|
_ = ifce.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -89,8 +89,8 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
|||||||
|
|
||||||
func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
|
||||||
defer func() {
|
defer func() {
|
||||||
// log.Println("tunRead return")
|
base.Debug("tunRead return", cSess.IpAddr)
|
||||||
ifce.Close()
|
_ = ifce.Close()
|
||||||
}()
|
}()
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
@@ -73,9 +73,9 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Server", fmt.Sprintf("%s %s", base.APP_NAME, base.APP_VER))
|
w.Header().Set("Server", fmt.Sprintf("%s %s", base.APP_NAME, base.APP_VER))
|
||||||
w.Header().Set("X-CSTP-Version", "1")
|
w.Header().Set("X-CSTP-Version", "1")
|
||||||
w.Header().Set("X-CSTP-Protocol", "Copyright (c) 2004 Cisco Systems, Inc.")
|
w.Header().Set("X-CSTP-Protocol", "Copyright (c) 2004 Cisco Systems, Inc.")
|
||||||
w.Header().Set("X-CSTP-Address", cSess.IpAddr.String()) // 分配的ip地址
|
w.Header().Set("X-CSTP-Address", cSess.IpAddr.String()) // 分配的ip地址
|
||||||
w.Header().Set("X-CSTP-Netmask", base.Cfg.Ipv4Netmask) // 子网掩码
|
w.Header().Set("X-CSTP-Netmask", sessdata.IpPool.Ipv4Mask.String()) // 子网掩码
|
||||||
w.Header().Set("X-CSTP-Hostname", hn) // 机器名称
|
w.Header().Set("X-CSTP-Hostname", hn) // 机器名称
|
||||||
|
|
||||||
// 允许本地LAN访问vpn网络,必须放在路由的第一个
|
// 允许本地LAN访问vpn网络,必须放在路由的第一个
|
||||||
if cSess.Group.AllowLan {
|
if cSess.Group.AllowLan {
|
||||||
@@ -131,15 +131,16 @@ func LinkTunnel(w http.ResponseWriter, r *http.Request) {
|
|||||||
// w.Header().Set("X-CSTP-Post-Auth-XML", ``)
|
// w.Header().Set("X-CSTP-Post-Auth-XML", ``)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
h := w.Header().Clone()
|
hClone := w.Header().Clone()
|
||||||
headers := make([]byte, 0)
|
headers := make([]byte, 0)
|
||||||
buf := bytes.NewBuffer(headers)
|
buf := bytes.NewBuffer(headers)
|
||||||
h.Write(buf)
|
_ = hClone.Write(buf)
|
||||||
base.Debug(string(buf.Bytes()))
|
base.Debug(buf.String())
|
||||||
|
|
||||||
hj := w.(http.Hijacker)
|
hj := w.(http.Hijacker)
|
||||||
conn, _, err := hj.Hijack()
|
conn, _, err := hj.Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
base.Error(err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
91
server/handler/payload.go
Normal file
91
server/handler/payload.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bjdgyc/anylink/dbdata"
|
||||||
|
"github.com/bjdgyc/anylink/sessdata"
|
||||||
|
"github.com/songgao/water/waterutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
|
||||||
|
payload := &sessdata.Payload{
|
||||||
|
LType: lType,
|
||||||
|
PType: pType,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloadInData(cSess, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
|
||||||
|
// 进行Acl规则判断
|
||||||
|
check := checkLinkAcl(cSess.Group, payload)
|
||||||
|
if !check {
|
||||||
|
// 校验不通过直接丢弃
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
closed := false
|
||||||
|
select {
|
||||||
|
case cSess.PayloadIn <- payload:
|
||||||
|
case <-cSess.CloseChan:
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
|
||||||
|
payload := &sessdata.Payload{
|
||||||
|
LType: lType,
|
||||||
|
PType: pType,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloadOutData(cSess, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func payloadOutData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
|
||||||
|
closed := false
|
||||||
|
|
||||||
|
select {
|
||||||
|
case cSess.PayloadOut <- payload:
|
||||||
|
case <-cSess.CloseChan:
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acl规则校验
|
||||||
|
func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
|
||||||
|
if payload.LType == sessdata.LTypeIPData && payload.PType == 0x00 && len(group.LinkAcl) > 0 {
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ip_dst := waterutil.IPv4Destination(payload.Data)
|
||||||
|
ip_port := waterutil.IPv4DestinationPort(payload.Data)
|
||||||
|
// fmt.Println("sent:", ip_dst, ip_port)
|
||||||
|
|
||||||
|
// 优先放行dns端口
|
||||||
|
for _, v := range group.ClientDns {
|
||||||
|
if v.Val == ip_dst.String() && ip_port == 53 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range group.LinkAcl {
|
||||||
|
// 循环判断ip和端口
|
||||||
|
if v.IpNet.Contains(ip_dst) {
|
||||||
|
if v.Port == ip_port || v.Port == 0 {
|
||||||
|
if v.Action == dbdata.Allow {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
@@ -6,7 +6,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bjdgyc/anylink/base"
|
"github.com/bjdgyc/anylink/base"
|
||||||
@@ -14,22 +13,28 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey)
|
||||||
|
return &cert, err
|
||||||
|
}
|
||||||
|
|
||||||
func startTls() {
|
func startTls() {
|
||||||
addr := base.Cfg.ServerAddr
|
addr := base.Cfg.ServerAddr
|
||||||
certFile := base.Cfg.CertFile
|
certFile := base.Cfg.CertFile
|
||||||
keyFile := base.Cfg.CertKey
|
keyFile := base.Cfg.CertKey
|
||||||
|
|
||||||
logger := log.New(os.Stdout, "[SERVER]", log.Lshortfile|log.Ldate)
|
|
||||||
// 设置tls信息
|
// 设置tls信息
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: []string{"http/1.1"},
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
GetCertificate: GetCertificate,
|
||||||
}
|
}
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: initRoute(),
|
Handler: initRoute(),
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
ErrorLog: logger,
|
ErrorLog: base.GetBaseLog(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var ln net.Listener
|
var ln net.Listener
|
||||||
@@ -57,9 +62,9 @@ func initRoute() http.Handler {
|
|||||||
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
|
r.HandleFunc("/", LinkAuth).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
|
r.HandleFunc("/CSCOSSLC/tunnel", LinkTunnel).Methods(http.MethodConnect)
|
||||||
r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet)
|
r.HandleFunc("/otp_qr", LinkOtpQr).Methods(http.MethodGet)
|
||||||
r.PathPrefix("/down_files/").Handler(
|
r.PathPrefix("/files/").Handler(
|
||||||
http.StripPrefix("/down_files/",
|
http.StripPrefix("/files/",
|
||||||
http.FileServer(http.Dir(base.Cfg.DownFilesPath)),
|
http.FileServer(http.Dir(base.Cfg.FilesPath)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
r.NotFoundHandler = http.HandlerFunc(notFound)
|
r.NotFoundHandler = http.HandlerFunc(notFound)
|
@@ -21,5 +21,5 @@ func Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Stop() {
|
func Stop() {
|
||||||
dbdata.Stop()
|
_ = dbdata.Stop()
|
||||||
}
|
}
|
@@ -1,4 +1,7 @@
|
|||||||
// AnyLink 是一个企业级远程办公vpn软件,可以支持多人同时在线使用。
|
// AnyLink 是一个企业级远程办公vpn软件,可以支持多人同时在线使用。
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
@@ -48,7 +48,7 @@ func tableLookup(ip net.IP) *Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 判断老化过期时间
|
// 判断老化过期时间
|
||||||
tsub := time.Now().Sub(addr.disTime)
|
tsub := time.Since(addr.disTime)
|
||||||
switch addr.Type {
|
switch addr.Type {
|
||||||
case TypeNormal:
|
case TypeNormal:
|
||||||
if tsub > StaleTimeNormal {
|
if tsub > StaleTimeNormal {
|
@@ -48,7 +48,7 @@ func doPing(ip string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.SetReadDeadline(time.Now().Add(time.Second * 2))
|
_ = conn.SetReadDeadline(time.Now().Add(time.Second * 2))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
buf := make([]byte, 512)
|
buf := make([]byte, 512)
|
@@ -198,8 +198,10 @@ func (p *Conn) checkPrefixOnce() {
|
|||||||
func (p *Conn) checkPrefix() error {
|
func (p *Conn) checkPrefix() error {
|
||||||
if p.proxyHeaderTimeout != 0 {
|
if p.proxyHeaderTimeout != 0 {
|
||||||
readDeadLine := time.Now().Add(p.proxyHeaderTimeout)
|
readDeadLine := time.Now().Add(p.proxyHeaderTimeout)
|
||||||
p.conn.SetReadDeadline(readDeadLine)
|
_ = p.conn.SetReadDeadline(readDeadLine)
|
||||||
defer p.conn.SetReadDeadline(time.Time{})
|
defer func() {
|
||||||
|
_ = p.conn.SetReadDeadline(time.Time{})
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incrementally check each byte of the prefix
|
// Incrementally check each byte of the prefix
|
@@ -45,8 +45,6 @@ func CopyStruct(a interface{}, b interface{}, fields ...string) (err error) {
|
|||||||
// a中有同名的字段并且类型一致才复制
|
// a中有同名的字段并且类型一致才复制
|
||||||
if f.IsValid() && f.Kind() == bValue.Kind() {
|
if f.IsValid() && f.Kind() == bValue.Kind() {
|
||||||
f.Set(bValue)
|
f.Set(bValue)
|
||||||
} else {
|
|
||||||
// fmt.Printf("no such field or different kind, fieldName: %s\n", name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
@@ -19,7 +19,8 @@ type ipPoolConfig struct {
|
|||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
// 计算动态ip
|
// 计算动态ip
|
||||||
Ipv4Gateway net.IP
|
Ipv4Gateway net.IP
|
||||||
Ipv4IPNet net.IPNet
|
Ipv4Mask net.IP
|
||||||
|
Ipv4IPNet *net.IPNet
|
||||||
IpLongMin uint32
|
IpLongMin uint32
|
||||||
IpLongMax uint32
|
IpLongMax uint32
|
||||||
}
|
}
|
||||||
@@ -27,11 +28,12 @@ type ipPoolConfig struct {
|
|||||||
func initIpPool() {
|
func initIpPool() {
|
||||||
|
|
||||||
// 地址处理
|
// 地址处理
|
||||||
// ip地址
|
_, ipNet, err := net.ParseCIDR(base.Cfg.Ipv4CIDR)
|
||||||
ip := net.ParseIP(base.Cfg.Ipv4Network)
|
if err != nil {
|
||||||
// 子网掩码
|
panic(err)
|
||||||
maskIp := net.ParseIP(base.Cfg.Ipv4Netmask).To4()
|
}
|
||||||
IpPool.Ipv4IPNet = net.IPNet{IP: ip, Mask: net.IPMask(maskIp)}
|
IpPool.Ipv4IPNet = ipNet
|
||||||
|
IpPool.Ipv4Mask = net.IP(ipNet.Mask)
|
||||||
IpPool.Ipv4Gateway = net.ParseIP(base.Cfg.Ipv4Gateway)
|
IpPool.Ipv4Gateway = net.ParseIP(base.Cfg.Ipv4Gateway)
|
||||||
|
|
||||||
// 网络地址零值
|
// 网络地址零值
|
||||||
@@ -74,11 +76,11 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
mi.Username = username
|
mi.Username = username
|
||||||
mi.LastLogin = tNow
|
mi.LastLogin = tNow
|
||||||
// 回写db数据
|
// 回写db数据
|
||||||
dbdata.Save(mi)
|
_ = dbdata.Save(mi)
|
||||||
ipActive[ipStr] = true
|
ipActive[ipStr] = true
|
||||||
return ip
|
return ip
|
||||||
} else {
|
} else {
|
||||||
dbdata.Del(mi)
|
_ = dbdata.Del(mi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
if err != nil && dbdata.CheckErrNotFound(err) {
|
if err != nil && dbdata.CheckErrNotFound(err) {
|
||||||
// 该ip没有被使用
|
// 该ip没有被使用
|
||||||
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
||||||
dbdata.Save(mi)
|
_ = dbdata.Save(mi)
|
||||||
ipActive[ipStr] = true
|
ipActive[ipStr] = true
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
@@ -121,10 +123,10 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
|
|
||||||
// 已经超过租期
|
// 已经超过租期
|
||||||
if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second {
|
if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second {
|
||||||
dbdata.Del(v)
|
_ = dbdata.Del(v)
|
||||||
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
mi := &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
||||||
// 重写db数据
|
// 重写db数据
|
||||||
dbdata.Save(mi)
|
_ = dbdata.Save(mi)
|
||||||
ipActive[ipStr] = true
|
ipActive[ipStr] = true
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
@@ -145,7 +147,7 @@ func AcquireIp(username, macAddr string) net.IP {
|
|||||||
ipStr := ip.String()
|
ipStr := ip.String()
|
||||||
mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
|
||||||
// 回写db数据
|
// 回写db数据
|
||||||
dbdata.Save(mi)
|
_ = dbdata.Save(mi)
|
||||||
ipActive[ipStr] = true
|
ipActive[ipStr] = true
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
@@ -160,6 +162,6 @@ func ReleaseIp(ip net.IP, macAddr string) {
|
|||||||
err := dbdata.One("IpAddr", ip, mi)
|
err := dbdata.One("IpAddr", ip, mi)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mi.LastLogin = time.Now()
|
mi.LastLogin = time.Now()
|
||||||
dbdata.Save(mi)
|
_ = dbdata.Save(mi)
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -12,45 +12,53 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func preIpData() {
|
func preData(tmpDir string) {
|
||||||
base.Cfg.Ipv4Network = "192.168.3.0"
|
base.Test()
|
||||||
base.Cfg.Ipv4Netmask = "255.255.255.0"
|
tmpDb := path.Join(tmpDir, "test.db")
|
||||||
base.Cfg.Ipv4Pool = []string{"192.168.3.1", "192.168.3.199"}
|
|
||||||
tmpDb := path.Join(os.TempDir(), "anylink_test.db")
|
|
||||||
base.Cfg.DbFile = tmpDb
|
base.Cfg.DbFile = tmpDb
|
||||||
|
base.Cfg.Ipv4CIDR = "192.168.3.0/24"
|
||||||
|
base.Cfg.Ipv4Pool = []string{"192.168.3.1", "192.168.3.199"}
|
||||||
|
base.Cfg.MaxClient = 100
|
||||||
|
base.Cfg.MaxUserClient = 3
|
||||||
|
|
||||||
dbdata.Start()
|
dbdata.Start()
|
||||||
|
group := dbdata.Group{
|
||||||
|
Name: "group1",
|
||||||
|
Bandwidth: 1000,
|
||||||
|
}
|
||||||
|
_ = dbdata.Save(&group)
|
||||||
|
initIpPool()
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeIpdata() {
|
func cleardata(tmpDir string) {
|
||||||
dbdata.Stop()
|
_ = dbdata.Stop()
|
||||||
tmpDb := path.Join(os.TempDir(), "anylink_test.db")
|
tmpDb := path.Join(tmpDir, "test.db")
|
||||||
os.Remove(tmpDb)
|
os.Remove(tmpDb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIpPool(t *testing.T) {
|
func TestIpPool(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
preIpData()
|
tmp := t.TempDir()
|
||||||
defer closeIpdata()
|
preData(tmp)
|
||||||
|
defer cleardata(tmp)
|
||||||
initIpPool()
|
|
||||||
|
|
||||||
var ip net.IP
|
var ip net.IP
|
||||||
|
|
||||||
for i := 1; i <= 100; i++ {
|
for i := 1; i <= 100; i++ {
|
||||||
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i))
|
_ = AcquireIp("user", fmt.Sprintf("mac-%d", i))
|
||||||
}
|
}
|
||||||
ip = AcquireIp("user", fmt.Sprintf("mac-new"))
|
ip = AcquireIp("user", "mac-new")
|
||||||
assert.True(net.IPv4(192, 168, 3, 101).Equal(ip))
|
assert.True(net.IPv4(192, 168, 3, 101).Equal(ip))
|
||||||
for i := 102; i <= 199; i++ {
|
for i := 102; i <= 199; i++ {
|
||||||
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i))
|
ip = AcquireIp("user", fmt.Sprintf("mac-%d", i))
|
||||||
}
|
}
|
||||||
assert.True(net.IPv4(192, 168, 3, 199).Equal(ip))
|
assert.True(net.IPv4(192, 168, 3, 199).Equal(ip))
|
||||||
ip = AcquireIp("user", fmt.Sprintf("mac-nil"))
|
ip = AcquireIp("user", "mac-nil")
|
||||||
assert.Nil(ip)
|
assert.Nil(ip)
|
||||||
|
|
||||||
ReleaseIp(net.IPv4(192, 168, 3, 88), "mac-88")
|
ReleaseIp(net.IPv4(192, 168, 3, 88), "mac-88")
|
||||||
ReleaseIp(net.IPv4(192, 168, 3, 77), "mac-77")
|
ReleaseIp(net.IPv4(192, 168, 3, 77), "mac-77")
|
||||||
// 最早过期的ip
|
// 从头循环获取可用ip
|
||||||
ip = AcquireIp("user", "mac-release-new")
|
ip = AcquireIp("user", "mac-release-new")
|
||||||
assert.True(net.IPv4(192, 168, 3, 88).Equal(ip))
|
assert.True(net.IPv4(192, 168, 3, 77).Equal(ip))
|
||||||
}
|
}
|
@@ -43,10 +43,11 @@ func TestLimitClient(t *testing.T) {
|
|||||||
func TestLimitWait(t *testing.T) {
|
func TestLimitWait(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
limit := NewLimitRater(1, 2)
|
limit := NewLimitRater(1, 2)
|
||||||
limit.Wait(2)
|
|
||||||
start := time.Now()
|
|
||||||
err := limit.Wait(2)
|
err := limit.Wait(2)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
|
start := time.Now()
|
||||||
|
err = limit.Wait(2)
|
||||||
|
assert.Nil(err)
|
||||||
err = limit.Wait(1)
|
err = limit.Wait(1)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
end := time.Now()
|
end := time.Now()
|
@@ -34,10 +34,7 @@ func (o Onlines) Len() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o Onlines) Less(i, j int) bool {
|
func (o Onlines) Less(i, j int) bool {
|
||||||
if bytes.Compare(o[i].Ip, o[j].Ip) < 0 {
|
return bytes.Compare(o[i].Ip, o[j].Ip) < 0
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Onlines) Swap(i, j int) {
|
func (o Onlines) Swap(i, j int) {
|
@@ -3,7 +3,6 @@ package sessdata
|
|||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -78,13 +77,13 @@ func checkSession() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
timeout := time.Duration(base.Cfg.SessionTimeout) * time.Second
|
timeout := time.Duration(base.Cfg.SessionTimeout) * time.Second
|
||||||
tick := time.Tick(time.Second * 60)
|
tick := time.NewTicker(time.Second * 60)
|
||||||
for range tick {
|
for range tick.C {
|
||||||
sessMux.Lock()
|
sessMux.Lock()
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
for k, v := range sessions {
|
for k, v := range sessions {
|
||||||
v.mux.Lock()
|
v.mux.Lock()
|
||||||
if v.IsActive != true {
|
if !v.IsActive {
|
||||||
if t.Sub(v.LastLogin) > timeout {
|
if t.Sub(v.LastLogin) > timeout {
|
||||||
delete(sessions, k)
|
delete(sessions, k)
|
||||||
}
|
}
|
||||||
@@ -133,12 +132,12 @@ func (s *Session) NewConn() *ConnSession {
|
|||||||
macAddr := s.MacAddr
|
macAddr := s.MacAddr
|
||||||
username := s.Username
|
username := s.Username
|
||||||
s.mux.Unlock()
|
s.mux.Unlock()
|
||||||
if active == true {
|
if active {
|
||||||
s.CSess.Close()
|
s.CSess.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
limit := LimitClient(username, false)
|
limit := LimitClient(username, false)
|
||||||
if limit == false {
|
if !limit {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// 获取客户端mac地址
|
// 获取客户端mac地址
|
||||||
@@ -191,7 +190,7 @@ func (s *Session) NewConn() *ConnSession {
|
|||||||
|
|
||||||
func (cs *ConnSession) Close() {
|
func (cs *ConnSession) Close() {
|
||||||
cs.closeOnce.Do(func() {
|
cs.closeOnce.Do(func() {
|
||||||
log.Println("closeOnce:", cs.IpAddr)
|
base.Info("closeOnce:", cs.IpAddr)
|
||||||
cs.Sess.mux.Lock()
|
cs.Sess.mux.Lock()
|
||||||
defer cs.Sess.mux.Unlock()
|
defer cs.Sess.mux.Unlock()
|
||||||
|
|
||||||
@@ -208,8 +207,10 @@ func (cs *ConnSession) Close() {
|
|||||||
const BandwidthPeriodSec = 2 // 流量速率统计周期(秒)
|
const BandwidthPeriodSec = 2 // 流量速率统计周期(秒)
|
||||||
|
|
||||||
func (cs *ConnSession) ratePeriod() {
|
func (cs *ConnSession) ratePeriod() {
|
||||||
tick := time.Tick(time.Second * BandwidthPeriodSec)
|
tick := time.NewTicker(time.Second * BandwidthPeriodSec)
|
||||||
for range tick {
|
defer tick.Stop()
|
||||||
|
|
||||||
|
for range tick.C {
|
||||||
select {
|
select {
|
||||||
case <-cs.CloseChan:
|
case <-cs.CloseChan:
|
||||||
return
|
return
|
||||||
@@ -296,6 +297,17 @@ func CloseSess(token string) {
|
|||||||
sess.CSess.Close()
|
sess.CSess.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CloseCSess(token string) {
|
||||||
|
sessMux.Lock()
|
||||||
|
defer sessMux.Unlock()
|
||||||
|
sess, ok := sessions[token]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sess.CSess.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func DelSessByStoken(stoken string) {
|
func DelSessByStoken(stoken string) {
|
||||||
stoken = strings.TrimSpace(stoken)
|
stoken = strings.TrimSpace(stoken)
|
||||||
sarr := strings.Split(stoken, "@")
|
sarr := strings.Split(stoken, "@")
|
38
server/sessdata/session_test.go
Normal file
38
server/sessdata/session_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package sessdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewSession(t *testing.T) {
|
||||||
|
ast := assert.New(t)
|
||||||
|
sessions = make(map[string]*Session)
|
||||||
|
sess := NewSession("")
|
||||||
|
token := sess.Token
|
||||||
|
v, ok := sessions[token]
|
||||||
|
ast.True(ok)
|
||||||
|
ast.Equal(sess, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnSession(t *testing.T) {
|
||||||
|
ast := assert.New(t)
|
||||||
|
tmp := t.TempDir()
|
||||||
|
preData(tmp)
|
||||||
|
defer cleardata(tmp)
|
||||||
|
|
||||||
|
sess := NewSession("")
|
||||||
|
sess.Group = "group1"
|
||||||
|
sess.MacAddr = "00:15:5d:50:14:43"
|
||||||
|
|
||||||
|
cSess := sess.NewConn()
|
||||||
|
|
||||||
|
err := cSess.RateLimit(100, true)
|
||||||
|
ast.Nil(err)
|
||||||
|
ast.Equal(cSess.BandwidthUp, uint32(100))
|
||||||
|
err = cSess.RateLimit(200, false)
|
||||||
|
ast.Nil(err)
|
||||||
|
ast.Equal(cSess.BandwidthDown, uint32(200))
|
||||||
|
cSess.Close()
|
||||||
|
}
|
@@ -1,30 +0,0 @@
|
|||||||
package sessdata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewSession(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
sessions = make(map[string]*Session)
|
|
||||||
sess := NewSession("")
|
|
||||||
token := sess.Token
|
|
||||||
v, ok := sessions[token]
|
|
||||||
assert.True(ok)
|
|
||||||
assert.Equal(sess, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConnSession(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
preIpData()
|
|
||||||
defer closeIpdata()
|
|
||||||
sess := NewSession("")
|
|
||||||
cSess := sess.NewConn()
|
|
||||||
cSess.RateLimit(100, true)
|
|
||||||
assert.Equal(cSess.BandwidthUp, uint32(100))
|
|
||||||
cSess.RateLimit(200, false)
|
|
||||||
assert.Equal(cSess.BandwidthDown, uint32(200))
|
|
||||||
cSess.Close()
|
|
||||||
}
|
|
13
systemd/anylink.service
Normal file
13
systemd/anylink.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=VPN Server Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory= /usr/local/anylink-deploy
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/local/anylink-deploy/anylink -conf="conf/server.toml"
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
3
web/.browserslistrc
Normal file
3
web/.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
17
web/.eslintrc.js
Normal file
17
web/.eslintrc.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/essential',
|
||||||
|
'eslint:recommended'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
||||||
|
}
|
||||||
|
}
|
24
web/.gitignore
vendored
Normal file
24
web/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
/ui
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
21
web/LICENSE
Normal file
21
web/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 bjdgyc
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
30
web/README.md
Normal file
30
web/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# anylink-web
|
||||||
|
|
||||||
|
## Repo
|
||||||
|
|
||||||
|
> github: https://github.com/bjdgyc/anylink-web
|
||||||
|
|
||||||
|
> gitee: https://gitee.com/bjdgyc/anylink-web
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
web/babel.config.js
Normal file
5
web/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
26204
web/package-lock.json
generated
Normal file
26204
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
web/package.json
Normal file
29
web/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "anylink-web",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.20.0",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"echarts": "^4.9.0",
|
||||||
|
"element-ui": "^2.4.5",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-count-to": "^1.0.13",
|
||||||
|
"vue-router": "^3.4.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||||
|
"@vue/cli-service": "~4.5.0",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"vue-cli-plugin-element": "~1.0.1",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
}
|
||||||
|
}
|
BIN
web/public/favicon.ico
Normal file
BIN
web/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
17
web/public/index.html
Normal file
17
web/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title>AnyLink</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but AnyLink doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
84
web/src/App.vue
Normal file
84
web/src/App.vue
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'app',
|
||||||
|
components: {},
|
||||||
|
created() {
|
||||||
|
const token = sessionStorage.getItem('jwtToken');
|
||||||
|
console.log("App created", token)
|
||||||
|
if (token) {
|
||||||
|
this.$root.isLogin = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log("App mounted")
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentComponent: function () {
|
||||||
|
var isLogin = this.$root.isLogin
|
||||||
|
console.log("App isLogin", isLogin)
|
||||||
|
if (isLogin) {
|
||||||
|
return "layout";
|
||||||
|
}
|
||||||
|
return "login";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
/*color: #2c3e50;*/
|
||||||
|
/*border: 1px solid red;*/
|
||||||
|
height: 100%;
|
||||||
|
/*width:100%;*/
|
||||||
|
/*box-sizing: border-box;*/
|
||||||
|
/*padding: 4px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*space vertical*/
|
||||||
|
.sh-10 {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-20 {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*space horizontal*/
|
||||||
|
.sw-10 {
|
||||||
|
height: 1px;
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-20 {
|
||||||
|
height: 1px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-left-10 {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
BIN
web/src/assets/logo.png
Normal file
BIN
web/src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
37
web/src/components/Cell.vue
Normal file
37
web/src/components/Cell.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="monitor">
|
||||||
|
<div class="monitor-left">{{ left }}</div>
|
||||||
|
<div class="monitor-right">{{ right }}</div>
|
||||||
|
</div>
|
||||||
|
<el-divider v-if="divider"></el-divider>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Cell",
|
||||||
|
props: {
|
||||||
|
left: {},
|
||||||
|
right: {},
|
||||||
|
divider: {type: Boolean}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.monitor {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-left {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-right {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
82
web/src/components/LineChart.vue
Normal file
82
web/src/components/LineChart.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<div id="line-chart" :style="{height:height,width:width}"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LineChart',
|
||||||
|
props: {
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '350px'
|
||||||
|
},
|
||||||
|
// title,xAxis,series
|
||||||
|
chartData: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initChart()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initChart() {
|
||||||
|
let chart = echarts.init(this.$el)
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: this.chartData.title || '折线图'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis'
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
// toolbox: {
|
||||||
|
// feature: {
|
||||||
|
// saveAsImage: {}
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: this.chartData.xname
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
series: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
let xdata = this.chartData.xdata
|
||||||
|
for (let key in xdata) {
|
||||||
|
// window.console.log(key);
|
||||||
|
let a = {
|
||||||
|
name: key,
|
||||||
|
type: 'line',
|
||||||
|
data: xdata[key]
|
||||||
|
};
|
||||||
|
option.series.push(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.setOption(option)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
63
web/src/layout/Layout.vue
Normal file
63
web/src/layout/Layout.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<el-container style="height: 100%;">
|
||||||
|
<!--侧边栏菜单-->
|
||||||
|
<el-aside :width="is_active?'200':'64'">
|
||||||
|
<LayoutAside :is_active="is_active" :route_path="route_path"/>
|
||||||
|
</el-aside>
|
||||||
|
|
||||||
|
<el-container>
|
||||||
|
<!--正文头部内容-->
|
||||||
|
<el-header>
|
||||||
|
<!--监听子组件的变量事件-->
|
||||||
|
<LayoutHeader :is_active.sync="is_active" :route_name="route_name"/>
|
||||||
|
</el-header>
|
||||||
|
<!--正文内容-->
|
||||||
|
<!--style="background-color: rgb(240, 242, 245);"-->
|
||||||
|
<el-main style="background-color: #fbfbfb">
|
||||||
|
<!-- 对应的组件内容渲染到router-view中 -->
|
||||||
|
<!--子组件上报route信息-->
|
||||||
|
<router-view :route_path.sync="route_path" :route_name.sync="route_name"></router-view>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import LayoutAside from "@/layout/LayoutAside";
|
||||||
|
import LayoutHeader from "@/layout/LayoutHeader";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Layout",
|
||||||
|
components: {LayoutHeader, LayoutAside},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
is_active: true,
|
||||||
|
route_path: '/index',
|
||||||
|
route_name: ['首页'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
route_path: function (val) {
|
||||||
|
// var w = document.getElementById('layout-menu').clientWidth;
|
||||||
|
window.console.log('is_active', val)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
window.console.log('layout-route', this.$route)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.el-header {
|
||||||
|
background-color: #fff;
|
||||||
|
/*box-shadow: 0 1px 4px rgba(0, 21, 41, .08);*/
|
||||||
|
color: #333;
|
||||||
|
line-height: 60px;
|
||||||
|
/*width: 100%;*/
|
||||||
|
|
||||||
|
border-bottom: 1px solid #d8dce5;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
78
web/src/layout/LayoutAside.vue
Normal file
78
web/src/layout/LayoutAside.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<!--background-color="#304156"-->
|
||||||
|
<!--text-color="#bfcbd9"-->
|
||||||
|
<!--active-text-color="#409EFF"-->
|
||||||
|
<!--:unique-opened="false"-->
|
||||||
|
|
||||||
|
<!--<div class="layout-aside" :style="aside_style">-->
|
||||||
|
|
||||||
|
<el-menu :collapse="!is_active"
|
||||||
|
:default-active="route_path"
|
||||||
|
:style="is_active?'width:200px':''"
|
||||||
|
router
|
||||||
|
class="layout-menu"
|
||||||
|
:collapse-transition="false"
|
||||||
|
|
||||||
|
background-color="#545c64"
|
||||||
|
text-color="#fff"
|
||||||
|
active-text-color="#ffd04b"
|
||||||
|
>
|
||||||
|
<el-menu-item index="/admin/home">
|
||||||
|
<i class="el-icon-s-home"></i>
|
||||||
|
<span slot="title">首页</span>
|
||||||
|
</el-menu-item>
|
||||||
|
|
||||||
|
<el-submenu index="1">
|
||||||
|
<template slot="title">
|
||||||
|
<i class="el-icon-menu"></i>
|
||||||
|
<span slot="title">基础信息</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-menu-item index="/admin/set/system">系统信息</el-menu-item>
|
||||||
|
<el-menu-item index="/admin/set/soft">软件配置</el-menu-item>
|
||||||
|
<el-menu-item index="/admin/set/other">其他设置</el-menu-item>
|
||||||
|
</el-submenu>
|
||||||
|
|
||||||
|
<el-submenu index="2">
|
||||||
|
<template slot="title">
|
||||||
|
<i class="el-icon-s-custom"></i>
|
||||||
|
<span slot="title">用户信息</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-menu-item index="/admin/user/list">用户列表</el-menu-item>
|
||||||
|
<el-menu-item index="/admin/user/online">在线用户</el-menu-item>
|
||||||
|
<el-menu-item index="/admin/user/ip_map">IP映射</el-menu-item>
|
||||||
|
</el-submenu>
|
||||||
|
|
||||||
|
<el-submenu index="3">
|
||||||
|
<template slot="title">
|
||||||
|
<i class="el-icon-s-order"></i>
|
||||||
|
<span slot="title">用户组信息</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-menu-item index="/admin/group/list">用户组列表</el-menu-item>
|
||||||
|
</el-submenu>
|
||||||
|
|
||||||
|
|
||||||
|
</el-menu>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "LayoutAside",
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
props: ['is_active', 'route_path'],
|
||||||
|
mounted() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.layout-menu {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
82
web/src/layout/LayoutHeader.vue
Normal file
82
web/src/layout/LayoutHeader.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="layout-header">
|
||||||
|
<div>
|
||||||
|
<i @click="toggleClick" :class="is_active ? 'el-icon-s-fold' : 'el-icon-s-unfold'" class="toggle-icon"
|
||||||
|
style="font-size: 26px;"></i>
|
||||||
|
|
||||||
|
<el-breadcrumb separator="/" class="app-breadcrumb">
|
||||||
|
<el-breadcrumb-item v-for="(item, index) in route_name" :key="index">{{ item }}</el-breadcrumb-item>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dropdown trigger="click" @command="handleCommand">
|
||||||
|
<i class="el-icon-setting" style="margin-right: 15px"></i>
|
||||||
|
<el-dropdown-menu slot="dropdown">
|
||||||
|
<el-dropdown-item command="logout">退出</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
<span style="font-size: 12px;">{{ admin_user }}</span>
|
||||||
|
</el-dropdown>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {getUser, removeToken} from "@/plugins/token";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Layoutheader",
|
||||||
|
props: ['route_name'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
is_active: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
admin_user() {
|
||||||
|
return getUser();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 菜单栏开关按钮
|
||||||
|
toggleClick() {
|
||||||
|
this.is_active = !this.is_active
|
||||||
|
// 触发事件,抛出到上层
|
||||||
|
this.$emit('update:is_active', this.is_active)
|
||||||
|
},
|
||||||
|
handleCommand() {
|
||||||
|
console.log("handleCommand")
|
||||||
|
// 退出 删除登录信息
|
||||||
|
removeToken()
|
||||||
|
this.$router.push("/login");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.layout-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background .3s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-icon:hover {
|
||||||
|
background: rgba(0, 0, 0, .025)
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-breadcrumb {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
/*line-height: 20;*/
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
22
web/src/main.js
Normal file
22
web/src/main.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import './plugins/element'
|
||||||
|
import "./plugins/mixin";
|
||||||
|
import request from './plugins/request'
|
||||||
|
import router from "./plugins/router";
|
||||||
|
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
|
||||||
|
const vm = new Vue({
|
||||||
|
data: {
|
||||||
|
// 判断是否登录
|
||||||
|
isLogin: false,
|
||||||
|
},
|
||||||
|
router,
|
||||||
|
render: h => h(App),
|
||||||
|
}).$mount('#app')
|
||||||
|
|
||||||
|
request(vm)
|
160
web/src/pages/Home.vue
Normal file
160
web/src/pages/Home.vue
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<el-row :gutter="40" class="panel-group">
|
||||||
|
<el-col :span="6" class="card-panel-col">
|
||||||
|
<div class="card-panel">
|
||||||
|
<i class="el-icon-user-solid" style="font-size:50px;color: #f4516c;"></i>
|
||||||
|
<div class="card-panel-description">
|
||||||
|
<div class="card-panel-text">在线数</div>
|
||||||
|
<countTo :startVal='0' :endVal='counts.online' :duration='2000' class="panel-num"></countTo>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="6" class="card-panel-col">
|
||||||
|
<div class="card-panel">
|
||||||
|
<i class="el-icon-user-solid" style="font-size:50px;color: #36a3f7"></i>
|
||||||
|
<div class="card-panel-description">
|
||||||
|
<div class="card-panel-text">用户数</div>
|
||||||
|
<countTo :startVal='0' :endVal='counts.user' :duration='2000' class="panel-num"></countTo>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="6" class="card-panel-col">
|
||||||
|
<div class="card-panel">
|
||||||
|
<i class="el-icon-wallet" style="font-size:50px;color:#34bfa3"></i>
|
||||||
|
<div class="card-panel-description">
|
||||||
|
<div class="card-panel-text">用户组数</div>
|
||||||
|
<countTo :startVal='0' :endVal='counts.group' :duration='2000' class="panel-num"></countTo>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="6" class="card-panel-col">
|
||||||
|
<div class="card-panel">
|
||||||
|
<i class="el-icon-s-order" style="font-size:50px;color:#40c9c6"></i>
|
||||||
|
<div class="card-panel-description">
|
||||||
|
<div class="card-panel-text">IP映射数</div>
|
||||||
|
<countTo :startVal='0' :endVal='counts.ip_map' :duration='2000' class="panel-num"></countTo>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row class="line-chart">
|
||||||
|
<LineChart :chart-data="lineChartUser"/>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row class="line-chart">
|
||||||
|
<LineChart :chart-data="lineChartOrder"/>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import countTo from 'vue-count-to';
|
||||||
|
import LineChart from "@/components/LineChart";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const lineChartUser = {
|
||||||
|
title: '每日在线统计',
|
||||||
|
xname: ['2019-12-13', '2019-12-14', '2019-12-15', '2019-12-16', '2019-12-17', '2019-12-18', '2019-12-19'],
|
||||||
|
xdata: {
|
||||||
|
'test1': [10, 120, 11, 134, 105, 10, 15],
|
||||||
|
'test2': [10, 82, 91, 14, 162, 10, 15]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineChartOrder = {
|
||||||
|
title: '每日流量统计',
|
||||||
|
xname: ['2019-12-13', '2019-12-14', '2019-12-15', '2019-12-16', '2019-12-17', '2019-12-18', '2019-12-19'],
|
||||||
|
xdata: {
|
||||||
|
'test1': [100, 120, 161, 134, 105, 160, 165],
|
||||||
|
'test2': [120, 82, 91, 154, 162, 140, 145]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Home",
|
||||||
|
components: {
|
||||||
|
LineChart,
|
||||||
|
countTo,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
counts: {
|
||||||
|
online: 0,
|
||||||
|
user: 0,
|
||||||
|
group: 0,
|
||||||
|
ip_map: 0,
|
||||||
|
},
|
||||||
|
lineChartUser: lineChartUser,
|
||||||
|
lineChartOrder: lineChartOrder,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$emit('update:route_path', this.$route.path)
|
||||||
|
this.$emit('update:route_name', ['首页'])
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getData() {
|
||||||
|
axios.get('/set/home').then(resp => {
|
||||||
|
var data = resp.data.data
|
||||||
|
console.log(data);
|
||||||
|
this.counts = data.counts
|
||||||
|
}).catch(error => {
|
||||||
|
this.$message.error('哦,请求出错');
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-panel {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
border: 1px solid red;
|
||||||
|
padding: 30px 0;
|
||||||
|
|
||||||
|
color: #666;
|
||||||
|
background: #fff;
|
||||||
|
/*box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);*/
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
||||||
|
border-color: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-panel-description {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-panel-text {
|
||||||
|
line-height: 18px;
|
||||||
|
color: rgba(0, 0, 0, .45);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-num {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-chart {
|
||||||
|
background: #fff;
|
||||||
|
padding: 0 16px;
|
||||||
|
margin-top: 40px;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
||||||
|
border-color: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
</style>
|
118
web/src/pages/Login.vue
Normal file
118
web/src/pages/Login.vue
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="login">
|
||||||
|
<el-card style="width: 550px;">
|
||||||
|
|
||||||
|
<div class="issuer">AnyLink SSL VPN管理后台</div>
|
||||||
|
|
||||||
|
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
|
||||||
|
<el-form-item label="管理用户名" prop="admin_user">
|
||||||
|
<el-input v-model="ruleForm.admin_user"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="管理密码" prop="admin_pass">
|
||||||
|
<el-input type="password" v-model="ruleForm.admin_pass" autocomplete="off"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :loading="isLoading" @click="submitForm('ruleForm')">登录</el-button>
|
||||||
|
<el-button @click="resetForm('ruleForm')">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from "axios";
|
||||||
|
import qs from "qs";
|
||||||
|
import {setToken, setUser} from "@/plugins/token";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Login",
|
||||||
|
mounted() {
|
||||||
|
// 进入login,删除登录信息
|
||||||
|
console.log("login created")
|
||||||
|
//绑定事件
|
||||||
|
window.addEventListener('keydown', this.keyDown);
|
||||||
|
},
|
||||||
|
destroyed(){
|
||||||
|
window.removeEventListener('keydown',this.keyDown,false);
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
ruleForm: {},
|
||||||
|
rules: {
|
||||||
|
admin_user: [
|
||||||
|
{required: true, message: '请输入用户名', trigger: 'blur'},
|
||||||
|
{max: 50, message: '长度小于 50 个字符', trigger: 'blur'}
|
||||||
|
],
|
||||||
|
admin_pass: [
|
||||||
|
{required: true, message: '请输入密码', trigger: 'blur'},
|
||||||
|
{min: 6, message: '长度大于 6 个字符', trigger: 'blur'}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
keyDown(e) {
|
||||||
|
//如果是回车则执行登录方法
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
this.submitForm('ruleForm');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitForm(formName) {
|
||||||
|
this.$refs[formName].validate((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
console.log('error submit!!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.isLoading = true
|
||||||
|
|
||||||
|
// alert('submit!');
|
||||||
|
axios.post('/base/login', qs.stringify(this.ruleForm)).then(resp => {
|
||||||
|
var rdata = resp.data
|
||||||
|
if (rdata.code === 0) {
|
||||||
|
this.$message.success(rdata.msg);
|
||||||
|
setToken(rdata.data.token)
|
||||||
|
setUser(rdata.data.admin_user)
|
||||||
|
this.$router.push("/home");
|
||||||
|
} else {
|
||||||
|
this.$message.error(rdata.msg);
|
||||||
|
}
|
||||||
|
console.log(rdata);
|
||||||
|
}).catch(error => {
|
||||||
|
this.$message.error('哦,请求出错');
|
||||||
|
console.log(error);
|
||||||
|
}).finally(() => {
|
||||||
|
this.isLoading = false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetForm(formName) {
|
||||||
|
this.$refs[formName].resetFields();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login {
|
||||||
|
/*border: 1px solid red;*/
|
||||||
|
height: 100%;
|
||||||
|
/*margin: 0 auto;*/
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issuer {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user