From 2eb8abf61e6aae94ef3cefae33c2a08595cc5fce Mon Sep 17 00:00:00 2001 From: fgsfds <4870330+fgsfds@users.noreply.github.com> Date: Wed, 17 Sep 2025 16:19:55 +0500 Subject: [PATCH] Improved xray logs display handling (#3475) * improved xray logs handling * fix download Xray Logs * Update index.html --- web/html/index.html | 125 ++++++++++++++++++++++++++---------------- web/service/server.go | 73 ++++++++++++++++++------ 2 files changed, 135 insertions(+), 63 deletions(-) diff --git a/web/html/index.html b/web/html/index.html index 819d6df2..4ddf26b5 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -365,8 +365,7 @@ SysLog - +
@@ -401,8 +400,7 @@ Proxy - +
@@ -796,59 +794,74 @@ }; const xraylogModal = { - visible: false, - logs: [], - rows: 20, - showDirect: true, - showBlocked: true, - showProxy: true, - loading: false, - show(logs) { - this.visible = true; - this.logs = logs; - this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record..."; - }, - formatLogs(logs) { - let formattedLogs = ''; + visible: false, + logs: [], + rows: 20, + showDirect: true, + showBlocked: true, + showProxy: true, + loading: false, + show(logs) { + this.visible = true; + this.logs = logs; + this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record..."; + }, + formatLogs(logs) { + let formattedLogs = ` + - const parts = log.split(' '); - - if (parts.length === 10) { - const dateTime = `${parts[0]} ${parts[1]}`; - const from = `${parts[3]}`; - const to = `${parts[5].replace(/^\/+/, "")}`; + + + + + + + + + +`; + logs.reverse().forEach((log, index) => { let outboundColor = ''; - if (parts[9] === "b") { + if (log.Event === 1) { outboundColor = ' style="color: #e04141;"'; //red for blocked } - else if (parts[9] === "p") { + else if (log.Event === 2) { outboundColor = ' style="color: #3c89e8;"'; //blue for proxies } - formattedLogs += ` -${dateTime} - ${parts[2]} - ${from} - ${parts[4]} - ${to} - ${parts.slice(6, 9).join(' ')} -`; - } else { - formattedLogs += `${log}`; - } - }); + let text = ``; + if (log.Email !== "") { + text = ``; + } - return formattedLogs; - }, - hide() { - this.visible = false; - }, - }; + formattedLogs += ` + + + + + + + ${text} + +`; + }); + return formattedLogs += "
DateFromToInboundOutboundEmail
${log.Email}
${new Date(log.DateTime).toLocaleString()}${log.FromAddress}${log.ToAddress}${log.Inbound}${log.Outbound}
"; + }, + hide() { + this.visible = false; + }, + }; const backupModal = { visible: false, show() { @@ -1023,6 +1036,25 @@ ${dateTime} await PromiseUtil.sleep(500); xraylogModal.loading = false; }, + downloadXrayLogs() { + if (!Array.isArray(this.xraylogModal.logs) || this.xraylogModal.logs.length === 0) { + FileManager.downloadTextFile('', 'x-ui.log'); + return; + } + const lines = this.xraylogModal.logs.map(l => { + try { + const dt = l.DateTime ? new Date(l.DateTime) : null; + const dateStr = dt && !isNaN(dt.getTime()) ? dt.toISOString() : ''; + const eventMap = { 0: 'DIRECT', 1: 'BLOCKED', 2: 'PROXY' }; + const eventText = eventMap[l.Event] || String(l.Event ?? ''); + const emailPart = l.Email ? ` Email=${l.Email}` : ''; + return `${dateStr} FROM=${l.FromAddress || ''} TO=${l.ToAddress || ''} INBOUND=${l.Inbound || ''} OUTBOUND=${l.Outbound || ''}${emailPart} EVENT=${eventText}`.trim(); + } catch (e) { + return JSON.stringify(l); + } + }).join('\n'); + FileManager.downloadTextFile(lines, 'x-ui.log'); + }, async openConfig() { this.loading(true); const msg = await HttpUtil.get('/panel/api/server/getConfigJson'); @@ -1071,7 +1103,6 @@ ${dateTime} fileInput.click(); }, }, - computed: {}, async mounted() { if (window.location.protocol !== "https:") { this.showAlert = true; diff --git a/web/service/server.go b/web/service/server.go index f0ad3265..670e622e 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -174,6 +174,16 @@ type CPUSample struct { Cpu float64 `json:"cpu"` // percent 0..100 } +type LogEntry struct { + DateTime time.Time + FromAddress string + ToAddress string + Inbound string + Outbound string + Email string + Event int +} + func getPublicIP(url string) string { client := &http.Client{ Timeout: 3 * time.Second, @@ -704,19 +714,25 @@ func (s *ServerService) GetXrayLogs( showBlocked string, showProxy string, freedoms []string, - blackholes []string) []string { + blackholes []string) []LogEntry { + + const ( + Direct = iota + Blocked + Proxied + ) countInt, _ := strconv.Atoi(count) - var lines []string + var entries []LogEntry pathToAccessLog, err := xray.GetAccessLogPath() if err != nil { - return lines + return nil } file, err := os.Open(pathToAccessLog) if err != nil { - return lines + return nil } defer file.Close() @@ -735,37 +751,62 @@ func (s *ServerService) GetXrayLogs( continue } - //adding suffixes to further distinguish entries by outbound - if hasSuffix(line, freedoms) { + var entry LogEntry + parts := strings.Fields(line) + + for i, part := range parts { + + if i == 0 { + dateTime, err := time.Parse("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1]) + if err != nil { + continue + } + entry.DateTime = dateTime + } + + if part == "from" { + entry.FromAddress = parts[i+1] + } else if part == "accepted" { + entry.ToAddress = parts[i+1] + } else if strings.HasPrefix(part, "[") { + entry.Inbound = part[1:] + } else if strings.HasSuffix(part, "]") { + entry.Outbound = part[:len(part)-1] + } else if part == "email:" { + entry.Email = parts[i+1] + } + } + + if logEntryContains(line, freedoms) { if showDirect == "false" { continue } - line = line + " f" - } else if hasSuffix(line, blackholes) { + entry.Event = Direct + } else if logEntryContains(line, blackholes) { if showBlocked == "false" { continue } - line = line + " b" + entry.Event = Blocked } else { if showProxy == "false" { continue } - line = line + " p" + entry.Event = Proxied } - lines = append(lines, line) + entries = append(entries, entry) } - if len(lines) > countInt { - lines = lines[len(lines)-countInt:] + if len(entries) > countInt { + entries = entries[len(entries)-countInt:] } - return lines + return entries } -func hasSuffix(line string, suffixes []string) bool { +func logEntryContains(line string, suffixes []string) bool { for _, sfx := range suffixes { - if strings.HasSuffix(line, sfx+"]") { + if strings.Contains(line, sfx+"]") { return true } }