Files
Nexus/frontend/src/composables/files/useFilesPermissions.ts
T
2026-06-02 05:05:26 +08:00

102 lines
3.0 KiB
TypeScript

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<void | undefined>
}) {
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<FilesCapabilityResult>(
`/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,
}
}