mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 10:25:52 +02:00
feat: read nginx log
This commit is contained in:
parent
376b0535f0
commit
6e3004b0bc
10 changed files with 260 additions and 11 deletions
|
@ -5,3 +5,7 @@ JwtSecret =
|
|||
Email =
|
||||
HTTPChallengePort = 9180
|
||||
StartCmd = login
|
||||
|
||||
[nginx_log]
|
||||
AccessLogPath = /var/log/nginx/access.log
|
||||
ErrorLogPath = /var/log/nginx/error.log
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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)
|
||||
|
|
93
frontend/src/views/nginx_log/NginxLog.vue
Normal file
93
frontend/src/views/nginx_log/NginxLog.vue
Normal 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
3
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
|
||||
)
|
||||
|
|
3
go.sum
3
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=
|
||||
|
|
|
@ -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
124
server/api/nginx_log.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -92,6 +92,9 @@ func InitRouter() *gin.Engine {
|
|||
|
||||
// pty
|
||||
g.GET("pty", api.Pty)
|
||||
|
||||
// Nginx log
|
||||
g.GET("nginx_log", api.NginxLog)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue