[log] display logs
This commit is contained in:
@@ -151,6 +151,12 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
|||||||
case "onlines":
|
case "onlines":
|
||||||
onlines, err := a.StatsService.GetOnlines()
|
onlines, err := a.StatsService.GetOnlines()
|
||||||
jsonObj(c, onlines, err)
|
jsonObj(c, onlines, err)
|
||||||
|
case "logs":
|
||||||
|
service := c.Query("s")
|
||||||
|
count := c.Query("c")
|
||||||
|
level := c.Query("l")
|
||||||
|
logs := a.ServerService.GetLogs(service, count, level)
|
||||||
|
jsonObj(c, logs, nil)
|
||||||
default:
|
default:
|
||||||
jsonMsg(c, "API call", nil)
|
jsonMsg(c, "API call", nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
@@ -135,3 +138,24 @@ func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
|||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetLogs(service string, count string, level string) []string {
|
||||||
|
c, _ := strconv.Atoi(count)
|
||||||
|
var lines []string
|
||||||
|
if service == "sing-box" {
|
||||||
|
cmdArgs := []string{"journalctl", "-u", service, "--no-pager", "-n", count, "-p", level}
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to run journalctl command!"}
|
||||||
|
}
|
||||||
|
lines = strings.Split(out.String(), "\n")
|
||||||
|
} else {
|
||||||
|
lines = logger.GetLogs(c, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<LogVue
|
||||||
|
v-model="logModal.visible"
|
||||||
|
:visible="logModal.visible"
|
||||||
|
:logType="logModal.logType"
|
||||||
|
@close="closeLogs"
|
||||||
|
/>
|
||||||
<v-container class="fill-height">
|
<v-container class="fill-height">
|
||||||
<v-responsive :class="reloadItems.length>0 ? 'fill-height text-center' : 'align-center'" >
|
<v-responsive :class="reloadItems.length>0 ? 'fill-height text-center' : 'align-center'" >
|
||||||
<v-row class="d-flex align-center justify-center">
|
<v-row class="d-flex align-center justify-center">
|
||||||
@@ -45,7 +51,7 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i">
|
<v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i">
|
||||||
<v-card class="rounded-lg" variant="outlined" height="200px"
|
<v-card class="rounded-lg" variant="outlined" height="210px"
|
||||||
:title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title">
|
:title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title">
|
||||||
<v-card-text style="padding: 0 16px;" align="center" justify="center">
|
<v-card-text style="padding: 0 16px;" align="center" justify="center">
|
||||||
<Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" />
|
<Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" />
|
||||||
@@ -80,13 +86,19 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="3">S-UI</v-col>
|
<v-col cols="3">S-UI</v-col>
|
||||||
<v-col cols="9">
|
<v-col cols="9">
|
||||||
<v-chip density="compact" color="primary" variant="flat">
|
<v-chip density="compact" color="blue">
|
||||||
<v-tooltip activator="parent" location="top">
|
<v-tooltip activator="parent" location="top">
|
||||||
{{ $t('main.info.threads') }}: {{ tilesData.sys?.appThreads }}<br />
|
{{ $t('main.info.threads') }}: {{ tilesData.sys?.appThreads }}<br />
|
||||||
{{ $t('main.info.memory') }}: {{ HumanReadable.sizeFormat(tilesData.sys?.appMem) }}
|
{{ $t('main.info.memory') }}: {{ HumanReadable.sizeFormat(tilesData.sys?.appMem) }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
v{{ tilesData.sys?.appVersion }}
|
v{{ tilesData.sys?.appVersion }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
|
<v-chip density="compact" color="transparent" style="cursor: pointer;" @click="openLogs('s-ui')">
|
||||||
|
<v-tooltip activator="parent" location="top">
|
||||||
|
S-UI Logs
|
||||||
|
</v-tooltip>
|
||||||
|
<v-icon icon="mdi-list-box-outline" color="blue" />
|
||||||
|
</v-chip>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="3">{{ $t('main.info.uptime') }}</v-col>
|
<v-col cols="3">{{ $t('main.info.uptime') }}</v-col>
|
||||||
<v-col cols="9">{{ HumanReadable.formatSecond(tilesData.uptime) }}</v-col>
|
<v-col cols="9">{{ HumanReadable.formatSecond(tilesData.uptime) }}</v-col>
|
||||||
@@ -98,6 +110,12 @@
|
|||||||
<v-col cols="8">
|
<v-col cols="8">
|
||||||
<v-chip density="compact" color="success" variant="flat" v-if="tilesData.sbd?.running">{{ $t('yes') }}</v-chip>
|
<v-chip density="compact" color="success" variant="flat" v-if="tilesData.sbd?.running">{{ $t('yes') }}</v-chip>
|
||||||
<v-chip density="compact" color="error" variant="flat" v-else>{{ $t('no') }}</v-chip>
|
<v-chip density="compact" color="error" variant="flat" v-else>{{ $t('no') }}</v-chip>
|
||||||
|
<v-chip density="compact" color="transparent" style="cursor: pointer;" @click="openLogs('sing-box')">
|
||||||
|
<v-tooltip activator="parent" location="top">
|
||||||
|
Sing-Box Logs
|
||||||
|
</v-tooltip>
|
||||||
|
<v-icon icon="mdi-list-box-outline" :color="tilesData.sbd?.running ? 'success': 'error'" />
|
||||||
|
</v-chip>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="4">{{ $t('main.info.memory') }}</v-col>
|
<v-col cols="4">{{ $t('main.info.memory') }}</v-col>
|
||||||
<v-col cols="8">
|
<v-col cols="8">
|
||||||
@@ -148,6 +166,7 @@ import Gauge from '@/components/tiles/Gauge.vue'
|
|||||||
import History from '@/components/tiles/History.vue'
|
import History from '@/components/tiles/History.vue'
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
|
import LogVue from '@/layouts/modals/Logs.vue'
|
||||||
|
|
||||||
const menu = ref(false)
|
const menu = ref(false)
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
@@ -215,4 +234,19 @@ onMounted(() => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
stopTimer()
|
stopTimer()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const logModal = ref({
|
||||||
|
visible: false,
|
||||||
|
logType: "s-ui"
|
||||||
|
})
|
||||||
|
|
||||||
|
const openLogs = (logType: string) => {
|
||||||
|
logModal.value.logType = logType
|
||||||
|
logModal.value.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeLogs = () => {
|
||||||
|
logModal.value.logType = "s-ui"
|
||||||
|
logModal.value.visible = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="90%" max-width="1200" :loading="loading">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ (logType == 's-ui'? "S-UI" : "Sing-Box") + " logs" }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="Level"
|
||||||
|
:items="logLevels"
|
||||||
|
v-model="logLevel"
|
||||||
|
@update:model-value="loadData">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="Count"
|
||||||
|
:items="[10,20,30,50,100]"
|
||||||
|
v-model.number="logCount"
|
||||||
|
@update:model-value="loadData">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="auto" align="center" justify="center">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-refresh"
|
||||||
|
variant="tonal"
|
||||||
|
:loading="loading"
|
||||||
|
@click="loadData">
|
||||||
|
<v-icon />
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card style="background-color: background" dir="ltr" v-html="lines.join('<br />')"></v-card>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="outlined"
|
||||||
|
@click="$emit('close')"
|
||||||
|
>
|
||||||
|
{{ $t('actions.close') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import HttpUtils from '@/plugins/httputil';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['logType', 'visible'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
lines: [],
|
||||||
|
logLevel: 'info',
|
||||||
|
logLevels: [
|
||||||
|
{ title: 'DEBUG', value: 'debug' },
|
||||||
|
{ title: 'INFO', value: 'info' },
|
||||||
|
{ title: 'WARNING', value: 'warn' },
|
||||||
|
{ title: 'ERROR', value: 'error' },
|
||||||
|
],
|
||||||
|
logCount: 10,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadData() {
|
||||||
|
this.loading = true
|
||||||
|
const data = await HttpUtils.get('api/logs',{ s: this.$props.logType, c: this.logCount, l: this.logLevel })
|
||||||
|
if (data.success) {
|
||||||
|
this.lines = data.obj?? []
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
this.lines = []
|
||||||
|
this.logLevel = 'info'
|
||||||
|
this.logCount = 10
|
||||||
|
if (newValue) {
|
||||||
|
this.loadData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user