mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-03-03 01:33:33 +03:00

* UI Improvements Better Table Update QR Code Modal Better Info Modal Compression HTML files Better Dropdown Menu Better Calendar and more .. Remove files Minor Fixes
660 lines
26 KiB
HTML
660 lines
26 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
{{template "head" .}}
|
|
<style>
|
|
@media (min-width: 769px) {
|
|
.ant-layout-content {
|
|
margin: 24px 16px;
|
|
}
|
|
.ant-card-hoverable {
|
|
margin-inline: 0.3rem;
|
|
}
|
|
.ant-alert-error {
|
|
margin-inline: 0.3rem;
|
|
}
|
|
}
|
|
.ant-col-sm-24 {
|
|
margin-top: 10px;
|
|
}
|
|
.ant-card-dark h2 {
|
|
color: var(--dark-color-text-primary);
|
|
}
|
|
</style>
|
|
|
|
<body>
|
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
|
{{ template "commonSider" . }}
|
|
<a-layout id="content-layout">
|
|
<a-layout-content>
|
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip">
|
|
<transition name="list" appear>
|
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
|
message='{{ i18n "secAlertTitle" }}'
|
|
color="red"
|
|
description='{{ i18n "secAlertSsl" }}'
|
|
show-icon closable>
|
|
</a-alert>
|
|
</transition>
|
|
<transition name="list" appear>
|
|
<a-row>
|
|
<a-card hoverable>
|
|
<a-row>
|
|
<a-col :sm="24" :md="12">
|
|
<a-row>
|
|
<a-col :span="12" style="text-align: center">
|
|
<a-progress type="dashboard" status="normal"
|
|
:stroke-color="status.cpu.color"
|
|
:percent="status.cpu.percent"></a-progress>
|
|
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
|
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
|
</a-col>
|
|
<a-col :span="12" style="text-align: center">
|
|
<a-progress type="dashboard" status="normal"
|
|
:stroke-color="status.mem.color"
|
|
:percent="status.mem.percent"></a-progress>
|
|
<div>
|
|
<b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
|
</div>
|
|
</a-col>
|
|
</a-row>
|
|
</a-col>
|
|
<a-col :sm="24" :md="12">
|
|
<a-row>
|
|
<a-col :span="12" style="text-align: center">
|
|
<a-progress type="dashboard" status="normal"
|
|
:stroke-color="status.swap.color"
|
|
:percent="status.swap.percent"></a-progress>
|
|
<div>
|
|
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
|
</div>
|
|
</a-col>
|
|
<a-col :span="12" style="text-align: center">
|
|
<a-progress type="dashboard" status="normal"
|
|
:stroke-color="status.disk.color"
|
|
:percent="status.disk.percent"></a-progress>
|
|
<div>
|
|
<b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
|
</div>
|
|
</a-col>
|
|
</a-row>
|
|
</a-col>
|
|
</a-row>
|
|
</a-card>
|
|
</a-row>
|
|
</transition>
|
|
<transition name="list" appear>
|
|
<a-row>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<b>3X-UI:</b>
|
|
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
|
<a rel="noopener" href="https://t.me/XrayUI" target="_blank"><a-tag color="green">@XrayUI</a-tag></a>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
|
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
|
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
|
|
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]] </a-tag>
|
|
<a-popover v-if="status.xray.state === State.Error" :overlay-class-name="themeSwitcher.currentTheme">
|
|
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
|
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
|
</span>
|
|
<template slot="content">
|
|
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
|
</template>
|
|
<a-icon type="question-circle"></a-icon>
|
|
</a-popover>
|
|
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
|
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
|
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<b>{{ i18n "menu.link" }}:</b>
|
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
|
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
|
<a-tag color="green">
|
|
<a-tooltip>
|
|
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
|
<template slot="title">
|
|
{{ i18n "pages.index.systemLoadDesc" }}
|
|
</template>
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<b>{{ i18n "usage"}}:</b>
|
|
<a-tag color="green"> RAM: [[ sizeFormat(status.appStats.mem) ]] </a-tag>
|
|
<a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<a-row>
|
|
<a-col :span="12">
|
|
<a-tag>
|
|
<a-tooltip>
|
|
<a-icon type="global"></a-icon> IPv4
|
|
<template slot="title">
|
|
[[ status.publicIP.ipv4 ]]
|
|
</template>
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-tag>
|
|
<a-tooltip>
|
|
<a-icon type="global"></a-icon> IPv6
|
|
<template slot="title">
|
|
[[ status.publicIP.ipv6 ]]
|
|
</template>
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-col>
|
|
</a-row>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<a-row>
|
|
<a-col :span="12">
|
|
<a-tag>
|
|
<a-tooltip>
|
|
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
|
<template slot="title">
|
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
|
</template>
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-tag>
|
|
<a-tooltip>
|
|
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
|
<template slot="title">
|
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
|
</template>
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-col>
|
|
</a-row>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<a-row>
|
|
<a-col :span="12">
|
|
<a-tag>
|
|
<a-tooltip>
|
|
<a-icon type="arrow-up"></a-icon> Up: [[ sizeFormat(status.netIO.up) ]]/s
|
|
<template slot="title">
|
|
{{ i18n "pages.index.upSpeed" }}
|
|
</template>
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-tag>
|
|
<a-tooltip>
|
|
<a-icon type="arrow-down"></a-icon> Down: [[ sizeFormat(status.netIO.down) ]]/s
|
|
<template slot="title">
|
|
{{ i18n "pages.index.downSpeed" }}
|
|
</template>
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-col>
|
|
</a-row>
|
|
</a-card>
|
|
</a-col>
|
|
<a-col :sm="24" :lg="12">
|
|
<a-card hoverable>
|
|
<a-row>
|
|
<a-col :span="12">
|
|
<a-tag>
|
|
<a-tooltip>
|
|
<a-icon type="cloud-upload"></a-icon>
|
|
<template slot="title">
|
|
{{ i18n "pages.index.totalSent" }}
|
|
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-tag>
|
|
<a-tooltip>
|
|
<a-icon type="cloud-download"></a-icon>
|
|
<template slot="title">
|
|
{{ i18n "pages.index.totalReceive" }}
|
|
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
|
|
</a-tooltip>
|
|
</a-tag>
|
|
</a-col>
|
|
</a-row>
|
|
</a-card>
|
|
</a-col>
|
|
</a-row>
|
|
</transition>
|
|
</a-spin>
|
|
</a-layout-content>
|
|
</a-layout>
|
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
|
|
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
|
|
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
|
|
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
|
|
<template v-for="version, index in versionModal.versions">
|
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'" style="margin-right: 12px; margin-bottom: 12px"
|
|
@click="switchV2rayVersion(version)">
|
|
[[ version ]]
|
|
</a-tag>
|
|
</template>
|
|
</a-modal>
|
|
<a-modal id="log-modal" v-model="logModal.visible"
|
|
:closable="true" @cancel="() => logModal.visible = false"
|
|
:class="themeSwitcher.currentTheme"
|
|
width="800px" footer="">
|
|
<template slot="title">
|
|
{{ i18n "pages.index.logs" }}
|
|
<a-icon :spin="logModal.loading"
|
|
type="sync"
|
|
style="vertical-align: middle; margin-left: 10px;"
|
|
:disabled="logModal.loading"
|
|
@click="openLogs()">
|
|
</a-icon>
|
|
</template>
|
|
<a-form layout="inline">
|
|
<a-form-item style="margin-right: 0.5rem;">
|
|
<a-input-group compact>
|
|
<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>
|
|
<a-select-option value="50">50</a-select-option>
|
|
<a-select-option value="100">100</a-select-option>
|
|
</a-select>
|
|
<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>
|
|
<a-select-option value="notice">Notice</a-select-option>
|
|
<a-select-option value="warning">Warning</a-select-option>
|
|
<a-select-option value="err">Error</a-select-option>
|
|
</a-select>
|
|
</a-input-group>
|
|
</a-form-item>
|
|
<a-form-item>
|
|
<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"
|
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs?.join('\n'))" download="x-ui.log">
|
|
</a-button>
|
|
</a-form-item>
|
|
</a-form>
|
|
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logModal.formattedLogs"></div>
|
|
</a-modal>
|
|
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
|
:closable="true" footer=""
|
|
:class="themeSwitcher.currentTheme">
|
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
|
:message="backupModal.description"
|
|
show-icon>
|
|
</a-alert>
|
|
<a-space direction="horizontal" style="text-align: center; margin-bottom: 10px;">
|
|
<a-button type="primary" @click="exportDatabase()">
|
|
[[ backupModal.exportText ]]
|
|
</a-button>
|
|
<a-button type="primary" @click="importDatabase()">
|
|
[[ backupModal.importText ]]
|
|
</a-button>
|
|
</a-space>
|
|
</a-modal>
|
|
</a-layout>
|
|
{{template "js" .}}
|
|
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
|
|
{{template "component/themeSwitcher" .}}
|
|
{{template "textModal"}}
|
|
<script>
|
|
const State = {
|
|
Running: "running",
|
|
Stop: "stop",
|
|
Error: "error",
|
|
}
|
|
Object.freeze(State);
|
|
|
|
class CurTotal {
|
|
|
|
constructor(current, total) {
|
|
this.current = current;
|
|
this.total = total;
|
|
}
|
|
|
|
get percent() {
|
|
if (this.total === 0) {
|
|
return 0;
|
|
}
|
|
return toFixed(this.current / this.total * 100, 2);
|
|
}
|
|
|
|
get color() {
|
|
const percent = this.percent;
|
|
if (percent < 80) {
|
|
return '#008771'; // Green
|
|
} else if (percent < 90) {
|
|
return "#f37b24"; // Orange
|
|
} else {
|
|
return "#cf3c3c"; // Red
|
|
}
|
|
}
|
|
}
|
|
|
|
class Status {
|
|
constructor(data) {
|
|
this.cpu = new CurTotal(0, 0);
|
|
this.cpuCores = 0;
|
|
this.cpuSpeedMhz = 0;
|
|
this.disk = new CurTotal(0, 0);
|
|
this.loads = [0, 0, 0];
|
|
this.mem = new CurTotal(0, 0);
|
|
this.netIO = { up: 0, down: 0 };
|
|
this.netTraffic = { sent: 0, recv: 0 };
|
|
this.publicIP = { ipv4: 0, ipv6: 0 };
|
|
this.swap = new CurTotal(0, 0);
|
|
this.tcpCount = 0;
|
|
this.udpCount = 0;
|
|
this.uptime = 0;
|
|
this.appUptime = 0;
|
|
this.appStats = {threads: 0, mem: 0, uptime: 0};
|
|
this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" };
|
|
|
|
if (data == null) {
|
|
return;
|
|
}
|
|
this.cpu = new CurTotal(data.cpu, 100);
|
|
this.cpuCores = data.cpuCores;
|
|
this.cpuSpeedMhz = data.cpuSpeedMhz;
|
|
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
|
this.loads = data.loads.map(load => toFixed(load, 2));
|
|
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
|
this.netIO = data.netIO;
|
|
this.netTraffic = data.netTraffic;
|
|
this.publicIP = data.publicIP;
|
|
this.swap = new CurTotal(data.swap.current, data.swap.total);
|
|
this.tcpCount = data.tcpCount;
|
|
this.udpCount = data.udpCount;
|
|
this.uptime = data.uptime;
|
|
this.appUptime = data.appUptime;
|
|
this.appStats = data.appStats;
|
|
this.xray = data.xray;
|
|
switch (this.xray.state) {
|
|
case State.Running:
|
|
this.xray.color = "green";
|
|
break;
|
|
case State.Stop:
|
|
this.xray.color = "orange";
|
|
break;
|
|
case State.Error:
|
|
this.xray.color = "red";
|
|
break;
|
|
default:
|
|
this.xray.color = "gray";
|
|
}
|
|
}
|
|
}
|
|
|
|
const versionModal = {
|
|
visible: false,
|
|
versions: [],
|
|
show(versions) {
|
|
this.visible = true;
|
|
this.versions = versions;
|
|
},
|
|
hide() {
|
|
this.visible = false;
|
|
},
|
|
};
|
|
|
|
const logModal = {
|
|
visible: false,
|
|
logs: [],
|
|
rows: 20,
|
|
level: 'info',
|
|
syslog: false,
|
|
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 levels = ["DEBUG","INFO","NOTICE","WARNING","ERROR"];
|
|
const levelColors = ["#3c89e8","#008771","#008771","#f37b24","#e04141","#bcbcbc"];
|
|
|
|
logs.forEach((log, index) => {
|
|
let [data, message] = log.split(" - ",2);
|
|
const parts = data.split(" ")
|
|
if(index>0) formattedLogs += '<br>';
|
|
|
|
if (parts.length === 3) {
|
|
const d = parts[0];
|
|
const t = parts[1];
|
|
const level = parts[2];
|
|
const levelIndex = levels.indexOf(level,levels) || 5;
|
|
|
|
//formattedLogs += `<span style="color: gray;">${index + 1}.</span>`;
|
|
formattedLogs += `<span style="color: ${levelColors[0]};">${d} ${t}</span> `;
|
|
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${level}</span>`;
|
|
} else {
|
|
const levelIndex = levels.indexOf(data,levels) || 5;
|
|
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${data}</span>`;
|
|
}
|
|
|
|
if(message){
|
|
if(message.startsWith("XRAY:"))
|
|
message = "<b>XRAY: </b>" + message.substring(5);
|
|
else
|
|
message = "<b>X-UI: </b>" + message;
|
|
}
|
|
|
|
formattedLogs += message ? ' - ' + message : '';
|
|
});
|
|
|
|
return formattedLogs;
|
|
},
|
|
hide() {
|
|
this.visible = false;
|
|
},
|
|
};
|
|
|
|
const backupModal = {
|
|
visible: false,
|
|
title: '',
|
|
description: '',
|
|
exportText: '',
|
|
importText: '',
|
|
show({
|
|
title = '{{ i18n "pages.index.backupTitle" }}',
|
|
description = '{{ i18n "pages.index.backupDescription" }}',
|
|
exportText = '{{ i18n "pages.index.exportDatabase" }}',
|
|
importText = '{{ i18n "pages.index.importDatabase" }}',
|
|
}) {
|
|
this.title = title;
|
|
this.description = description;
|
|
this.exportText = exportText;
|
|
this.importText = importText;
|
|
this.visible = true;
|
|
},
|
|
hide() {
|
|
this.visible = false;
|
|
},
|
|
};
|
|
|
|
const app = new Vue({
|
|
delimiters: ['[[', ']]'],
|
|
el: '#app',
|
|
data: {
|
|
siderDrawer,
|
|
themeSwitcher,
|
|
status: new Status(),
|
|
versionModal,
|
|
logModal,
|
|
backupModal,
|
|
spinning: false,
|
|
loadingTip: '{{ i18n "loading"}}',
|
|
showAlert: false,
|
|
},
|
|
methods: {
|
|
loading(spinning, tip = '{{ i18n "loading"}}') {
|
|
this.spinning = spinning;
|
|
this.loadingTip = tip;
|
|
},
|
|
async getStatus() {
|
|
try {
|
|
const msg = await HttpUtil.post('/server/status');
|
|
if (msg.success) {
|
|
this.setStatus(msg.obj);
|
|
}
|
|
} catch (e) {
|
|
console.error("Failed to get status:", e);
|
|
}
|
|
},
|
|
setStatus(data) {
|
|
this.status = new Status(data);
|
|
},
|
|
async openSelectV2rayVersion() {
|
|
this.loading(true);
|
|
const msg = await HttpUtil.post('server/getXrayVersion');
|
|
this.loading(false);
|
|
if (!msg.success) {
|
|
return;
|
|
}
|
|
versionModal.show(msg.obj);
|
|
},
|
|
switchV2rayVersion(version) {
|
|
this.$confirm({
|
|
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
|
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
|
okText: '{{ i18n "confirm"}}',
|
|
class: themeSwitcher.currentTheme,
|
|
cancelText: '{{ i18n "cancel"}}',
|
|
onOk: async () => {
|
|
versionModal.hide();
|
|
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
|
await HttpUtil.post(`/server/installXray/${version}`);
|
|
this.loading(false);
|
|
},
|
|
});
|
|
},
|
|
async stopXrayService() {
|
|
this.loading(true);
|
|
const msg = await HttpUtil.post('server/stopXrayService');
|
|
this.loading(false);
|
|
if (!msg.success) {
|
|
return;
|
|
}
|
|
},
|
|
async restartXrayService() {
|
|
this.loading(true);
|
|
const msg = await HttpUtil.post('server/restartXrayService');
|
|
this.loading(false);
|
|
if (!msg.success) {
|
|
return;
|
|
}
|
|
},
|
|
async openLogs(){
|
|
logModal.loading = true;
|
|
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
|
if (!msg.success) {
|
|
return;
|
|
}
|
|
logModal.show(msg.obj);
|
|
await PromiseUtil.sleep(500);
|
|
logModal.loading = false;
|
|
},
|
|
async openConfig() {
|
|
this.loading(true);
|
|
const msg = await HttpUtil.post('server/getConfigJson');
|
|
this.loading(false);
|
|
if (!msg.success) {
|
|
return;
|
|
}
|
|
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
|
|
},
|
|
openBackup() {
|
|
backupModal.show({
|
|
title: '{{ i18n "pages.index.backupTitle" }}',
|
|
description: '{{ i18n "pages.index.backupDescription" }}',
|
|
exportText: '{{ i18n "pages.index.exportDatabase" }}',
|
|
importText: '{{ i18n "pages.index.importDatabase" }}',
|
|
});
|
|
},
|
|
exportDatabase() {
|
|
window.location = basePath + 'server/getDb';
|
|
},
|
|
importDatabase() {
|
|
const fileInput = document.createElement('input');
|
|
fileInput.type = 'file';
|
|
fileInput.accept = '.db';
|
|
fileInput.addEventListener('change', async (event) => {
|
|
const dbFile = event.target.files[0];
|
|
if (dbFile) {
|
|
const formData = new FormData();
|
|
formData.append('db', dbFile);
|
|
backupModal.hide();
|
|
this.loading(true);
|
|
const uploadMsg = await HttpUtil.post('server/importDB', formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
}
|
|
});
|
|
this.loading(false);
|
|
if (!uploadMsg.success) {
|
|
return;
|
|
}
|
|
this.loading(true);
|
|
const restartMsg = await HttpUtil.post("/panel/setting/restartPanel");
|
|
this.loading(false);
|
|
if (restartMsg.success) {
|
|
this.loading(true);
|
|
await PromiseUtil.sleep(5000);
|
|
location.reload();
|
|
}
|
|
}
|
|
});
|
|
fileInput.click();
|
|
},
|
|
},
|
|
async mounted() {
|
|
if (window.location.protocol !== "https:") {
|
|
this.showAlert = true;
|
|
}
|
|
while (true) {
|
|
try {
|
|
await this.getStatus();
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
await PromiseUtil.sleep(2000);
|
|
}
|
|
},
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|