3 Commits
v1.2.0 ... main

4 changed files with 68 additions and 36 deletions

View File

@@ -271,6 +271,18 @@ async def update_user(request, username):
return json(response) return json(response)
@bp.delete("/users/<username>")
async def delete_user(request, username):
verify(request, privileged=True)
if username not in config.config.users:
raise BadRequest("User does not exist")
try:
config.del_user(username)
except Exception as e:
raise BadRequest(str(e)) from e
return json({"message": f"User {username} deleted"})
@bp.put("/config/public") @bp.put("/config/public")
async def update_public(request): async def update_public(request):
verify(request, privileged=True) verify(request, privileged=True)

View File

@@ -78,7 +78,7 @@ const filesystemdl = async (sel: SelectedItems, handle: FileSystemDirectoryHandl
h = await h.getDirectoryHandle(dir.normalize('NFC'), { create: true }) h = await h.getDirectoryHandle(dir.normalize('NFC'), { create: true })
} catch (error) { } catch (error) {
console.error('Failed to create directory', hdir, error) console.error('Failed to create directory', hdir, error)
return throw new Error(`Failed to create directory ${hdir}: ${error}`)
} }
console.log('Created', hdir) console.log('Created', hdir)
} }
@@ -90,37 +90,42 @@ const filesystemdl = async (sel: SelectedItems, handle: FileSystemDirectoryHandl
fileHandle = await h.getFileHandle(name, { create: true }) fileHandle = await h.getFileHandle(name, { create: true })
} catch (error) { } catch (error) {
console.error('Failed to create file', rel, full, hdir + name, error) console.error('Failed to create file', rel, full, hdir + name, error)
return throw new Error(`Failed to create file ${hdir + name}: ${error}`)
} }
const writable = await fileHandle.createWritable() try {
const url = `/files/${rel}` const writable = await fileHandle.createWritable()
console.log('Fetching', url) const url = `/files/${rel}`
const res = await fetch(url) console.log('Fetching', url)
if (!res.ok) { const res = await fetch(url)
store.error = `Failed to download ${url}: ${res.status} ${res.statusText}` if (!res.ok) {
throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`) store.error = `Failed to download ${url}: ${res.status} ${res.statusText}`
} throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`)
if (res.body) {
++store.dprogress.fileidx
const reader = res.body.getReader()
await writable.truncate(0)
store.error = "Direct download."
store.dprogress.tlast = Date.now()
while (true) {
const { value, done } = await reader.read()
if (done) break
await writable.write(value)
const now = Date.now()
const size = value.byteLength
store.dprogress.xfer += size
store.dprogress.filepos += size
store.dprogress.statbytes += size
store.dprogress.statdur += now - store.dprogress.tlast
store.dprogress.tlast = now
} }
if (res.body) {
++store.dprogress.fileidx
const reader = res.body.getReader()
await writable.truncate(0)
store.error = "Direct download."
store.dprogress.tlast = Date.now()
while (true) {
const { value, done } = await reader.read()
if (done) break
await writable.write(value)
const now = Date.now()
const size = value.byteLength
store.dprogress.xfer += size
store.dprogress.filepos += size
store.dprogress.statbytes += size
store.dprogress.statdur += now - store.dprogress.tlast
store.dprogress.tlast = now
}
}
await writable.close()
console.log('Saved', hdir + name)
} catch (error) {
console.error('Failed to write file', hdir + name, error)
throw new Error(`Failed to write file ${hdir + name}: ${error}`)
} }
await writable.close()
console.log('Saved', hdir + name)
} }
statReset() statReset()
} }

View File

@@ -14,9 +14,9 @@
</div> </div>
<h3>Users</h3> <h3>Users</h3>
<button @click="addUser" class="button" title="Add new user"> Add User</button> <button @click="addUser" class="button" title="Add new user"> Add User</button>
<div v-if="success" class="success-message"> <div v-if="success" class="success-message" @click="copySuccess(false)">
{{ success }} {{ success }}
<button @click="copySuccess" class="button small" title="Copy to clipboard"><EFBFBD></button> <button v-if="success.includes('Password:') || success.includes('New password:')" @click.stop="copySuccess(true)" class="button small" title="Copy to clipboard">{{ copyButtonText }}</button>
</div> </div>
<table class="user-table"> <table class="user-table">
<thead> <thead>
@@ -70,6 +70,7 @@ const loading = ref(true)
const users = ref<User[]>([]) const users = ref<User[]>([])
const error = ref('') const error = ref('')
const success = ref('') const success = ref('')
const copyButtonText = ref('📋')
const serverSettings = reactive({ const serverSettings = reactive({
public: false public: false
}) })
@@ -170,11 +171,25 @@ const deleteUserAction = async (username: string) => {
} }
} }
const copySuccess = async () => { const copySuccess = async (isButtonClick: boolean = false) => {
const passwordMatch = success.value.match(/Password: (.+)/) const passwordMatch = success.value.match(/(?:Password|New password): (.+)/)
if (passwordMatch) { if (passwordMatch) {
await navigator.clipboard.writeText(passwordMatch[1]) await navigator.clipboard.writeText(passwordMatch[1])
// Maybe flash or something, but for now just copy if (isButtonClick) {
// Show "Copied!" indication on button
copyButtonText.value = '✅ Copied!'
// Hide password and button immediately after copying
const baseMessage = success.value.replace(/(?:Password|New password): .+/, 'Password copied to clipboard!')
success.value = baseMessage
// Hide the entire message after 3 seconds
setTimeout(() => {
success.value = ''
copyButtonText.value = '📋'
}, 3000)
} else {
// Just hide the message when clicking elsewhere
success.value = ''
}
} }
} }

View File

@@ -73,8 +73,8 @@ watchEffect(() => {
store.query = props.query store.query = props.query
}) })
watch(documents, (docs) => { watch([() => props.path.join('/'), () => props.query], () => {
store.prefs.gallery = docs.some(d => d.previewable) store.prefs.gallery = documents.value.some(d => d.previewable)
}, { immediate: true }) }, { immediate: true })
</script> </script>