Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfgsfds <4870330+fgsfds@users.noreply.github.com>2025-08-04 19:47:48 +0300
committerGitHub <noreply@github.com>2025-08-04 19:47:48 +0300
commit957f3dbb5449475f330d863171134b56e37da4ad (patch)
tree9dce77a898311da6ee98be18e7d7c12d383703c5 /web/html/index.html
parent05e60af283e4cb0e7c290b1fafd8f3d54028bda8 (diff)
Added xray access log viewer (#3309)
* added xray access log viewer * made modal window width adaptive * hide logs button if xray logs are disabled
Diffstat (limited to 'web/html/index.html')
-rw-r--r--web/html/index.html114
1 files changed, 112 insertions, 2 deletions
diff --git a/web/html/index.html b/web/html/index.html
index db678cd6..4089e6a6 100644
--- a/web/html/index.html
+++ b/web/html/index.html
@@ -167,7 +167,10 @@
<span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
</a-col>
<a-col>
- <a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-tag>
+ <a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-icon>
+ </a-col>
+ <a-col>
+ <a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openXrayLogs()"></a-icon>
</a-col>
</a-row>
</span>
@@ -179,6 +182,10 @@
</template>
</template>
<template #actions>
+ <a-space v-if="app.ipLimitEnable" direction="horizontal" @click="openXrayLogs()" :style="{ justifyContent: 'center' }">
+ <a-icon type="bars"></a-icon>
+ <span v-if="!isMobile">{{ i18n "pages.index.logs" }}</span>
+ </a-space>
<a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }">
<a-icon type="poweroff"></a-icon>
<span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span>
@@ -422,6 +429,40 @@
</a-form>
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div>
</a-modal>
+ <a-modal id="xraylog-modal"
+ v-model="xraylogModal.visible"
+ :closable="true" @cancel="() => xraylogModal.visible = false"
+ :class="themeSwitcher.currentTheme"
+ width="80vw"
+ footer="">
+ <template slot="title">
+ {{ i18n "pages.index.logs" }}
+ <a-icon :spin="xraylogModal.loading"
+ type="sync"
+ :style="{ verticalAlign: 'middle', marginLeft: '10px' }"
+ :disabled="xraylogModal.loading"
+ @click="openXrayLogs()">
+ </a-icon>
+ </template>
+ <a-form layout="inline">
+ <a-form-item :style="{ marginRight: '0.5rem' }">
+ <a-input-group compact>
+ <a-select size="small" v-model="xraylogModal.rows" :style="{ width: '70px' }"
+ @change="openXrayLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
+ <a-select-option value="10">10</a-select-option>
+ <a-select-option value="20">20</a-select-option>
+ <a-select-option value="50">50</a-select-option>
+ <a-select-option value="100">100</a-select-option>
+ <a-select-option value="500">500</a-select-option>
+ </a-select>
+ </a-input-group>
+ </a-form-item>
+ <a-form-item :style="{ float: 'right' }">
+ <a-button type="primary" icon="download" @click="FileManager.downloadTextFile(xraylogModal.logs?.join('\n'), 'x-ui.log')"></a-button>
+ </a-form-item>
+ </a-form>
+ <div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="xraylogModal.formattedLogs"></div>
+ </a-modal>
<a-modal id="backup-modal"
v-model="backupModal.visible"
title='{{ i18n "pages.index.backupTitle" }}'
@@ -606,6 +647,57 @@
},
};
+ const xraylogModal = {
+ visible: false,
+ logs: [],
+ rows: 20,
+ 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 = '';
+
+ logs.forEach((log, index) => {
+ if(index > 0) formattedLogs += '<br>';
+
+ const parts = log.split(' ');
+
+ if(parts.length === 9) {
+ const dateTime = `<b>${parts[0]} ${parts[1]}</b>`;
+ const from = `<b>${parts[3]}</b>`;
+ const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`;
+
+ let outboundColor = '';
+ if (parts[8].startsWith('blocked')) {
+ outboundColor = ' style="color: #e04141;"';
+ }
+ else if (!parts[8].startsWith('direct')) {
+ outboundColor = ' style="color: #3c89e8;"';
+ }
+
+ formattedLogs += `<span${outboundColor}>
+${dateTime}
+ ${parts[2]}
+ ${from}
+ ${parts[4]}
+ ${to}
+ ${parts.slice(6).join(' ')}
+</span>`;
+ } else {
+ formattedLogs += `<span>${parts.join(' ')}</span>`;
+ }
+ });
+
+ return formattedLogs;
+ },
+ hide() {
+ this.visible = false;
+ },
+ };
+
const backupModal = {
visible: false,
show() {
@@ -629,10 +721,12 @@
status: new Status(),
versionModal,
logModal,
+ xraylogModal,
backupModal,
loadingTip: '{{ i18n "loading"}}',
showAlert: false,
- showIp: false
+ showIp: false,
+ ipLimitEnable: false,
},
methods: {
loading(spinning, tip = '{{ i18n "loading"}}') {
@@ -721,6 +815,16 @@
await PromiseUtil.sleep(500);
logModal.loading = false;
},
+ async openXrayLogs(){
+ xraylogModal.loading = true;
+ const msg = await HttpUtil.post('server/xraylogs/'+xraylogModal.rows);
+ if (!msg.success) {
+ return;
+ }
+ xraylogModal.show(msg.obj);
+ await PromiseUtil.sleep(500);
+ xraylogModal.loading = false;
+ },
async openConfig() {
this.loading(true);
const msg = await HttpUtil.post('server/getConfigJson');
@@ -773,6 +877,12 @@
if (window.location.protocol !== "https:") {
this.showAlert = true;
}
+
+ const msg = await HttpUtil.post('/panel/setting/defaultSettings');
+ if (msg.success) {
+ this.ipLimitEnable = msg.obj.ipLimitEnable;
+ }
+
while (true) {
try {
await this.getStatus();