Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 6 additions & 15 deletions apps/files_external/src/actions/enterCredentialsAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,20 @@ import type { StorageConfig } from '../services/externalStorage.ts'
import LoginSvg from '@mdi/svg/svg/login.svg?raw'
import axios from '@nextcloud/axios'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { DefaultType, FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
import { generateUrl } from '@nextcloud/router'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import Vue, { defineAsyncComponent } from 'vue'
import { defineAsyncComponent } from 'vue'
import { isMissingAuthConfig, STORAGE_STATUS } from '../utils/credentialsUtils.ts'
import { isNodeExternalStorage } from '../utils/externalStorageUtils.ts'

// Add password confirmation interceptors as
// the backend requires the user to confirm their password
addPasswordConfirmationInterceptors(axios)

type CredentialResponse = {
login?: string
password?: string
}

/**
* Set credentials for external storage
*
Expand Down Expand Up @@ -55,7 +51,9 @@ async function setCredentials(node: Node, login: string, password: string): Prom

// Success update config attribute
showSuccess(t('files_external', 'New configuration successfully saved'))
Vue.set(node.attributes, 'config', config)
node.attributes.config = config
emit('files:node:updated', node)

return true
}

Expand Down Expand Up @@ -86,14 +84,7 @@ export const action = new FileAction({
},

async exec({ nodes }) {
const { login, password } = await new Promise<CredentialResponse>((resolve) => spawnDialog(
defineAsyncComponent(() => import('../views/CredentialsDialog.vue')),
{},
(args) => {
resolve(args as CredentialResponse)
},
))

const { login, password } = await spawnDialog(defineAsyncComponent(() => import('../views/CredentialsDialog.vue'))) ?? {}
if (login && password) {
try {
await setCredentials(nodes[0], login, password)
Expand Down
83 changes: 42 additions & 41 deletions apps/files_external/src/actions/inlineStorageCheckAction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
Expand All @@ -8,9 +8,9 @@ import type { StorageConfig } from '../services/externalStorage.ts'

import AlertSvg from '@mdi/svg/svg/alert-circle.svg?raw'
import { showWarning } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import Vue from 'vue'
import { t } from '@nextcloud/l10n'
import { getStatus } from '../services/externalStorage.ts'
import { isMissingAuthConfig, STORAGE_STATUS } from '../utils/credentialsUtils.ts'
import { isNodeExternalStorage } from '../utils/externalStorageUtils.ts'
Expand All @@ -31,7 +31,8 @@ export const action = new FileAction({
* Use this function to check the storage availability
* We then update the node attributes directly.
*
* @param node The node to render inline
* @param context - The action context
* @param context.nodes - The node to render inline
*/
async renderInline({ nodes }) {
if (nodes.length !== 1 || !nodes[0]) {
Expand All @@ -44,43 +45,43 @@ export const action = new FileAction({
span.innerHTML = t('files_external', 'Checking storage …')

let config = null as unknown as StorageConfig
getStatus(node.attributes.id, node.attributes.scope === 'system')
.then((response) => {
config = response.data
Vue.set(node.attributes, 'config', config)

if (config.status !== STORAGE_STATUS.SUCCESS) {
throw new Error(config?.statusMessage || t('files_external', 'There was an error with this external storage.'))
}

span.remove()
})
.catch((error) => {
// If axios failed or if something else prevented
// us from getting the config
if ((error as AxiosError).response && !config) {
showWarning(t('files_external', 'We were unable to check the external storage {basename}', {
basename: node.basename,
}))
}

// Reset inline status
span.innerHTML = ''

// Checking if we really have an error
const isWarning = !config ? false : isMissingAuthConfig(config)
const overlay = document.createElement('span')
overlay.classList.add(`files-list__row-status--${isWarning ? 'warning' : 'error'}`)

// Only show an icon for errors, warning like missing credentials
// have a dedicated inline action button
if (!isWarning) {
span.innerHTML = AlertSvg
span.title = (error as Error).message
}

span.prepend(overlay)
})
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) {
throw new Error(config?.statusMessage || t('files_external', 'There was an error with this external storage.'))
}

span.remove()
} catch (error) {
// If axios failed or if something else prevented
// us from getting the config
if ((error as AxiosError).response && !config) {
showWarning(t('files_external', 'We were unable to check the external storage {basename}', {
basename: node.basename,
}))
}

// Reset inline status
span.innerHTML = ''

// Checking if we really have an error
const isWarning = !config ? false : isMissingAuthConfig(config)
const overlay = document.createElement('span')
overlay.classList.add(`files-list__row-status--${isWarning ? 'warning' : 'error'}`)

// Only show an icon for errors, warning like missing credentials
// have a dedicated inline action button
if (!isWarning) {
span.innerHTML = AlertSvg
span.title = (error as Error).message
}

span.prepend(overlay)
}

return span
},
Expand Down
2 changes: 1 addition & 1 deletion apps/files_external/src/actions/openInFilesAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { StorageConfig } from '../services/externalStorage.ts'

import { getCurrentUser } from '@nextcloud/auth'
import { DefaultType, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { STORAGE_STATUS } from '../utils/credentialsUtils.ts'

Expand Down
7 changes: 4 additions & 3 deletions apps/files_external/src/init.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import FolderNetworkSvg from '@mdi/svg/svg/folder-network-outline.svg?raw'
import { Column, getNavigation, registerFileAction, View } from '@nextcloud/files'
/**
/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import FolderNetworkSvg from '@mdi/svg/svg/folder-network-outline.svg?raw'
import { Column, getNavigation, registerFileAction, View } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
import { action as enterCredentialsAction } from './actions/enterCredentialsAction.ts'
Expand Down
5 changes: 3 additions & 2 deletions apps/files_external/src/services/externalStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ export type MountEntry = {
}

/**
* Convert an OCS api result (mount entry) to a Folder instance
*
* @param ocsEntry
* @param ocsEntry - The OCS mount entry
*/
function entryToFolder(ocsEntry: MountEntry): Folder {
const path = (ocsEntry.path + '/' + ocsEntry.name).replace(/^\//gm, '')
Expand All @@ -69,7 +70,7 @@ function entryToFolder(ocsEntry: MountEntry): Folder {
}

/**
*
* Fetch the contents of external storage mounts
*/
export async function getContents(): Promise<ContentsWithRoot> {
const response = await axios.get(generateOcsUrl('apps/files_external/api/v1/mounts')) as AxiosResponse<OCSResponse<MountEntry[]>>
Expand Down
4 changes: 3 additions & 1 deletion apps/files_external/src/utils/credentialsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export enum STORAGE_STATUS {
}

/**
* @param config
* Check if the given storage configuration is missing authentication configuration
*
* @param config - The storage configuration to check
*/
export function isMissingAuthConfig(config: StorageConfig) {
// If we don't know the status, assume it is ok
Expand Down
8 changes: 5 additions & 3 deletions apps/files_external/src/utils/externalStorageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Node } from '@nextcloud/files'
import type { INode } from '@nextcloud/files'
import type { MountEntry } from '../services/externalStorage.ts'

import { FileType } from '@nextcloud/files'

/**
* @param node
* Check if the given node represents an external storage mount
*
* @param node - The node to check
*/
export function isNodeExternalStorage(node: Node) {
export function isNodeExternalStorage(node: INode) {
// Not a folder, not a storage
if (node.type === FileType.File) {
return false
Expand Down
65 changes: 22 additions & 43 deletions apps/files_external/src/views/CredentialsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import { t } from '@nextcloud/l10n'
import { ref } from 'vue'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
import NcTextField from '@nextcloud/vue/components/NcTextField'

defineEmits<{
close: [payload?: { login: string, password: string }]
}>()

const login = ref('')
const password = ref('')

const dialogButtons: InstanceType<typeof NcDialog>['buttons'] = [{
label: t('files_external', 'Confirm'),
type: 'submit',
variant: 'primary',
}]
</script>

<template>
<NcDialog
:buttons="dialogButtons"
Expand Down Expand Up @@ -44,46 +66,3 @@
required />
</NcDialog>
</template>

<script lang="ts">
import { t } from '@nextcloud/l10n'
import { defineComponent } from 'vue'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
import NcTextField from '@nextcloud/vue/components/NcTextField'

export default defineComponent({
name: 'CredentialsDialog',

components: {
NcDialog,
NcNoteCard,
NcTextField,
NcPasswordField,
},

setup() {
return {
t,
}
},

data() {
return {
login: '',
password: '',
}
},

computed: {
dialogButtons() {
return [{
label: t('files_external', 'Confirm'),
type: 'submit',
variant: 'primary',
}]
},
},
})
</script>
4 changes: 0 additions & 4 deletions build/frontend-legacy/webpack.modules.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ module.exports = {
'settings-personal': path.join(__dirname, 'apps/files/src', 'main-settings-personal.ts'),
'reference-files': path.join(__dirname, 'apps/files/src', 'reference-files.ts'),
},
files_external: {
init: path.join(__dirname, 'apps/files_external/src', 'init.ts'),
settings: path.join(__dirname, 'apps/files_external/src', 'settings.js'),
},
files_sharing: {
additionalScripts: path.join(__dirname, 'apps/files_sharing/src', 'additionalScripts.js'),
collaboration: path.join(__dirname, 'apps/files_sharing/src', 'collaborationresourceshandler.js'),
Expand Down
4 changes: 4 additions & 0 deletions build/frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const modules = {
'settings-admin': resolve(import.meta.dirname, 'apps/federatedfilesharing/src', 'settings-admin.ts'),
'settings-personal': resolve(import.meta.dirname, 'apps/federatedfilesharing/src', 'settings-personal.ts'),
},
files_external: {
init: resolve(import.meta.dirname, 'apps/files_external/src', 'init.ts'),
settings: resolve(import.meta.dirname, 'apps/files_external/src', 'settings.js'),
},
files_reminders: {
init: resolve(import.meta.dirname, 'apps/files_reminders/src', 'files-init.ts'),
},
Expand Down
Loading