Skip to content

Commit

Permalink
feat: auto switch backend
Browse files Browse the repository at this point in the history
  • Loading branch information
Zephyruso committed Dec 22, 2024
1 parent 529fe0e commit b153fd0
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 12 deletions.
26 changes: 25 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useSetup } from '@/composables/setup'
import { ROUTE_NAME } from '@/config'
import router from '@/router'
import { activeBackend, activeUuid, removeBackend } from '@/store/setup'
import type { Config, DNSQuery, Proxy, ProxyProvider, Rule, RuleProvider } from '@/types'
import type { Backend, Config, DNSQuery, Proxy, ProxyProvider, Rule, RuleProvider } from '@/types'
import axios from 'axios'
import ReconnectingWebSocket from 'reconnectingwebsocket'
import { computed, nextTick, ref, watch } from 'vue'
Expand Down Expand Up @@ -206,6 +206,30 @@ export const fetchTrafficAPI = <T>() => {
return createWebSocket<T>('traffic')
}

export const isBackendAvailable = async (backend: Backend, timeout: number = 5000) => {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)

try {
const res = await fetch(
`${backend.protocol}://${backend.host}:${backend.port}${backend.secondaryPath ?? '/'}version`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${backend.password}`,
},
signal: controller.signal,
},
)

return res.ok
} catch {
return false
} finally {
clearTimeout(timeoutId)
}
}

export const fetchIsUIUpdateAvailable = async () => {
const response = await fetch('https://api.github.com/repos/Zephyruso/zashboard/releases/latest')
const { tag_name } = await response.json()
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,6 @@ export default {
secondaryPathTip: 'If present, start with a "/", otherwise leave it empty.',
logRetentionLimit: 'Log Retention Limit',
DNSQuery: 'DNS Query',
currentBackendUnavailable: 'Current backend is unavailable. Try to switch to another backend?',
confirm: 'Confirm',
}
2 changes: 2 additions & 0 deletions src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,6 @@ export default {
secondaryPathTip: '如果有的话以/开头,没有则留空不填',
logRetentionLimit: '日志保留条数',
DNSQuery: 'DNS 查询',
currentBackendUnavailable: '当前后端不可用,尝试切换到其他后端?',
confirm: '确定',
}
10 changes: 1 addition & 9 deletions src/store/setup.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import type { Backend } from '@/types'
import { useStorage } from '@vueuse/core'
import { isEqual, omit } from 'lodash'
import { v4 as uuid } from 'uuid'
import { computed } from 'vue'

type Backend = {
host: string
port: number
secondaryPath: string
password: string
protocol: string
uuid: string
}

export const backendList = useStorage<Backend[]>('setup/api-list', [])
export const activeUuid = useStorage<string>('setup/active-uuid', '')
export const activeBackend = computed(() =>
Expand Down
9 changes: 9 additions & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
export type Backend = {
host: string
port: number
secondaryPath: string
password: string
protocol: string
uuid: string
}

export type Config = {
port: number
'socks-port': number
Expand Down
75 changes: 73 additions & 2 deletions src/views/HomePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,35 @@
</div>
</div>
</div>

<dialog
id="autoSwitchBackend"
ref="modalRef"
class="modal"
>
<div class="modal-box">
<h3 class="text-lg font-bold">{{ $t('currentBackendUnavailable') }}</h3>
<div class="flex justify-end gap-2">
<button
class="btn btn-sm"
@click="closeModal"
>
{{ $t('cancel') }}
</button>
<button
class="btn btn-primary btn-sm"
@click="autoSwitchBackend"
>
{{ $t('confirm') }}
</button>
</div>
</div>
</dialog>
</div>
</template>

<script setup lang="ts">
import { isBackendAvailable } from '@/api'
import ConnectionCtrl from '@/components/sidebar/ConnectionCtrl.vue'
import LogsCtrl from '@/components/sidebar/LogsCtrl.vue'
import ProxiesCtrl from '@/components/sidebar/ProxiesCtrl.vue'
Expand All @@ -70,13 +95,14 @@ import { initLogs } from '@/store/logs'
import { fetchProxies } from '@/store/proxies'
import { fetchRules } from '@/store/rules'
import { isSiderbarCollapsed } from '@/store/settings'
import { activeUuid } from '@/store/setup'
import { activeBackend, activeUuid, backendList } from '@/store/setup'
import { initSatistic } from '@/store/statistics'
import { Bars3Icon } from '@heroicons/vue/24/outline'
import { useSwipe } from '@vueuse/core'
import { useDocumentVisibility, useSwipe } from '@vueuse/core'
import { computed, ref, watch, type Component } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
const modalRef = ref<HTMLDialogElement | null>(null)
const isPWA = (() => {
return window.matchMedia('(display-mode: standalone)').matches || navigator.standalone
})()
Expand Down Expand Up @@ -144,4 +170,49 @@ watch(
immediate: true,
},
)
const closeModal = () => {
modalRef.value?.close()
}
const autoSwitchBackend = async () => {
const otherEnds = backendList.value.filter((end) => end.uuid !== activeUuid.value)
closeModal()
const avaliable = (
await Promise.all(
otherEnds.map(async (end) => {
return (await isBackendAvailable(end)) ? end : null
}),
)
).filter((end) => end !== null)
if (avaliable.length > 0) {
activeUuid.value = avaliable[0].uuid
router.push({ name: ROUTE_NAME.proxies })
}
}
const documentVisible = useDocumentVisibility()
watch(
documentVisible,
async () => {
if (!activeBackend.value || backendList.value.length === 0 || !documentVisible.value) {
return
}
try {
const isAvailable = await isBackendAvailable(activeBackend.value)
if (!isAvailable) {
modalRef.value?.showModal()
}
} catch {
modalRef.value?.showModal()
}
},
{
immediate: true,
},
)
</script>

0 comments on commit b153fd0

Please sign in to comment.