|
@@ -39,7 +39,7 @@
|
|
|
</template>
|
|
|
</a-tooltip>
|
|
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
|
|
- <a-button size="small" type="default" class="ml-8" @click="openCpuHistory()">
|
|
|
+ <a-button size="small" shape="circle" class="ml-8" @click="openCpuHistory()">
|
|
|
<a-icon type="history" />
|
|
|
</a-button>
|
|
|
</a-tooltip>
|
|
@@ -343,7 +343,7 @@
|
|
|
<a-form layout="inline">
|
|
|
<a-form-item class="mr-05">
|
|
|
<a-input-group compact>
|
|
|
- <a-select size="small" v-model="logModal.rows" class="w-70" @change="openLogs()"
|
|
|
+ <a-select size="small" v-model="logModal.rows" :style="{ width: '70px' }" @change="openLogs()"
|
|
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
|
<a-select-option value="10">10</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="500">500</a-select-option>
|
|
|
</a-select>
|
|
|
- <a-select size="small" v-model="logModal.level" class="w-95" @change="openLogs()"
|
|
|
+ <a-select size="small" v-model="logModal.level" :style="{ width: '95px' }" @change="openLogs()"
|
|
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
|
<a-select-option value="debug">Debug</a-select-option>
|
|
|
<a-select-option value="info">Info</a-select-option>
|
|
@@ -365,8 +365,7 @@
|
|
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
|
|
</a-form-item>
|
|
|
<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>
|
|
|
<div class="ant-input log-container" v-html="logModal.formattedLogs"></div>
|
|
@@ -382,7 +381,7 @@
|
|
|
<a-form layout="inline">
|
|
|
<a-form-item class="mr-05">
|
|
|
<a-input-group compact>
|
|
|
- <a-select size="small" v-model="xraylogModal.rows" class="w-70" @change="openXrayLogs()"
|
|
|
+ <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>
|
|
@@ -401,8 +400,7 @@
|
|
|
<a-checkbox v-model="xraylogModal.showProxy" @change="openXrayLogs()">Proxy</a-checkbox>
|
|
|
</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-button type="primary" icon="download" @click="downloadXrayLogs"></a-button>
|
|
|
</a-form-item>
|
|
|
</a-form>
|
|
|
<div class="ant-input log-container" v-html="xraylogModal.formattedLogs"></div>
|
|
@@ -431,7 +429,7 @@
|
|
|
@cancel="() => cpuHistoryModal.visible = false" :class="themeSwitcher.currentTheme" width="900px" footer="">
|
|
|
<template slot="title">
|
|
|
CPU History
|
|
|
- <a-select size="small" v-model="cpuHistoryModal.bucket" class="ml-10" style="width: 140px"
|
|
|
+ <a-select size="small" v-model="cpuHistoryModal.bucket" class="ml-10" style="width: 80px"
|
|
|
@change="fetchCpuHistoryBucket">
|
|
|
<a-select-option :value="2">2s</a-select-option>
|
|
|
<a-select-option :value="30">30s</a-select-option>
|
|
@@ -441,7 +439,7 @@
|
|
|
<a-select-option :value="300">5m</a-select-option>
|
|
|
</a-select>
|
|
|
</template>
|
|
|
- <div style="padding: 8px 0;">
|
|
|
+ <div style="padding:16px">
|
|
|
<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"
|
|
|
:max-points="cpuHistoryLong.length" :fill-opacity="0.18" :marker-radius="3.2" :show-tooltip="true" />
|
|
@@ -466,7 +464,7 @@
|
|
|
strokeWidth: { type: Number, default: 2 },
|
|
|
maxPoints: { type: Number, default: 120 },
|
|
|
showGrid: { type: Boolean, default: true },
|
|
|
- gridColor: { type: String, default: 'rgba(255,255,255,0.08)' },
|
|
|
+ gridColor: { type: String, default: 'rgba(0,0,0,0.1)' },
|
|
|
fillOpacity: { type: Number, default: 0.15 },
|
|
|
showMarker: { type: Boolean, default: true },
|
|
|
markerRadius: { type: Number, default: 2.8 },
|
|
@@ -540,7 +538,7 @@
|
|
|
const h = this.drawHeight
|
|
|
const w = this.drawWidth
|
|
|
// draw at 25%, 50%, 75%
|
|
|
- return [0.25, 0.5, 0.75]
|
|
|
+ return [0, 0.25, 0.5, 0.75, 1]
|
|
|
.map(r => Math.round(this.paddingTop + h * r))
|
|
|
.map(y => ({ x1: this.paddingLeft, y1: y, x2: this.paddingLeft + w, y2: y }))
|
|
|
},
|
|
@@ -606,7 +604,7 @@
|
|
|
},
|
|
|
},
|
|
|
template: `
|
|
|
- <svg width="100%" :height="height" :viewBox="viewBoxAttr" preserveAspectRatio="none" style="display:block"
|
|
|
+ <svg width="100%" :height="height" :viewBox="viewBoxAttr" preserveAspectRatio="none" class="idx-cpu-history-svg"
|
|
|
@mousemove="onMouseMove" @mouseleave="onMouseLeave">
|
|
|
<defs>
|
|
|
<linearGradient id="spkGrad" x1="0" y1="0" x2="0" y2="1">
|
|
@@ -615,16 +613,16 @@
|
|
|
</linearGradient>
|
|
|
</defs>
|
|
|
<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"/>
|
|
|
+ <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" />
|
|
|
</g>
|
|
|
<g v-if="showAxes">
|
|
|
<!-- Y ticks/labels -->
|
|
|
<g v-for="(t,i) in yTicks" :key="'y'+i">
|
|
|
- <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>
|
|
|
+ <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>
|
|
|
</g>
|
|
|
<!-- X ticks/labels -->
|
|
|
<g v-for="(t,i) in xTicks" :key="'x'+i">
|
|
|
- <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>
|
|
|
+ <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>
|
|
|
</g>
|
|
|
</g>
|
|
|
<path v-if="areaPath" :d="areaPath" fill="url(#spkGrad)" stroke="none" />
|
|
@@ -632,9 +630,9 @@
|
|
|
<circle v-if="showMarker && lastPoint" :cx="lastPoint[0]" :cy="lastPoint[1]" :r="markerRadius" :fill="stroke" />
|
|
|
<!-- Hover marker/tooltip -->
|
|
|
<g v-if="showTooltip && hoverIdx >= 0">
|
|
|
- <line :x1="pointsArr[hoverIdx][0]" :x2="pointsArr[hoverIdx][0]" :y1="paddingTop" :y2="paddingTop + drawHeight" stroke="rgba(255,255,255,0.25)" stroke-width="1" />
|
|
|
+ <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" />
|
|
|
<circle :cx="pointsArr[hoverIdx][0]" :cy="pointsArr[hoverIdx][1]" r="3.5" :fill="stroke" />
|
|
|
- <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>
|
|
|
+ <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>
|
|
|
</g>
|
|
|
</svg>
|
|
|
`,
|
|
@@ -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 = '';
|
|
|
-
|
|
|
- logs.forEach((log, index) => {
|
|
|
- if (index > 0) formattedLogs += '<br>';
|
|
|
+ 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 = `
|
|
|
+<style>
|
|
|
+ table {
|
|
|
+ border-collapse: collapse;
|
|
|
+ width: auto;
|
|
|
+ }
|
|
|
|
|
|
- const parts = log.split(' ');
|
|
|
+ table td, table th {
|
|
|
+ padding: 2px 15px;
|
|
|
+ }
|
|
|
+</style>
|
|
|
|
|
|
- if (parts.length === 10) {
|
|
|
- const dateTime = `<b>${parts[0]} ${parts[1]}</b>`;
|
|
|
- const from = `<b>${parts[3]}</b>`;
|
|
|
- const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`;
|
|
|
+<table>
|
|
|
+ <tr>
|
|
|
+ <th>Date</th>
|
|
|
+ <th>From</th>
|
|
|
+ <th>To</th>
|
|
|
+ <th>Inbound</th>
|
|
|
+ <th>Outbound</th>
|
|
|
+ <th>Email</th>
|
|
|
+ </tr>
|
|
|
+`;
|
|
|
|
|
|
+ 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 += `<span${outboundColor}>
|
|
|
-${dateTime}
|
|
|
- ${parts[2]}
|
|
|
- ${from}
|
|
|
- ${parts[4]}
|
|
|
- ${to}
|
|
|
- ${parts.slice(6, 9).join(' ')}
|
|
|
-</span>`;
|
|
|
- } else {
|
|
|
- formattedLogs += `<span>${log}</span>`;
|
|
|
- }
|
|
|
- });
|
|
|
+ let text = ``;
|
|
|
+ if (log.Email !== "") {
|
|
|
+ text = `<td>${log.Email}</td>`;
|
|
|
+ }
|
|
|
|
|
|
- return formattedLogs;
|
|
|
- },
|
|
|
- hide() {
|
|
|
- this.visible = false;
|
|
|
- },
|
|
|
- };
|
|
|
+ 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 += "</table>";
|
|
|
+ },
|
|
|
+ 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;
|