import { computed } from 'vue' import { storeToRefs } from 'pinia' import { http } from '@/api' import { useSnackbar } from '@/composables/useSnackbar' import { useFilesStore } from '@/stores/files' import type { FilesCapabilityResult } from '@/types/api' import { buildFilesAccessHint, isEntryLikelyUnreadable, } from '@/utils/fileMode' import { FILES_HINT_DISMISSED_KEY } from './constants' import { isNotFoundError } from './useFilesBrowse' export function useFilesPermissions(options: { browse: (opts?: { force?: boolean }) => Promise }) { const snackbar = useSnackbar() const store = useFilesStore() const { selectedServer, files, browseError, filesCapability, lastReadDeniedPath, accessHintDismissed, setupSudoLoading, setupSudoDone, } = storeToRefs(store) const sshUserForFiles = computed(() => filesCapability.value?.ssh_user ?? null) const unreadableFileCount = computed(() => { const user = sshUserForFiles.value if (!user) return 0 return files.value.filter((f) => isEntryLikelyUnreadable(f, user)).length }) const filesAccessHint = computed(() => { if (accessHintDismissed.value || !selectedServer.value) return null if (browseError.value && isNotFoundError(browseError.value)) return null const cap = filesCapability.value return buildFilesAccessHint({ sshUser: cap?.ssh_user, canSudo: cap?.can_sudo_nopasswd ?? false, whitelistOk: cap?.whitelist_ok ?? false, isRootSsh: cap?.is_root ?? false, filesElevation: cap?.files_elevation, capabilityMessage: cap?.message, lastReadDeniedPath: lastReadDeniedPath.value, unreadableCount: unreadableFileCount.value, }) }) function dismissFilesAccessHint() { accessHintDismissed.value = true sessionStorage.setItem(FILES_HINT_DISMISSED_KEY, '1') } async function refreshFilesCapability() { if (!selectedServer.value) { filesCapability.value = null return } try { filesCapability.value = await http.get( `/servers/${selectedServer.value}/files-capability`, ) } catch { filesCapability.value = null } } async function setupFilesSudo() { if (!selectedServer.value) return setupSudoLoading.value = true try { const res = await http.post<{ ok: boolean; message: string }>( `/servers/${selectedServer.value}/setup-files-sudo`, {}, ) setupSudoDone.value = true snackbar(res.message || 'sudoers 配置成功,文件管理器现在可自动提权', 'success') dismissFilesAccessHint() await refreshFilesCapability() await options.browse({ force: true }) } catch (e: unknown) { const msg = e instanceof Error ? e.message : '配置失败' snackbar(`一键配置失败:${msg}`, 'error') } finally { setupSudoLoading.value = false } } return { sshUserForFiles, filesAccessHint, dismissFilesAccessHint, refreshFilesCapability, setupFilesSudo, } }