mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-19 08:23:03 +00:00
Improved xray logs display handling (#3475)
* improved xray logs handling * fix download Xray Logs * Update index.html
This commit is contained in:
parent
299572a4c2
commit
2eb8abf61e
2 changed files with 135 additions and 63 deletions
|
@ -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>
|
||||||
|
@ -796,59 +794,74 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const xraylogModal = {
|
const xraylogModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
logs: [],
|
logs: [],
|
||||||
rows: 20,
|
rows: 20,
|
||||||
showDirect: true,
|
showDirect: true,
|
||||||
showBlocked: true,
|
showBlocked: true,
|
||||||
showProxy: true,
|
showProxy: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
show(logs) {
|
show(logs) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.logs = logs;
|
this.logs = logs;
|
||||||
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>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return formattedLogs;
|
formattedLogs += `
|
||||||
},
|
<tr ${outboundColor}>
|
||||||
hide() {
|
<td><b>${new Date(log.DateTime).toLocaleString()}</b></td>
|
||||||
this.visible = false;
|
<td>${log.FromAddress}</td>
|
||||||
},
|
<td>${log.ToAddress}</td>
|
||||||
};
|
<td>${log.Inbound}</td>
|
||||||
|
<td>${log.Outbound}</td>
|
||||||
|
${text}
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return formattedLogs += "</table>";
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue