diff --git a/frontend/src/api/nginx_log.ts b/frontend/src/api/nginx_log.ts
new file mode 100644
index 00000000..3d5a7963
--- /dev/null
+++ b/frontend/src/api/nginx_log.ts
@@ -0,0 +1,16 @@
+import http from '@/lib/http'
+
+interface IData {
+ type: string
+ conf_name: string
+ server_idx: number
+ directive_idx: number
+}
+
+const nginx_log = {
+ page(page = 0, data: IData) {
+ return http.post('/nginx_log?page=' + page, data)
+ }
+}
+
+export default nginx_log
diff --git a/frontend/src/views/nginx_log/NginxLog.vue b/frontend/src/views/nginx_log/NginxLog.vue
index 389850ed..2693bbc7 100644
--- a/frontend/src/views/nginx_log/NginxLog.vue
+++ b/frontend/src/views/nginx_log/NginxLog.vue
@@ -5,6 +5,8 @@ import {nextTick, onMounted, onUnmounted, reactive, ref, watch} from 'vue'
import ReconnectingWebSocket from 'reconnecting-websocket'
import {useRoute, useRouter} from 'vue-router'
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
+import nginx_log from '@/api/nginx_log'
+import {debounce} from 'lodash'
const {$gettext} = useGettext()
@@ -18,7 +20,6 @@ function logType() {
}
const control = reactive({
- fetch: 'new',
type: logType(),
conf_name: route.query.conf_name,
server_idx: parseInt(route.query.server_idx as string),
@@ -30,26 +31,51 @@ function openWs() {
websocket.onopen = () => {
websocket.send(JSON.stringify({
- ...control,
- fetch: 'new'
+ ...control
}))
}
websocket.onmessage = (m: any) => {
- const para = document.createElement('p')
- para.appendChild(document.createTextNode(m.data.trim()));
+ addLog(m.data)
+ }
+}
- (logContainer.value as any as Node).appendChild(para);
+function addLog(data: string, prepend: boolean = false) {
+ const para = document.createElement('p')
+ para.appendChild(document.createTextNode(data.trim()))
- (logContainer.value as any as Element).scroll({
- top: (logContainer.value as any as Element).scrollHeight,
- left: 0,
- behavior: 'smooth'
- })
+ const node = (logContainer.value as any as Node)
+
+ if (prepend) {
+ node.insertBefore(para, node.firstChild)
+ } else {
+ node.appendChild(para)
}
+ const elem = (logContainer.value as any as Element)
+ elem.scroll({
+ top: elem.scrollHeight,
+ left: 0,
+ })
+}
+
+const page = ref(0)
+
+function init() {
+ nginx_log.page(0, {
+ conf_name: (route.query.conf_name as string),
+ type: logType(),
+ server_idx: 0,
+ directive_idx: 0
+ }).then(r => {
+ page.value = r.page - 1
+ r.content.split('\n').forEach((v: string) => {
+ addLog(v)
+ })
+ })
}
onMounted(() => {
+ init()
openWs()
})
@@ -66,6 +92,8 @@ watch(auto_refresh, (value) => {
})
watch(route, () => {
+ init()
+
control.type = logType();
(logContainer.value as any as Element).innerHTML = ''
@@ -88,6 +116,31 @@ onUnmounted(() => {
})
const router = useRouter()
+const loading = ref(false)
+
+function on_scroll_log() {
+ if (!loading.value && page.value > 0) {
+ loading.value = true
+ const elem = (logContainer.value as any as Element)
+ if (elem.scrollTop / elem.scrollHeight < 0.333) {
+ nginx_log.page(page.value, {
+ conf_name: (route.query.conf_name as string),
+ type: logType(),
+ server_idx: 0,
+ directive_idx: 0
+ }).then(r => {
+ page.value = r.page - 1
+ r.content.split('\n').forEach((v: string) => {
+ addLog(v, true)
+ })
+ }).finally(() => {
+ loading.value = false
+ })
+ } else {
+ loading.value = false
+ }
+ }
+}
@@ -97,20 +150,11 @@ const router = useRouter()
-
-
-
- All logs
-
-
- New logs
-
-
-
-
+
@@ -125,6 +169,7 @@ const router = useRouter()
height: 60vh;
overflow: scroll;
padding: 5px;
+ margin-bottom: 0;
p {
font-size: 12px;
diff --git a/server/api/nginx_log.go b/server/api/nginx_log.go
index 80bb9231..7c7fcae9 100644
--- a/server/api/nginx_log.go
+++ b/server/api/nginx_log.go
@@ -8,89 +8,185 @@ import (
"github.com/gorilla/websocket"
"github.com/hpcloud/tail"
"github.com/pkg/errors"
+ "github.com/spf13/cast"
"io"
"log"
"net/http"
+ "os"
"path/filepath"
)
+const (
+ PageSize = 128 * 1024
+)
+
type controlStruct struct {
- Fetch string `json:"fetch"`
Type string `json:"type"`
ConfName string `json:"conf_name"`
ServerIdx int `json:"server_idx"`
DirectiveIdx int `json:"directive_idx"`
}
-func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
- defer func() {
- if err := recover(); err != nil {
- log.Println("tailNginxLog recovery", err)
- _ = ws.WriteMessage(websocket.TextMessage, err.([]byte))
+type nginxLogPageResp struct {
+ Content string `json:"content"`
+ Page int64 `json:"page"`
+}
+
+func GetNginxLogPage(c *gin.Context) {
+ page := cast.ToInt64(c.Query("page"))
+ if page < 0 {
+ page = 0
+ }
+
+ var control controlStruct
+ if !BindAndValid(c, &control) {
+ return
+ }
+
+ logPath, err := getLogPath(&control)
+
+ if err != nil {
+ log.Println("error GetNginxLogPage", err)
+ return
+ }
+
+ f, err := os.Open(logPath)
+
+ if err != nil {
+ c.JSON(http.StatusOK, nginxLogPageResp{})
+ log.Println("error GetNginxLogPage open file", err)
+ return
+ }
+
+ logFileStat, err := os.Stat(logPath)
+
+ if err != nil {
+ c.JSON(http.StatusOK, nginxLogPageResp{})
+ log.Println("error GetNginxLogPage stat", err)
+ return
+ }
+
+ totalPage := logFileStat.Size() / PageSize
+
+ if logFileStat.Size()%PageSize > 0 {
+ totalPage++
+ }
+
+ var buf []byte
+ var offset int64
+ if page == 0 {
+ page = totalPage
+ }
+
+ buf = make([]byte, PageSize)
+ offset = (page - 1) * PageSize
+
+ // seek
+ _, err = f.Seek(offset, io.SeekStart)
+ if err != nil && err != io.EOF {
+ c.JSON(http.StatusOK, nginxLogPageResp{})
+ log.Println("error GetNginxLogPage seek", err)
+ return
+ }
+
+ n, err := f.Read(buf)
+
+ if err != nil && err != io.EOF {
+ c.JSON(http.StatusOK, nginxLogPageResp{})
+ log.Println("error GetNginxLogPage read buf", err)
+ return
+ }
+
+ c.JSON(http.StatusOK, nginxLogPageResp{
+ Page: page,
+ Content: string(buf[:n]),
+ })
+}
+
+func getLogPath(control *controlStruct) (logPath string, err error) {
+ switch control.Type {
+ case "site":
+ var config *nginx.NgxConfig
+ path := filepath.Join(nginx.GetNginxConfPath("sites-available"), control.ConfName)
+ config, err = nginx.ParseNgxConfig(path)
+ if err != nil {
+ err = errors.Wrap(err, "error parsing ngx config")
return
}
- }()
- control := <-controlChan
+ if control.ServerIdx >= len(config.Servers) {
+ err = errors.New("serverIdx out of range")
+ return
+ }
- for {
- var seek tail.SeekInfo
- if control.Fetch != "all" {
- seek.Offset = 0
- seek.Whence = io.SeekEnd
- }
- var logPath string
- switch control.Type {
- case "site":
- path := filepath.Join(nginx.GetNginxConfPath("sites-available"), control.ConfName)
- config, err := nginx.ParseNgxConfig(path)
- if err != nil {
- errChan <- errors.Wrap(err, "error parsing ngx config")
- return
- }
+ if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
+ err = errors.New("DirectiveIdx out of range")
+ return
+ }
- if control.ServerIdx >= len(config.Servers) {
- errChan <- errors.New("serverIdx out of range")
- return
- }
+ directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
- if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
- errChan <- errors.New("DirectiveIdx out of range")
- return
- }
+ switch directive.Directive {
+ case "access_log", "error_log":
+ // ok
+ default:
+ err = errors.New("directive.Params neither access_log nor error_log")
+ return
+ }
- directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
+ if directive.Params == "" {
+ err = errors.New("directive.Params is empty")
+ return
+ }
- switch directive.Directive {
- case "access_log", "error_log":
- // ok
- default:
- errChan <- errors.New("directive.Params neither access_log nor error_log")
- return
- }
+ logPath = directive.Params
- if directive.Params == "" {
- errChan <- errors.New("directive.Params is empty")
- return
- }
+ case "error":
+ if settings.NginxLogSettings.ErrorLogPath == "" {
+ err = errors.New("settings.NginxLogSettings.ErrorLogPath is empty," +
+ " see https://github.com/0xJacky/nginx-ui/wiki/Nginx-Log-Configuration for more information")
+ return
+ }
+ logPath = settings.NginxLogSettings.ErrorLogPath
- logPath = directive.Params
+ default:
+ if settings.NginxLogSettings.AccessLogPath == "" {
+ err = errors.New("settings.NginxLogSettings.AccessLogPath is empty," +
+ " see https://github.com/0xJacky/nginx-ui/wiki/Nginx-Log-Configuration for more information")
+ return
+ }
+ logPath = settings.NginxLogSettings.AccessLogPath
+ }
- case "error":
- if settings.NginxLogSettings.ErrorLogPath == "" {
- errChan <- errors.New("settings.NginxLogSettings.ErrorLogPath is empty," +
- " see https://github.com/0xJacky/nginx-ui/wiki/Nginx-Log-Configuration for more information")
- return
- }
- logPath = settings.NginxLogSettings.ErrorLogPath
+ return
+}
- default:
- if settings.NginxLogSettings.AccessLogPath == "" {
- errChan <- errors.New("settings.NginxLogSettings.AccessLogPath is empty," +
- " see https://github.com/0xJacky/nginx-ui/wiki/Nginx-Log-Configuration for more information")
+func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Println("tailNginxLog recovery", err)
+ err = ws.WriteMessage(websocket.TextMessage, err.([]byte))
+ if err != nil {
+ log.Println(err)
return
}
- logPath = settings.NginxLogSettings.AccessLogPath
+ return
+ }
+ }()
+
+ control := <-controlChan
+
+ for {
+ logPath, err := getLogPath(&control)
+
+ if err != nil {
+ errChan <- err
+ return
+ }
+
+ seek := tail.SeekInfo{
+ Offset: 0,
+ Whence: io.SeekEnd,
}
// Create a tail
diff --git a/server/router/routers.go b/server/router/routers.go
index b52defbd..f44fd886 100644
--- a/server/router/routers.go
+++ b/server/router/routers.go
@@ -95,6 +95,7 @@ func InitRouter() *gin.Engine {
// Nginx log
g.GET("nginx_log", api.NginxLog)
+ g.POST("nginx_log", api.GetNginxLogPage)
}
}