This commit is contained in:
Jacky 2021-08-31 12:13:12 +08:00
parent ad421905a8
commit dd6e19657a
162 changed files with 15071 additions and 932 deletions

View file

@ -1,104 +1,96 @@
package api
import (
"encoding/json"
"fmt"
"github.com/0xJacky/Nginx-UI/server/tool"
"github.com/dustin/go-humanize"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/mackerelio/go-osstat/cpu"
"github.com/mackerelio/go-osstat/loadavg"
"github.com/mackerelio/go-osstat/memory"
"github.com/mackerelio/go-osstat/uptime"
"net/http"
"strconv"
"time"
"encoding/json"
"fmt"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"net/http"
"runtime"
"strconv"
"time"
"github.com/dustin/go-humanize"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func Analytic(c *gin.Context) {
// upgrade http to websocket
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// upgrade http to websocket
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer ws.Close()
defer ws.Close()
for {
// read
mt, message, err := ws.ReadMessage()
if err != nil {
break
}
if string(message) == "ping" {
response := make(gin.H)
response := make(gin.H)
for {
// read
mt, message, err := ws.ReadMessage()
if err != nil {
break
}
for {
memoryStat, err := memory.Get()
if err != nil {
fmt.Println(err)
return
}
response["memory_total"] = humanize.Bytes(memoryStat.Total)
response["memory_used"] = humanize.Bytes(memoryStat.Used)
response["memory_cached"] = humanize.Bytes(memoryStat.Cached)
response["memory_free"] = humanize.Bytes(memoryStat.Free)
memoryStat, err := mem.VirtualMemory()
if err != nil {
fmt.Println(err)
return
}
response["memory_pressure"] = memoryStat.Used * 100 / memoryStat.Total
response["memory_total"] = humanize.Bytes(memoryStat.Total)
response["memory_used"] = humanize.Bytes(memoryStat.Used)
response["memory_cached"] = humanize.Bytes(memoryStat.Cached)
response["memory_free"] = humanize.Bytes(memoryStat.Free)
before, err := cpu.Get()
if err != nil {
fmt.Println(err)
}
time.Sleep(time.Duration(1) * time.Second)
after, err := cpu.Get()
if err != nil {
fmt.Println(err)
}
response["memory_pressure"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f", memoryStat.UsedPercent), 64)
total := float64(after.Total - before.Total)
cpuTimesBefore, _ := cpu.Times(false)
time.Sleep(1000 * time.Millisecond)
cpuTimesAfter, _ := cpu.Times(false)
threadNum := runtime.GOMAXPROCS(0)
response["cpu_user"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f",
float64(after.User-before.User)/total*100), 64)
cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
response["cpu_system"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f",
float64(after.System-before.System)/total*100), 64)
response["cpu_idle"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f",
float64(after.Idle-before.Idle)/total*100), 64)
response["cpu_user"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f",
cpuUserUsage*100), 64)
response["uptime"], _ = uptime.Get()
response["uptime"] = response["uptime"].(time.Duration) / time.Second
response["loadavg"], _ = loadavg.Get()
response["cpu_system"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f",
cpuSystemUsage*100), 64)
used, _total, percentage, err := tool.DiskUsage(".")
response["cpu_idle"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f",
(1-cpuUserUsage+cpuSystemUsage)*100), 64)
response["disk_used"] = used
response["disk_total"] = _total
response["disk_percentage"] = percentage
response["uptime"], _ = host.Uptime()
response["loadavg"], _ = load.Avg()
if err != nil {
fmt.Println(err)
return
}
m, err := json.Marshal(response)
if err != nil {
fmt.Println(err)
return
}
message = m
}
// write
err = ws.WriteMessage(mt, message)
if err != nil {
break
}
}
diskUsage, _ := disk.Usage(".")
response["disk_used"] = humanize.Bytes(diskUsage.Used)
response["disk_total"] = humanize.Bytes(diskUsage.Total)
response["disk_percentage"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f", diskUsage.UsedPercent), 64)
m, _ := json.Marshal(response)
message = m
// write
err = ws.WriteMessage(mt, message)
if err != nil {
break
}
time.Sleep(800 * time.Microsecond)
}
}
}

View file

