refactor(files_external): adjust files integration

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2026-01-12 22:30:45 +01:00 committed by nextcloud-command
parent a05c285979
commit e76f9284ce
8 changed files with 72 additions and 73 deletions

View file

@ -1,14 +1,14 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { View } from '@nextcloud/files'
import type { StorageConfig } from '../services/externalStorage.ts'
import type { IStorage } from '../types.ts'
import { DefaultType, File, FileAction, Folder, Permission } from '@nextcloud/files'
import { describe, expect, test } from 'vitest'
import { STORAGE_STATUS } from '../utils/credentialsUtils.ts'
import { StorageStatus } from '../types.ts'
import { action } from './enterCredentialsAction.ts'
const view = {
@ -31,8 +31,8 @@ describe('Enter credentials action conditions tests', () => {
permissions: Permission.ALL,
attributes: {
config: {
status: STORAGE_STATUS.SUCCESS,
} as StorageConfig,
status: StorageStatus.Success,
} as IStorage,
},
})
@ -72,8 +72,8 @@ describe('Enter credentials action enabled tests', () => {
scope: 'system',
backend: 'SFTP',
config: {
status: STORAGE_STATUS.SUCCESS,
} as StorageConfig,
status: StorageStatus.Success,
} as IStorage,
},
})
@ -87,9 +87,9 @@ describe('Enter credentials action enabled tests', () => {
scope: 'system',
backend: 'SFTP',
config: {
status: STORAGE_STATUS.INCOMPLETE_CONF,
status: StorageStatus.IncompleteConf,
userProvided: true,
} as StorageConfig,
} as IStorage,
},
})
@ -103,9 +103,9 @@ describe('Enter credentials action enabled tests', () => {
scope: 'system',
backend: 'SFTP',
config: {
status: STORAGE_STATUS.INCOMPLETE_CONF,
status: StorageStatus.IncompleteConf,
authMechanism: 'password::global::user',
} as StorageConfig,
} as IStorage,
},
})
@ -119,7 +119,7 @@ describe('Enter credentials action enabled tests', () => {
scope: 'system',
backend: 'SFTP',
config: {
} as StorageConfig,
} as IStorage,
},
})

View file

@ -1,11 +1,11 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { AxiosResponse } from '@nextcloud/axios'
import type { Node } from '@nextcloud/files'
import type { StorageConfig } from '../services/externalStorage.ts'
import type { INode } from '@nextcloud/files'
import type { IStorage } from '../types.ts'
import LoginSvg from '@mdi/svg/svg/login.svg?raw'
import axios from '@nextcloud/axios'
@ -17,7 +17,8 @@ import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextc
import { generateUrl } from '@nextcloud/router'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import { defineAsyncComponent } from 'vue'
import { isMissingAuthConfig, STORAGE_STATUS } from '../utils/credentialsUtils.ts'
import { StorageStatus } from '../types.ts'
import { isMissingAuthConfig } from '../utils/credentialsUtils.ts'
import { isNodeExternalStorage } from '../utils/externalStorageUtils.ts'
// Add password confirmation interceptors as
@ -31,7 +32,7 @@ addPasswordConfirmationInterceptors(axios)
* @param login The username
* @param password The password
*/
async function setCredentials(node: Node, login: string, password: string): Promise<null | true> {
async function setCredentials(node: INode, login: string, password: string): Promise<null | true> {
const configResponse = await axios.request({
method: 'PUT',
url: generateUrl('apps/files_external/userglobalstorages/{id}', { id: node.attributes.id }),
@ -39,10 +40,10 @@ async function setCredentials(node: Node, login: string, password: string): Prom
data: {
backendOptions: { user: login, password },
},
}) as AxiosResponse<StorageConfig>
}) as AxiosResponse<IStorage>
const config = configResponse.data
if (config.status !== STORAGE_STATUS.SUCCESS) {
if (config.status !== StorageStatus.Success) {
showError(t('files_external', 'Unable to update this external storage config. {statusMessage}', {
statusMessage: config?.statusMessage || '',
}))
@ -75,7 +76,7 @@ export const action = new FileAction({
return false
}
const config = (node.attributes?.config || {}) as StorageConfig
const config = (node.attributes?.config || {}) as IStorage
if (isMissingAuthConfig(config)) {
return true
}
@ -87,7 +88,7 @@ export const action = new FileAction({
const { login, password } = await spawnDialog(defineAsyncComponent(() => import('../views/CredentialsDialog.vue'))) ?? {}
if (login && password) {
try {
await setCredentials(nodes[0], login, password)
await setCredentials(nodes[0]!, login, password)
showSuccess(t('files_external', 'Credentials successfully set'))
} catch (error) {
showError(t('files_external', 'Error while setting credentials: {error}', {

View file

@ -4,7 +4,7 @@
*/
import type { AxiosError } from '@nextcloud/axios'
import type { StorageConfig } from '../services/externalStorage.ts'
import type { IStorage } from '../types.ts'
import AlertSvg from '@mdi/svg/svg/alert-circle.svg?raw'
import { showWarning } from '@nextcloud/dialogs'
@ -12,7 +12,8 @@ import { emit } from '@nextcloud/event-bus'
import { FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { getStatus } from '../services/externalStorage.ts'
import { isMissingAuthConfig, STORAGE_STATUS } from '../utils/credentialsUtils.ts'
import { StorageStatus } from '../types.ts'
import { isMissingAuthConfig } from '../utils/credentialsUtils.ts'
import { isNodeExternalStorage } from '../utils/externalStorageUtils.ts'
import '../css/fileEntryStatus.scss'
@ -44,14 +45,14 @@ export const action = new FileAction({
span.className = 'files-list__row-status'
span.innerHTML = t('files_external', 'Checking storage …')
let config = null as unknown as StorageConfig
let config: IStorage | undefined
try {
const { data } = await getStatus(node.attributes.id, node.attributes.scope === 'system')
config = data
node.attributes.config = config
emit('files:node:updated', node)
if (config.status !== STORAGE_STATUS.SUCCESS) {
if (config.status !== StorageStatus.Success) {
throw new Error(config?.statusMessage || t('files_external', 'There was an error with this external storage.'))
}

View file

@ -1,16 +1,19 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { View } from '@nextcloud/files'
import type { StorageConfig } from '../services/externalStorage.ts'
import type { IStorage } from '../types.ts'
import * as dialogs from '@nextcloud/dialogs'
import { DefaultType, FileAction, Folder, Permission } from '@nextcloud/files'
import { describe, expect, test, vi } from 'vitest'
import { STORAGE_STATUS } from '../utils/credentialsUtils.ts'
import { StorageStatus } from '../types.ts'
import { action } from './openInFilesAction.ts'
vi.mock('@nextcloud/dialogs', { spy: true })
const view = {
id: 'files',
name: 'Files',
@ -31,8 +34,8 @@ describe('Open in files action conditions tests', () => {
permissions: Permission.ALL,
attributes: {
config: {
status: STORAGE_STATUS.SUCCESS,
} as StorageConfig,
status: StorageStatus.Success,
} as IStorage,
},
})
@ -64,8 +67,8 @@ describe('Open in files action conditions tests', () => {
permissions: Permission.ALL,
attributes: {
config: {
status: STORAGE_STATUS.ERROR,
} as StorageConfig,
status: StorageStatus.Error,
} as IStorage,
},
})
expect(action.displayName({
@ -102,6 +105,7 @@ describe('Open in files action enabled tests', () => {
describe('Open in files action execute tests', () => {
test('Open in files', async () => {
const goToRouteMock = vi.fn()
// @ts-expect-error - mocking for tests
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const storage = new Folder({
@ -112,8 +116,8 @@ describe('Open in files action execute tests', () => {
permissions: Permission.ALL,
attributes: {
config: {
status: STORAGE_STATUS.SUCCESS,
} as StorageConfig,
status: StorageStatus.Success,
} as IStorage,
},
})
@ -130,8 +134,8 @@ describe('Open in files action execute tests', () => {
})
test('Open in files broken storage', async () => {
const confirmMock = vi.fn()
window.OC = { dialogs: { confirm: confirmMock } }
// @ts-expect-error - spy added by vitest
dialogs.showConfirmation.mockImplementationOnce(() => Promise.resolve(true))
const storage = new Folder({
id: 1,
@ -141,8 +145,8 @@ describe('Open in files action execute tests', () => {
permissions: Permission.ALL,
attributes: {
config: {
status: STORAGE_STATUS.ERROR,
} as StorageConfig,
status: StorageStatus.Error,
} as IStorage,
},
})
@ -154,6 +158,6 @@ describe('Open in files action execute tests', () => {
})
// Silent action
expect(exec).toBe(null)
expect(confirmMock).toBeCalledTimes(1)
expect(dialogs.showConfirmation).toHaveBeenCalledOnce()
})
})

View file

@ -1,20 +1,22 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { StorageConfig } from '../services/externalStorage.ts'
import type { IStorage } from '../types.ts'
import { getCurrentUser } from '@nextcloud/auth'
import { showConfirmation } from '@nextcloud/dialogs'
import { DefaultType, FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { STORAGE_STATUS } from '../utils/credentialsUtils.ts'
import { StorageStatus } from '../types.ts'
export const action = new FileAction({
id: 'open-in-files-external-storage',
displayName: ({ nodes }) => {
const config = nodes?.[0]?.attributes?.config as StorageConfig || { status: STORAGE_STATUS.INDETERMINATE }
if (config.status !== STORAGE_STATUS.SUCCESS) {
const config = nodes?.[0]?.attributes?.config as IStorage || { status: StorageStatus.Indeterminate }
if (config.status !== StorageStatus.Success) {
return t('files_external', 'Examine this faulty external storage configuration')
}
return t('files', 'Open in Files')
@ -24,18 +26,18 @@ export const action = new FileAction({
enabled: ({ view }) => view.id === 'extstoragemounts',
async exec({ nodes }) {
const config = nodes[0]?.attributes?.config as StorageConfig
if (config?.status !== STORAGE_STATUS.SUCCESS) {
window.OC.dialogs.confirm(
t('files_external', 'There was an error with this external storage. Do you want to review this mount point config in the settings page?'),
t('files_external', 'External mount error'),
(redirect) => {
if (redirect === true) {
const scope = getCurrentUser()?.isAdmin ? 'admin' : 'user'
window.location.href = generateUrl(`/settings/${scope}/externalstorages`)
}
},
)
const config = nodes[0]?.attributes?.config as IStorage
if (config?.status !== StorageStatus.Success) {
const redirect = await showConfirmation({
name: t('files_external', 'External mount error'),
text: t('files_external', 'There was an error with this external storage. Do you want to review this mount point config in the settings page?'),
labelConfirm: t('files_external', 'Open settings'),
labelReject: t('files_external', 'Ignore'),
})
if (redirect === true) {
const scope = getCurrentUser()?.isAdmin ? 'admin' : 'user'
window.location.href = generateUrl(`/settings/${scope}/externalstorages`)
}
return null
}

View file

@ -12,7 +12,7 @@ import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import { Folder, Permission } from '@nextcloud/files'
import { generateOcsUrl, generateRemoteUrl, generateUrl } from '@nextcloud/router'
import { STORAGE_STATUS } from '../utils/credentialsUtils.ts'
import { StorageStatus } from '../types.ts'
export const rootPath = `/files/${getCurrentUser()?.uid}`
@ -43,7 +43,7 @@ function entryToFolder(ocsEntry: MountEntry): Folder {
source: generateRemoteUrl('dav' + rootPath + '/' + path),
root: rootPath,
owner: getCurrentUser()?.uid || null,
permissions: ocsEntry.config.status !== STORAGE_STATUS.SUCCESS
permissions: ocsEntry.config.status !== StorageStatus.Success
? Permission.NONE
: ocsEntry?.permissions || Permission.READ,
attributes: {

View file

@ -1,29 +1,20 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { StorageConfig } from '../services/externalStorage.ts'
import type { IStorage } from '../types.ts'
// @see https://github.com/nextcloud/server/blob/ac2bc2384efe3c15ff987b87a7432bc60d545c67/lib/public/Files/StorageNotAvailableException.php#L41
export enum STORAGE_STATUS {
SUCCESS = 0,
ERROR = 1,
INDETERMINATE = 2,
INCOMPLETE_CONF = 3,
UNAUTHORIZED = 4,
TIMEOUT = 5,
NETWORK_ERROR = 6,
}
import { StorageStatus } from '../types.ts'
/**
* Check if the given storage configuration is missing authentication configuration
*
* @param config - The storage configuration to check
*/
export function isMissingAuthConfig(config: StorageConfig) {
export function isMissingAuthConfig(config: IStorage) {
// If we don't know the status, assume it is ok
if (!config.status || config.status === STORAGE_STATUS.SUCCESS) {
if (config.status === undefined || config.status === StorageStatus.Success) {
return false
}

View file

@ -1,4 +1,4 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/