aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorGrigorii K. Shartsev <me@shgk.me>2024-10-09 12:37:33 +0200
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>2024-10-28 12:08:41 +0000
commit5bb1211caef8f47387d17a2f4ab5ac7ae87dc408 (patch)
tree28b54deebe23ec89bf6f5efb2a80cc393cdda964 /apps
parentd717176e9c7c50da0fcc04f5bbbc65e3dcd0cdaa (diff)
downloadnextcloud-server-5bb1211caef8f47387d17a2f4ab5ac7ae87dc408.tar.gz
nextcloud-server-5bb1211caef8f47387d17a2f4ab5ac7ae87dc408.zip
fix(files): handle empty view with error
Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
Diffstat (limited to 'apps')
-rw-r--r--apps/files/src/utils/davUtils.ts36
-rw-r--r--apps/files/src/views/FilesList.vue58
2 files changed, 77 insertions, 17 deletions
diff --git a/apps/files/src/utils/davUtils.ts b/apps/files/src/utils/davUtils.ts
index 38137a04d16..cf49df5b278 100644
--- a/apps/files/src/utils/davUtils.ts
+++ b/apps/files/src/utils/davUtils.ts
@@ -4,6 +4,8 @@
*/
import { getCurrentUser } from '@nextcloud/auth'
+import { t } from '@nextcloud/l10n'
+import type { WebDAVClientError } from 'webdav'
/**
* Check whether this is a public share
@@ -21,3 +23,37 @@ export function getToken() {
const tokenElement = document.getElementById('sharingToken') as (HTMLInputElement | null)
return tokenElement?.value
}
+
+/**
+ * Whether error is a WebDAVClientError
+ * @param error - Any exception
+ * @return {boolean} - Whether error is a WebDAVClientError
+ */
+function isWebDAVClientError(error: unknown): error is WebDAVClientError {
+ return error instanceof Error && 'status' in error && 'response' in error
+}
+
+/**
+ * Get a localized error message from webdav request
+ * @param error - An exception from webdav request
+ * @return {string} Localized error message for end user
+ */
+export function humanizeWebDAVError(error: unknown) {
+ if (error instanceof Error) {
+ if (isWebDAVClientError(error)) {
+ const status = error.status || error.response?.status || 0
+ if ([400, 404, 405].includes(status)) {
+ return t('files', 'Folder not found')
+ } else if (status === 403) {
+ return t('files', 'This operation is forbidden')
+ } else if (status === 500) {
+ return t('files', 'This directory is unavailable, please check the logs or contact the administrator')
+ } else if (status === 503) {
+ return t('files', 'Storage is temporarily not available')
+ }
+ }
+ return t('files', 'Unexpected error: {error}', { error: error.message })
+ }
+
+ return t('files', 'Unknown error')
+}
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index ba206aacad1..314e95f7c49 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -89,22 +89,38 @@
:name="t('files', 'Loading current folder')" />
<!-- Empty content placeholder -->
- <NcEmptyContent v-else-if="!loading && isEmptyDir"
- :name="currentView?.emptyTitle || t('files', 'No files in here')"
- :description="currentView?.emptyCaption || t('files', 'Upload some content or sync with your devices!')"
- data-cy-files-content-empty>
- <template #action>
- <NcButton v-if="dir !== '/'"
- :aria-label="t('files', 'Go to the previous folder')"
- type="primary"
- :to="toPreviousDir">
- {{ t('files', 'Go back') }}
- </NcButton>
- </template>
- <template #icon>
- <NcIconSvgWrapper :svg="currentView.icon" />
- </template>
- </NcEmptyContent>
+ <template v-else-if="!loading && isEmptyDir">
+ <!-- Empty due to error -->
+ <NcEmptyContent v-if="error" :name="error" data-cy-files-content-error>
+ <template #action>
+ <NcButton type="secondary" @click="fetchContent">
+ <template #icon>
+ <IconReload :size="20" />
+ </template>
+ {{ t('files', 'Retry') }}
+ </NcButton>
+ </template>
+ <template #icon>
+ <IconAlertCircleOutline />
+ </template>
+ </NcEmptyContent>
+ <!-- Default empty directory view -->
+ <NcEmptyContent v-else
+ :name="currentView?.emptyTitle || t('files', 'No files in here')"
+ :description="currentView?.emptyCaption || t('files', 'Upload some content or sync with your devices!')"
+ data-cy-files-content-empty>
+ <template v-if="dir !== '/'" #action>
+ <NcButton :aria-label="t('files', 'Go to the previous folder')"
+ type="primary"
+ :to="toPreviousDir">
+ {{ t('files', 'Go back') }}
+ </NcButton>
+ </template>
+ <template #icon>
+ <NcIconSvgWrapper :svg="currentView.icon" />
+ </template>
+ </NcEmptyContent>
+ </template>
<!-- File list -->
<FilesListVirtual v-else
@@ -135,6 +151,8 @@ import { join, dirname } from 'path'
import { Parser } from 'xml2js'
import { defineComponent } from 'vue'
+import IconAlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
+import IconReload from 'vue-material-design-icons/Reload.vue'
import LinkIcon from 'vue-material-design-icons/Link.vue'
import ListViewIcon from 'vue-material-design-icons/FormatListBulletedSquare.vue'
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
@@ -160,6 +178,8 @@ import filesListWidthMixin from '../mixins/filesListWidth.ts'
import filesSortingMixin from '../mixins/filesSorting.ts'
import logger from '../logger.js'
import DragAndDropNotice from '../components/DragAndDropNotice.vue'
+import { humanizeWebDAVError } from '../utils/davUtils.ts'
+
import debounce from 'debounce'
const isSharingEnabled = (getCapabilities() as { files_sharing?: boolean })?.files_sharing !== undefined
@@ -182,6 +202,8 @@ export default defineComponent({
AccountPlusIcon,
UploadPicker,
ViewGridIcon,
+ IconAlertCircleOutline,
+ IconReload,
},
mixins: [
@@ -221,8 +243,8 @@ export default defineComponent({
return {
filterText: '',
loading: true,
+ error: null as string | null,
promise: null as Promise<ContentsWithRoot> | CancelablePromise<ContentsWithRoot> | null,
-
unsubscribeStoreCallback: () => {},
}
},
@@ -475,6 +497,7 @@ export default defineComponent({
methods: {
async fetchContent() {
this.loading = true
+ this.error = null
const dir = this.dir
const currentView = this.currentView
@@ -523,6 +546,7 @@ export default defineComponent({
})
} catch (error) {
logger.error('Error while fetching content', { error })
+ this.error = humanizeWebDAVError(error)
} finally {
this.loading = false
}