@ -1,14 +1,46 @@
package api
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
"github.com/gin-gonic/gin"
"log"
"net/http"
ut "github.com/go-playground/universal-translator"
val "github.com/go-playground/validator/v10"
)
func ErrorHandler(c *gin.Context, err error) {
log.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
log.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
}
type ValidError struct {
Key string
Message string
}
type ValidErrors gin.H
func BindAndValid(c *gin.Context, v interface{}) (bool, ValidErrors) {
errs := make(ValidErrors)
err := c.ShouldBind(v)
if err != nil {
v := c.Value("trans")
trans, _ := v.(ut.Translator)
verrs, ok := err.(val.ValidationErrors)
if !ok {
return false, errs
}
for key, value := range verrs.Translate(trans) {
errs[key] = value
}
return false, errs
}
return true, nil
}

View file

@ -1,34 +1,38 @@
package api
import (
"crypto/md5"
"fmt"
"github.com/0xJacky/Nginx-UI/server/model"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"log"
"net/http"
)
type LoginUser struct {
Name string `json:"name"`
Password string `json:"password"`
Name string `json:"name" binding:"required,max=255"`
Password string `json:"password" binding:"required,max=255"`
}
func Login(c *gin.Context) {
var user LoginUser
err := c.BindJSON(&user)
ok, verrs := BindAndValid(c, &user)
if !ok {
c.JSON(http.StatusNotAcceptable, gin.H{
"errors": verrs,
})
return
}
u, err := model.GetUser(user.Name)
if err != nil {
log.Println(err)
c.JSON(http.StatusForbidden, gin.H{
"message": "Incorrect name or password",
})
return
}
var u model.Auth
u, err = model.GetUser(user.Name)
if err != nil {
log.Println(err)
}
data := []byte(user.Password)
has := md5.Sum(data)
md5str := fmt.Sprintf("%x", has) // 将[]byte转成16进制
if u.Password != md5str {
if err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(user.Password)); err != nil {
c.JSON(http.StatusForbidden, gin.H{
"message": "Incorrect name or password",
})

View file

@ -5,7 +5,8 @@ import (
"encoding/json"
"github.com/0xJacky/Nginx-UI/server/tool"
"github.com/gin-gonic/gin"
"io"
"github.com/gorilla/websocket"
"io"
"log"
"net/http"
"os"
@ -69,6 +70,11 @@ func CertInfo(c *gin.Context) {
func IssueCert(c *gin.Context) {
domain := c.Param("domain")
var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// upgrade http to websocket
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)

View file

@ -1,23 +1,23 @@
package api
import (
"github.com/0xJacky/Nginx-UI/server/tool"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"github.com/0xJacky/Nginx-UI/server/tool"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"os"
"path/filepath"
)
func GetDomains(c *gin.Context) {
orderBy := c.Query("order_by")
sort := c.DefaultQuery("sort", "desc")
orderBy := c.Query("order_by")
sort := c.DefaultQuery("sort", "desc")
mySort := map[string]string{
"enabled": "bool",
"name": "string",
"modify": "time",
}
mySort := map[string]string{
"enabled": "bool",
"name": "string",
"modify": "time",
}
configFiles, err := ioutil.ReadDir(tool.GetNginxConfPath("sites-available"))
@ -75,7 +75,7 @@ func GetDomain(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"message": err.Error(),
})
return
return
}
ErrorHandler(c, err)
return
@ -83,8 +83,8 @@ func GetDomain(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"enabled": enabled,
"name": name,
"config": string(content),
"name": name,
"config": string(content),
})
}
@ -98,34 +98,43 @@ func EditDomain(c *gin.Context) {
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
if _, err = os.Stat(path); err == nil {
origContent, err = ioutil.ReadFile(path)
if err != nil {
ErrorHandler(c, err)
return
}
}
if request["content"] != "" && request["content"] != string(origContent) {
// model.CreateBackup(path)
err := ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
origContent, err = ioutil.ReadFile(path)
if err != nil {
ErrorHandler(c, err)
return
}
}
if _, err := os.Stat(filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)); err == nil {
output := tool.ReloadNginx()
if output != "" {
if request["content"] != "" && request["content"] != string(origContent) {
// model.CreateBackup(path)
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
if err != nil {
ErrorHandler(c, err)
return
}
}
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
if _, err = os.Stat(enabledConfigFilePath); err == nil {
// 测试配置文件,不通过则撤回修改
err = tool.TestNginxConf(enabledConfigFilePath)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
"message": err.Error(),
})
return
}
output := tool.ReloadNginx()
if output != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
}
GetDomain(c)
GetDomain(c)
}
func EnableDomain(c *gin.Context) {
@ -146,14 +155,24 @@ func EnableDomain(c *gin.Context) {
return
}
output := tool.ReloadNginx()
if output != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
// 测试配置文件,不通过则撤回修改
err = tool.TestNginxConf(enabledConfigFilePath)
if err != nil {
_ = os.Remove(enabledConfigFilePath)
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
}
output := tool.ReloadNginx()
if output != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
@ -180,19 +199,18 @@ func DisableDomain(c *gin.Context) {
output := tool.ReloadNginx()
if output != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
func DeleteDomain(c *gin.Context) {
func DeleteDomain(c *gin.Context) {
var err error
name := c.Param("name")
availablePath := filepath.Join(tool.GetNginxConfPath("sites-available"), name)

135
server/api/user.go Normal file
View file

@ -0,0 +1,135 @@
package api
import (
"errors"
"github.com/0xJacky/Nginx-UI/server/model"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"golang.org/x/crypto/bcrypt"
"net/http"
)
func GetUsers(c *gin.Context) {
curd := model.NewCurd(&model.Auth{})
var list []model.Auth
err := curd.GetList(&list)
if err != nil {
ErrorHandler(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"data": list,
})
}
func GetUser(c *gin.Context) {
curd := model.NewCurd(&model.Auth{})
id := c.Param("id")
var user model.Auth
err := curd.First(&user, id)
if err != nil {
ErrorHandler(c, err)
return
}
c.JSON(http.StatusOK, user)
}
type UserJson struct {
Name string `json:"name" binding:"required,max=255"`
Password string `json:"password" binding:"max=255"`
}
func AddUser(c *gin.Context) {
var json UserJson
ok, verrs := BindAndValid(c, &json)
if !ok {
c.JSON(http.StatusNotAcceptable, gin.H{
"errors": verrs,
})
return
}
curd := model.NewCurd(&model.Auth{})
pwd, err := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost)
if err != nil {
ErrorHandler(c, err)
return
}
json.Password = string(pwd)
user := model.Auth{
Name: json.Name,
Password: json.Password,
}
err = curd.Add(&user)
if err != nil {
ErrorHandler(c, err)
return
}
c.JSON(http.StatusOK, user)
}
func EditUser(c *gin.Context) {
var json UserJson
ok, verrs := BindAndValid(c, &json)
if !ok {
c.JSON(http.StatusNotAcceptable, gin.H{
"errors": verrs,
})
}
curd := model.NewCurd(&model.Auth{})
var user, edit model.Auth
err := curd.First(&user, c.Param("id"))
if err != nil {
ErrorHandler(c, err)
return
}
edit.Name = json.Name
// 改密码加密
if json.Password != "" {
var pwd []byte
pwd, err = bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost)
if err != nil {
ErrorHandler(c, err)
return
}
edit.Password = string(pwd)
}
err = curd.Edit(&user, &edit)
if err != nil {
ErrorHandler(c, err)
return
}
c.JSON(http.StatusOK, user)
}
func DeleteUser(c *gin.Context) {
id := c.Param("id")
if cast.ToInt(id) == 1 {
ErrorHandler(c, errors.New("不允许删除默认账户"))
return
}
curd := model.NewCurd(&model.Auth{})
err := curd.Delete(&model.Auth{}, "id", id)
if err != nil {
ErrorHandler(c, err)
return
}
c.JSON(http.StatusNoContent, gin.H{})
}

View file

@ -7,9 +7,13 @@ require (
github.com/dustin/go-humanize v1.0.0
github.com/gin-gonic/gin v1.7.4
github.com/go-acme/lego/v4 v4.4.0
github.com/go-playground/universal-translator v0.17.0
github.com/go-playground/validator/v10 v10.4.1
github.com/gorilla/websocket v1.4.2
github.com/mackerelio/go-osstat v0.2.0
github.com/shirou/gopsutil/v3 v3.21.7
github.com/spf13/cast v1.3.1
github.com/unknwon/com v1.0.1
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
gopkg.in/ini.v1 v1.62.0
gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.21.14

View file

@ -44,6 +44,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.0/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -118,6 +120,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
@ -261,8 +265,6 @@ github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9
github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc=
github.com/mackerelio/go-osstat v0.2.0 h1:UVn9Am/OOj2Ig0LNNHLqiHeXsZWmMNcMPZ3h+z/8+h8=
github.com/mackerelio/go-osstat v0.2.0/go.mod h1:UzRL8dMCCTqG5WdRtsxbuljMpZt9PCAGXqxPst5QtaY=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
@ -364,6 +366,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil/v3 v3.21.7 h1:PnTqQamUjwEDSgn+nBGu0qSDV/CfvyiR/gwTH3i7HTU=
github.com/shirou/gopsutil/v3 v3.21.7/go.mod h1:RGl11Y7XMTQPmHh8F0ayC6haKNBgH4PXMJuTAcMOlz4=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -383,6 +387,7 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@ -402,6 +407,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tklauser/go-sysconf v0.3.7 h1:HT7h4+536gjqeq1ZIJPgOl1rg1XFatQGVZWp7Py53eg=
github.com/tklauser/go-sysconf v0.3.7/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4=
github.com/tklauser/numcpus v0.2.3 h1:nQ0QYpiritP6ViFhrKYsiv6VVxOpum2Gks5GhnJbS/8=
github.com/tklauser/numcpus v0.2.3/go.mod h1:vpEPS/JC+oZGGQ/My/vJnNsvMDQL6PwOqt8dsCw5j+E=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
@ -542,6 +551,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -563,8 +573,8 @@ golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

BIN
server/main Executable file

Binary file not shown.

View file

@ -1,7 +1,8 @@
package main
import (
"github.com/0xJacky/Nginx-UI/server/model"
"flag"
"github.com/0xJacky/Nginx-UI/server/model"
"github.com/0xJacky/Nginx-UI/server/router"
"github.com/0xJacky/Nginx-UI/server/settings"
"github.com/0xJacky/Nginx-UI/server/tool"
@ -9,11 +10,17 @@ import (
)
func main() {
settings.Init()
var dbPath string
var confPath string
flag.StringVar(&confPath, "c", "app.ini", "Specify the conf path to load")
flag.StringVar(&dbPath, "d", "database.db", "Specify the database path to load")
flag.Parse()
settings.Init(confPath)
r := router.InitRouter()
model.Init()
model.Init(dbPath)
log.Printf("nginx config dir path: %s", tool.GetNginxConfPath(""))

View file

@ -10,7 +10,7 @@ type Auth struct {
Model
Name string `json:"name"`
Password string `json:"password"`
Password string `json:"-"`
}
type AuthToken struct {

38
server/model/curd.go Normal file
View file

@ -0,0 +1,38 @@
package model
type Curd struct {
Model interface{}
}
func NewCurd(Model interface{}) *Curd {
return &Curd{Model: Model}
}
func (c *Curd) GetList(dest interface{}) (err error) {
err = db.Model(c.Model).Scan(dest).Error
return
}
func (c *Curd) First(dest interface{}, conds ...interface{}) (err error) {
err = db.Model(c.Model).First(dest, conds).Error
return
}
func (c *Curd) Add(value interface{}) (err error) {
err = db.Model(c.Model).Create(value).Error
if err != nil {
return err
}
err = db.Find(value).Error
return
}
func (c *Curd) Edit(orig interface{}, new interface{}) (err error) {
err = db.Model(orig).Updates(new).Error
return
}
func (c *Curd) Delete(value interface{}, conds ...interface{}) (err error) {
err = db.Model(c.Model).Delete(value, conds).Error
return
}

View file

@ -1,20 +1,29 @@
package model
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"time"
)
var db *gorm.DB
type Model struct {
gorm.Model
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `gorm:"index" json:"deleted_at"`
}
func Init() {
func Init(dbPath string) {
var err error
db, err = gorm.Open(sqlite.Open("database.db"), &gorm.Config{})
db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
PrepareStmt: true,
})
log.Println("database.db")
if err != nil {
@ -23,11 +32,11 @@ func Init() {
// Migrate the schema
AutoMigrate(&ConfigBackup{})
AutoMigrate(&Auth{})
AutoMigrate(&AuthToken{})
AutoMigrate(&Auth{})
AutoMigrate(&AuthToken{})
}
func AutoMigrate(model interface{}) {
func AutoMigrate(model interface{}) {
err := db.AutoMigrate(model)
if err != nil {
log.Fatal(err)

BIN
server/nginx-ui@linux-amd64 Executable file

Binary file not shown.

View file

@ -47,33 +47,37 @@ func InitRouter() *gin.Engine {
"message": "Hello World",
})
})
r.GET("/analytic", api.Analytic)
r.POST("/login", api.Login)
r.DELETE("/logout", api.Logout)
endpoint := r.Group("/", authRequired())
g := r.Group("/", authRequired())
{
endpoint.GET("domains", api.GetDomains)
endpoint.GET("domain/:name", api.GetDomain)
endpoint.POST("domain/:name", api.EditDomain)
endpoint.POST("domain/:name/enable", api.EnableDomain)
endpoint.POST("domain/:name/disable", api.DisableDomain)
endpoint.DELETE("domain/:name", api.DeleteDomain)
g.GET("/users", api.GetUsers)
g.GET("/user/:id", api.GetUser)
g.POST("/user", api.AddUser)
g.POST("/user/:id", api.EditUser)
g.DELETE("/user/:id", api.DeleteUser)
endpoint.GET("configs", api.GetConfigs)
endpoint.GET("config/:name", api.GetConfig)
endpoint.POST("config", api.AddConfig)
endpoint.POST("config/:name", api.EditConfig)
g.GET("domains", api.GetDomains)
g.GET("domain/:name", api.GetDomain)
g.POST("domain/:name", api.EditDomain)
g.POST("domain/:name/enable", api.EnableDomain)
g.POST("domain/:name/disable", api.DisableDomain)
g.DELETE("domain/:name", api.DeleteDomain)
endpoint.GET("backups", api.GetFileBackupList)
endpoint.GET("backup/:id", api.GetFileBackup)
g.GET("configs", api.GetConfigs)
g.GET("config/:name", api.GetConfig)
g.POST("config", api.AddConfig)
g.POST("config/:name", api.EditConfig)
endpoint.GET("template/:name", api.GetTemplate)
g.GET("backups", api.GetFileBackupList)
g.GET("backup/:id", api.GetFileBackup)
endpoint.GET("analytic", api.Analytic)
g.GET("template/:name", api.GetTemplate)
endpoint.GET("cert/issue/:domain", api.IssueCert)
endpoint.GET("cert/:domain/info", api.CertInfo)
g.GET("cert/issue/:domain", api.IssueCert)
g.GET("cert/:domain/info", api.CertInfo)
}
return r

View file

@ -8,30 +8,31 @@ import (
var Conf *ini.File
type Server struct {
HttpPort string
RunMode string
WebSocketToken string
JwtSecret string
HTTPChallengePort string
Email string
HttpPort string
RunMode string
WebSocketToken string
JwtSecret string
HTTPChallengePort string
Email string
}
var ServerSettings = &Server{}
func Init() {
var err error
Conf, err = ini.Load("app.ini")
if err != nil {
log.Fatalf("setting.Setup, fail to parse 'app.ini': %v", err)
}
func Init(confPath string) {
var err error
mapTo("server", ServerSettings)
Conf, err = ini.Load(confPath)
if err != nil {
log.Fatalf("setting.Setup, fail to parse '%s': %v", confPath, err)
}
mapTo("server", ServerSettings)
}
func mapTo(section string, v interface{}) {
err := Conf.Section(section).MapTo(v)
if err != nil {
log.Fatalf("Cfg.MapTo %s err: %v", section, err)
}
err := Conf.Section(section).MapTo(v)
if err != nil {
log.Fatalf("Cfg.MapTo %s err: %v", section, err)
}
}

View file

@ -1,62 +1,36 @@
package test
import (
"fmt"
humanize "github.com/dustin/go-humanize"
"github.com/mackerelio/go-osstat/cpu"
"github.com/mackerelio/go-osstat/disk"
"github.com/mackerelio/go-osstat/memory"
"os"
"runtime"
"testing"
"time"
"fmt"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"runtime"
"testing"
"time"
)
func TestGetArch(t *testing.T) {
fmt.Println("os:", runtime.GOOS)
fmt.Println("threads:", runtime.GOMAXPROCS(0))
func TestGoPsutil(t *testing.T) {
fmt.Println("os:", runtime.GOOS)
fmt.Println("threads:", runtime.GOMAXPROCS(0))
memoryStat, err := memory.Get()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return
}
fmt.Println("memory total:", humanize.Bytes(memoryStat.Total))
fmt.Println("memory used:", humanize.Bytes(memoryStat.Used))
fmt.Println("memory cached:", humanize.Bytes(memoryStat.Cached))
fmt.Println("memory free:", humanize.Bytes(memoryStat.Free))
v, _ := mem.VirtualMemory()
before, err := cpu.Get()
if err != nil {
fmt.Println(err)
}
time.Sleep(time.Duration(1) * time.Second)
after, err := cpu.Get()
if err != nil {
fmt.Println(err)
}
total := float64(after.Total - before.Total)
fmt.Printf("cpu user: %f %%\n", float64(after.User-before.User)/total*100)
fmt.Printf("cpu system: %f %%\n", float64(after.System-before.System)/total*100)
fmt.Printf("cpu idle: %f %%\n", float64(after.Idle-before.Idle)/total*100)
loadAvg, _ := load.Avg()
err = diskUsage(".")
fmt.Println("loadavg", loadAvg.String())
if err != nil {
fmt.Println(err)
}
}
func diskUsage(path string) error {
di, err := disk.GetInfo(path)
if err != nil {
return err
}
percentage := (float64(di.Total-di.Free) / float64(di.Total)) * 100
fmt.Printf("%s of %s disk space used (%0.2f%%)\n",
humanize.Bytes(di.Total-di.Free),
humanize.Bytes(di.Total),
percentage,
)
return nil
fmt.Printf("Total: %v, Free:%v, UsedPercent:%f%%\n", v.Total, v.Free, v.UsedPercent)
cpuTimesBefore, _ := cpu.Times(false)
time.Sleep(1000*time.Millisecond)
cpuTimesAfter, _ := cpu.Times(false)
threadNum := runtime.GOMAXPROCS(0)
fmt.Println(cpuTimesBefore[0].String(), "\n", cpuTimesAfter[0].String())
cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
fmt.Printf("%.2f, %.2f\n", cpuUserUsage*100, cpuSystemUsage*100)
diskUsage, _ := disk.Usage(".")
fmt.Println(diskUsage.String())
}

View file

@ -1,19 +0,0 @@
package tool
import (
"fmt"
"github.com/dustin/go-humanize"
"github.com/mackerelio/go-osstat/disk"
"strconv"
)
func DiskUsage(path string) (string, string, float64, error) {
di, err := disk.GetInfo(path)
if err != nil {
return "", "", 0, err
}
percentage := (float64(di.Total-di.Free) / float64(di.Total)) * 100
percentage, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", percentage), 64)
return humanize.Bytes(di.Total-di.Free), humanize.Bytes(di.Total),
percentage, nil
}

View file

@ -1,37 +1,52 @@
package tool
import (
"errors"
"log"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
func ReloadNginx() string {
out, err := exec.Command("nginx", "-s", "reload").CombinedOutput()
func TestNginxConf(filePath string) error {
out, err := exec.Command("nginx", "-t").CombinedOutput()
if err != nil {
log.Println(err)
}
output := string(out)
log.Println(output)
if strings.Contains(output, "failed") {
return errors.New(output)
}
return nil
}
if err != nil {
func ReloadNginx() string {
out, err := exec.Command("nginx", "-s", "reload").CombinedOutput()
if err != nil {
log.Println(err)
}
output := string(out)
log.Println(output)
log.Println(output)
return output
}
func GetNginxConfPath(dir string) string {
out, err := exec.Command("nginx", "-V").CombinedOutput()
if err != nil {
log.Fatal(err)
}
// fmt.Printf("%s\n", out)
out, err := exec.Command("nginx", "-V").CombinedOutput()
if err != nil {
log.Fatal(err)
}
// fmt.Printf("%s\n", out)
r, _ := regexp.Compile("--conf-path=(.*)/(.*.conf)")
r, _ := regexp.Compile("--conf-path=(.*)/(.*.conf)")
confPath := r.FindStringSubmatch(string(out))[1]
confPath := r.FindStringSubmatch(string(out))[1]
// fmt.Println(confPath)
// fmt.Println(confPath)
return filepath.Join(confPath, dir)
return filepath.Join(confPath, dir)
}