feat: read nginx log

This commit is contained in:
0xJacky 2022-08-30 14:03:59 +08:00
parent 376b0535f0
commit 6e3004b0bc
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
10 changed files with 260 additions and 11 deletions

View file

@ -5,3 +5,7 @@ JwtSecret =
Email =
HTTPChallengePort = 9180
StartCmd = login
[nginx_log]
AccessLogPath = /var/log/nginx/access.log
ErrorLogPath = /var/log/nginx/error.log

View file

@ -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'),

View file

@ -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)

View file

@ -0,0 +1,93 @@
<script setup lang="ts">
import {useGettext} from 'vue3-gettext'
import ws from '@/lib/websocket'
import {nextTick, onMounted, reactive, ref, watch} from 'vue'
import ReconnectingWebSocket from 'reconnecting-websocket'
const {$gettext} = useGettext()
const logContainer = ref(null)
let websocket: ReconnectingWebSocket | WebSocket
const control = reactive({
fetch: 'new'
})
function openWs() {
websocket = ws('/api/nginx_log')
websocket.send(JSON.stringify(control))
websocket.onopen = () => {
(logContainer.value as any as Element).innerHTML = ''
}
websocket.onmessage = (m: any) => {
const para = document.createElement('p')
para.appendChild(document.createTextNode(m.data.trim()));
(logContainer.value as any as Node).appendChild(para);
(logContainer.value as any as Element).scroll({
top: (logContainer.value as any as Element).scrollHeight,
left: 0,
behavior: 'smooth'
})
}
}
onMounted(() => {
openWs()
})
const auto_refresh = ref(true)
watch(auto_refresh, (value) => {
if (value) {
openWs()
} else {
websocket.close()
}
})
watch(control, () => {
(logContainer.value as any as Element).innerHTML = ''
auto_refresh.value = true
nextTick(() => {
websocket.send(JSON.stringify(control))
})
})
</script>
<template>
<a-card :title="$gettext('Nginx Log')" :bordered="false">
<a-form layout="vertical">
<a-form-item :label="$gettext('Auto Refresh')">
<a-switch v-model:checked="auto_refresh"/>
</a-form-item>
<a-form-item :label="$gettext('Fetch')">
<a-select v-model:value="control.fetch" style="max-width: 200px">
<a-select-option value="all">All logs</a-select-option>
<a-select-option value="new">New logs</a-select-option>
</a-select>
</a-form-item>
</a-form>
<a-card>
<pre class="nginx-log-container" ref="logContainer"></pre>
</a-card>
</a-card>
</template>
<style lang="less">
.nginx-log-container {
height: 60vh;
overflow: scroll;
padding: 5px;
p {
font-size: 12px;
line-height: 1;
}
}
</style>

3
go.mod
View file

@ -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
)

3
go.sum
View file

@ -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=

View file

@ -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)

124
server/api/nginx_log.go Normal file
View file

@ -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
}
}

View file

@ -92,6 +92,9 @@ func InitRouter() *gin.Engine {
// pty
g.GET("pty", api.Pty)
// Nginx log
g.GET("nginx_log", api.NginxLog)
}
}

View file

@ -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,
"nginx_log": NginxLogSettings,
}
func init() {