mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-20 07:45:48 +00:00
first commit
This commit is contained in:
17
web/html/common/head.html
Normal file
17
web/html/common/head.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{define "head"}}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
{{end}}
|
||||
20
web/html/common/js.html
Normal file
20
web/html/common/js.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{define "js"}}
|
||||
<script src="{{ .base_path }}assets/vue@2.6.12/vue.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/axios/axios.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/models.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
|
||||
<script>
|
||||
const basePath = '{{ .base_path }}';
|
||||
axios.defaults.baseURL = basePath;
|
||||
</script>
|
||||
{{end}}
|
||||
67
web/html/common/prompt_modal.html
Normal file
67
web/html/common/prompt_modal.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{{define "promptModel"}}
|
||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||
:ok-text="promptModal.okText" cancel-text="取消">
|
||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||
v-model="promptModal.value"
|
||||
:autosize="{minRows: 10, maxRows: 20}"
|
||||
@keydown.enter.native="promptModal.keyEnter"
|
||||
@keydown.ctrl.83="promptModal.ctrlS"></a-input>
|
||||
</a-modal>
|
||||
|
||||
<script>
|
||||
|
||||
const promptModal = {
|
||||
title: '',
|
||||
type: '',
|
||||
value: '',
|
||||
okText: '确定',
|
||||
visible: false,
|
||||
keyEnter(e) {
|
||||
if (this.type !== 'textarea') {
|
||||
e.preventDefault();
|
||||
this.ok();
|
||||
}
|
||||
},
|
||||
ctrlS(e) {
|
||||
if (this.type === 'textarea') {
|
||||
e.preventDefault();
|
||||
promptModal.confirm(promptModal.value);
|
||||
}
|
||||
},
|
||||
ok() {
|
||||
promptModal.close();
|
||||
promptModal.confirm(promptModal.value);
|
||||
},
|
||||
confirm() {},
|
||||
open({
|
||||
title='',
|
||||
type='text',
|
||||
value='',
|
||||
okText='确定',
|
||||
confirm=() => {},
|
||||
}) {
|
||||
this.title = title;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
promptModalApp.$nextTick(() => {
|
||||
document.querySelector('#prompt-modal-input').focus();
|
||||
});
|
||||
},
|
||||
close() {
|
||||
this.visible = false;
|
||||
}
|
||||
};
|
||||
|
||||
const promptModalApp = new Vue({
|
||||
el: '#prompt-modal',
|
||||
data: {
|
||||
promptModal: promptModal,
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
90
web/html/login/login.html
Normal file
90
web/html/login/login.html
Normal file
@@ -0,0 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{template "head" .}}
|
||||
<style>
|
||||
|
||||
#app {
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
margin: 20px 0 50px 0;
|
||||
}
|
||||
|
||||
.ant-btn, .ant-input {
|
||||
height: 50px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper .ant-input-prefix {
|
||||
left: 23px;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<a-layout id="app" v-cloak>
|
||||
<transition name="list" appear>
|
||||
<a-layout-content>
|
||||
<a-row type="flex" justify="center">
|
||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
||||
<h1>{{ .title }}</h1>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row type="flex" justify="center">
|
||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
||||
<a-form>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
||||
@keydown.enter.native="login" autofocus>
|
||||
<a-icon slot="prefix" type="user" style="color: rgba(0,0,0,.25)"/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input type="password" v-model.trim="user.password"
|
||||
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
||||
<a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-layout-content>
|
||||
</transition>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script>
|
||||
const leftColor = RandomUtil.randomIntRange(0x222222, 0xFFFFFF / 2).toString(16);
|
||||
const rightColor = RandomUtil.randomIntRange(0xFFFFFF / 2, 0xDDDDDD).toString(16);
|
||||
const deg = RandomUtil.randomIntRange(0, 360);
|
||||
const background = `linear-gradient(${deg}deg, #${leftColor} 10%, #${rightColor} 100%)`;
|
||||
document.querySelector('#app').style.background = background;
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
data: {
|
||||
loading: false,
|
||||
user: new User(),
|
||||
},
|
||||
methods: {
|
||||
async login() {
|
||||
this.loading = true;
|
||||
const msg = await HttpUtil.post('/login', this.user);
|
||||
this.loading = false;
|
||||
if (msg.success) {
|
||||
location.href = basePath + 'xui/';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
69
web/html/x-ui/common_sider.html
Normal file
69
web/html/x-ui/common_sider.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{{define "menuItems"}}
|
||||
<a-menu-item key="{{ .base_path }}xui/">
|
||||
<a-icon type="dashboard"></a-icon>
|
||||
<span>系统状态</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/accounts">
|
||||
<a-icon type="user"></a-icon>
|
||||
<span>账号列表</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/setting">
|
||||
<a-icon type="setting"></a-icon>
|
||||
<span>面板设置</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/clients">
|
||||
<a-icon type="laptop"></a-icon>
|
||||
<span>客户端</span>
|
||||
</a-menu-item>
|
||||
<a-sub-menu>
|
||||
<template slot="title">
|
||||
<a-icon type="link"></a-icon>
|
||||
<span>其他</span>
|
||||
</template>
|
||||
<a-menu-item key="https://github.com/sprov065/x-ui/">
|
||||
<a-icon type="github"></a-icon>
|
||||
<span>Github</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-menu-item key="{{ .base_path }}logout">
|
||||
<a-icon type="logout"></a-icon>
|
||||
<span>退出登录</span>
|
||||
</a-menu-item>
|
||||
{{end}}
|
||||
|
||||
|
||||
{{define "commonSider"}}
|
||||
<a-layout-sider id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||
<a-menu theme="dark" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||
{{template "menuItems" .}}
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
||||
@close="siderDrawer.close()"
|
||||
:visible="siderDrawer.visible" :wrap-style="{ padding: 0 }">
|
||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||
</div>
|
||||
<a-menu theme="light" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||
{{template "menuItems" .}}
|
||||
</a-menu>
|
||||
</a-drawer>
|
||||
<script>
|
||||
|
||||
const siderDrawer = {
|
||||
visible: false,
|
||||
show() {
|
||||
this.visible = true;
|
||||
},
|
||||
close() {
|
||||
this.visible = false;
|
||||
},
|
||||
change() {
|
||||
this.visible = !this.visible;
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
333
web/html/x-ui/index.html
Normal file
333
web/html/x-ui/index.html
Normal file
@@ -0,0 +1,333 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{template "head" .}}
|
||||
<style>
|
||||
.ant-layout-content {
|
||||
margin: 24px 16px;
|
||||
}
|
||||
|
||||
.ant-col-sm-24 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<a-layout id="app" v-cloak>
|
||||
{{ template "commonSider" . }}
|
||||
<a-layout id="content-layout">
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||
<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>CPU</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>
|
||||
内存: [[ 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>
|
||||
swap: [[ 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>
|
||||
硬盘: [[ 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" :md="12">
|
||||
<a-card hoverable>
|
||||
xray 状态:
|
||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||
<a-tooltip v-if="status.xray.stat === State.Error">
|
||||
<template slot="title">
|
||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-tag color="green" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||
<a-tag color="blue" @click="openSelectV2rayVersion">切换版本</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
运行时间:
|
||||
<a-tag color="#87d068">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
系统自启动以来的运行时间
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
系统负载: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
tcp / udp 连接数: [[ status.tcpCount ]] / [[ status.udpCount ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
所有网卡的总连接数
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-icon type="arrow-up"></a-icon>
|
||||
[[ sizeFormat(status.netIO.up) ]] / S
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
所有网卡的总上传速度
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-icon type="arrow-down"></a-icon>
|
||||
[[ sizeFormat(status.netIO.down) ]] / S
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
所有网卡的总下载速度
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-icon type="cloud-upload"></a-icon>
|
||||
[[ sizeFormat(status.netTraffic.sent) ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
系统启动以来所有网卡的总上传流量
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-icon type="cloud-download"></a-icon>
|
||||
[[ sizeFormat(status.netTraffic.recv) ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
系统启动以来所有网卡的总下载流量
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</transition>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
<a-modal id="version-modal" v-model="versionModal.visible" title="切换版本"
|
||||
:closable="true" @ok="() => versionModal.visible = false"
|
||||
ok-text="确定" cancel-text="取消">
|
||||
<h2>点击你想切换的版本</h2>
|
||||
<h2>请谨慎选择,旧版本可能配置不兼容</h2>
|
||||
<template v-for="version, index in versionModal.versions">
|
||||
<a-tag :color="index % 2 == 0 ? 'blue' : 'green'"
|
||||
style="margin: 10px" @click="switchV2rayVersion(version)">
|
||||
[[ version ]]
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-modal>
|
||||
</a-layout>
|
||||
</body>
|
||||
{{template "js" .}}
|
||||
<script>
|
||||
|
||||
const State = {
|
||||
Running: "running",
|
||||
Stop: "stop",
|
||||
Error: "error",
|
||||
}
|
||||
Object.freeze(State);
|
||||
|
||||
class CurTotal {
|
||||
current = 0
|
||||
total = 0
|
||||
|
||||
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 '#67C23A';
|
||||
} else if (percent < 90) {
|
||||
return '#E6A23C';
|
||||
} else {
|
||||
return '#F56C6C';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Status {
|
||||
cpu = new CurTotal(0, 0);
|
||||
disk = new CurTotal(0, 0);
|
||||
loads = [0, 0, 0];
|
||||
mem = new CurTotal(0, 0);
|
||||
netIO = {up: 0, down: 0};
|
||||
netTraffic = {sent: 0, recv: 0};
|
||||
swap = new CurTotal(0, 0);
|
||||
tcpCount = 0;
|
||||
udpCount = 0;
|
||||
uptime = 0;
|
||||
xray = {state: State.Stop, errorMsg: "", version: "", color: ""};
|
||||
|
||||
constructor(data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
this.cpu = new CurTotal(data.cpu, 100);
|
||||
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.swap = new CurTotal(data.swap.current, data.swap.total);
|
||||
this.tcpCount = data.tcpCount;
|
||||
this.udpCount = data.udpCount;
|
||||
this.uptime = data.uptime;
|
||||
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 app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
data: {
|
||||
status: new Status(),
|
||||
versionModal,
|
||||
spinning: false,
|
||||
loadingTip: '加载中',
|
||||
},
|
||||
methods: {
|
||||
loading(spinning, tip = '加载中') {
|
||||
this.spinning = spinning;
|
||||
this.loadingTip = tip;
|
||||
},
|
||||
async getStatus() {
|
||||
const msg = await HttpUtil.post('/server/status');
|
||||
if (msg.success) {
|
||||
this.setStatus(msg.obj);
|
||||
}
|
||||
},
|
||||
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: '切换 xray 版本',
|
||||
content: '是否切换 xray 版本至' + ` ${version}?`,
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
versionModal.hide();
|
||||
this.loading(true, '安装中,请不要刷新此页面');
|
||||
await HttpUtil.post(`/server/installXray/${version}`);
|
||||
this.loading(false);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
while (true) {
|
||||
try {
|
||||
await this.getStatus();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
await PromiseUtil.sleep(2000);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
</html>
|
||||
Reference in New Issue
Block a user