refactor(files): P1 split composables, Pinia store, directory cache
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user