mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-10-27 02:24:40 +00:00
Compare commits
No commits in common. "db7e7dcd2956722a7c39306d59be55f4bc0c1a42" and "299572a4c23f9ea70c5008c86ec6ee1c02012f99" have entirely different histories.
db7e7dcd29
...
299572a4c2
6 changed files with 83 additions and 229 deletions
|
|
@ -1 +1 @@
|
||||||
2.8.1
|
2.8.0
|
||||||
2
web/assets/css/custom.min.css
vendored
2
web/assets/css/custom.min.css
vendored
File diff suppressed because one or more lines are too long
|
|
@ -568,7 +568,8 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}</td>
|
<td>{{ i18n "pages.inbounds.periodicTrafficResetTitle" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag color="blue">[[ dbInbound.trafficReset ]]</a-tag>
|
<a-tag color="blue">[[ i18n("pages.inbounds.periodicTrafficReset." +
|
||||||
|
dbInbound.trafficReset) ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<a-button size="small" shape="circle" class="ml-8" @click="openCpuHistory()">
|
<a-button size="small" type="default" class="ml-8" @click="openCpuHistory()">
|
||||||
<a-icon type="history" />
|
<a-icon type="history" />
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
@ -343,7 +343,7 @@
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item class="mr-05">
|
<a-form-item class="mr-05">
|
||||||
<a-input-group compact>
|
<a-input-group compact>
|
||||||
<a-select size="small" v-model="logModal.rows" :style="{ width: '70px' }" @change="openLogs()"
|
<a-select size="small" v-model="logModal.rows" class="w-70" @change="openLogs()"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
|
|
@ -351,7 +351,7 @@
|
||||||
<a-select-option value="100">100</a-select-option>
|
<a-select-option value="100">100</a-select-option>
|
||||||
<a-select-option value="500">500</a-select-option>
|
<a-select-option value="500">500</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select size="small" v-model="logModal.level" :style="{ width: '95px' }" @change="openLogs()"
|
<a-select size="small" v-model="logModal.level" class="w-95" @change="openLogs()"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
<a-select-option value="info">Info</a-select-option>
|
<a-select-option value="info">Info</a-select-option>
|
||||||
|
|
@ -365,7 +365,8 @@
|
||||||
<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" @click="FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')"></a-button>
|
<a-button type="primary" icon="download"
|
||||||
|
@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>
|
||||||
|
|
@ -381,7 +382,7 @@
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item class="mr-05">
|
<a-form-item class="mr-05">
|
||||||
<a-input-group compact>
|
<a-input-group compact>
|
||||||
<a-select size="small" v-model="xraylogModal.rows" :style="{ width: '70px' }" @change="openXrayLogs()"
|
<a-select size="small" v-model="xraylogModal.rows" class="w-70" @change="openXrayLogs()"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
|
|
@ -400,7 +401,8 @@
|
||||||
<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" @click="downloadXrayLogs"></a-button>
|
<a-button type="primary" icon="download"
|
||||||
|
@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>
|
||||||
|
|
@ -429,7 +431,7 @@
|
||||||
@cancel="() => cpuHistoryModal.visible = false" :class="themeSwitcher.currentTheme" width="900px" footer="">
|
@cancel="() => cpuHistoryModal.visible = false" :class="themeSwitcher.currentTheme" width="900px" footer="">
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
CPU History
|
CPU History
|
||||||
<a-select size="small" v-model="cpuHistoryModal.bucket" class="ml-10" style="width: 80px"
|
<a-select size="small" v-model="cpuHistoryModal.bucket" class="ml-10" style="width: 140px"
|
||||||
@change="fetchCpuHistoryBucket">
|
@change="fetchCpuHistoryBucket">
|
||||||
<a-select-option :value="2">2s</a-select-option>
|
<a-select-option :value="2">2s</a-select-option>
|
||||||
<a-select-option :value="30">30s</a-select-option>
|
<a-select-option :value="30">30s</a-select-option>
|
||||||
|
|
@ -439,7 +441,7 @@
|
||||||
<a-select-option :value="300">5m</a-select-option>
|
<a-select-option :value="300">5m</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
<div style="padding:16px">
|
<div style="padding: 8px 0;">
|
||||||
<sparkline :data="cpuHistoryLong" :labels="cpuHistoryLabels" :vb-width="840" :height="220"
|
<sparkline :data="cpuHistoryLong" :labels="cpuHistoryLabels" :vb-width="840" :height="220"
|
||||||
:stroke="status.cpu.color" :stroke-width="2.2" :show-grid="true" :show-axes="true" :tick-count-x="5"
|
:stroke="status.cpu.color" :stroke-width="2.2" :show-grid="true" :show-axes="true" :tick-count-x="5"
|
||||||
:max-points="cpuHistoryLong.length" :fill-opacity="0.18" :marker-radius="3.2" :show-tooltip="true" />
|
:max-points="cpuHistoryLong.length" :fill-opacity="0.18" :marker-radius="3.2" :show-tooltip="true" />
|
||||||
|
|
@ -464,7 +466,7 @@
|
||||||
strokeWidth: { type: Number, default: 2 },
|
strokeWidth: { type: Number, default: 2 },
|
||||||
maxPoints: { type: Number, default: 120 },
|
maxPoints: { type: Number, default: 120 },
|
||||||
showGrid: { type: Boolean, default: true },
|
showGrid: { type: Boolean, default: true },
|
||||||
gridColor: { type: String, default: 'rgba(0,0,0,0.1)' },
|
gridColor: { type: String, default: 'rgba(255,255,255,0.08)' },
|
||||||
fillOpacity: { type: Number, default: 0.15 },
|
fillOpacity: { type: Number, default: 0.15 },
|
||||||
showMarker: { type: Boolean, default: true },
|
showMarker: { type: Boolean, default: true },
|
||||||
markerRadius: { type: Number, default: 2.8 },
|
markerRadius: { type: Number, default: 2.8 },
|
||||||
|
|
@ -538,7 +540,7 @@
|
||||||
const h = this.drawHeight
|
const h = this.drawHeight
|
||||||
const w = this.drawWidth
|
const w = this.drawWidth
|
||||||
// draw at 25%, 50%, 75%
|
// draw at 25%, 50%, 75%
|
||||||
return [0, 0.25, 0.5, 0.75, 1]
|
return [0.25, 0.5, 0.75]
|
||||||
.map(r => Math.round(this.paddingTop + h * r))
|
.map(r => Math.round(this.paddingTop + h * r))
|
||||||
.map(y => ({ x1: this.paddingLeft, y1: y, x2: this.paddingLeft + w, y2: y }))
|
.map(y => ({ x1: this.paddingLeft, y1: y, x2: this.paddingLeft + w, y2: y }))
|
||||||
},
|
},
|
||||||
|
|
@ -604,7 +606,7 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<svg width="100%" :height="height" :viewBox="viewBoxAttr" preserveAspectRatio="none" class="idx-cpu-history-svg"
|
<svg width="100%" :height="height" :viewBox="viewBoxAttr" preserveAspectRatio="none" style="display:block"
|
||||||
@mousemove="onMouseMove" @mouseleave="onMouseLeave">
|
@mousemove="onMouseMove" @mouseleave="onMouseLeave">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="spkGrad" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="spkGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
|
@ -613,16 +615,16 @@
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<g v-if="showGrid">
|
<g v-if="showGrid">
|
||||||
<line v-for="(g,i) in gridLines" :key="i" :x1="g.x1" :y1="g.y1" :x2="g.x2" :y2="g.y2" :stroke="gridColor" stroke-width="1" class="cpu-grid-line" />
|
<line v-for="(g,i) in gridLines" :key="i" :x1="g.x1" :y1="g.y1" :x2="g.x2" :y2="g.y2" :stroke="gridColor" stroke-width="1"/>
|
||||||
</g>
|
</g>
|
||||||
<g v-if="showAxes">
|
<g v-if="showAxes">
|
||||||
<!-- Y ticks/labels -->
|
<!-- Y ticks/labels -->
|
||||||
<g v-for="(t,i) in yTicks" :key="'y'+i">
|
<g v-for="(t,i) in yTicks" :key="'y'+i">
|
||||||
<text class="cpu-grid-y-text" :x="Math.max(0, paddingLeft - 4)" :y="t.y + 4" text-anchor="end" font-size="10" fill="rgba(0,0,0,0.3)" v-text="t.label"></text>
|
<text :x="Math.max(0, paddingLeft - 4)" :y="t.y + 4" text-anchor="end" font-size="10" fill="rgba(200,200,200,0.8)" v-text="t.label"></text>
|
||||||
</g>
|
</g>
|
||||||
<!-- X ticks/labels -->
|
<!-- X ticks/labels -->
|
||||||
<g v-for="(t,i) in xTicks" :key="'x'+i">
|
<g v-for="(t,i) in xTicks" :key="'x'+i">
|
||||||
<text class="cpu-grid-x-text" :x="t.x" :y="paddingTop + drawHeight + 22" text-anchor="middle" font-size="10" fill="rgba(0,0,0,0.3)" v-text="t.label"></text>
|
<text :x="t.x" :y="paddingTop + drawHeight + 14" text-anchor="middle" font-size="10" fill="rgba(200,200,200,0.8)" v-text="t.label"></text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<path v-if="areaPath" :d="areaPath" fill="url(#spkGrad)" stroke="none" />
|
<path v-if="areaPath" :d="areaPath" fill="url(#spkGrad)" stroke="none" />
|
||||||
|
|
@ -630,9 +632,9 @@
|
||||||
<circle v-if="showMarker && lastPoint" :cx="lastPoint[0]" :cy="lastPoint[1]" :r="markerRadius" :fill="stroke" />
|
<circle v-if="showMarker && lastPoint" :cx="lastPoint[0]" :cy="lastPoint[1]" :r="markerRadius" :fill="stroke" />
|
||||||
<!-- Hover marker/tooltip -->
|
<!-- Hover marker/tooltip -->
|
||||||
<g v-if="showTooltip && hoverIdx >= 0">
|
<g v-if="showTooltip && hoverIdx >= 0">
|
||||||
<line class="cpu-grid-h-line" :x1="pointsArr[hoverIdx][0]" :x2="pointsArr[hoverIdx][0]" :y1="paddingTop" :y2="paddingTop + drawHeight" stroke="rgba(0,0,0,0.2)" stroke-width="1" />
|
<line :x1="pointsArr[hoverIdx][0]" :x2="pointsArr[hoverIdx][0]" :y1="paddingTop" :y2="paddingTop + drawHeight" stroke="rgba(255,255,255,0.25)" stroke-width="1" />
|
||||||
<circle :cx="pointsArr[hoverIdx][0]" :cy="pointsArr[hoverIdx][1]" r="3.5" :fill="stroke" />
|
<circle :cx="pointsArr[hoverIdx][0]" :cy="pointsArr[hoverIdx][1]" r="3.5" :fill="stroke" />
|
||||||
<text class="cpu-grid-text" :x="pointsArr[hoverIdx][0]" :y="paddingTop + 12" text-anchor="middle" font-size="11" fill="rgba(0,0,0,0.8)" v-text="fmtHoverText()"></text>
|
<text :x="pointsArr[hoverIdx][0]" :y="paddingTop + 12" text-anchor="middle" font-size="11" fill="#fff" style="paint-order: stroke; stroke: rgba(0,0,0,0.35); stroke-width: 3;" v-text="fmtHoverText()"></text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
`,
|
`,
|
||||||
|
|
@ -794,74 +796,59 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
table td, table th {
|
logs.forEach((log, index) => {
|
||||||
padding: 2px 15px;
|
if (index > 0) formattedLogs += '<br>';
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<table>
|
const parts = log.split(' ');
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
if (parts.length === 10) {
|
||||||
<th>From</th>
|
const dateTime = `<b>${parts[0]} ${parts[1]}</b>`;
|
||||||
<th>To</th>
|
const from = `<b>${parts[3]}</b>`;
|
||||||
<th>Inbound</th>
|
const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`;
|
||||||
<th>Outbound</th>
|
|
||||||
<th>Email</th>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
|
|
||||||
logs.reverse().forEach((log, index) => {
|
|
||||||
let outboundColor = '';
|
let outboundColor = '';
|
||||||
if (log.Event === 1) {
|
if (parts[9] === "b") {
|
||||||
outboundColor = ' style="color: #e04141;"'; //red for blocked
|
outboundColor = ' style="color: #e04141;"'; //red for blocked
|
||||||
}
|
}
|
||||||
else if (log.Event === 2) {
|
else if (parts[9] === "p") {
|
||||||
outboundColor = ' style="color: #3c89e8;"'; //blue for proxies
|
outboundColor = ' style="color: #3c89e8;"'; //blue for proxies
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = ``;
|
formattedLogs += `<span${outboundColor}>
|
||||||
if (log.Email !== "") {
|
${dateTime}
|
||||||
text = `<td>${log.Email}</td>`;
|
${parts[2]}
|
||||||
}
|
${from}
|
||||||
|
${parts[4]}
|
||||||
|
${to}
|
||||||
|
${parts.slice(6, 9).join(' ')}
|
||||||
|
</span>`;
|
||||||
|
} else {
|
||||||
|
formattedLogs += `<span>${log}</span>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
formattedLogs += `
|
return formattedLogs;
|
||||||
<tr ${outboundColor}>
|
},
|
||||||
<td><b>${new Date(log.DateTime).toLocaleString()}</b></td>
|
hide() {
|
||||||
<td>${log.FromAddress}</td>
|
this.visible = false;
|
||||||
<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() {
|
||||||
|
|
@ -1036,25 +1023,6 @@
|
||||||
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');
|
||||||
|
|
@ -1103,6 +1071,7 @@
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
{{ template "page/body_start" .}}
|
{{ template "page/body_start" .}}
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' login-app'">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' login-app'">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content class="under min-h-0">
|
<a-layout-content class="under min-h-100vh">
|
||||||
<div class="waves-header">
|
<div class="waves-header">
|
||||||
<div class="waves-inner-header"></div>
|
<div class="waves-inner-header"></div>
|
||||||
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<a-row type="flex" justify="center" align="middle" class="h-100 overflow-y-auto overflow-x-hidden">
|
<a-row type="flex" justify="center" align="middle" class="h-100 overflow-hidden-auto">
|
||||||
<a-col :xs="22" :sm="12" :md="10" :lg="8" :xl="6" :xxl="5" id="login" class="my-3rem">
|
<a-col :xs="22" :sm="12" :md="10" :lg="8" :xl="6" :xxl="5" id="login" class="my-3rem">
|
||||||
<template v-if="!loadingStates.fetched">
|
<template v-if="!loadingStates.fetched">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|
@ -184,80 +184,5 @@
|
||||||
newWord.classList.add('is-visible');
|
newWord.classList.add('is-visible');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const pm_input_selector = 'input.ant-input, textarea.ant-input';
|
|
||||||
const pm_strip_props = [
|
|
||||||
'background',
|
|
||||||
'background-color',
|
|
||||||
'background-image',
|
|
||||||
'color'
|
|
||||||
];
|
|
||||||
|
|
||||||
const pm_observed_forms = new WeakSet();
|
|
||||||
|
|
||||||
function pm_strip_inline(el) {
|
|
||||||
if (!el || el.nodeType !== 1 || !el.matches?.(pm_input_selector)) return;
|
|
||||||
|
|
||||||
let did_change = false;
|
|
||||||
for (const prop of pm_strip_props) {
|
|
||||||
if (el.style.getPropertyValue(prop)) {
|
|
||||||
el.style.removeProperty(prop);
|
|
||||||
did_change = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (did_change && el.style.length === 0) {
|
|
||||||
el.removeAttribute('style');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function pm_attach_observer(form) {
|
|
||||||
if (pm_observed_forms.has(form)) return;
|
|
||||||
pm_observed_forms.add(form);
|
|
||||||
|
|
||||||
form.querySelectorAll(pm_input_selector).forEach(pm_strip_inline);
|
|
||||||
|
|
||||||
const pm_mo = new MutationObserver(mutations => {
|
|
||||||
for (const m of mutations) {
|
|
||||||
if (m.type === 'attributes') {
|
|
||||||
pm_strip_inline(m.target);
|
|
||||||
} else if (m.type === 'childList') {
|
|
||||||
for (const n of m.addedNodes) {
|
|
||||||
if (n.nodeType !== 1) continue;
|
|
||||||
if (n.matches?.(pm_input_selector)) pm_strip_inline(n);
|
|
||||||
n.querySelectorAll?.(pm_input_selector).forEach(pm_strip_inline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pm_mo.observe(form, {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ['style'],
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function pm_init() {
|
|
||||||
document.querySelectorAll('form.ant-form').forEach(pm_attach_observer);
|
|
||||||
const pm_host = document.getElementById('login') || document.body;
|
|
||||||
const pm_wait_for_forms = new MutationObserver(mutations => {
|
|
||||||
for (const m of mutations) {
|
|
||||||
for (const n of m.addedNodes) {
|
|
||||||
if (n.nodeType !== 1) continue;
|
|
||||||
if (n.matches?.('form.ant-form')) pm_attach_observer(n);
|
|
||||||
n.querySelectorAll?.('form.ant-form').forEach(pm_attach_observer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
pm_wait_for_forms.observe(pm_host, { childList: true, subtree: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', pm_init, { once: true });
|
|
||||||
} else {
|
|
||||||
pm_init();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{{ template "page/body_end" .}}
|
{{ template "page/body_end" .}}
|
||||||
|
|
@ -174,16 +174,6 @@ 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,
|
||||||
|
|
@ -714,25 +704,19 @@ func (s *ServerService) GetXrayLogs(
|
||||||
showBlocked string,
|
showBlocked string,
|
||||||
showProxy string,
|
showProxy string,
|
||||||
freedoms []string,
|
freedoms []string,
|
||||||
blackholes []string) []LogEntry {
|
blackholes []string) []string {
|
||||||
|
|
||||||
const (
|
|
||||||
Direct = iota
|
|
||||||
Blocked
|
|
||||||
Proxied
|
|
||||||
)
|
|
||||||
|
|
||||||
countInt, _ := strconv.Atoi(count)
|
countInt, _ := strconv.Atoi(count)
|
||||||
var entries []LogEntry
|
var lines []string
|
||||||
|
|
||||||
pathToAccessLog, err := xray.GetAccessLogPath()
|
pathToAccessLog, err := xray.GetAccessLogPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(pathToAccessLog)
|
file, err := os.Open(pathToAccessLog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return lines
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
|
@ -751,62 +735,37 @@ func (s *ServerService) GetXrayLogs(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry LogEntry
|
//adding suffixes to further distinguish entries by outbound
|
||||||
parts := strings.Fields(line)
|
if hasSuffix(line, freedoms) {
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
entry.Event = Direct
|
line = line + " f"
|
||||||
} else if logEntryContains(line, blackholes) {
|
} else if hasSuffix(line, blackholes) {
|
||||||
if showBlocked == "false" {
|
if showBlocked == "false" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
entry.Event = Blocked
|
line = line + " b"
|
||||||
} else {
|
} else {
|
||||||
if showProxy == "false" {
|
if showProxy == "false" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
entry.Event = Proxied
|
line = line + " p"
|
||||||
}
|
}
|
||||||
|
|
||||||
entries = append(entries, entry)
|
lines = append(lines, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entries) > countInt {
|
if len(lines) > countInt {
|
||||||
entries = entries[len(entries)-countInt:]
|
lines = lines[len(lines)-countInt:]
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func logEntryContains(line string, suffixes []string) bool {
|
func hasSuffix(line string, suffixes []string) bool {
|
||||||
for _, sfx := range suffixes {
|
for _, sfx := range suffixes {
|
||||||
if strings.Contains(line, sfx+"]") {
|
if strings.HasSuffix(line, sfx+"]") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue