diff --git a/apps/files_external/src/actions/enterCredentialsAction.spec.ts b/apps/files_external/src/actions/enterCredentialsAction.spec.ts index e2b7b18c692..5e353f716f0 100644 --- a/apps/files_external/src/actions/enterCredentialsAction.spec.ts +++ b/apps/files_external/src/actions/enterCredentialsAction.spec.ts @@ -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, }, }) diff --git a/apps/files_external/src/actions/enterCredentialsAction.ts b/apps/files_external/src/actions/enterCredentialsAction.ts index 79bb6a2bb84..4eec3d11865 100644 --- a/apps/files_external/src/actions/enterCredentialsAction.ts +++ b/apps/files_external/src/actions/enterCredentialsAction.ts @@ -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 { +async function setCredentials(node: INode, login: string, password: string): Promise { 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 + }) as AxiosResponse 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}', { diff --git a/apps/files_external/src/actions/inlineStorageCheckAction.ts b/apps/files_external/src/actions/inlineStorageCheckAction.ts index 01ef4db706c..2e481993f9a 100644 --- a/apps/files_external/src/actions/inlineStorageCheckAction.ts +++ b/apps/files_external/src/actions/inlineStorageCheckAction.ts @@ -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.')) } diff --git a/apps/files_external/src/actions/openInFilesAction.spec.ts b/apps/files_external/src/actions/openInFilesAction.spec.ts index dc6990d375b..fff1688ec18 100644 --- a/apps/files_external/src/actions/openInFilesAction.spec.ts +++ b/apps/files_external/src/actions/openInFilesAction.spec.ts @@ -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() }) }) diff --git a/apps/files_external/src/actions/openInFilesAction.ts b/apps/files_external/src/actions/openInFilesAction.ts index 55c1a7b084d..d6bccb9e9e1 100644 --- a/apps/files_external/src/actions/openInFilesAction.ts +++ b/apps/files_external/src/actions/openInFilesAction.ts @@ -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 } diff --git a/apps/files_external/src/services/externalStorage.ts b/apps/files_external/src/services/externalStorage.ts index ac5277bfc15..637f1d42f57 100644 --- a/apps/files_external/src/services/externalStorage.ts +++ b/apps/files_external/src/services/externalStorage.ts @@ -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: { diff --git a/apps/files_external/src/utils/credentialsUtils.ts b/apps/files_external/src/utils/credentialsUtils.ts index ed95fe8a68b..7eaeed9a703 100644 --- a/apps/files_external/src/utils/credentialsUtils.ts +++ b/apps/files_external/src/utils/credentialsUtils.ts @@ -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 } diff --git a/apps/files_external/src/utils/externalStorageUtils.ts b/apps/files_external/src/utils/externalStorageUtils.ts index f28e30a02b0..0856b4a66bc 100644 --- a/apps/files_external/src/utils/externalStorageUtils.ts +++ b/apps/files_external/src/utils/externalStorageUtils.ts @@ -1,4 +1,4 @@ -/** +/*! * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */