feat(docker/liserAllUsers): 增加docker部署方式,新增显示所有用户列表
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@ statik
|
|||||||
openvpn-manager
|
openvpn-manager
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
|
target
|
||||||
17
README.md
17
README.md
@@ -37,7 +37,19 @@ go get github.com/rakyll/statik
|
|||||||
go install github.com/rakyll/statik
|
go install github.com/rakyll/statik
|
||||||
statik -src=$PWD/public
|
statik -src=$PWD/public
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o openvpn-manager main.go
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o openvpn-manager main.go
|
||||||
nohup ./openvpn-manager -host openvpn服务端主机IP地址 -port openvpn管理端口 -admin-passwd OpenVPN Manager管理员admin的密码 >> /var/log/openvpn-manager.log 2>&1 &
|
nohup ./openvpn-manager -host openvpn服务端主机IP地址 -port openvpn管理端口 -admin-passwd OpenVPN Manager管理员admin的密码 -psw-file /etc/openvpn/server/psw-file >> /var/log/openvpn-manager.log 2>&1 &
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3、docker方式部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull curiouser/openvpn-manager:v1
|
||||||
|
docker run \
|
||||||
|
-v $PWD/psw-file:/etc/openvpn/server/psw-file:ro \
|
||||||
|
-p 30080:9090 \
|
||||||
|
-it \
|
||||||
|
curiouser/openvpn-manager:v1 \
|
||||||
|
-host 172.16.1.2 -port 32099 -admin-passwd 12356789 -psw-file /etc/openvpn/server/psw-file
|
||||||
```
|
```
|
||||||
|
|
||||||
# 四、TODO
|
# 四、TODO
|
||||||
@@ -46,4 +58,5 @@ nohup ./openvpn-manager -host openvpn服务端主机IP地址 -port openvpn管理
|
|||||||
- [ ] 增加显示最近五条登录日志
|
- [ ] 增加显示最近五条登录日志
|
||||||
- [ ] 增加新增用户的功能
|
- [ ] 增加新增用户的功能
|
||||||
- [ ] 增加对低版本OpenVPN的支持
|
- [ ] 增加对低版本OpenVPN的支持
|
||||||
- [ ] 增加Docker部署方式及二进制包下载集成
|
- [x] 增加Docker部署方式
|
||||||
|
- [x] 增加显示所有用户功能
|
||||||
26
docker/Dockerfile
Normal file
26
docker/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
FROM alpine:3.11.5
|
||||||
|
|
||||||
|
ENV LANG=en_US.UTF-8 \
|
||||||
|
LANGUAGE=en_US.UTF-8 \
|
||||||
|
TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
|
||||||
|
RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories \
|
||||||
|
&& apk add --no-cache dumb-init ca-certificates tzdata bash bash-doc bash-completion \
|
||||||
|
&& wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub \
|
||||||
|
&& wget -q https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.29-r0/glibc-2.29-r0.apk \
|
||||||
|
&& wget -q https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.29-r0/glibc-bin-2.29-r0.apk \
|
||||||
|
&& wget -q https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.29-r0/glibc-i18n-2.29-r0.apk \
|
||||||
|
&& apk add glibc-2.29-r0.apk glibc-bin-2.29-r0.apk glibc-i18n-2.29-r0.apk \
|
||||||
|
&& rm -rf /opt/jdk-8u241-linux-x64.tar.gz /usr/lib/jvm glibc-2.29-r0.apk glibc-bin-2.29-r0.apk glibc-i18n-2.29-r0.apk \
|
||||||
|
&& /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true \
|
||||||
|
&& echo "export LANG=$LANG" > /etc/profile.d/locale.sh \
|
||||||
|
&& apk del glibc-i18n \
|
||||||
|
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& echo "Asia/Shanghai" > /etc/timezone
|
||||||
|
|
||||||
|
ENTRYPOINT ["dumb-init", "--","/applications/openvpn-manager"]
|
||||||
|
|
||||||
|
COPY target /applications
|
||||||
|
|
||||||
|
WORKDIR /applications
|
||||||
11
docker/Makefile
Normal file
11
docker/Makefile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
REGISTRY = docker.io
|
||||||
|
IMAGE_REPO = curiouser
|
||||||
|
IMAGE_NAME = openvpn-manager
|
||||||
|
IMAGE_VERSION = v1
|
||||||
|
all: build push
|
||||||
|
build:
|
||||||
|
rm -rf statik target && statik -src=public
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o target/openvpn-manager main.go
|
||||||
|
docker build --rm -f docker/Dockerfile -t ${REGISTRY}/${IMAGE_REPO}/${IMAGE_NAME}:${IMAGE_VERSION} .
|
||||||
|
push:
|
||||||
|
docker push ${REGISTRY}/${IMAGE_REPO}/${IMAGE_NAME}:${IMAGE_VERSION}
|
||||||
55
main.go
55
main.go
@@ -1,13 +1,15 @@
|
|||||||
//go:generate statik -src=./public
|
//go:generate statik -src=$PWD/public
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "curiouser.com/openvpn-manager/statik"
|
_ "curiouser.com/openvpn-manager/statik"
|
||||||
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rakyll/statik/fs"
|
"github.com/rakyll/statik/fs"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -32,13 +34,22 @@ type OnlineClients struct {
|
|||||||
Onlineclients []ClientConInfo `json:"onlineclient"`
|
Onlineclients []ClientConInfo `json:"onlineclient"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Users struct {
|
||||||
|
Users []User `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ip_reg = regexp.MustCompile(`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`)
|
ip_reg = regexp.MustCompile(`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`)
|
||||||
port_reg = regexp.MustCompile(`\((.*?)\)`)
|
port_reg = regexp.MustCompile(`\((.*?)\)`)
|
||||||
omhost string
|
omhost string
|
||||||
omport string
|
omport string
|
||||||
ompasswd string
|
ompasswd string
|
||||||
omadminpassw string
|
omadminpassw string
|
||||||
|
omvpnpswfile string
|
||||||
|
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
authorizedRoute *gin.RouterGroup
|
authorizedRoute *gin.RouterGroup
|
||||||
@@ -49,7 +60,8 @@ func init() {
|
|||||||
flag.StringVar(&omhost, "host", "", "OpenVPN服务端地址")
|
flag.StringVar(&omhost, "host", "", "OpenVPN服务端地址")
|
||||||
flag.StringVar(&omport, "port", "", "OpenVPN服务端管理端口,默认为空")
|
flag.StringVar(&omport, "port", "", "OpenVPN服务端管理端口,默认为空")
|
||||||
flag.StringVar(&ompasswd, "passwd", "", "OpenVPN服务端管理端口密码")
|
flag.StringVar(&ompasswd, "passwd", "", "OpenVPN服务端管理端口密码")
|
||||||
flag.StringVar(&omadminpassw,"admin-passwd","","OpenVPN Manager管理员admin的密码")
|
flag.StringVar(&omadminpassw, "admin-passwd", "", "OpenVPN Manager管理员admin的密码")
|
||||||
|
flag.StringVar(&omvpnpswfile, "psw-file", "", "OpenVPN用于验证用户的密码文件")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
@@ -64,8 +76,8 @@ func init() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
if omhost == "" && omport == "" && omadminpassw == ""{
|
if omhost == "" && omport == "" && omadminpassw == "" && omvpnpswfile == "" {
|
||||||
fmt.Println("没有设置OpenVPN服务端的主机IP地址、管理端口及管理员密码,请在启动命令后添加'-host','-port','-admin-passwd'参数设置")
|
fmt.Println("没有设置OpenVPN服务端的主机IP地址、管理端口及管理员密码,请在启动命令后添加'-host','-port','-admin-passwd','-omvpnpswfile'参数设置")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
} else if omhost == "" {
|
} else if omhost == "" {
|
||||||
fmt.Println("OpenVPN服务端主机IP地址没有设置,无法启动。请在启动命令后添加'-host'参数设置IP地址")
|
fmt.Println("OpenVPN服务端主机IP地址没有设置,无法启动。请在启动命令后添加'-host'参数设置IP地址")
|
||||||
@@ -73,9 +85,12 @@ func main() {
|
|||||||
} else if omport == "" {
|
} else if omport == "" {
|
||||||
fmt.Println("OpenVPN管理端口没有设置,无法启动。请在启动命令后添加'-port'参数设置管理端口号")
|
fmt.Println("OpenVPN管理端口没有设置,无法启动。请在启动命令后添加'-port'参数设置管理端口号")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}else if omadminpassw == "" {
|
} else if omadminpassw == "" {
|
||||||
fmt.Println("OpenVPN Manager管理员admin用户的密码没有设置,无法启动。请在启动命令后添加'-admin-passwd'参数进行设置")
|
fmt.Println("OpenVPN Manager管理员admin用户的密码没有设置,无法启动。请在启动命令后添加'-admin-passwd'参数进行设置")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
} else if omvpnpswfile == "" {
|
||||||
|
fmt.Println("OpenVPN用于验证用户的密码文件路径没有设置,无法启动。请在启动命令后添加'-omvpnpswfile'参数进行设置")
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
statikFS, err := fs.New()
|
statikFS, err := fs.New()
|
||||||
@@ -90,6 +105,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
authorizedRoute.StaticFS("/public", statikFS)
|
authorizedRoute.StaticFS("/public", statikFS)
|
||||||
|
//authorizedRoute.StaticFS("/public",http.Dir("./public"))
|
||||||
authorizedRoute.StaticFile("/favicon.ico", "./public/favicon.ico")
|
authorizedRoute.StaticFile("/favicon.ico", "./public/favicon.ico")
|
||||||
|
|
||||||
authorizedRoute.GET("/getOnlineClients", func(context *gin.Context) {
|
authorizedRoute.GET("/getOnlineClients", func(context *gin.Context) {
|
||||||
@@ -125,6 +141,24 @@ func main() {
|
|||||||
username := context.PostForm("username")
|
username := context.PostForm("username")
|
||||||
_ = sendDataToSocket(omhost+":"+omport, "kill "+username)
|
_ = sendDataToSocket(omhost+":"+omport, "kill "+username)
|
||||||
})
|
})
|
||||||
|
authorizedRoute.GET("/getAllUsers", func(context *gin.Context) {
|
||||||
|
filedata, err := ioutil.ReadFile(omvpnpswfile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("读取文件失败!")
|
||||||
|
}
|
||||||
|
r2 := csv.NewReader(strings.NewReader(string(filedata)))
|
||||||
|
ss, _ := r2.ReadAll()
|
||||||
|
sz := len(ss)
|
||||||
|
var euser *User
|
||||||
|
var eusers Users
|
||||||
|
// 循环取数据
|
||||||
|
for i := 0; i < sz; i++ {
|
||||||
|
euser = &User{Username: strings.Split(ss[i][0], " ")[0]}
|
||||||
|
eusers.Users = append(eusers.Users, *euser)
|
||||||
|
}
|
||||||
|
json, _ := json.Marshal(eusers)
|
||||||
|
context.JSON(http.StatusOK, string(json))
|
||||||
|
})
|
||||||
fmt.Println("OpenVPN Manager监听端口9090,访问地址:http://127.0.0.1:9090")
|
fmt.Println("OpenVPN Manager监听端口9090,访问地址:http://127.0.0.1:9090")
|
||||||
router.Run(":9090")
|
router.Run(":9090")
|
||||||
|
|
||||||
@@ -153,4 +187,3 @@ func sendDataToSocket(conf string, msg string) (resData []byte) {
|
|||||||
return buf1
|
return buf1
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let reg = /\\/g;
|
|
||||||
function getBytesSize(size) {//把字节转换成正常文件大小
|
function getBytesSize(size) {//把字节转换成正常文件大小
|
||||||
if (!size) return "";
|
if (!size) return "";
|
||||||
var num = 1024.00; //byte
|
var num = 1024.00; //byte
|
||||||
@@ -22,7 +21,6 @@
|
|||||||
return (size / Math.pow(num, 3)).toFixed(2) + "G"; //G
|
return (size / Math.pow(num, 3)).toFixed(2) + "G"; //G
|
||||||
return (size / Math.pow(num, 4)).toFixed(2) + "T"; //T
|
return (size / Math.pow(num, 4)).toFixed(2) + "T"; //T
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDuring(mss){
|
function formatDuring(mss){
|
||||||
var days = parseInt(mss / ( 60 * 60 * 24));
|
var days = parseInt(mss / ( 60 * 60 * 24));
|
||||||
var hours = parseInt((mss % ( 60 * 60 * 24)) / ( 60 * 60));
|
var hours = parseInt((mss % ( 60 * 60 * 24)) / ( 60 * 60));
|
||||||
@@ -34,11 +32,11 @@
|
|||||||
var ajaxObj = new XMLHttpRequest();
|
var ajaxObj = new XMLHttpRequest();
|
||||||
ajaxObj.open('get', '/getOnlineClients');
|
ajaxObj.open('get', '/getOnlineClients');
|
||||||
ajaxObj.send();
|
ajaxObj.send();
|
||||||
|
ajaxObj.responseType = 'json';
|
||||||
ajaxObj.onreadystatechange = function () {
|
ajaxObj.onreadystatechange = function () {
|
||||||
if (ajaxObj.readyState == 4 && ajaxObj.status == 200) {
|
if (ajaxObj.readyState == 4 && ajaxObj.status == 200) {
|
||||||
var originData = ajaxObj.responseText.replace(reg,'').substr(1)
|
var res = this.response;
|
||||||
var originJsonData = originData.substring(0,originData.length - 1)
|
var myObj = JSON.parse(res);
|
||||||
var myObj = JSON.parse(originJsonData);
|
|
||||||
var txt = "";
|
var txt = "";
|
||||||
for (let x=0;x < myObj.onlineclient.length;x++){
|
for (let x=0;x < myObj.onlineclient.length;x++){
|
||||||
var nowTs=parseInt(new Date().getTime()/1000)
|
var nowTs=parseInt(new Date().getTime()/1000)
|
||||||
@@ -67,8 +65,30 @@
|
|||||||
ajaxObj.open('post', "/kickOutClientByCN",false,);
|
ajaxObj.open('post', "/kickOutClientByCN",false,);
|
||||||
ajaxObj.send(formData);
|
ajaxObj.send(formData);
|
||||||
};
|
};
|
||||||
|
function getAllUsers(){
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('get', "/getAllUsers");
|
||||||
|
xhr.send();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4 && xhr.status == 200) {
|
||||||
|
var res = this.response;
|
||||||
|
var myObj = JSON.parse(res);
|
||||||
|
var usertxt = "";
|
||||||
|
for (let x=0;x < myObj.users.length;x++){
|
||||||
|
usertxt += "<tr>"
|
||||||
|
+ "<td>" + (x+1) + "</td>"
|
||||||
|
+ "<td>" + myObj.users[x].username + "</td>"
|
||||||
|
+ "</tr>";
|
||||||
|
console.log(myObj.users[x].username)
|
||||||
|
}
|
||||||
|
document.querySelector("#allusers").insertAdjacentHTML('afterBegin',usertxt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
window.onload=function() {
|
window.onload=function() {
|
||||||
getOnlineClients();
|
getOnlineClients();
|
||||||
|
getAllUsers();
|
||||||
document.getElementById("onlineClientsTable").addEventListener('click', function (e) {
|
document.getElementById("onlineClientsTable").addEventListener('click', function (e) {
|
||||||
if(e.target.className === 'kickOutBtn') {
|
if(e.target.className === 'kickOutBtn') {
|
||||||
kickOutClientByCN(e.target.getAttribute('client_username'));
|
kickOutClientByCN(e.target.getAttribute('client_username'));
|
||||||
@@ -76,11 +96,18 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 align="center">OpenVPN在线用户列表</h1>
|
<h1 align="center">OpenVPN在线用户列表</h1>
|
||||||
<table width="1300" height="100" border="2" cellpadding="15" cellspacing="0" align="center" >
|
<table width="1300" height="100" border="2" cellpadding="15" cellspacing="0" align="center" >
|
||||||
<tr> <th>ID</th> <th>用户名</th> <th>客户端路由公网IP地址</th> <th>端口</th> <th>虚拟IP地址</th> <th>接收数据大小</th> <th>发送数据大小</th> <th>连接时间</th> <th>是否下线用户</th> </tr>
|
<tr> <th>ID</th> <th>用户名</th> <th>客户端路由公网IP地址</th> <th>端口</th> <th>虚拟IP地址</th> <th>接收数据大小</th> <th>发送数据大小</th> <th>连接时间</th> <th>是否下线用户</th> </tr>
|
||||||
<tbody id="onlineClientsTable"> </tbody>
|
<tbody id="onlineClientsTable"> </tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<h1 align="center">所有用户列表</h1>
|
||||||
|
<table width="100" height="100" border="2" cellpadding="15" cellspacing="0" align="center" >
|
||||||
|
<tr> <th></th> <th>用户名</th> </tr>
|
||||||
|
<tbody id="allusers"> </tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user