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