Skip to content

Commit 35606bc

Browse files
authored
Merge pull request #59033 from nextcloud/fix/files-external-issues
fix(files_external): properly handle API errors
2 parents 3be8a65 + a24fc77 commit 35606bc

370 files changed

Lines changed: 1016 additions & 901 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/files_external/src/components/AddExternalStorageDialog/AddExternalStorageDialog.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const open = defineModel<boolean>('open', { default: true })
3333
const {
3434
storage = { backendOptions: {}, mountOptions: {}, type: isAdmin ? 'system' : 'personal' },
3535
} = defineProps<{
36-
storage?: Partial<IStorage> & { backendOptions: IStorage['backendOptions'] }
36+
storage?: Partial<IStorage>
3737
}>()
3838
3939
defineEmits<{
@@ -88,7 +88,7 @@ watch(authMechanisms, () => {
8888
:label="t('files_external', 'Folder name')"
8989
required />
9090

91-
<MountOptions v-model="internalStorage.mountOptions" />
91+
<MountOptions v-model="internalStorage.mountOptions!" />
9292

9393
<ApplicableEntities
9494
v-if="isAdmin"
@@ -112,13 +112,13 @@ watch(authMechanisms, () => {
112112
required />
113113

114114
<BackendConfiguration
115-
v-if="backend"
115+
v-if="backend && internalStorage.backendOptions"
116116
v-model="internalStorage.backendOptions"
117117
:class="$style.externalStorageDialog__configuration"
118118
:configuration="backend.configuration" />
119119

120120
<AuthMechanismConfiguration
121-
v-if="authMechanism"
121+
v-if="authMechanism && internalStorage.backendOptions"
122122
v-model="internalStorage.backendOptions"
123123
:class="$style.externalStorageDialog__configuration"
124124
:authMechanism="authMechanism" />

apps/files_external/src/components/AddExternalStorageDialog/MountOptions.vue

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,14 @@ import NcButton from '@nextcloud/vue/components/NcButton'
1414
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
1515
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
1616
import NcSelect from '@nextcloud/vue/components/NcSelect'
17+
import { parseMountOptions } from '../../store/storages.ts'
1718
import { MountOptionsCheckFilesystem } from '../../types.ts'
1819
1920
const mountOptions = defineModel<Partial<IMountOptions>>({ required: true })
2021
watchEffect(() => {
2122
if (Object.keys(mountOptions.value).length === 0) {
22-
mountOptions.value.encrypt = true
23-
mountOptions.value.previews = true
24-
mountOptions.value.enable_sharing = false
25-
mountOptions.value.filesystem_check_changes = MountOptionsCheckFilesystem.OncePerRequest
26-
mountOptions.value.encoding_compatibility = false
27-
mountOptions.value.readonly = false
23+
// parse and initialize with defaults if needed
24+
mountOptions.value = parseMountOptions(mountOptions.value)
2825
}
2926
})
3027

apps/files_external/src/store/storages.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { IStorage } from '../types.d.ts'
6+
import type { IStorage } from '../types.ts'
77

88
import axios from '@nextcloud/axios'
99
import { loadState } from '@nextcloud/initial-state'
1010
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
1111
import { generateUrl } from '@nextcloud/router'
1212
import { defineStore } from 'pinia'
1313
import { ref, toRaw } from 'vue'
14+
import { MountOptionsCheckFilesystem } from '../types.ts'
1415

1516
const { isAdmin } = loadState<{ isAdmin: boolean }>('files_external', 'settings')
1617

@@ -30,7 +31,7 @@ export const useStorages = defineStore('files_external--storages', () => {
3031
toRaw(storage),
3132
{ confirmPassword: PwdConfirmationMode.Strict },
3233
)
33-
globalStorages.value.push(data)
34+
globalStorages.value.push(parseStorage(data))
3435
}
3536

3637
/**
@@ -45,7 +46,7 @@ export const useStorages = defineStore('files_external--storages', () => {
4546
toRaw(storage),
4647
{ confirmPassword: PwdConfirmationMode.Strict },
4748
)
48-
userStorages.value.push(data)
49+
userStorages.value.push(parseStorage(data))
4950
}
5051

5152
/**
@@ -77,7 +78,7 @@ export const useStorages = defineStore('files_external--storages', () => {
7778
{ confirmPassword: PwdConfirmationMode.Strict },
7879
)
7980

80-
overrideStorage(data)
81+
overrideStorage(parseStorage(data))
8182
}
8283

8384
/**
@@ -87,7 +88,7 @@ export const useStorages = defineStore('files_external--storages', () => {
8788
*/
8889
async function reloadStorage(storage: IStorage) {
8990
const { data } = await axios.get(getUrl(storage))
90-
overrideStorage(data)
91+
overrideStorage(parseStorage(data))
9192
}
9293

9394
// initialize the store
@@ -111,6 +112,7 @@ export const useStorages = defineStore('files_external--storages', () => {
111112
const url = `apps/files_external/${type}`
112113
const { data } = await axios.get<Record<number, IStorage>>(generateUrl(url))
113114
return Object.values(data)
115+
.map(parseStorage)
114116
}
115117

116118
/**
@@ -150,3 +152,45 @@ export const useStorages = defineStore('files_external--storages', () => {
150152
}
151153
}
152154
})
155+
156+
/**
157+
* @param storage - The storage from API
158+
*/
159+
function parseStorage(storage: IStorage) {
160+
return {
161+
...storage,
162+
mountOptions: parseMountOptions(storage.mountOptions),
163+
}
164+
}
165+
166+
/**
167+
* Parse the mount options and convert string boolean values to
168+
* actual booleans and numeric strings to numbers
169+
*
170+
* @param options - The mount options to parse
171+
*/
172+
export function parseMountOptions(options: IStorage['mountOptions']) {
173+
const mountOptions = { ...options }
174+
mountOptions.encrypt = convertBooleanOptions(mountOptions.encrypt, true)
175+
mountOptions.previews = convertBooleanOptions(mountOptions.previews, true)
176+
mountOptions.enable_sharing = convertBooleanOptions(mountOptions.enable_sharing, false)
177+
mountOptions.filesystem_check_changes = typeof mountOptions.filesystem_check_changes === 'string'
178+
? Number.parseInt(mountOptions.filesystem_check_changes)
179+
: (mountOptions.filesystem_check_changes ?? MountOptionsCheckFilesystem.OncePerRequest)
180+
mountOptions.encoding_compatibility = convertBooleanOptions(mountOptions.encoding_compatibility, false)
181+
mountOptions.readonly = convertBooleanOptions(mountOptions.readonly, false)
182+
return mountOptions
183+
}
184+
185+
/**
186+
* Convert backend encoding of boolean options
187+
*
188+
* @param option - The option value from API
189+
* @param fallback - The fallback (default) value
190+
*/
191+
function convertBooleanOptions(option: unknown, fallback = false) {
192+
if (option === undefined) {
193+
return fallback
194+
}
195+
return option === true || option === 'true' || option === '1'
196+
}

apps/files_external/src/views/ExternalStoragesSection.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ async function addStorage(storage?: Partial<IStorage>) {
5959
}
6060
newStorage.value = undefined
6161
} catch (error) {
62-
logger.error('Failed to add external storage', { error })
62+
logger.error('Failed to add external storage', { error, storage })
63+
newStorage.value = { ...storage }
6364
showDialog.value = true
6465
}
6566
}
@@ -134,8 +135,8 @@ async function addStorage(storage?: Partial<IStorage>) {
134135
</NcButton>
135136

136137
<AddExternalStorageDialog
137-
v-model="newStorage"
138138
v-model:open="showDialog"
139+
:storage="newStorage"
139140
@close="addStorage" />
140141

141142
<UserMountSettings v-if="settings.isAdmin" />
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import{f as g,B as y,u as o,o as n,g as p,N as h,w as v,v as _,t as V,E as k,i as x,c as d,H as K,I as M,K as w,L as U,h as f,r as q}from"./runtime-dom.esm-bundler-CKxgtjB6.chunk.mjs";import{c as E}from"./index-2a6kOrDy.chunk.mjs";import{a as L}from"./index-C1xmmKTZ-DF4bDPEh.chunk.mjs";import{t as s}from"./translation-DoG5ZELJ-XUrtIRvk.chunk.mjs";import{g as N}from"./createElementId-DhjFt1I9-DdwCqgaq.chunk.mjs";import{N as S}from"./autolink-U5pBzLgI-D1NMH9bI.chunk.mjs";import{N as j}from"./NcSelect-B1uITk_3-0e2p8qEi.chunk.mjs";import{N as A}from"./NcCheckboxRadioSwitch-D0gFwEVl-BbcIGLyw.chunk.mjs";import{N as B}from"./NcPasswordField-BOLzDHBJ-CKSnPt2n.chunk.mjs";import{_ as C}from"./TrashCanOutline-DmYYOQ4b.chunk.mjs";import{C as c,a as b}from"./types-Dild8a3G.chunk.mjs";import{l as z}from"./logger-resIultJ.chunk.mjs";const P=g({__name:"ConfigurationEntry",props:k({configKey:{},configOption:{}},{modelValue:{type:[String,Boolean],default:""},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const a=y(e,"modelValue");return(t,i)=>e.configOption.type!==o(c).Boolean?(n(),p(h(e.configOption.type===o(c).Password?o(B):o(C)),{key:0,modelValue:a.value,"onUpdate:modelValue":i[0]||(i[0]=l=>a.value=l),name:e.configKey,required:!(e.configOption.flags&o(b).Optional),label:e.configOption.value,title:e.configOption.tooltip},null,8,["modelValue","name","required","label","title"])):(n(),p(o(A),{key:1,modelValue:a.value,"onUpdate:modelValue":i[1]||(i[1]=l=>a.value=l),type:"switch",title:e.configOption.tooltip},{default:v(()=>[_(V(e.configOption.value),1)]),_:1},8,["modelValue","title"]))}}),R=g({__name:"AuthMechanismRsa",props:k({authMechanism:{}},{modelValue:{required:!0},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const a=y(e,"modelValue"),t=q();x(t,()=>{t.value&&(a.value.private_key="",a.value.public_key="")});async function i(){try{const{data:l}=await E.post(N("/apps/files_external/ajax/public_key.php"),{keyLength:t.value});a.value.private_key=l.data.private_key,a.value.public_key=l.data.public_key}catch(l){z.error("Error generating RSA key pair",{error:l}),L(s("files_external","Error generating key pair"))}}return(l,m)=>(n(),d("div",null,[(n(!0),d(K,null,M(e.authMechanism.configuration,(r,u)=>w((n(),p(P,{key:r.value,modelValue:a.value[u],"onUpdate:modelValue":O=>a.value[u]=O,configKey:u,configOption:r},null,8,["modelValue","onUpdate:modelValue","configKey","configOption"])),[[U,!(r.flags&o(b).Hidden)]])),128)),f(o(j),{modelValue:t.value,"onUpdate:modelValue":m[0]||(m[0]=r=>t.value=r),clearable:!1,inputLabel:o(s)("files_external","Key size"),options:[1024,2048,4096],required:""},null,8,["modelValue","inputLabel"]),f(o(S),{disabled:!t.value,wide:"",onClick:i},{default:v(()=>[_(V(o(s)("files_external","Generate keys")),1)]),_:1},8,["disabled"])]))}}),$=Object.freeze(Object.defineProperty({__proto__:null,default:R},Symbol.toStringTag,{value:"Module"}));export{$ as A,P as _};
2+
//# sourceMappingURL=AuthMechanismRsa-CX_mdruD.chunk.mjs.map
File renamed without changes.

0 commit comments

Comments
 (0)