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:
Diffstat (limited to 'web/html/index.html')
-rw-r--r--web/html/index.html752
1 files changed, 752 insertions, 0 deletions
diff --git a/web/html/index.html b/web/html/index.html
new file mode 100644
index 00000000..1be7df13
--- /dev/null
+++ b/web/html/index.html
@@ -0,0 +1,752 @@
+<!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);
+ }
+ .ant-backup-list-item {
+ gap: 10px;
+ }
+ .ant-xray-version-list-item {
+ --padding: 12px;
+ padding: var(--padding) !important;
+ gap: var(--padding);
+ }
+ .dark .ant-backup-list-item svg,
+ .dark .ant-badge-status-text,
+ .dark .ant-card-extra {
+ color: var(--dark-color-text-primary);
+ }
+ .dark .ant-card-actions>li {
+ color: rgba(255, 255, 255, 0.55);
+ }
+ .dark .ant-radio-inner {
+ background-color: var(--dark-color-surface-100);
+ border-color: var(--dark-color-surface-600);
+ }
+ .dark .ant-radio-checked .ant-radio-inner {
+ border-color: var(--color-primary-100);
+ }
+ .dark .ant-backup-list,
+ .dark .ant-xray-version-list,
+ .dark .ant-card-actions,
+ .dark .ant-card-actions>li:not(:last-child) {
+ border-color: var(--dark-color-stroke);
+ }
+ .ant-card-actions {
+ background: transparent;
+ }
+ .ip-hidden {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ filter: blur(10px);
+ }
+ .running-animation .ant-badge-status-dot {
+ animation: runningAnimation 1.2s linear infinite;
+ }
+ .running-animation .ant-badge-status-processing:after {
+ border-color: var(--color-primary-100);
+ }
+ @keyframes runningAnimation {
+ 0%,
+ 50%,
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ 10% {
+ transform: scale(1.5);
+ opacity: .2;
+ }
+ }
+</style>
+
+<body>
+ <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
+ <a-sidebar></a-sidebar>
+ <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="{ marginBottom: '10px' }"
+ message='{{ i18n "secAlertTitle" }}'
+ color="red"
+ description='{{ i18n "secAlertSsl" }}'
+ show-icon closable>
+ </a-alert>
+ </transition>
+ <transition name="list" appear>
+ <template>
+ <a-row v-if="!status.isLoaded">
+ <a-card hoverable :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent' }">
+ <a-spin tip='{{ i18n "loading" }}'></a-spin>
+ </a-card>
+ </a-row>
+ <a-row v-else>
+ <a-row>
+ <a-card hoverable>
+ <a-row>
+ <a-col :sm="24" :md="12">
+ <a-row>
+ <a-col :span="12" :style="{ textAlign: 'center' }">
+ <a-progress type="dashboard" status="normal"
+ :stroke-color="status.cpu.color"
+ :percent="status.cpu.percent"></a-progress>
+ <div>
+ <b>{{ i18n "pages.index.cpu" }}:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]]
+ <a-tooltip>
+ <a-icon type="area-chart"></a-icon>
+ <template slot="title">
+ <div><b>{{ i18n "pages.index.logicalProcessors" }}:</b> [[ (status.logicalPro) ]]</div>
+ <div><b>{{ i18n "pages.index.frequency" }}:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
+ </template>
+ </a-tooltip>
+ </div>
+ </a-col>
+ <a-col :span="12" :style="{ textAlign: '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> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]]
+ </div>
+ </a-col>
+ </a-row>
+ </a-col>
+ <a-col :sm="24" :md="12">
+ <a-row>
+ <a-col :span="12" :style="{ textAlign: 'center' }">
+ <a-progress type="dashboard" status="normal"
+ :stroke-color="status.swap.color"
+ :percent="status.swap.percent"></a-progress>
+ <div>
+ <b>{{ i18n "pages.index.swap" }}:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]]
+ </div>
+ </a-col>
+ <a-col :span="12" :style="{ textAlign: 'center' }">
+ <a-progress type="dashboard" status="normal"
+ :stroke-color="status.disk.color"
+ :percent="status.disk.percent"></a-progress>
+ <div>
+ <b>{{ i18n "pages.index.storage"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]]
+ </div>
+ </a-col>
+ </a-row>
+ </a-col>
+ </a-row>
+ </a-card>
+ </a-row>
+ <a-col :sm="24" :lg="12">
+ <a-card hoverable>
+ <template #title>
+ <a-space direction="horizontal">
+ <span>{{ i18n "pages.index.xrayStatus" }}</span>
+ <a-tag v-if="isMobile && status.xray.version != 'Unknown'" color="green">
+ v[[ status.xray.version ]]
+ </a-tag>
+ </a-space>
+ </template>
+ <template #extra>
+ <template v-if="status.xray.state != 'error'">
+ <a-badge status="processing" class="running-animation" :text="status.xray.stateMsg" :color="status.xray.color"/>
+ </template>
+ <template v-else>
+ <a-popover :overlay-class-name="themeSwitcher.currentTheme">
+ <span slot="title">
+ <a-row type="flex" align="middle" justify="space-between">
+ <a-col>
+ <span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
+ </a-col>
+ <a-col>
+ <a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-tag>
+ </a-col>
+ </a-row>
+ </span>
+ <template slot="content">
+ <span :style="{ maxWidth: '400px' }" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</span>
+ </template>
+ <a-badge :text="status.xray.stateMsg" :color="status.xray.color"/>
+ </a-popover>
+ </template>
+ </template>
+ <template #actions>
+ <a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }">
+ <a-icon type="poweroff"></a-icon>
+ <span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span>
+ </a-space>
+ <a-space direction="horizontal" @click="restartXrayService" :style="{ justifyContent: 'center' }">
+ <a-icon type="reload"></a-icon>
+ <span v-if="!isMobile">{{ i18n "pages.index.restartXray" }}</span>
+ </a-space>
+ <a-space direction="horizontal" @click="openSelectV2rayVersion" :style="{ justifyContent: 'center' }">
+ <a-icon type="tool"></a-icon>
+ <span v-if="!isMobile">
+ [[ status.xray.version != 'Unknown' ? `v${status.xray.version}` : '{{ i18n "pages.index.xraySwitch" }}' ]]
+ </span>
+ </a-space>
+ </template>
+ </a-card>
+ </a-col>
+ <a-col :sm="24" :lg="12">
+ <a-card title='{{ i18n "menu.link" }}' hoverable>
+ <template #actions>
+ <a-space direction="horizontal" @click="openLogs()" :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="openConfig" :style="{ justifyContent: 'center' }">
+ <a-icon type="control"></a-icon>
+ <span v-if="!isMobile">{{ i18n "pages.index.config" }}</span>
+ </a-space>
+ <a-space direction="horizontal" @click="openBackup" :style="{ justifyContent: 'center' }">
+ <a-icon type="cloud-server"></a-icon>
+ <span v-if="!isMobile">{{ i18n "pages.index.backup" }}</span>
+ </a-space>
+ </template>
+ </a-card>
+ </a-col>
+ <a-col :sm="24" :lg="12">
+ <a-card title='3X-UI' hoverable>
+ <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 title='{{ i18n "pages.index.operationHours" }}' hoverable>
+ <a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
+ <a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
+ </a-card>
+ </a-col>
+ <a-col :sm="24" :lg="12">
+ <a-card title='{{ i18n "pages.index.systemLoad" }}' hoverable>
+ <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 title='{{ i18n "usage"}}' hoverable>
+ <a-tag color="green"> {{ i18n "pages.index.memory" }}: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag>
+ <a-tag color="green"> {{ i18n "pages.index.threads" }}: [[ status.appStats.threads ]] </a-tag>
+ </a-card>
+ </a-col>
+ <a-col :sm="24" :lg="12">
+ <a-card title='{{ i18n "pages.index.overallSpeed" }}' hoverable>
+ <a-row :gutter="isMobile ? [8,8] : 0">
+ <a-col :span="12">
+ <a-custom-statistic title='{{ i18n "pages.index.upload" }}' :value="SizeFormatter.sizeFormat(status.netIO.up)">
+ <template #prefix>
+ <a-icon type="arrow-up" />
+ </template>
+ <template #suffix>
+ /s
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ <a-col :span="12">
+ <a-custom-statistic title='{{ i18n "pages.index.download" }}' :value="SizeFormatter.sizeFormat(status.netIO.down)">
+ <template #prefix>
+ <a-icon type="arrow-down" />
+ </template>
+ <template #suffix>
+ /s
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ </a-row>
+ </a-card>
+ </a-col>
+ <a-col :sm="24" :lg="12">
+ <a-card title='{{ i18n "pages.index.totalData" }}' hoverable>
+ <a-row :gutter="isMobile ? [8,8] : 0">
+ <a-col :span="12">
+ <a-custom-statistic title='{{ i18n "pages.index.sent" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.sent)">
+ <template #prefix>
+ <a-icon type="cloud-upload" />
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ <a-col :span="12">
+ <a-custom-statistic title='{{ i18n "pages.index.received" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.recv)">
+ <template #prefix>
+ <a-icon type="cloud-download" />
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ </a-row>
+ </a-card>
+ </a-col>
+ <a-col :sm="24" :lg="12">
+ <a-card title='{{ i18n "pages.index.ipAddresses" }}' hoverable>
+ <template #extra>
+ <a-tooltip>
+ <template #title>
+ {{ i18n "pages.index.toggleIpVisibility" }}
+ </template>
+ <a-icon :type="showIp ? 'eye' : 'eye-invisible'" :style="{ fontSize: '1rem' }" @click="showIp = !showIp"></a-icon>
+ </a-tooltip>
+ </template>
+ <a-row :class="showIp ? 'ip-visible' : 'ip-hidden'" :gutter="isMobile ? [8,8] : 0">
+ <a-col :span="isMobile ? 24 : 12">
+ <a-custom-statistic title="IPv4" :value="status.publicIP.ipv4">
+ <template #prefix>
+ <a-icon type="global" />
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ <a-col :span="isMobile ? 24 : 12">
+ <a-custom-statistic title="IPv6" :value="status.publicIP.ipv6">
+ <template #prefix>
+ <a-icon type="global" />
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ </a-row>
+ </a-card>
+ </a-col>
+ <a-col :sm="24" :lg="12">
+ <a-card title='{{ i18n "pages.index.connectionCount" }}' hoverable>
+ <a-row :gutter="isMobile ? [8,8] : 0">
+ <a-col :span="12">
+ <a-custom-statistic title="TCP" :value="status.tcpCount">
+ <template #prefix>
+ <a-icon type="swap" />
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ <a-col :span="12">
+ <a-custom-statistic title="UDP" :value="status.udpCount">
+ <template #prefix>
+ <a-icon type="swap" />
+ </template>
+ </a-custom-statistic>
+ </a-col>
+ </a-row>
+ </a-card>
+ </a-col>
+ </a-row>
+ </template>
+ </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="{ marginBottom: '12px', width: '100%' }"
+ message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
+ <a-list class="ant-xray-version-list" bordered :style="{ width: '100%' }">
+ <a-list-item class="ant-xray-version-list-item" v-for="version, index in versionModal.versions">
+ <a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ version ]]</a-tag>
+ <a-radio :class="themeSwitcher.currentTheme" :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
+ </a-list-item>
+ </a-list>
+ </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="{ verticalAlign: 'middle', marginLeft: '10px' }"
+ :disabled="logModal.loading"
+ @click="openLogs()">
+ </a-icon>
+ </template>
+ <a-form layout="inline">
+ <a-form-item :style="{ marginRight: '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-option value="500">500</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', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div>
+ </a-modal>
+ <a-modal id="backup-modal"
+ v-model="backupModal.visible"
+ title='{{ i18n "pages.index.backupTitle" }}'
+ :closable="true"
+ footer=""
+ :class="themeSwitcher.currentTheme">
+ <a-list class="ant-backup-list" bordered :style="{ width: '100%' }">
+ <a-list-item class="ant-backup-list-item">
+ <a-list-item-meta>
+ <template #title>{{ i18n "pages.index.exportDatabase" }}</template>
+ <template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template>
+ </a-list-item-meta>
+ <a-button @click="exportDatabase()" type="primary" icon="download"/>
+ </a-list-item>
+ <a-list-item class="ant-backup-list-item">
+ <a-list-item-meta>
+ <template #title>{{ i18n "pages.index.importDatabase" }}</template>
+ <template #description>{{ i18n "pages.index.importDatabaseDesc" }}</template>
+ </a-list-item-meta>
+ <a-button @click="importDatabase()" type="primary" icon="upload" />
+ </a-list-item>
+ </a-list>
+ </a-modal>
+ </a-layout>
+{{template "js" .}}
+{{template "component/aSidebar" .}}
+{{template "component/aThemeSwitch" .}}
+{{template "component/aCustomStatistic" .}}
+{{template "modals/textModal"}}
+<script>
+ class CurTotal {
+
+ constructor(current, total) {
+ this.current = current;
+ this.total = total;
+ }
+
+ get percent() {
+ if (this.total === 0) {
+ return 0;
+ }
+ return NumberFormatter.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, isLoaded = false) {
+ this.cpu = new CurTotal(0, 0);
+ this.cpuCores = 0;
+ this.logicalPro = 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: 'stop', stateMsg: "", errorMsg: "", version: "", color: "" };
+
+ if (data == null) {
+ return;
+ }
+
+ this.isLoaded = isLoaded;
+ this.cpu = new CurTotal(data.cpu, 100);
+ this.cpuCores = data.cpuCores;
+ this.logicalPro = data.logicalPro;
+ this.cpuSpeedMhz = data.cpuSpeedMhz;
+ this.disk = new CurTotal(data.disk.current, data.disk.total);
+ this.loads = data.loads.map(load => NumberFormatter.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 'running':
+ this.xray.color = "green";
+ this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusRunning" }}';
+ break;
+ case 'stop':
+ this.xray.color = "orange";
+ this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusStop" }}';
+ break;
+ case 'error':
+ this.xray.color = "red";
+ this.xray.stateMsg ='{{ i18n "pages.index.xrayStatusError" }}';
+ break;
+ default:
+ this.xray.color = "gray";
+ this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusUnknown" }}';
+ break;
+ }
+ }
+ }
+
+ 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,
+ show() {
+ this.visible = true;
+ },
+ hide() {
+ this.visible = false;
+ },
+ };
+
+ const app = new Vue({
+ delimiters: ['[[', ']]'],
+ el: '#app',
+ mixins: [MediaQueryMixin],
+ data: {
+ themeSwitcher,
+ status: new Status(),
+ versionModal,
+ logModal,
+ backupModal,
+ spinning: false,
+ loadingTip: '{{ i18n "loading"}}',
+ showAlert: false,
+ showIp: 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, true);
+ }
+ } catch (e) {
+ console.error("Failed to get status:", e);
+ }
+ },
+ setStatus(data, isLoaded = false) {
+ this.status = new Status(data, isLoaded);
+ },
+ 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();
+ },
+ 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>