diff --git a/web/html/index.html b/web/html/index.html
index c7884abb..cb0f13eb 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(/^\/+/, "")}`;
+
+
+ | Date |
+ From |
+ To |
+ Inbound |
+ Outbound |
+ Email |
+
+`;
+ 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 = `${log.Email} | `;
+ }
- return formattedLogs;
- },
- hide() {
- this.visible = false;
- },
- };
+ formattedLogs += `
+
+ | ${new Date(log.DateTime).toLocaleString()} |
+ ${log.FromAddress} |
+ ${log.ToAddress} |
+ ${log.Inbound} |
+ ${log.Outbound} |
+ ${text}
+
+`;
+ });
+ return formattedLogs += "
";
+ },
+ 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
}
}