Improved xray logs display handling (#3475)

* improved xray logs handling

* fix download Xray Logs

* Update index.html
This commit is contained in:
fgsfds 2025-09-17 16:19:55 +05:00 committed by GitHub
parent 299572a4c2
commit 2eb8abf61e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 135 additions and 63 deletions

View file

@ -365,8 +365,7 @@
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox> <a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
</a-form-item> </a-form-item>
<a-form-item style="float: right;"> <a-form-item style="float: right;">
<a-button type="primary" icon="download" <a-button type="primary" icon="download" @click="FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')"></a-button>
@click="FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')"></a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="ant-input log-container" v-html="logModal.formattedLogs"></div> <div class="ant-input log-container" v-html="logModal.formattedLogs"></div>
@ -401,8 +400,7 @@
<a-checkbox v-model="xraylogModal.showProxy" @change="openXrayLogs()">Proxy</a-checkbox> <a-checkbox v-model="xraylogModal.showProxy" @change="openXrayLogs()">Proxy</a-checkbox>
</a-form-item> </a-form-item>
<a-form-item style="float: right;"> <a-form-item style="float: right;">
<a-button type="primary" icon="download" <a-button type="primary" icon="download" @click="downloadXrayLogs"></a-button>
@click="FileManager.downloadTextFile(xraylogModal.logs?.join('\n'), 'x-ui.log')"></a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="ant-input log-container" v-html="xraylogModal.formattedLogs"></div> <div class="ant-input log-container" v-html="xraylogModal.formattedLogs"></div>
@ -809,46 +807,61 @@
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record..."; this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
}, },
formatLogs(logs) { formatLogs(logs) {
let formattedLogs = ''; let formattedLogs = `
<style>
table {
border-collapse: collapse;
width: auto;
}
logs.forEach((log, index) => { table td, table th {
if (index > 0) formattedLogs += '<br>'; padding: 2px 15px;
}
</style>
const parts = log.split(' '); <table>
<tr>
if (parts.length === 10) { <th>Date</th>
const dateTime = `<b>${parts[0]} ${parts[1]}</b>`; <th>From</th>
const from = `<b>${parts[3]}</b>`; <th>To</th>
const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`; <th>Inbound</th>
<th>Outbound</th>
<th>Email</th>
</tr>
`;
logs.reverse().forEach((log, index) => {
let outboundColor = ''; let outboundColor = '';
if (parts[9] === "b") { if (log.Event === 1) {
outboundColor = ' style="color: #e04141;"'; //red for blocked outboundColor = ' style="color: #e04141;"'; //red for blocked
} }
else if (parts[9] === "p") { else if (log.Event === 2) {
outboundColor = ' style="color: #3c89e8;"'; //blue for proxies outboundColor = ' style="color: #3c89e8;"'; //blue for proxies
} }
formattedLogs += `<span${outboundColor}> let text = ``;
${dateTime} if (log.Email !== "") {
${parts[2]} text = `<td>${log.Email}</td>`;
${from}
${parts[4]}
${to}
${parts.slice(6, 9).join(' ')}
</span>`;
} else {
formattedLogs += `<span>${log}</span>`;
} }
formattedLogs += `
<tr ${outboundColor}>
<td><b>${new Date(log.DateTime).toLocaleString()}</b></td>
<td>${log.FromAddress}</td>
<td>${log.ToAddress}</td>
<td>${log.Inbound}</td>
<td>${log.Outbound}</td>
${text}
</tr>
`;
}); });
return formattedLogs; return formattedLogs += "</table>";
}, },
hide() { hide() {
this.visible = false; this.visible = false;
}, },
}; };
const backupModal = { const backupModal = {
visible: false, visible: false,
show() { show() {
@ -1023,6 +1036,25 @@ ${dateTime}
await PromiseUtil.sleep(500); await PromiseUtil.sleep(500);
xraylogModal.loading = false; 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() { async openConfig() {
this.loading(true); this.loading(true);
const msg = await HttpUtil.get('/panel/api/server/getConfigJson'); const msg = await HttpUtil.get('/panel/api/server/getConfigJson');
@ -1071,7 +1103,6 @@ ${dateTime}
fileInput.click(); fileInput.click();
}, },
}, },
computed: {},
async mounted() { async mounted() {
if (window.location.protocol !== "https:") { if (window.location.protocol !== "https:") {
this.showAlert = true; this.showAlert = true;

View file

@ -174,6 +174,16 @@ type CPUSample struct {
Cpu float64 `json:"cpu"` // percent 0..100 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 { func getPublicIP(url string) string {
client := &http.Client{ client := &http.Client{
Timeout: 3 * time.Second, Timeout: 3 * time.Second,
@ -704,19 +714,25 @@ func (s *ServerService) GetXrayLogs(
showBlocked string, showBlocked string,
showProxy string, showProxy string,
freedoms []string, freedoms []string,
blackholes []string) []string { blackholes []string) []LogEntry {
const (
Direct = iota
Blocked
Proxied
)
countInt, _ := strconv.Atoi(count) countInt, _ := strconv.Atoi(count)
var lines []string var entries []LogEntry
pathToAccessLog, err := xray.GetAccessLogPath() pathToAccessLog, err := xray.GetAccessLogPath()
if err != nil { if err != nil {
return lines return nil
} }
file, err := os.Open(pathToAccessLog) file, err := os.Open(pathToAccessLog)
if err != nil { if err != nil {
return lines return nil
} }
defer file.Close() defer file.Close()
@ -735,37 +751,62 @@ func (s *ServerService) GetXrayLogs(
continue continue
} }
//adding suffixes to further distinguish entries by outbound var entry LogEntry
if hasSuffix(line, freedoms) { 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" { if showDirect == "false" {
continue continue
} }
line = line + " f" entry.Event = Direct
} else if hasSuffix(line, blackholes) { } else if logEntryContains(line, blackholes) {
if showBlocked == "false" { if showBlocked == "false" {
continue continue
} }
line = line + " b" entry.Event = Blocked
} else { } else {
if showProxy == "false" { if showProxy == "false" {
continue continue
} }
line = line + " p" entry.Event = Proxied
} }
lines = append(lines, line) entries = append(entries, entry)
} }
if len(lines) > countInt { if len(entries) > countInt {
lines = lines[len(lines)-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 { for _, sfx := range suffixes {
if strings.HasSuffix(line, sfx+"]") { if strings.Contains(line, sfx+"]") {
return true return true
} }
} }