2023-02-09 19:18:06 +00:00
<!DOCTYPE html>
< html lang = "en" >
{{template "head" .}}
< style >
2024-04-20 18:45:36 +00:00
@media (min-width: 769px) {
.ant-layout-content {
margin: 24px 16px;
2023-02-09 19:18:06 +00:00
}
2024-04-20 18:45:36 +00:00
.ant-card-hoverable {
margin-inline: 0.3rem;
2023-02-09 19:18:06 +00:00
}
2024-04-20 18:45:36 +00:00
.ant-alert-error {
margin-inline: 0.3rem;
2024-02-17 16:22:23 +00:00
}
2024-04-20 18:45:36 +00:00
}
.ant-col-sm-24 {
margin-top: 10px;
}
.ant-card-dark h2 {
color: var(--dark-color-text-primary);
}
2025-03-08 13:41:23 +00:00
.ant-backup-list-item {
2025-03-09 06:01:27 +00:00
gap: 10px;
2025-03-08 13:41:23 +00:00
}
2025-05-06 16:10:58 +00:00
.ant-version-list-item {
2025-03-24 12:22:12 +00:00
--padding: 12px;
padding: var(--padding) !important;
gap: var(--padding);
}
2025-05-06 16:10:58 +00:00
.dark .ant-version-list-item svg{
color: var(--dark-color-text-primary);
}
2025-03-15 11:15:46 +00:00
.dark .ant-backup-list-item svg,
.dark .ant-badge-status-text,
.dark .ant-card-extra {
2025-03-18 08:51:05 +00:00
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);
2025-03-15 11:15:46 +00:00
}
2025-03-18 08:51:05 +00:00
.dark .ant-radio-checked .ant-radio-inner {
border-color: var(--color-primary-100);
2025-03-08 13:41:23 +00:00
}
.dark .ant-backup-list,
2025-05-06 16:10:58 +00:00
.dark .ant-version-list,
2025-03-15 11:15:46 +00:00
.dark .ant-card-actions,
.dark .ant-card-actions>li:not(:last-child) {
2025-03-08 13:41:23 +00:00
border-color: var(--dark-color-stroke);
}
2025-03-15 11:15:46 +00:00
.ant-card-actions {
2025-03-18 08:51:05 +00:00
background: transparent;
}
2025-03-15 11:15:46 +00:00
.ip-hidden {
2025-03-18 08:51:05 +00:00
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
2025-03-15 11:15:46 +00:00
filter: blur(10px);
}
2025-03-18 08:51:05 +00:00
.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;
}
}
2023-02-09 19:18:06 +00:00
< / style >
2023-05-08 14:44:22 +00:00
2023-02-09 19:18:06 +00:00
< body >
2024-03-20 10:43:37 +00:00
< a-layout id = "app" v-cloak :class = "themeSwitcher.currentTheme" >
2025-03-24 11:19:27 +00:00
< a-sidebar > < / a-sidebar >
2023-12-04 18:17:38 +00:00
< a-layout id = "content-layout" >
2024-03-20 10:43:37 +00:00
< a-layout-content >
2024-04-02 09:33:40 +00:00
< a-spin :spinning = "spinning" :delay = "200" :tip = "loadingTip" >
< transition name = "list" appear >
2025-04-06 09:40:33 +00:00
< a-alert type = "error" v-if = "showAlert" :style = "{ marginBottom: '10px' }"
2024-04-02 09:33:40 +00:00
message='{{ i18n "secAlertTitle" }}'
color="red"
description='{{ i18n "secAlertSsl" }}'
show-icon closable>
< / a-alert >
< / transition >
2025-03-18 08:51:05 +00:00
< transition name = "list" appear >
< template >
< a-row v-if = "!status.isLoaded" >
2025-04-06 09:40:33 +00:00
< a-card hoverable :style = "{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent' }" >
< a-spin tip = '{{ i18n "loading" }}' > < / a-spin >
2025-03-18 08:51:05 +00:00
< / a-card >
< / a-row >
< a-row v-else >
2024-04-02 09:33:40 +00:00
< a-row >
2025-03-18 08:51:05 +00:00
< a-card hoverable >
2024-04-02 09:33:40 +00:00
< a-row >
2025-03-18 08:51:05 +00:00
< a-col :sm = "24" :md = "12" >
< a-row >
2025-04-06 09:40:33 +00:00
< a-col :span = "12" :style = "{ textAlign: 'center' }" >
2025-03-18 08:51:05 +00:00
< a-progress type = "dashboard" status = "normal"
:stroke-color="status.cpu.color"
:percent="status.cpu.percent">< / a-progress >
2025-04-06 09:40:33 +00:00
< 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 >
2025-03-18 08:51:05 +00:00
< / a-col >
2025-04-06 09:40:33 +00:00
< a-col :span = "12" :style = "{ textAlign: 'center' }" >
2025-03-18 08:51:05 +00:00
< 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 >
2024-04-02 09:33:40 +00:00
< / a-col >
2025-03-18 08:51:05 +00:00
< a-col :sm = "24" :md = "12" >
< a-row >
2025-04-06 09:40:33 +00:00
< a-col :span = "12" :style = "{ textAlign: 'center' }" >
2025-03-18 08:51:05 +00:00
< a-progress type = "dashboard" status = "normal"
:stroke-color="status.swap.color"
:percent="status.swap.percent">< / a-progress >
< div >
2025-04-06 09:40:33 +00:00
< b > {{ i18n "pages.index.swap" }}:< / b > [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]]
2025-03-18 08:51:05 +00:00
< / div >
< / a-col >
2025-04-06 09:40:33 +00:00
< a-col :span = "12" :style = "{ textAlign: 'center' }" >
2025-03-18 08:51:05 +00:00
< a-progress type = "dashboard" status = "normal"
:stroke-color="status.disk.color"
:percent="status.disk.percent">< / a-progress >
< div >
2025-04-06 09:40:33 +00:00
< b > {{ i18n "pages.index.storage"}}:< / b > [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]]
2025-03-18 08:51:05 +00:00
< / div >
< / a-col >
< / a-row >
2024-04-02 09:33:40 +00:00
< / a-col >
< / a-row >
2025-03-18 08:51:05 +00:00
< / a-card >
2024-04-02 09:33:40 +00:00
< / a-row >
2025-03-15 11:15:46 +00:00
< a-col :sm = "24" :lg = "12" >
2025-03-17 11:26:07 +00:00
< 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 >
2025-03-15 11:15:46 +00:00
< template # extra >
2025-04-06 09:40:33 +00:00
< template v-if = "status.xray.state != 'error'" >
< a-badge status = "processing" class = "running-animation" :text = "status.xray.stateMsg" :color = "status.xray.color" / >
2024-04-02 09:33:40 +00:00
< / template >
2025-03-15 11:15:46 +00:00
< template v-else >
< a-popover :overlay-class-name = "themeSwitcher.currentTheme" >
2025-04-06 09:40:33 +00:00
< 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 >
2025-03-15 11:15:46 +00:00
< / span >
< template slot = "content" >
2025-04-06 09:40:33 +00:00
< span :style = "{ maxWidth: '400px' }" v-for = "line in status.xray.errorMsg.split('\n')" > [[ line ]]< / span >
2024-04-02 09:33:40 +00:00
< / template >
2025-04-06 09:40:33 +00:00
< a-badge :text = "status.xray.stateMsg" :color = "status.xray.color" / >
2025-03-15 11:15:46 +00:00
< / a-popover >
< / template >
< / template >
< template # actions >
2025-04-06 09:40:33 +00:00
< a-space direction = "horizontal" @ click = "stopXrayService" :style = "{ justifyContent: 'center' }" >
2025-03-15 11:15:46 +00:00
< a-icon type = "poweroff" > < / a-icon >
2025-03-17 11:26:07 +00:00
< span v-if = "!isMobile" > {{ i18n "pages.index.stopXray" }}< / span >
2025-03-15 11:15:46 +00:00
< / a-space >
2025-04-06 09:40:33 +00:00
< a-space direction = "horizontal" @ click = "restartXrayService" :style = "{ justifyContent: 'center' }" >
2025-03-15 11:15:46 +00:00
< a-icon type = "reload" > < / a-icon >
2025-03-17 11:26:07 +00:00
< span v-if = "!isMobile" > {{ i18n "pages.index.restartXray" }}< / span >
2025-03-15 11:15:46 +00:00
< / a-space >
2025-04-06 09:40:33 +00:00
< a-space direction = "horizontal" @ click = "openSelectV2rayVersion" :style = "{ justifyContent: 'center' }" >
2025-03-15 11:15:46 +00:00
< a-icon type = "tool" > < / a-icon >
2025-03-17 11:26:07 +00:00
< span v-if = "!isMobile" >
[[ status.xray.version != 'Unknown' ? `v${status.xray.version}` : '{{ i18n "pages.index.xraySwitch" }}' ]]
< / span >
2025-03-15 11:15:46 +00:00
< / a-space >
< / template >
< / a-card >
< / a-col >
< a-col :sm = "24" :lg = "12" >
< a-card title = '{{ i18n "menu.link" }}' hoverable >
< template # actions >
2025-04-06 09:40:33 +00:00
< a-space direction = "horizontal" @ click = "openLogs()" :style = "{ justifyContent: 'center' }" >
2025-03-15 11:15:46 +00:00
< a-icon type = "bars" > < / a-icon >
2025-03-17 11:26:07 +00:00
< span v-if = "!isMobile" > {{ i18n "pages.index.logs" }}< / span >
2025-03-15 11:15:46 +00:00
< / a-space >
2025-04-06 09:40:33 +00:00
< a-space direction = "horizontal" @ click = "openConfig" :style = "{ justifyContent: 'center' }" >
2025-03-15 11:15:46 +00:00
< a-icon type = "control" > < / a-icon >
2025-03-17 11:26:07 +00:00
< span v-if = "!isMobile" > {{ i18n "pages.index.config" }}< / span >
2025-03-15 11:15:46 +00:00
< / a-space >
2025-04-06 09:40:33 +00:00
< a-space direction = "horizontal" @ click = "openBackup" :style = "{ justifyContent: 'center' }" >
2025-03-15 11:15:46 +00:00
< a-icon type = "cloud-server" > < / a-icon >
2025-03-17 11:26:07 +00:00
< span v-if = "!isMobile" > {{ i18n "pages.index.backup" }}< / span >
2025-03-15 11:15:46 +00:00
< / 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 >
2025-04-06 09:40:33 +00:00
< 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 >
2025-03-15 11:15:46 +00:00
< / a-card >
< / a-col >
< a-col :sm = "24" :lg = "12" >
2025-03-18 08:51:05 +00:00
< a-card title = '{{ i18n "pages.index.overallSpeed" }}' hoverable >
2025-04-06 09:40:33 +00:00
< a-row :gutter = "isMobile ? [8,8] : 0" >
2025-03-18 08:51:05 +00:00
< a-col :span = "12" >
< a-custom-statistic title = '{{ i18n "pages.index.upload" }}' :value = "SizeFormatter.sizeFormat(status.netIO.up)" >
2025-03-15 11:15:46 +00:00
< template # prefix >
2025-03-18 08:51:05 +00:00
< a-icon type = "arrow-up" / >
< / template >
< template # suffix >
/s
2024-04-02 09:33:40 +00:00
< / template >
2025-03-17 11:26:07 +00:00
< / a-custom-statistic >
2025-03-15 11:15:46 +00:00
< / a-col >
2025-03-18 08:51:05 +00:00
< a-col :span = "12" >
< a-custom-statistic title = '{{ i18n "pages.index.download" }}' :value = "SizeFormatter.sizeFormat(status.netIO.down)" >
2025-03-15 11:15:46 +00:00
< template # prefix >
2025-03-18 08:51:05 +00:00
< a-icon type = "arrow-down" / >
< / template >
< template # suffix >
/s
2024-04-02 09:33:40 +00:00
< / template >
2025-03-17 11:26:07 +00:00
< / a-custom-statistic >
2025-03-15 11:15:46 +00:00
< / a-col >
< / a-row >
< / a-card >
< / a-col >
< a-col :sm = "24" :lg = "12" >
2025-03-18 08:51:05 +00:00
< a-card title = '{{ i18n "pages.index.totalData" }}' hoverable >
2025-04-06 09:40:33 +00:00
< a-row :gutter = "isMobile ? [8,8] : 0" >
2025-03-18 08:51:05 +00:00
< a-col :span = "12" >
< a-custom-statistic title = '{{ i18n "pages.index.sent" }}' :value = "SizeFormatter.sizeFormat(status.netTraffic.sent)" >
2025-03-15 11:15:46 +00:00
< template # prefix >
2025-03-18 08:51:05 +00:00
< a-icon type = "cloud-upload" / >
2024-04-02 09:33:40 +00:00
< / template >
2025-03-17 11:26:07 +00:00
< / a-custom-statistic >
2025-03-15 11:15:46 +00:00
< / a-col >
2025-03-18 08:51:05 +00:00
< a-col :span = "12" >
< a-custom-statistic title = '{{ i18n "pages.index.received" }}' :value = "SizeFormatter.sizeFormat(status.netTraffic.recv)" >
2025-03-15 11:15:46 +00:00
< template # prefix >
2025-03-18 08:51:05 +00:00
< a-icon type = "cloud-download" / >
2024-04-02 09:33:40 +00:00
< / template >
2025-03-17 11:26:07 +00:00
< / a-custom-statistic >
2025-03-15 11:15:46 +00:00
< / a-col >
< / a-row >
< / a-card >
< / a-col >
< a-col :sm = "24" :lg = "12" >
2025-03-18 08:51:05 +00:00
< 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 >
2025-04-06 09:40:33 +00:00
< a-row :class = "showIp ? 'ip-visible' : 'ip-hidden'" :gutter = "isMobile ? [8,8] : 0" >
< a-col :span = "isMobile ? 24 : 12" >
2025-03-18 21:06:55 +00:00
< a-custom-statistic title = "IPv4" :value = "status.publicIP.ipv4" >
2025-03-15 11:15:46 +00:00
< template # prefix >
2025-03-18 21:06:55 +00:00
< a-icon type = "global" / >
2025-03-15 11:15:46 +00:00
< / template >
2025-03-17 11:26:07 +00:00
< / a-custom-statistic >
2025-03-15 11:15:46 +00:00
< / a-col >
2025-04-06 09:40:33 +00:00
< a-col :span = "isMobile ? 24 : 12" >
2025-03-18 21:06:55 +00:00
< a-custom-statistic title = "IPv6" :value = "status.publicIP.ipv6" >
2025-03-15 11:15:46 +00:00
< template # prefix >
2025-03-18 21:06:55 +00:00
< a-icon type = "global" / >
2025-03-15 11:15:46 +00:00
< / template >
2025-03-17 11:26:07 +00:00
< / a-custom-statistic >
2025-03-15 11:15:46 +00:00
< / a-col >
< / a-row >
< / a-card >
< / a-col >
< a-col :sm = "24" :lg = "12" >
2025-03-18 08:51:05 +00:00
< a-card title = '{{ i18n "pages.index.connectionCount" }}' hoverable >
2025-04-06 09:40:33 +00:00
< a-row :gutter = "isMobile ? [8,8] : 0" >
2025-03-18 08:51:05 +00:00
< a-col :span = "12" >
< a-custom-statistic title = "TCP" :value = "status.tcpCount" >
2025-03-15 11:15:46 +00:00
< template # prefix >
2025-03-18 08:51:05 +00:00
< a-icon type = "swap" / >
2025-03-15 11:15:46 +00:00
< / template >
2025-03-17 11:26:07 +00:00
< / a-custom-statistic >
2025-03-15 11:15:46 +00:00
< / a-col >
2025-03-18 08:51:05 +00:00
< a-col :span = "12" >
< a-custom-statistic title = "UDP" :value = "status.udpCount" >
2025-03-15 11:15:46 +00:00
< template # prefix >
2025-03-18 08:51:05 +00:00
< a-icon type = "swap" / >
2025-03-15 11:15:46 +00:00
< / template >
2025-03-17 11:26:07 +00:00
< / a-custom-statistic >
2025-03-15 11:15:46 +00:00
< / a-col >
< / a-row >
< / a-card >
< / a-col >
< / a-row >
< / template >
2024-04-02 09:33:40 +00:00
< / transition >
< / a-spin >
2024-03-20 10:43:37 +00:00
< / a-layout-content >
2023-02-09 19:18:06 +00:00
< / a-layout >
2024-03-11 12:44:24 +00:00
< a-modal id = "version-modal" v-model = "versionModal.visible" title = '{{ i18n "pages.index.xraySwitch" }}' :closable = "true"
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
2025-05-06 16:10:58 +00:00
< a-collapse default-active-key = "1" >
< a-collapse-panel key = "1" header = 'Xray' >
< a-alert type = "warning" :style = "{ marginBottom: '12px', width: '100%' }" message = '{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon > < / a-alert >
< a-list class = "ant-version-list" bordered :style = "{ width: '100%' }" >
< a-list-item class = "ant-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-collapse-panel >
< a-collapse-panel key = "2" header = 'Geofiles' >
< a-list class = "ant-version-list" bordered :style = "{ width: '100%' }" >
< a-list-item class = "ant-version-list-item" v-for = "file, index in ['geosite.dat', 'geoip.dat', 'geosite_IR.dat', 'geoip_IR.dat', 'geosite_RU.dat', 'geoip_RU.dat']" >
< a-tag :color = "index % 2 == 0 ? 'purple' : 'green'" > [[ file ]]< / a-tag >
< a-icon type = "reload" @ click = "updateGeofile(file)" :style = "{ marginRight: '8px' }" / >
< / a-list-item >
< / a-list >
< / a-collapse-panel >
< / a-collapse >
2023-02-09 19:18:06 +00:00
< / a-modal >
2024-02-21 11:46:27 +00:00
< a-modal id = "log-modal" v-model = "logModal.visible"
2024-03-20 10:43:37 +00:00
: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"
2025-04-06 09:40:33 +00:00
:style="{ verticalAlign: 'middle', marginLeft: '10px' }"
2024-03-20 10:43:37 +00:00
:disabled="logModal.loading"
@click="openLogs()">
< / a-icon >
< / template >
< a-form layout = "inline" >
2025-04-06 09:40:33 +00:00
< a-form-item :style = "{ marginRight: '0.5rem' }" >
2024-03-20 10:43:37 +00:00
< a-input-group compact >
2025-04-06 09:40:33 +00:00
< a-select size = "small" v-model = "logModal.rows" :style = "{ width: '70px' }"
2024-03-20 10:43:37 +00:00
@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 >
2024-10-15 19:49:36 +00:00
< a-select-option value = "500" > 500< / a-select-option >
2024-03-20 10:43:37 +00:00
< / a-select >
2025-04-06 09:40:33 +00:00
< a-select size = "small" v-model = "logModal.level" :style = "{ width: '95px' }"
2024-03-20 10:43:37 +00:00
@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 >
2025-04-06 09:40:33 +00:00
< a-form-item :style = "{ float: 'right' }" >
2025-04-08 15:17:29 +00:00
< a-button type = "primary" icon = "download" @ click = "FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')" > < / a-button >
2024-03-20 10:43:37 +00:00
< / a-form-item >
< / a-form >
2025-04-06 09:40:33 +00:00
< div class = "ant-input" :style = "{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html = "logModal.formattedLogs" > < / div >
2023-03-24 13:44:26 +00:00
< / a-modal >
2025-03-08 13:41:23 +00:00
< a-modal id = "backup-modal"
v-model="backupModal.visible"
title='{{ i18n "pages.index.backupTitle" }}'
:closable="true"
footer=""
2024-03-20 10:43:37 +00:00
:class="themeSwitcher.currentTheme">
2025-04-06 09:40:33 +00:00
< a-list class = "ant-backup-list" bordered :style = "{ width: '100%' }" >
2025-03-18 08:51:05 +00:00
< a-list-item class = "ant-backup-list-item" >
2025-03-09 06:01:27 +00:00
< a-list-item-meta >
< template # title > {{ i18n "pages.index.exportDatabase" }}< / template >
< template # description > {{ i18n "pages.index.exportDatabaseDesc" }}< / template >
< / a-list-item-meta >
2025-03-18 08:51:05 +00:00
< a-button @ click = "exportDatabase()" type = "primary" icon = "download" / >
2025-03-09 06:01:27 +00:00
< / a-list-item >
2025-03-18 08:51:05 +00:00
< a-list-item class = "ant-backup-list-item" >
2025-03-09 06:01:27 +00:00
< a-list-item-meta >
< template # title > {{ i18n "pages.index.importDatabase" }}< / template >
< template # description > {{ i18n "pages.index.importDatabaseDesc" }}< / template >
< / a-list-item-meta >
2025-03-18 08:51:05 +00:00
< a-button @ click = "importDatabase()" type = "primary" icon = "upload" / >
2025-03-09 06:01:27 +00:00
< / a-list-item >
< / a-list >
2023-05-05 18:22:35 +00:00
< / a-modal >
2024-03-20 10:43:37 +00:00
< / a-layout >
2023-02-09 19:18:06 +00:00
{{template "js" .}}
2025-03-24 11:19:27 +00:00
{{template "component/aSidebar" .}}
2025-03-17 11:26:07 +00:00
{{template "component/aThemeSwitch" .}}
{{template "component/aCustomStatistic" .}}
2025-03-24 10:02:01 +00:00
{{template "modals/textModal"}}
2023-02-09 19:18:06 +00:00
< script >
class CurTotal {
constructor(current, total) {
this.current = current;
this.total = total;
}
get percent() {
if (this.total === 0) {
return 0;
}
2025-03-07 09:07:23 +00:00
return NumberFormatter.toFixed(this.current / this.total * 100, 2);
2023-02-09 19:18:06 +00:00
}
get color() {
const percent = this.percent;
if (percent < 80 ) {
2023-12-09 12:59:48 +00:00
return '#008771'; // Green
2023-02-09 19:18:06 +00:00
} else if (percent < 90 ) {
2023-12-09 12:59:48 +00:00
return "#f37b24"; // Orange
2023-02-09 19:18:06 +00:00
} else {
2023-12-09 12:59:48 +00:00
return "#cf3c3c"; // Red
2023-02-09 19:18:06 +00:00
}
}
}
class Status {
2025-03-15 11:15:46 +00:00
constructor(data, isLoaded = false) {
2023-02-09 19:18:06 +00:00
this.cpu = new CurTotal(0, 0);
2023-05-25 12:18:23 +00:00
this.cpuCores = 0;
2024-05-28 13:11:46 +00:00
this.logicalPro = 0;
2023-05-25 12:18:23 +00:00
this.cpuSpeedMhz = 0;
2023-02-09 19:18:06 +00:00
this.disk = new CurTotal(0, 0);
this.loads = [0, 0, 0];
this.mem = new CurTotal(0, 0);
2023-05-08 14:44:22 +00:00
this.netIO = { up: 0, down: 0 };
this.netTraffic = { sent: 0, recv: 0 };
2023-05-24 23:41:09 +00:00
this.publicIP = { ipv4: 0, ipv6: 0 };
2023-02-09 19:18:06 +00:00
this.swap = new CurTotal(0, 0);
this.tcpCount = 0;
this.udpCount = 0;
this.uptime = 0;
2023-08-08 21:07:05 +00:00
this.appUptime = 0;
this.appStats = {threads: 0, mem: 0, uptime: 0};
2025-04-06 09:40:33 +00:00
this.xray = { state: 'stop', stateMsg: "", errorMsg: "", version: "", color: "" };
2023-02-09 19:18:06 +00:00
if (data == null) {
2025-03-15 11:15:46 +00:00
return;
2023-02-09 19:18:06 +00:00
}
2025-03-15 11:15:46 +00:00
this.isLoaded = isLoaded;
2023-02-09 19:18:06 +00:00
this.cpu = new CurTotal(data.cpu, 100);
2023-05-25 12:18:23 +00:00
this.cpuCores = data.cpuCores;
2024-05-28 13:11:46 +00:00
this.logicalPro = data.logicalPro;
2023-05-25 12:18:23 +00:00
this.cpuSpeedMhz = data.cpuSpeedMhz;
2023-02-09 19:18:06 +00:00
this.disk = new CurTotal(data.disk.current, data.disk.total);
2025-03-07 09:07:23 +00:00
this.loads = data.loads.map(load => NumberFormatter.toFixed(load, 2));
2023-02-09 19:18:06 +00:00
this.mem = new CurTotal(data.mem.current, data.mem.total);
this.netIO = data.netIO;
this.netTraffic = data.netTraffic;
2023-05-24 23:41:09 +00:00
this.publicIP = data.publicIP;
2023-02-09 19:18:06 +00:00
this.swap = new CurTotal(data.swap.current, data.swap.total);
this.tcpCount = data.tcpCount;
this.udpCount = data.udpCount;
this.uptime = data.uptime;
2023-08-08 21:07:05 +00:00
this.appUptime = data.appUptime;
this.appStats = data.appStats;
2023-02-09 19:18:06 +00:00
this.xray = data.xray;
switch (this.xray.state) {
2025-04-06 09:40:33 +00:00
case 'running':
2023-02-09 19:18:06 +00:00
this.xray.color = "green";
2025-04-06 09:40:33 +00:00
this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusRunning" }}';
2023-02-09 19:18:06 +00:00
break;
2025-04-06 09:40:33 +00:00
case 'stop':
2023-02-09 19:18:06 +00:00
this.xray.color = "orange";
2025-04-06 09:40:33 +00:00
this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusStop" }}';
2023-02-09 19:18:06 +00:00
break;
2025-04-06 09:40:33 +00:00
case 'error':
2023-02-09 19:18:06 +00:00
this.xray.color = "red";
2025-04-06 09:40:33 +00:00
this.xray.stateMsg ='{{ i18n "pages.index.xrayStatusError" }}';
2023-02-09 19:18:06 +00:00
break;
default:
this.xray.color = "gray";
2025-04-06 09:40:33 +00:00
this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusUnknown" }}';
break;
2023-02-09 19:18:06 +00:00
}
}
}
const versionModal = {
visible: false,
versions: [],
show(versions) {
this.visible = true;
this.versions = versions;
},
hide() {
this.visible = false;
},
};
2023-03-24 13:44:26 +00:00
const logModal = {
visible: false,
2024-02-23 23:32:05 +00:00
logs: [],
2023-04-09 19:43:18 +00:00
rows: 20,
2023-07-31 16:41:47 +00:00
level: 'info',
syslog: false,
2023-12-10 12:36:02 +00:00
loading: false,
2023-07-31 16:41:47 +00:00
show(logs) {
2023-03-24 13:44:26 +00:00
this.visible = true;
2024-02-18 21:15:00 +00:00
this.logs = logs;
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
2023-12-10 12:36:02 +00:00
},
formatLogs(logs) {
let formattedLogs = '';
2023-12-10 14:06:42 +00:00
const levels = ["DEBUG","INFO","NOTICE","WARNING","ERROR"];
const levelColors = ["#3c89e8","#008771","#008771","#f37b24","#e04141","#bcbcbc"];
2023-12-10 12:36:02 +00:00
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];
2023-12-10 14:06:42 +00:00
const levelIndex = levels.indexOf(level,levels) || 5;
2023-12-10 12:36:02 +00:00
//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 {
2023-12-10 14:06:42 +00:00
const levelIndex = levels.indexOf(data,levels) || 5;
2023-12-10 12:36:02 +00:00
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;
2023-03-24 13:44:26 +00:00
},
hide() {
this.visible = false;
},
};
2023-05-05 18:22:35 +00:00
const backupModal = {
visible: false,
2025-03-08 13:41:23 +00:00
show() {
this.visible = true;
2023-05-05 18:22:35 +00:00
},
hide() {
2025-03-08 13:41:23 +00:00
this.visible = false;
2023-05-05 18:22:35 +00:00
},
};
2023-02-09 19:18:06 +00:00
const app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
2025-04-06 09:40:33 +00:00
mixins: [MediaQueryMixin],
2023-02-09 19:18:06 +00:00
data: {
2023-05-08 14:44:22 +00:00
themeSwitcher,
2023-02-09 19:18:06 +00:00
status: new Status(),
versionModal,
2023-03-24 13:44:26 +00:00
logModal,
2023-05-05 18:22:35 +00:00
backupModal,
2023-02-09 19:18:06 +00:00
spinning: false,
loadingTip: '{{ i18n "loading"}}',
2024-02-21 08:36:49 +00:00
showAlert: false,
2025-04-06 09:40:33 +00:00
showIp: false
2023-02-09 19:18:06 +00:00
},
methods: {
loading(spinning, tip = '{{ i18n "loading"}}') {
this.spinning = spinning;
this.loadingTip = tip;
},
async getStatus() {
2023-06-14 16:20:19 +00:00
try {
const msg = await HttpUtil.post('/server/status');
if (msg.success) {
2025-03-15 11:15:46 +00:00
this.setStatus(msg.obj, true);
2023-06-14 16:20:19 +00:00
}
} catch (e) {
console.error("Failed to get status:", e);
2023-02-09 19:18:06 +00:00
}
},
2025-03-15 11:15:46 +00:00
setStatus(data, isLoaded = false) {
this.status = new Status(data, isLoaded);
2023-02-09 19:18:06 +00:00
},
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"}}',
2025-05-06 16:10:58 +00:00
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}'.replace('#version#', version),
2023-02-09 19:18:06 +00:00
okText: '{{ i18n "confirm"}}',
2023-12-04 18:17:38 +00:00
class: themeSwitcher.currentTheme,
2023-02-09 19:18:06 +00:00
cancelText: '{{ i18n "cancel"}}',
onOk: async () => {
versionModal.hide();
2023-05-04 17:52:54 +00:00
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
2023-02-09 19:18:06 +00:00
await HttpUtil.post(`/server/installXray/${version}`);
this.loading(false);
},
});
2023-03-16 22:01:14 +00:00
},
2025-05-06 16:10:58 +00:00
updateGeofile(fileName) {
this.$confirm({
title: '{{ i18n "pages.index.geofileUpdateDialog" }}',
content: '{{ i18n "pages.index.geofileUpdateDialogDesc" }}'.replace("#filename#", fileName),
okText: '{{ i18n "confirm"}}',
class: themeSwitcher.currentTheme,
cancelText: '{{ i18n "cancel"}}',
onOk: async () => {
versionModal.hide();
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
await HttpUtil.post(`/server/updateGeofile/${fileName}`);
this.loading(false);
},
});
},
2023-03-16 22:01:14 +00:00
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;
}
2023-02-09 19:18:06 +00:00
},
2023-07-31 16:41:47 +00:00
async openLogs(){
2023-12-10 12:36:02 +00:00
logModal.loading = true;
2023-07-31 16:41:47 +00:00
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
2023-03-24 13:44:26 +00:00
if (!msg.success) {
return;
}
2023-07-31 16:41:47 +00:00
logModal.show(msg.obj);
2023-12-10 12:36:02 +00:00
await PromiseUtil.sleep(500);
logModal.loading = false;
2023-04-11 12:11:04 +00:00
},
2023-05-05 18:22:35 +00:00
async openConfig() {
2023-04-11 12:11:04 +00:00
this.loading(true);
const msg = await HttpUtil.post('server/getConfigJson');
this.loading(false);
if (!msg.success) {
return;
}
2023-05-05 18:22:35 +00:00
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
2023-04-11 12:11:04 +00:00
},
2023-05-05 18:22:35 +00:00
openBackup() {
2025-03-08 13:41:23 +00:00
backupModal.show();
2023-05-05 18:22:35 +00:00
},
exportDatabase() {
2023-04-11 12:11:04 +00:00
window.location = basePath + 'server/getDb';
2023-05-05 18:22:35 +00:00
},
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);
2023-05-12 18:06:05 +00:00
const restartMsg = await HttpUtil.post("/panel/setting/restartPanel");
2023-05-05 18:22:35 +00:00
this.loading(false);
if (restartMsg.success) {
this.loading(true);
await PromiseUtil.sleep(5000);
location.reload();
}
}
});
fileInput.click();
},
2023-02-09 19:18:06 +00:00
},
async mounted() {
2024-02-21 08:36:49 +00:00
if (window.location.protocol !== "https:") {
this.showAlert = true;
}
while (true) {
2023-02-09 19:18:06 +00:00
try {
await this.getStatus();
} catch (e) {
2024-02-21 08:36:49 +00:00
console.error(e);
2023-02-09 19:18:06 +00:00
}
await PromiseUtil.sleep(2000);
}
},
});
< / script >
< / body >
2023-12-19 09:36:36 +00:00
< / html >