7add9384c1
keep-alive 下终端切换不 dispose SSH;列表/推送/终端默认 newest first。 Co-authored-by: Cursor <cursoragent@cursor.com>
170 lines
4.7 KiB
TypeScript
170 lines
4.7 KiB
TypeScript
import { ref, computed, type Ref } from 'vue'
|
|
import { categoryDisplayLabel } from '@/composables/useServerCategories'
|
|
import { useServerSearchHistory } from '@/composables/useServerSearchHistory'
|
|
import { fetchPagePerPage } from '@/utils/paginatedFetch'
|
|
import type { PushServerItem } from './types'
|
|
|
|
const PUSH_SEARCH_HISTORY = 5
|
|
|
|
export function usePushServers(pushStatus: Ref<Record<number, string>>) {
|
|
const servers = ref<PushServerItem[]>([])
|
|
const serversLoading = ref(false)
|
|
const serverSearch = ref('')
|
|
const selectedIds = ref<Set<number>>(new Set())
|
|
|
|
const { items: pushSearchHistory, loadHistoryOnly, record: recordPushSearch } =
|
|
useServerSearchHistory('push')
|
|
|
|
const serverSearchHistory = computed(() =>
|
|
pushSearchHistory.value.slice(0, PUSH_SEARCH_HISTORY),
|
|
)
|
|
|
|
function commitServerSearch() {
|
|
void recordPushSearch(serverSearch.value)
|
|
}
|
|
|
|
function onServerSearchUpdate(val: string | null) {
|
|
serverSearch.value = val ?? ''
|
|
if (val == null || val === '') {
|
|
void recordPushSearch('')
|
|
return
|
|
}
|
|
const picked = String(val).trim()
|
|
if (pushSearchHistory.value.includes(picked)) {
|
|
serverSearch.value = picked
|
|
void recordPushSearch(picked)
|
|
}
|
|
}
|
|
|
|
async function loadSearchHistory() {
|
|
await loadHistoryOnly()
|
|
}
|
|
|
|
let refreshTimer: ReturnType<typeof setInterval> | null = null
|
|
|
|
async function loadServers(silent = false) {
|
|
if (!silent) serversLoading.value = true
|
|
try {
|
|
const res = await fetchPagePerPage<PushServerItem>(
|
|
'/servers/',
|
|
{ sort_by: 'created_at', sort_order: 'desc' },
|
|
1,
|
|
-1,
|
|
)
|
|
servers.value = res.items
|
|
} catch {
|
|
servers.value = []
|
|
} finally {
|
|
if (!silent) serversLoading.value = false
|
|
}
|
|
}
|
|
|
|
function startAutoRefresh(_isPushing?: () => boolean) {
|
|
stopAutoRefresh()
|
|
refreshTimer = setInterval(() => {
|
|
void loadServers(true)
|
|
}, 30_000)
|
|
}
|
|
|
|
function stopAutoRefresh() {
|
|
if (refreshTimer) {
|
|
clearInterval(refreshTimer)
|
|
refreshTimer = null
|
|
}
|
|
}
|
|
|
|
const filteredServers = computed(() => {
|
|
if (!serverSearch.value) return servers.value
|
|
const q = serverSearch.value.toLowerCase()
|
|
return servers.value.filter(
|
|
s =>
|
|
s.name.toLowerCase().includes(q) ||
|
|
(s.domain || '').toLowerCase().includes(q) ||
|
|
(s.target_path || '').toLowerCase().includes(q),
|
|
)
|
|
})
|
|
|
|
const selectAll = computed(() => {
|
|
const visible = filteredServers.value
|
|
return visible.length > 0 && visible.every(s => selectedIds.value.has(s.id))
|
|
})
|
|
|
|
const serversByCategory = computed(() => {
|
|
const groups: Record<string, PushServerItem[]> = {}
|
|
for (const s of filteredServers.value) {
|
|
const key = s.category?.trim() || 'uncategorized'
|
|
if (!groups[key]) groups[key] = []
|
|
groups[key].push(s)
|
|
}
|
|
return Object.entries(groups)
|
|
.map(([category, list]) => ({
|
|
category,
|
|
label: categoryDisplayLabel(category),
|
|
servers: list,
|
|
}))
|
|
.sort((a, b) => {
|
|
if (a.category === 'uncategorized') return 1
|
|
if (b.category === 'uncategorized') return -1
|
|
return a.label.localeCompare(b.label, 'zh')
|
|
})
|
|
})
|
|
|
|
function isCategoryAllSelected(category: string): boolean {
|
|
const group = serversByCategory.value.find(g => g.category === category)
|
|
if (!group || group.servers.length === 0) return false
|
|
return group.servers.every(s => selectedIds.value.has(s.id))
|
|
}
|
|
|
|
function toggleCategory(category: string) {
|
|
const group = serversByCategory.value.find(g => g.category === category)
|
|
if (!group) return
|
|
const next = new Set(selectedIds.value)
|
|
const allSelected = group.servers.every(srv => next.has(srv.id))
|
|
for (const srv of group.servers) {
|
|
if (allSelected) next.delete(srv.id)
|
|
else next.add(srv.id)
|
|
}
|
|
selectedIds.value = next
|
|
}
|
|
|
|
function toggleServer(id: number) {
|
|
const next = new Set(selectedIds.value)
|
|
if (next.has(id)) next.delete(id)
|
|
else next.add(id)
|
|
selectedIds.value = next
|
|
}
|
|
|
|
function toggleAll() {
|
|
const visible = filteredServers.value
|
|
const allSelected = visible.length > 0 && visible.every(s => selectedIds.value.has(s.id))
|
|
const next = new Set(selectedIds.value)
|
|
for (const srv of visible) {
|
|
if (allSelected) next.delete(srv.id)
|
|
else next.add(srv.id)
|
|
}
|
|
selectedIds.value = next
|
|
}
|
|
|
|
return {
|
|
servers,
|
|
serversLoading,
|
|
serverSearch,
|
|
serverSearchHistory,
|
|
selectedIds,
|
|
pushStatus,
|
|
filteredServers,
|
|
selectAll,
|
|
serversByCategory,
|
|
loadServers,
|
|
loadSearchHistory,
|
|
commitServerSearch,
|
|
onServerSearchUpdate,
|
|
startAutoRefresh,
|
|
stopAutoRefresh,
|
|
isCategoryAllSelected,
|
|
toggleCategory,
|
|
toggleServer,
|
|
toggleAll,
|
|
}
|
|
}
|