From 6e3004b0bc0cb986d4fcaed626a398485baa1fda Mon Sep 17 00:00:00 2001 From: 0xJacky Date: Tue, 30 Aug 2022 14:03:59 +0800 Subject: [PATCH] feat: read nginx log --- app.example.ini | 4 + frontend/src/routes/index.ts | 11 +- .../views/domain/ngx_conf/NgxConfigEditor.vue | 1 - frontend/src/views/nginx_log/NginxLog.vue | 93 +++++++++++++ go.mod | 3 + go.sum | 3 + server/api/domain.go | 16 +-- server/api/nginx_log.go | 124 ++++++++++++++++++ server/router/routers.go | 3 + server/settings/settings.go | 13 +- 10 files changed, 260 insertions(+), 11 deletions(-) create mode 100644 frontend/src/views/nginx_log/NginxLog.vue create mode 100644 server/api/nginx_log.go diff --git a/app.example.ini b/app.example.ini index 27406cc2..9f228802 100644 --- a/app.example.ini +++ b/app.example.ini @@ -5,3 +5,7 @@ JwtSecret = Email = HTTPChallengePort = 9180 StartCmd = login + +[nginx_log] +AccessLogPath = /var/log/nginx/access.log +ErrorLogPath = /var/log/nginx/error.log diff --git a/frontend/src/routes/index.ts b/frontend/src/routes/index.ts index 9506f40e..af40aef0 100644 --- a/frontend/src/routes/index.ts +++ b/frontend/src/routes/index.ts @@ -8,7 +8,8 @@ import { FileOutlined, HomeOutlined, InfoCircleOutlined, - UserOutlined + UserOutlined, + FileTextOutlined } from '@ant-design/icons-vue' const {$gettext} = gettext @@ -87,6 +88,14 @@ export const routes = [ icon: CodeOutlined } }, + { + path: 'nginx_log', + name: () => $gettext('Nginx Log'), + component: () => import('@/views/nginx_log/NginxLog.vue'), + meta: { + icon: FileTextOutlined + } + }, { path: 'about', name: () => $gettext('About'), diff --git a/frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue b/frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue index bede9e8e..eec0e512 100644 --- a/frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue +++ b/frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue @@ -20,7 +20,6 @@ const name = ref(route.params.name) function change_tls(r: any) { if (r) { // deep copy servers[0] to servers[1] - console.log(props.ngx_config) const server = JSON.parse(JSON.stringify(props.ngx_config.servers[0])) props.ngx_config.servers.push(server) diff --git a/frontend/src/views/nginx_log/NginxLog.vue b/frontend/src/views/nginx_log/NginxLog.vue new file mode 100644 index 00000000..e5bb0d45 --- /dev/null +++ b/frontend/src/views/nginx_log/NginxLog.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/go.mod b/go.mod index efa7bf58..8509978a 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-ole/go-ole v1.2.5 // indirect github.com/golang/protobuf v1.3.4 // indirect + github.com/hpcloud/tail v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.2 // indirect github.com/json-iterator/go v1.1.9 // indirect @@ -49,6 +50,8 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect golang.org/x/text v0.3.4 // indirect + gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index fb2d7f9e..91bdb0b0 100644 --- a/go.sum +++ b/go.sum @@ -227,6 +227,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= @@ -692,6 +693,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -704,6 +706,7 @@ gopkg.in/ns1/ns1-go.v2 v2.4.4/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYh gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/server/api/domain.go b/server/api/domain.go index 19b135a2..179cf534 100644 --- a/server/api/domain.go +++ b/server/api/domain.go @@ -224,8 +224,8 @@ func DisableDomain(c *gin.Context) { } // delete auto cert record - cert := model.Cert{Domain: c.Param("name")} - err = cert.Remove() + certModel := model.Cert{Domain: c.Param("name")} + err = certModel.Remove() if err != nil { ErrHandler(c, err) return @@ -265,8 +265,8 @@ func DeleteDomain(c *gin.Context) { return } - cert := model.Cert{Domain: name} - _ = cert.Remove() + certModel := model.Cert{Domain: name} + _ = certModel.Remove() err = os.Remove(availablePath) @@ -284,19 +284,19 @@ func DeleteDomain(c *gin.Context) { func AddDomainToAutoCert(c *gin.Context) { domain := c.Param("domain") - cert, err := model.FirstOrCreateCert(domain) + certModel, err := model.FirstOrCreateCert(domain) if err != nil { ErrHandler(c, err) return } - c.JSON(http.StatusOK, cert) + c.JSON(http.StatusOK, certModel) } func RemoveDomainFromAutoCert(c *gin.Context) { - cert := model.Cert{ + certModel := model.Cert{ Domain: c.Param("domain"), } - err := cert.Remove() + err := certModel.Remove() if err != nil { ErrHandler(c, err) diff --git a/server/api/nginx_log.go b/server/api/nginx_log.go new file mode 100644 index 00000000..080ac9e0 --- /dev/null +++ b/server/api/nginx_log.go @@ -0,0 +1,124 @@ +package api + +import ( + "encoding/json" + "github.com/0xJacky/Nginx-UI/server/settings" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/hpcloud/tail" + "github.com/pkg/errors" + "io" + "log" + "net/http" +) + +type controlStruct struct { + Fetch string `json:"fetch"` +} + +func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) { + defer func() { + if err := recover(); err != nil { + log.Println("tailNginxLog recovery", err) + return + } + }() + + var control controlStruct + + for { + var seek tail.SeekInfo + if control.Fetch != "all" { + seek.Offset = 0 + seek.Whence = io.SeekEnd + } + + // Create a tail + t, err := tail.TailFile( + settings.NginxLogSettings.AccessLogPath, tail.Config{Follow: true, + ReOpen: true, Location: &seek}) + + if err != nil { + errChan <- errors.Wrap(err, "error NginxAccessLog Tail") + return + } + + for { + var next = false + select { + case line := <-t.Lines: + // Print the text of each received line + err = ws.WriteMessage(websocket.TextMessage, []byte(line.Text)) + + if err != nil { + errChan <- errors.Wrap(err, "error NginxAccessLog write message") + return + } + case control = <-controlChan: + log.Println("control change") + next = true + break + } + if next { + break + } + } + } +} + +func handleLogControl(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) { + defer func() { + if err := recover(); err != nil { + log.Println("tailNginxLog recovery", err) + return + } + }() + + for { + msgType, payload, err := ws.ReadMessage() + if err != nil { + errChan <- errors.Wrap(err, "error NginxAccessLog read message") + return + } + + if msgType != websocket.TextMessage { + errChan <- errors.New("error NginxAccessLog message type") + return + } + + var msg controlStruct + err = json.Unmarshal(payload, &msg) + if err != nil { + errChan <- errors.Wrap(err, "Error ReadWsAndWritePty json.Unmarshal") + return + } + controlChan <- msg + } +} + +func NginxLog(c *gin.Context) { + 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 { + log.Println("[Error] NginxAccessLog Upgrade", err) + return + } + + defer ws.Close() + + errChan := make(chan error, 1) + controlChan := make(chan controlStruct, 1) + + go tailNginxLog(ws, controlChan, errChan) + go handleLogControl(ws, controlChan, errChan) + + if err = <-errChan; err != nil { + log.Println(err) + return + } +} diff --git a/server/router/routers.go b/server/router/routers.go index 21bc263c..b52defbd 100644 --- a/server/router/routers.go +++ b/server/router/routers.go @@ -92,6 +92,9 @@ func InitRouter() *gin.Engine { // pty g.GET("pty", api.Pty) + + // Nginx log + g.GET("nginx_log", api.NginxLog) } } diff --git a/server/settings/settings.go b/server/settings/settings.go index 25706f0e..cc182b02 100644 --- a/server/settings/settings.go +++ b/server/settings/settings.go @@ -28,6 +28,11 @@ type Server struct { PageSize int } +type NginxLog struct { + AccessLogPath string + ErrorLogPath string +} + var ServerSettings = &Server{ HttpPort: "9000", RunMode: "debug", @@ -38,10 +43,16 @@ var ServerSettings = &Server{ PageSize: 10, } +var NginxLogSettings = &NginxLog{ + AccessLogPath: "", + ErrorLogPath: "", +} + var ConfPath string var sections = map[string]interface{}{ - "server": ServerSettings, + "server": ServerSettings, + "nginx_log": NginxLogSettings, } func init() {