mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
move filetransfer to locks.
This commit is contained in:
parent
cdb9b185f2
commit
d17a7e8d82
@ -9,7 +9,7 @@
|
|||||||
<i :class="fileIcon(item)" />
|
<i :class="fileIcon(item)" />
|
||||||
<p>{{ item.name }}</p>
|
<p>{{ item.name }}</p>
|
||||||
<p class="file-size">{{ fileSize(item.size) }}</p>
|
<p class="file-size">{{ fileSize(item.size) }}</p>
|
||||||
<i v-if="item.type !== 'dir'" class="fas fa-download download" @click="() => download(item)" />
|
<i v-if="item.type !== 'dir'" class="fas fa-download download" @click="download(item)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="transfer-area">
|
<div class="transfer-area">
|
||||||
@ -31,7 +31,7 @@
|
|||||||
></i>
|
></i>
|
||||||
<p>{{ download.name }}</p>
|
<p>{{ download.name }}</p>
|
||||||
<p class="file-size">{{ Math.min(100, Math.round((download.progress / download.size) * 100)) }}%</p>
|
<p class="file-size">{{ Math.min(100, Math.round((download.progress / download.size) * 100)) }}%</p>
|
||||||
<i class="fas fa-xmark remove-transfer" @click="() => removeTransfer(download)"></i>
|
<i class="fas fa-xmark remove-transfer" @click="removeTransfer(download)"></i>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="download.status === 'failed'" class="transfer-error">{{ download.error }}</div>
|
<div v-if="download.status === 'failed'" class="transfer-error">{{ download.error }}</div>
|
||||||
<progress
|
<progress
|
||||||
@ -60,7 +60,7 @@
|
|||||||
></i>
|
></i>
|
||||||
<p>{{ upload.name }}</p>
|
<p>{{ upload.name }}</p>
|
||||||
<p class="file-size">{{ Math.min(100, Math.round((upload.progress / upload.size) * 100)) }}%</p>
|
<p class="file-size">{{ Math.min(100, Math.round((upload.progress / upload.size) * 100)) }}%</p>
|
||||||
<i class="fas fa-xmark remove-transfer" @click="() => removeTransfer(upload)"></i>
|
<i class="fas fa-xmark remove-transfer" @click="removeTransfer(upload)"></i>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="upload.status === 'failed'" class="transfer-error">{{ upload.error }}</div>
|
<div v-if="upload.status === 'failed'" class="transfer-error">{{ upload.error }}</div>
|
||||||
<progress
|
<progress
|
||||||
@ -75,8 +75,8 @@
|
|||||||
<div
|
<div
|
||||||
class="upload-area"
|
class="upload-area"
|
||||||
:class="{ 'upload-area-drag': uploadAreaDrag }"
|
:class="{ 'upload-area-drag': uploadAreaDrag }"
|
||||||
@dragover.prevent="() => (uploadAreaDrag = true)"
|
@dragover.prevent="uploadAreaDrag = true"
|
||||||
@dragleave.prevent="() => (uploadAreaDrag = false)"
|
@dragleave.prevent="uploadAreaDrag = false"
|
||||||
@drop.prevent="(e) => upload(e.dataTransfer)"
|
@drop.prevent="(e) => upload(e.dataTransfer)"
|
||||||
@click="openFileBrowser"
|
@click="openFileBrowser"
|
||||||
>
|
>
|
||||||
@ -322,6 +322,7 @@
|
|||||||
.get(url, {
|
.get(url, {
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
|
withCredentials: false,
|
||||||
onDownloadProgress: (x) => {
|
onDownloadProgress: (x) => {
|
||||||
transfer.progress = x.loaded
|
transfer.progress = x.loaded
|
||||||
|
|
||||||
@ -380,6 +381,7 @@
|
|||||||
this.$http
|
this.$http
|
||||||
.post(url, formdata, {
|
.post(url, formdata, {
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
|
withCredentials: false,
|
||||||
onUploadProgress: (x: any) => {
|
onUploadProgress: (x: any) => {
|
||||||
transfer.progress = x.loaded
|
transfer.progress = x.loaded
|
||||||
|
|
||||||
|
@ -31,6 +31,19 @@
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if="fileTransfer">
|
||||||
|
<i
|
||||||
|
:class="[{ disabled: !admin }, { locked: isLocked('file_transfer') }, 'fas', 'fa-file']"
|
||||||
|
@click="toggleLock('file_transfer')"
|
||||||
|
v-tooltip="{
|
||||||
|
content: lockedTooltip('file_transfer'),
|
||||||
|
placement: 'bottom',
|
||||||
|
offset: 5,
|
||||||
|
boundariesElement: 'body',
|
||||||
|
delay: { show: 300, hide: 100 },
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span v-if="showBadge" class="badge">•</span>
|
<span v-if="showBadge" class="badge">•</span>
|
||||||
<i class="fas fa-bars toggle" @click="toggleMenu" />
|
<i class="fas fa-bars toggle" @click="toggleMenu" />
|
||||||
@ -169,26 +182,24 @@
|
|||||||
return !this.side && this.readTexts != this.texts
|
return !this.side && this.readTexts != this.texts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get fileTransfer() {
|
||||||
|
return this.$accessor.remote.fileTransfer
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLock(resource: AdminLockResource) {
|
||||||
|
this.$accessor.toggleLock(resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLocked(resource: AdminLockResource): boolean {
|
||||||
|
return this.$accessor.isLocked(resource)
|
||||||
|
}
|
||||||
|
|
||||||
readTexts: number = 0
|
readTexts: number = 0
|
||||||
toggleMenu() {
|
toggleMenu() {
|
||||||
this.$accessor.client.toggleSide()
|
this.$accessor.client.toggleSide()
|
||||||
this.readTexts = this.texts
|
this.readTexts = this.texts
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLock(resource: AdminLockResource) {
|
|
||||||
if (!this.admin) return
|
|
||||||
|
|
||||||
if (this.isLocked(resource)) {
|
|
||||||
this.$accessor.unlock(resource)
|
|
||||||
} else {
|
|
||||||
this.$accessor.lock(resource)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isLocked(resource: AdminLockResource): boolean {
|
|
||||||
return resource in this.locked && this.locked[resource]
|
|
||||||
}
|
|
||||||
|
|
||||||
lockedTooltip(resource: AdminLockResource) {
|
lockedTooltip(resource: AdminLockResource) {
|
||||||
if (this.admin) {
|
if (this.admin) {
|
||||||
return this.$t(`locks.${resource}.` + (this.isLocked(resource) ? `unlock` : `lock`))
|
return this.$t(`locks.${resource}.` + (this.isLocked(resource) ? `unlock` : `lock`))
|
||||||
|
@ -44,20 +44,6 @@
|
|||||||
<span />
|
<span />
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="admin">
|
|
||||||
<span>{{ $t('setting.file_transfer') }}</span>
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" v-model="file_transfer" />
|
|
||||||
<span />
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li v-if="admin && file_transfer">
|
|
||||||
<span>{{ $t('setting.unpriv_file_transfer') }}</span>
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" v-model="unpriv_file_transfer" />
|
|
||||||
<span />
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li class="broadcast" v-if="admin">
|
<li class="broadcast" v-if="admin">
|
||||||
<div>
|
<div>
|
||||||
<span>{{ $t('setting.broadcast_title') }}</span>
|
<span>{{ $t('setting.broadcast_title') }}</span>
|
||||||
@ -380,22 +366,6 @@
|
|||||||
return this.$accessor.settings.keyboard_layout
|
return this.$accessor.settings.keyboard_layout
|
||||||
}
|
}
|
||||||
|
|
||||||
get file_transfer() {
|
|
||||||
return this.$accessor.settings.file_transfer
|
|
||||||
}
|
|
||||||
|
|
||||||
set file_transfer(value: boolean) {
|
|
||||||
this.$accessor.settings.setRemoteFileTransferStatus({ admin: value, unpriv: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
get unpriv_file_transfer() {
|
|
||||||
return this.$accessor.settings.unpriv_file_transfer
|
|
||||||
}
|
|
||||||
|
|
||||||
set unpriv_file_transfer(value: boolean) {
|
|
||||||
this.$accessor.settings.setRemoteFileTransferStatus({ admin: this.file_transfer, unpriv: value })
|
|
||||||
}
|
|
||||||
|
|
||||||
get broadcast_is_active() {
|
get broadcast_is_active() {
|
||||||
return this.$accessor.settings.broadcast_is_active
|
return this.$accessor.settings.broadcast_is_active
|
||||||
}
|
}
|
||||||
|
@ -95,10 +95,7 @@
|
|||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
get filetransferAllowed() {
|
get filetransferAllowed() {
|
||||||
return (
|
return this.$accessor.remote.fileTransfer && (this.$accessor.user.admin || !this.$accessor.isLocked('file_transfer'))
|
||||||
(this.$accessor.user.admin && this.$accessor.settings.file_transfer) ||
|
|
||||||
this.$accessor.settings.unpriv_file_transfer
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get tab() {
|
get tab() {
|
||||||
@ -106,6 +103,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch('tab', { immediate: true })
|
@Watch('tab', { immediate: true })
|
||||||
|
@Watch('filetransferAllowed', { immediate: true })
|
||||||
onTabChange() {
|
onTabChange() {
|
||||||
// do not show the files tab if file transfer is disabled
|
// do not show the files tab if file transfer is disabled
|
||||||
if (this.tab === 'files' && !this.filetransferAllowed) {
|
if (this.tab === 'files' && !this.filetransferAllowed) {
|
||||||
@ -113,6 +111,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Watch('filetransferAllowed')
|
||||||
|
onFileTransferAllowedChange() {
|
||||||
|
if (this.filetransferAllowed) {
|
||||||
|
this.$accessor.files.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
change(tab: string) {
|
change(tab: string) {
|
||||||
this.$accessor.client.setTab(tab)
|
this.$accessor.client.setTab(tab)
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,14 @@ export const locks = {
|
|||||||
notif_locked: 'Raum gesperrt',
|
notif_locked: 'Raum gesperrt',
|
||||||
notif_unlocked: 'Raum entsperrt',
|
notif_unlocked: 'Raum entsperrt',
|
||||||
},
|
},
|
||||||
|
file_transfer: {
|
||||||
|
lock: 'Dateiübertragung sperren (für Nutzer)',
|
||||||
|
unlock: 'Dateiübertragung entsperren (für Nutzer)',
|
||||||
|
locked: 'Dateiübertragung gesperrt (für Nutzer)',
|
||||||
|
unlocked: 'Dateiübertragung entsperrt (für Nutzer)',
|
||||||
|
notif_locked: 'Dateiübertragung gesperrt',
|
||||||
|
notif_unlocked: 'Dateiübertragung entsperrt',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -78,8 +86,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Emotes ignorieren',
|
ignore_emotes: 'Emotes ignorieren',
|
||||||
chat_sound: 'Chat-Sound abspielen',
|
chat_sound: 'Chat-Sound abspielen',
|
||||||
keyboard_layout: 'Tastaturbelegung',
|
keyboard_layout: 'Tastaturbelegung',
|
||||||
file_transfer: 'Dateiübertragung',
|
|
||||||
unpriv_file_transfer: 'Übertragung von Benutzerdateien',
|
|
||||||
broadcast_title: 'Live-Übertragung',
|
broadcast_title: 'Live-Übertragung',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,14 @@ export const locks = {
|
|||||||
notif_locked: 'locked the room',
|
notif_locked: 'locked the room',
|
||||||
notif_unlocked: 'unlocked the room',
|
notif_unlocked: 'unlocked the room',
|
||||||
},
|
},
|
||||||
|
file_transfer: {
|
||||||
|
lock: 'Lock File Transfer (for users)',
|
||||||
|
unlock: 'Unlock File Transfer (for users)',
|
||||||
|
locked: 'File Transfer Locked (for users)',
|
||||||
|
unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
notif_locked: 'locked file transfer',
|
||||||
|
notif_unlocked: 'unlocked file transfer',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -80,8 +88,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Ignore Emotes',
|
ignore_emotes: 'Ignore Emotes',
|
||||||
chat_sound: 'Play Chat Sound',
|
chat_sound: 'Play Chat Sound',
|
||||||
keyboard_layout: 'Keyboard Layout',
|
keyboard_layout: 'Keyboard Layout',
|
||||||
file_transfer: 'File Transfer',
|
|
||||||
unpriv_file_transfer: 'Non-admin File Transfer',
|
|
||||||
broadcast_title: 'Live Broadcast',
|
broadcast_title: 'Live Broadcast',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,15 @@ export const locks = {
|
|||||||
notif_locked: 'bloqueó la sala',
|
notif_locked: 'bloqueó la sala',
|
||||||
notif_unlocked: 'desbloqueó la sala',
|
notif_unlocked: 'desbloqueó la sala',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -84,8 +93,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Ignorar Emotes',
|
ignore_emotes: 'Ignorar Emotes',
|
||||||
chat_sound: 'Reproducir Sonidos Chat',
|
chat_sound: 'Reproducir Sonidos Chat',
|
||||||
keyboard_layout: 'Keyboard Layout',
|
keyboard_layout: 'Keyboard Layout',
|
||||||
file_transfer: 'Transferencia de archivos',
|
|
||||||
unpriv_file_transfer: 'Transferencia de archivos de usuario',
|
|
||||||
// TODO
|
// TODO
|
||||||
//broadcast_title: 'Live Broadcast',
|
//broadcast_title: 'Live Broadcast',
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,15 @@ export const locks = {
|
|||||||
notif_locked: 'lukittu huone',
|
notif_locked: 'lukittu huone',
|
||||||
notif_unlocked: 'vapautettu huone',
|
notif_unlocked: 'vapautettu huone',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -80,8 +89,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Estä emojit',
|
ignore_emotes: 'Estä emojit',
|
||||||
chat_sound: 'Soita viesti ääni',
|
chat_sound: 'Soita viesti ääni',
|
||||||
keyboard_layout: 'Näppäimistöasettelu',
|
keyboard_layout: 'Näppäimistöasettelu',
|
||||||
file_transfer: 'Tiedoston siirto',
|
|
||||||
unpriv_file_transfer: 'Käyttäjän tiedostojen siirto',
|
|
||||||
broadcast_title: 'Suora Lähetys',
|
broadcast_title: 'Suora Lähetys',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,15 @@ export const locks = {
|
|||||||
notif_locked: 'a vérouillé la salle',
|
notif_locked: 'a vérouillé la salle',
|
||||||
notif_unlocked: 'a dévérouillé la salle',
|
notif_unlocked: 'a dévérouillé la salle',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -84,8 +93,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Ignorer les Emotes',
|
ignore_emotes: 'Ignorer les Emotes',
|
||||||
chat_sound: 'Jouer le son du tchat',
|
chat_sound: 'Jouer le son du tchat',
|
||||||
keyboard_layout: 'Langue du clavier',
|
keyboard_layout: 'Langue du clavier',
|
||||||
file_transfer: 'Transfert de fichiers',
|
|
||||||
unpriv_file_transfer: "Transfert de fichiers d'utilisateurs",
|
|
||||||
// TODO
|
// TODO
|
||||||
//broadcast_title: 'Live Broadcast',
|
//broadcast_title: 'Live Broadcast',
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,15 @@ export const locks = {
|
|||||||
notif_locked: '방이 잠겼습니다',
|
notif_locked: '방이 잠겼습니다',
|
||||||
notif_unlocked: '방 잠금이 해제됐습니다',
|
notif_unlocked: '방 잠금이 해제됐습니다',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -78,8 +87,6 @@ export const setting = {
|
|||||||
ignore_emotes: '이모지 무시',
|
ignore_emotes: '이모지 무시',
|
||||||
chat_sound: '채팅 소리 재생',
|
chat_sound: '채팅 소리 재생',
|
||||||
keyboard_layout: '키보드 레이아웃',
|
keyboard_layout: '키보드 레이아웃',
|
||||||
file_transfer: '파일 전송',
|
|
||||||
unpriv_file_transfer: '사용자 파일 전송',
|
|
||||||
broadcast_title: '실시간 방송',
|
broadcast_title: '실시간 방송',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,15 @@ export const locks = {
|
|||||||
notif_locked: 'låste rommet',
|
notif_locked: 'låste rommet',
|
||||||
notif_unlocked: 'låste opp rommet',
|
notif_unlocked: 'låste opp rommet',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -84,8 +93,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Ignorer smilefjes',
|
ignore_emotes: 'Ignorer smilefjes',
|
||||||
chat_sound: 'Sludringslyd',
|
chat_sound: 'Sludringslyd',
|
||||||
keyboard_layout: 'Tastaturoppsett',
|
keyboard_layout: 'Tastaturoppsett',
|
||||||
file_transfer: 'Filoverførsel',
|
|
||||||
unpriv_file_transfer: 'Overførsel af brugerfiler',
|
|
||||||
// TODO
|
// TODO
|
||||||
//broadcast_title: 'Live Broadcast',
|
//broadcast_title: 'Live Broadcast',
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,15 @@ export const locks = {
|
|||||||
notif_locked: 'комната закрыта',
|
notif_locked: 'комната закрыта',
|
||||||
notif_unlocked: 'комната открыта',
|
notif_unlocked: 'комната открыта',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -80,8 +89,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Игнорировать эмоции',
|
ignore_emotes: 'Игнорировать эмоции',
|
||||||
chat_sound: 'Проигрывать звук чата',
|
chat_sound: 'Проигрывать звук чата',
|
||||||
keyboard_layout: 'Раскладка клавиатуры',
|
keyboard_layout: 'Раскладка клавиатуры',
|
||||||
file_transfer: 'Передача файлов',
|
|
||||||
unpriv_file_transfer: 'Передача файлов пользователей',
|
|
||||||
broadcast_title: 'Прямой эфир',
|
broadcast_title: 'Прямой эфир',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,15 @@ export const locks = {
|
|||||||
notif_locked: 'miestnosť bola zamknutá',
|
notif_locked: 'miestnosť bola zamknutá',
|
||||||
notif_unlocked: 'miestnosť bola odomknutá',
|
notif_unlocked: 'miestnosť bola odomknutá',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -83,8 +92,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Ignorovať smajlíky',
|
ignore_emotes: 'Ignorovať smajlíky',
|
||||||
chat_sound: 'Prehrávať zvuky chatu',
|
chat_sound: 'Prehrávať zvuky chatu',
|
||||||
keyboard_layout: 'Rozloženie klávesnice',
|
keyboard_layout: 'Rozloženie klávesnice',
|
||||||
file_transfer: 'Prenos súborov',
|
|
||||||
unpriv_file_transfer: 'Prenos súborov používateľa',
|
|
||||||
broadcast_title: 'Živé vysielanie',
|
broadcast_title: 'Živé vysielanie',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,15 @@ export const locks = {
|
|||||||
notif_locked: 'låste rummet',
|
notif_locked: 'låste rummet',
|
||||||
notif_unlocked: 'låste upp rummet',
|
notif_unlocked: 'låste upp rummet',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -84,8 +93,6 @@ export const setting = {
|
|||||||
ignore_emotes: 'Ignorera Emotes',
|
ignore_emotes: 'Ignorera Emotes',
|
||||||
chat_sound: 'Spela Chatt Ljud',
|
chat_sound: 'Spela Chatt Ljud',
|
||||||
keyboard_layout: 'Tangentbordslayout',
|
keyboard_layout: 'Tangentbordslayout',
|
||||||
file_transfer: 'Överföring av filer',
|
|
||||||
unpriv_file_transfer: 'Överföring av användarfiler',
|
|
||||||
// TODO
|
// TODO
|
||||||
//broadcast_title: 'Live Broadcast',
|
//broadcast_title: 'Live Broadcast',
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,15 @@ export const locks = {
|
|||||||
notif_locked: '锁上房间',
|
notif_locked: '锁上房间',
|
||||||
notif_unlocked: '解锁房间',
|
notif_unlocked: '解锁房间',
|
||||||
},
|
},
|
||||||
|
// TODO
|
||||||
|
//file_transfer: {
|
||||||
|
// lock: 'Lock File Transfer (for users)',
|
||||||
|
// unlock: 'Unlock File Transfer (for users)',
|
||||||
|
// locked: 'File Transfer Locked (for users)',
|
||||||
|
// unlocked: 'File Transfer Unlocked (for users)',
|
||||||
|
// notif_locked: 'locked file transfer',
|
||||||
|
// notif_unlocked: 'unlocked file transfer',
|
||||||
|
//},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setting = {
|
export const setting = {
|
||||||
@ -80,8 +89,6 @@ export const setting = {
|
|||||||
ignore_emotes: '忽略表情符号',
|
ignore_emotes: '忽略表情符号',
|
||||||
chat_sound: '播放聊天声音',
|
chat_sound: '播放聊天声音',
|
||||||
keyboard_layout: '键盘布局',
|
keyboard_layout: '键盘布局',
|
||||||
file_transfer: '文件传输',
|
|
||||||
unpriv_file_transfer: '用户文件传输',
|
|
||||||
broadcast_title: '现场流媒体',
|
broadcast_title: '现场流媒体',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ export const EVENT = {
|
|||||||
EMOTE: 'chat/emote',
|
EMOTE: 'chat/emote',
|
||||||
},
|
},
|
||||||
FILETRANSFER: {
|
FILETRANSFER: {
|
||||||
STATUS: 'filetransfer/status',
|
|
||||||
LIST: 'filetransfer/list',
|
LIST: 'filetransfer/list',
|
||||||
REFRESH: 'filetransfer/refresh',
|
REFRESH: 'filetransfer/refresh',
|
||||||
},
|
},
|
||||||
@ -98,10 +97,7 @@ export type SignalEvents =
|
|||||||
|
|
||||||
export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOTE
|
export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOTE
|
||||||
|
|
||||||
export type FileTransferEvents =
|
export type FileTransferEvents = typeof EVENT.FILETRANSFER.LIST | typeof EVENT.FILETRANSFER.REFRESH
|
||||||
| typeof EVENT.FILETRANSFER.STATUS
|
|
||||||
| typeof EVENT.FILETRANSFER.LIST
|
|
||||||
| typeof EVENT.FILETRANSFER.REFRESH
|
|
||||||
|
|
||||||
export type ScreenEvents = typeof EVENT.SCREEN.CONFIGURATIONS | typeof EVENT.SCREEN.RESOLUTION | typeof EVENT.SCREEN.SET
|
export type ScreenEvents = typeof EVENT.SCREEN.CONFIGURATIONS | typeof EVENT.SCREEN.RESOLUTION | typeof EVENT.SCREEN.SET
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ import {
|
|||||||
SystemInitPayload,
|
SystemInitPayload,
|
||||||
AdminLockResource,
|
AdminLockResource,
|
||||||
FileTransferListPayload,
|
FileTransferListPayload,
|
||||||
FileTransferStatusPayload,
|
|
||||||
} from './messages'
|
} from './messages'
|
||||||
|
|
||||||
interface NekoEvents extends BaseEvents {}
|
interface NekoEvents extends BaseEvents {}
|
||||||
@ -135,8 +134,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// System Events
|
// System Events
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
protected [EVENT.SYSTEM.INIT]({ implicit_hosting, locks }: SystemInitPayload) {
|
protected [EVENT.SYSTEM.INIT]({ implicit_hosting, locks, file_transfer }: SystemInitPayload) {
|
||||||
this.$accessor.remote.setImplicitHosting(implicit_hosting)
|
this.$accessor.remote.setImplicitHosting(implicit_hosting)
|
||||||
|
this.$accessor.remote.setFileTransfer(file_transfer)
|
||||||
|
|
||||||
for (const resource in locks) {
|
for (const resource in locks) {
|
||||||
this[EVENT.ADMIN.LOCK]({
|
this[EVENT.ADMIN.LOCK]({
|
||||||
@ -354,12 +354,8 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// Filetransfer Events
|
// File Transfer Events
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
protected [EVENT.FILETRANSFER.STATUS]({ admin, unpriv }: FileTransferStatusPayload) {
|
|
||||||
this.$accessor.settings.setLocalFileTransferStatus({ admin, unpriv })
|
|
||||||
}
|
|
||||||
|
|
||||||
protected [EVENT.FILETRANSFER.LIST]({ cwd, files }: FileTransferListPayload) {
|
protected [EVENT.FILETRANSFER.LIST]({ cwd, files }: FileTransferListPayload) {
|
||||||
this.$accessor.files.setCwd(cwd)
|
this.$accessor.files.setCwd(cwd)
|
||||||
this.$accessor.files.setFileList(files)
|
this.$accessor.files.setFileList(files)
|
||||||
|
@ -39,7 +39,6 @@ export type WebSocketPayloads =
|
|||||||
| ChatPayload
|
| ChatPayload
|
||||||
| ChatSendPayload
|
| ChatSendPayload
|
||||||
| EmojiSendPayload
|
| EmojiSendPayload
|
||||||
| FileTransferStatusPayload
|
|
||||||
| ScreenResolutionPayload
|
| ScreenResolutionPayload
|
||||||
| ScreenConfigurationsPayload
|
| ScreenConfigurationsPayload
|
||||||
| AdminPayload
|
| AdminPayload
|
||||||
@ -61,6 +60,7 @@ export interface SystemInit extends WebSocketMessage, SystemInitPayload {
|
|||||||
export interface SystemInitPayload {
|
export interface SystemInitPayload {
|
||||||
implicit_hosting: boolean
|
implicit_hosting: boolean
|
||||||
locks: Record<string, string>
|
locks: Record<string, string>
|
||||||
|
file_transfer: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// system/disconnect
|
// system/disconnect
|
||||||
@ -197,16 +197,6 @@ export interface EmojiSendPayload {
|
|||||||
/*
|
/*
|
||||||
FILE TRANSFER PAYLOADS
|
FILE TRANSFER PAYLOADS
|
||||||
*/
|
*/
|
||||||
export interface FileTransferStatusMessage extends WebSocketMessage, FileTransferStatusPayload {
|
|
||||||
event: typeof EVENT.FILETRANSFER.STATUS
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FileTransferStatusPayload {
|
|
||||||
admin: boolean
|
|
||||||
unpriv: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// file transfer list
|
|
||||||
export interface FileTransferListMessage extends WebSocketMessage, FileTransferListPayload {
|
export interface FileTransferListMessage extends WebSocketMessage, FileTransferListPayload {
|
||||||
event: FileTransferEvents
|
event: FileTransferEvents
|
||||||
}
|
}
|
||||||
@ -272,7 +262,7 @@ export interface AdminLockMessage extends WebSocketMessage, AdminLockPayload {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminLockResource = 'login' | 'control'
|
export type AdminLockResource = 'login' | 'control' | 'file_transfer'
|
||||||
|
|
||||||
export interface AdminLockPayload {
|
export interface AdminLockPayload {
|
||||||
resource: AdminLockResource
|
resource: AdminLockResource
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import { useAccessor, mutationTree, actionTree } from 'typed-vuex'
|
import { useAccessor, mutationTree, getterTree, actionTree } from 'typed-vuex'
|
||||||
import { EVENT } from '~/neko/events'
|
import { EVENT } from '~/neko/events'
|
||||||
import { AdminLockResource } from '~/neko/messages'
|
import { AdminLockResource } from '~/neko/messages'
|
||||||
import { get, set } from '~/utils/localstorage'
|
import { get, set } from '~/utils/localstorage'
|
||||||
@ -56,8 +56,12 @@ export const mutations = mutationTree(state, {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getters = getterTree(state, {
|
||||||
|
isLocked: (state) => (resource: AdminLockResource) => resource in state.locked && state.locked[resource],
|
||||||
|
})
|
||||||
|
|
||||||
export const actions = actionTree(
|
export const actions = actionTree(
|
||||||
{ state, mutations },
|
{ state, getters, mutations },
|
||||||
{
|
{
|
||||||
initialise(store) {
|
initialise(store) {
|
||||||
accessor.emoji.initialise()
|
accessor.emoji.initialise()
|
||||||
@ -80,6 +84,14 @@ export const actions = actionTree(
|
|||||||
$client.sendMessage(EVENT.ADMIN.UNLOCK, { resource })
|
$client.sendMessage(EVENT.ADMIN.UNLOCK, { resource })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleLock(_, resource: AdminLockResource) {
|
||||||
|
if (accessor.isLocked(resource)) {
|
||||||
|
accessor.unlock(resource)
|
||||||
|
} else {
|
||||||
|
accessor.lock(resource)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
login({ state }, { displayname, password }: { displayname: string; password: string }) {
|
login({ state }, { displayname, password }: { displayname: string; password: string }) {
|
||||||
accessor.setLogin({ displayname, password })
|
accessor.setLogin({ displayname, password })
|
||||||
$client.login(password, displayname)
|
$client.login(password, displayname)
|
||||||
@ -98,6 +110,7 @@ export const storePattern = {
|
|||||||
state,
|
state,
|
||||||
mutations,
|
mutations,
|
||||||
actions,
|
actions,
|
||||||
|
getters,
|
||||||
modules: { video, chat, files, user, remote, settings, client, emoji },
|
modules: { video, chat, files, user, remote, settings, client, emoji },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ export const state = () => ({
|
|||||||
clipboard: '',
|
clipboard: '',
|
||||||
locked: false,
|
locked: false,
|
||||||
implicitHosting: true,
|
implicitHosting: true,
|
||||||
|
fileTransfer: true,
|
||||||
keyboardModifierState: -1,
|
keyboardModifierState: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -53,6 +54,10 @@ export const mutations = mutationTree(state, {
|
|||||||
state.implicitHosting = val
|
state.implicitHosting = val
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setFileTransfer(state, val: boolean) {
|
||||||
|
state.fileTransfer = val
|
||||||
|
},
|
||||||
|
|
||||||
reset(state) {
|
reset(state) {
|
||||||
state.id = ''
|
state.id = ''
|
||||||
state.clipboard = ''
|
state.clipboard = ''
|
||||||
|
@ -20,9 +20,6 @@ export const state = () => {
|
|||||||
|
|
||||||
keyboard_layouts_list: {} as KeyboardLayouts,
|
keyboard_layouts_list: {} as KeyboardLayouts,
|
||||||
|
|
||||||
file_transfer: false,
|
|
||||||
unpriv_file_transfer: false,
|
|
||||||
|
|
||||||
broadcast_is_active: false,
|
broadcast_is_active: false,
|
||||||
broadcast_url: '',
|
broadcast_url: '',
|
||||||
}
|
}
|
||||||
@ -61,14 +58,6 @@ export const mutations = mutationTree(state, {
|
|||||||
set('keyboard_layout', value)
|
set('keyboard_layout', value)
|
||||||
},
|
},
|
||||||
|
|
||||||
setFileTransfer(state, value: boolean) {
|
|
||||||
state.file_transfer = value
|
|
||||||
},
|
|
||||||
|
|
||||||
setUnprivFileTransfer(state, value: boolean) {
|
|
||||||
state.unpriv_file_transfer = value
|
|
||||||
},
|
|
||||||
|
|
||||||
setKeyboardLayoutsList(state, value: KeyboardLayouts) {
|
setKeyboardLayoutsList(state, value: KeyboardLayouts) {
|
||||||
state.keyboard_layouts_list = value
|
state.keyboard_layouts_list = value
|
||||||
},
|
},
|
||||||
@ -90,22 +79,6 @@ export const actions = actionTree(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setLocalFileTransferStatus({ getters }, { admin, unpriv }) {
|
|
||||||
accessor.settings.setFileTransfer(admin)
|
|
||||||
accessor.settings.setUnprivFileTransfer(unpriv)
|
|
||||||
|
|
||||||
if (!admin || (!accessor.user.admin && !unpriv)) {
|
|
||||||
accessor.files.cancelAllTransfers()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessor.client.tab === 'files' && !unpriv) {
|
|
||||||
accessor.client.setTab('chat')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setRemoteFileTransferStatus({ getters }, { admin, unpriv }) {
|
|
||||||
$client.sendMessage(EVENT.FILETRANSFER.STATUS, { admin, unpriv })
|
|
||||||
},
|
|
||||||
|
|
||||||
broadcastStatus({ getters }, { url, isActive }) {
|
broadcastStatus({ getters }, { url, isActive }) {
|
||||||
accessor.settings.setBroadcastStatus({ url, isActive })
|
accessor.settings.setBroadcastStatus({ url, isActive })
|
||||||
},
|
},
|
||||||
|
@ -15,9 +15,8 @@ type WebSocket struct {
|
|||||||
|
|
||||||
ControlProtection bool
|
ControlProtection bool
|
||||||
|
|
||||||
FileTransfer bool
|
FileTransferEnabled bool
|
||||||
UnprivFileTransfer bool
|
FileTransferPath string
|
||||||
FileTransferPath string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (WebSocket) Init(cmd *cobra.Command) error {
|
func (WebSocket) Init(cmd *cobra.Command) error {
|
||||||
@ -46,13 +45,10 @@ func (WebSocket) Init(cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("file_transfer", false, "allow file transfer for admins")
|
// File transfer
|
||||||
if err := viper.BindPFlag("file_transfer", cmd.PersistentFlags().Lookup("file_transfer")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("unpriv_file_transfer", false, "allow file transfer for non admins")
|
cmd.PersistentFlags().Bool("file_transfer_enabled", true, "enable file transfer feature")
|
||||||
if err := viper.BindPFlag("unpriv_file_transfer", cmd.PersistentFlags().Lookup("unpriv_file_transfer")); err != nil {
|
if err := viper.BindPFlag("file_transfer_enabled", cmd.PersistentFlags().Lookup("file_transfer_enabled")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,8 +68,7 @@ func (s *WebSocket) Set() {
|
|||||||
|
|
||||||
s.ControlProtection = viper.GetBool("control_protection")
|
s.ControlProtection = viper.GetBool("control_protection")
|
||||||
|
|
||||||
s.FileTransfer = viper.GetBool("file_transfer")
|
s.FileTransferEnabled = viper.GetBool("file_transfer_enabled")
|
||||||
s.UnprivFileTransfer = viper.GetBool("unpriv_file_transfer")
|
|
||||||
s.FileTransferPath = viper.GetString("file_transfer_path")
|
s.FileTransferPath = viper.GetString("file_transfer_path")
|
||||||
s.FileTransferPath = filepath.Clean(s.FileTransferPath)
|
s.FileTransferPath = filepath.Clean(s.FileTransferPath)
|
||||||
}
|
}
|
||||||
|
@ -105,70 +105,77 @@ func New(conf *config.Server, webSocketHandler types.WebSocketHandler, desktop t
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Get("/file", func(w http.ResponseWriter, r *http.Request) {
|
// allow downloading and uploading files
|
||||||
password := r.URL.Query().Get("pwd")
|
if webSocketHandler.FileTransferEnabled() {
|
||||||
isAuthorized, err := webSocketHandler.CanTransferFiles(password)
|
router.Get("/file", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
password := r.URL.Query().Get("pwd")
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
isAuthorized, err := webSocketHandler.CanTransferFiles(password)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isAuthorized {
|
|
||||||
http.Error(w, "bad authorization", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := r.URL.Query().Get("filename")
|
|
||||||
badChars, _ := regexp.MatchString(`(?m)\.\.(?:\/|$)`, filename)
|
|
||||||
if filename == "" || badChars {
|
|
||||||
http.Error(w, "bad filename", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
path := webSocketHandler.MakeFilePath(filename)
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "not found or unable to open", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
|
||||||
io.Copy(w, f)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.Post("/file", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
password := r.URL.Query().Get("pwd")
|
|
||||||
isAuthorized, err := webSocketHandler.CanTransferFiles(password)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isAuthorized {
|
|
||||||
http.Error(w, "bad authorization", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.ParseMultipartForm(32 << 20)
|
|
||||||
for _, formheader := range r.MultipartForm.File["files"] {
|
|
||||||
formfile, err := formheader.Open()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("failed to open formdata file")
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
http.Error(w, "error writing file", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer formfile.Close()
|
|
||||||
f, err := os.OpenFile(webSocketHandler.MakeFilePath(formheader.Filename), os.O_WRONLY|os.O_CREATE, 0644)
|
if !isAuthorized {
|
||||||
|
http.Error(w, "bad authorization", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := r.URL.Query().Get("filename")
|
||||||
|
badChars, _ := regexp.MatchString(`(?m)\.\.(?:\/|$)`, filename)
|
||||||
|
if filename == "" || badChars {
|
||||||
|
http.Error(w, "bad filename", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := webSocketHandler.FileTransferPath(filename)
|
||||||
|
f, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "unable to open file for writing", http.StatusInternalServerError)
|
http.Error(w, "not found or unable to open", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
io.Copy(f, formfile)
|
|
||||||
}
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
})
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
|
||||||
|
io.Copy(w, f)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Post("/file", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
password := r.URL.Query().Get("pwd")
|
||||||
|
isAuthorized, err := webSocketHandler.CanTransferFiles(password)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isAuthorized {
|
||||||
|
http.Error(w, "bad authorization", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ParseMultipartForm(32 << 20)
|
||||||
|
for _, formheader := range r.MultipartForm.File["files"] {
|
||||||
|
filePath := webSocketHandler.FileTransferPath(formheader.Filename)
|
||||||
|
|
||||||
|
formfile, err := formheader.Open()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("failed to open formdata file")
|
||||||
|
http.Error(w, "error writing file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer formfile.Close()
|
||||||
|
|
||||||
|
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "unable to open file for writing", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
io.Copy(f, formfile)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = w.Write([]byte("true"))
|
_, _ = w.Write([]byte("true"))
|
||||||
|
@ -35,7 +35,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FILETRANSFER_STATUS = "filetransfer/status"
|
|
||||||
FILETRANSFER_LIST = "filetransfer/list"
|
FILETRANSFER_LIST = "filetransfer/list"
|
||||||
FILETRANSFER_REFRESH = "filetransfer/refresh"
|
FILETRANSFER_REFRESH = "filetransfer/refresh"
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ type SystemInit struct {
|
|||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
ImplicitHosting bool `json:"implicit_hosting"`
|
ImplicitHosting bool `json:"implicit_hosting"`
|
||||||
Locks map[string]string `json:"locks"`
|
Locks map[string]string `json:"locks"`
|
||||||
|
FileTransfer bool `json:"file_transfer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SystemMessage struct {
|
type SystemMessage struct {
|
||||||
@ -47,8 +48,8 @@ type SignalCandidate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MembersList struct {
|
type MembersList struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
Memebers []*types.Member `json:"members"`
|
Members []*types.Member `json:"members"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Member struct {
|
type Member struct {
|
||||||
@ -106,17 +107,7 @@ type EmoteSend struct {
|
|||||||
Emote string `json:"emote"`
|
Emote string `json:"emote"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileTransferTarget struct {
|
type FileTransferList struct {
|
||||||
Event string `json:"event"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileTransferStatus struct {
|
|
||||||
Event string `json:"event"`
|
|
||||||
Admin bool `json:"admin"`
|
|
||||||
Unpriv bool `json:"unpriv"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileList struct {
|
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
Cwd string `json:"cwd"`
|
Cwd string `json:"cwd"`
|
||||||
Files []types.FileListItem `json:"files"`
|
Files []types.FileListItem `json:"files"`
|
||||||
|
@ -34,8 +34,11 @@ type WebSocketHandler interface {
|
|||||||
Stats() Stats
|
Stats() Stats
|
||||||
IsLocked(resource string) bool
|
IsLocked(resource string) bool
|
||||||
IsAdmin(password string) (bool, error)
|
IsAdmin(password string) (bool, error)
|
||||||
|
|
||||||
|
// File Transfer
|
||||||
CanTransferFiles(password string) (bool, error)
|
CanTransferFiles(password string) (bool, error)
|
||||||
MakeFilePath(filename string) string
|
FileTransferPath(filename string) string
|
||||||
|
FileTransferEnabled() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileListItem struct {
|
type FileListItem struct {
|
||||||
|
@ -19,7 +19,12 @@ func (h *MessageHandler) adminLock(id string, session types.Session, payload *me
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.Resource != "login" && payload.Resource != "control" {
|
// allow only known resources
|
||||||
|
switch payload.Resource {
|
||||||
|
case "login":
|
||||||
|
case "control":
|
||||||
|
case "file_transfer":
|
||||||
|
default:
|
||||||
h.logger.Debug().Msg("unknown lock resource")
|
h.logger.Debug().Msg("unknown lock resource")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"m1k1o/neko/internal/types"
|
|
||||||
"m1k1o/neko/internal/types/event"
|
|
||||||
"m1k1o/neko/internal/types/message"
|
|
||||||
"m1k1o/neko/internal/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *MessageHandler) setFileTransferStatus(session types.Session, payload *message.FileTransferStatus) error {
|
|
||||||
if !session.Admin() {
|
|
||||||
h.logger.Debug().Msg("user not admin")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
h.state.SetFileTransferState(payload.Admin, payload.Unpriv)
|
|
||||||
|
|
||||||
err := h.sessions.Broadcast(message.FileTransferStatus{
|
|
||||||
Event: event.FILETRANSFER_STATUS,
|
|
||||||
Admin: payload.Admin,
|
|
||||||
Unpriv: payload.Admin && payload.Unpriv,
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := utils.ListFiles(h.state.FileTransferPath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := message.FileList{
|
|
||||||
Event: event.FILETRANSFER_LIST,
|
|
||||||
Cwd: h.state.FileTransferPath(),
|
|
||||||
Files: files,
|
|
||||||
}
|
|
||||||
|
|
||||||
if payload.Unpriv {
|
|
||||||
return h.sessions.Broadcast(msg, nil)
|
|
||||||
} else {
|
|
||||||
return h.sessions.AdminBroadcast(msg, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *MessageHandler) refresh(session types.Session) error {
|
|
||||||
if !(h.state.FileTransferEnabled() && session.Admin() || h.state.UnprivFileTransferEnabled()) {
|
|
||||||
return errors.New(session.Member().Name + " tried to refresh file list when they can't")
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := utils.ListFiles(h.state.FileTransferPath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return session.Send(
|
|
||||||
message.FileList{
|
|
||||||
Event: event.FILETRANSFER_LIST,
|
|
||||||
Cwd: h.state.FileTransferPath(),
|
|
||||||
Files: files,
|
|
||||||
})
|
|
||||||
}
|
|
42
server/internal/websocket/handler/filetransfer.go
Normal file
42
server/internal/websocket/handler/filetransfer.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"m1k1o/neko/internal/types"
|
||||||
|
"m1k1o/neko/internal/types/event"
|
||||||
|
"m1k1o/neko/internal/types/message"
|
||||||
|
"m1k1o/neko/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *MessageHandler) FileTransferRefresh(session types.Session) error {
|
||||||
|
fileTransferPath := h.state.FileTransferPath("") // root
|
||||||
|
|
||||||
|
// allow users only if file transfer is not locked
|
||||||
|
if session != nil && !(session.Admin() || !h.state.IsLocked("file_transfer")) {
|
||||||
|
h.logger.Debug().Msg("file transfer is locked for users")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := utils.ListFiles(fileTransferPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
message := message.FileTransferList{
|
||||||
|
Event: event.FILETRANSFER_LIST,
|
||||||
|
Cwd: fileTransferPath,
|
||||||
|
Files: files,
|
||||||
|
}
|
||||||
|
|
||||||
|
// send to just one user
|
||||||
|
if session != nil {
|
||||||
|
return session.Send(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcast to all admins
|
||||||
|
if h.state.IsLocked("file_transfer") {
|
||||||
|
return h.sessions.AdminBroadcast(message, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcast to all users
|
||||||
|
return h.sessions.Broadcast(message, nil)
|
||||||
|
}
|
@ -127,14 +127,8 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
|||||||
}), "%s failed", header.Event)
|
}), "%s failed", header.Event)
|
||||||
|
|
||||||
// File Transfer Events
|
// File Transfer Events
|
||||||
case event.FILETRANSFER_STATUS:
|
|
||||||
payload := &message.FileTransferStatus{}
|
|
||||||
return errors.Wrapf(
|
|
||||||
utils.Unmarshal(payload, raw, func() error {
|
|
||||||
return h.setFileTransferStatus(session, payload)
|
|
||||||
}), "%s failed", header.Event)
|
|
||||||
case event.FILETRANSFER_REFRESH:
|
case event.FILETRANSFER_REFRESH:
|
||||||
return errors.Wrapf(h.refresh(session), "%s failed", header.Event)
|
return errors.Wrapf(h.FileTransferRefresh(session), "%s failed", header.Event)
|
||||||
|
|
||||||
// Screen Events
|
// Screen Events
|
||||||
case event.SCREEN_RESOLUTION:
|
case event.SCREEN_RESOLUTION:
|
||||||
|
@ -17,6 +17,7 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error
|
|||||||
Event: event.SYSTEM_INIT,
|
Event: event.SYSTEM_INIT,
|
||||||
ImplicitHosting: h.webrtc.ImplicitControl(),
|
ImplicitHosting: h.webrtc.ImplicitControl(),
|
||||||
Locks: h.state.AllLocked(),
|
Locks: h.state.AllLocked(),
|
||||||
|
FileTransfer: h.state.FileTransferEnabled(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.SYSTEM_INIT)
|
h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.SYSTEM_INIT)
|
||||||
return err
|
return err
|
||||||
@ -34,14 +35,21 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send file list if file transfer is enabled
|
||||||
|
if h.state.FileTransferEnabled() && (session.Admin() || !h.state.IsLocked("file_transfer")) {
|
||||||
|
if err := h.FileTransferRefresh(session); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) SessionConnected(id string, session types.Session) error {
|
func (h *MessageHandler) SessionConnected(id string, session types.Session) error {
|
||||||
// send list of members to session
|
// send list of members to session
|
||||||
if err := session.Send(message.MembersList{
|
if err := session.Send(message.MembersList{
|
||||||
Event: event.MEMBER_LIST,
|
Event: event.MEMBER_LIST,
|
||||||
Memebers: h.sessions.Members(),
|
Members: h.sessions.Members(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.MEMBER_LIST)
|
h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.MEMBER_LIST)
|
||||||
return err
|
return err
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
package state
|
package state
|
||||||
|
|
||||||
|
import "path/filepath"
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
banned map[string]string // IP -> session ID (that banned it)
|
banned map[string]string // IP -> session ID (that banned it)
|
||||||
locked map[string]string // resource name -> session ID (that locked it)
|
locked map[string]string // resource name -> session ID (that locked it)
|
||||||
|
|
||||||
fileTransferEnabled bool // admins can transfer files
|
fileTransferEnabled bool
|
||||||
fileTransferUnprivEnabled bool // all users can transfer files
|
fileTransferPath string // path where files are located
|
||||||
fileTransferPath string // path where files are located
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(fileTransferEnabled bool, fileTransferUnprivEnabled bool, fileTransferPath string) *State {
|
func New(fileTransferEnabled bool, fileTransferPath string) *State {
|
||||||
return &State{
|
return &State{
|
||||||
banned: make(map[string]string),
|
banned: make(map[string]string),
|
||||||
locked: make(map[string]string),
|
locked: make(map[string]string),
|
||||||
|
|
||||||
fileTransferEnabled: fileTransferEnabled,
|
fileTransferEnabled: fileTransferEnabled,
|
||||||
fileTransferUnprivEnabled: fileTransferUnprivEnabled,
|
fileTransferPath: fileTransferPath,
|
||||||
fileTransferPath: fileTransferPath,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,21 +68,17 @@ func (s *State) AllLocked() map[string]string {
|
|||||||
return s.locked
|
return s.locked
|
||||||
}
|
}
|
||||||
|
|
||||||
// File Transfer
|
// File transfer
|
||||||
|
|
||||||
|
func (s *State) FileTransferPath(filename string) string {
|
||||||
|
if filename == "" {
|
||||||
|
return s.fileTransferPath
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanPath := filepath.Clean(filename)
|
||||||
|
return filepath.Join(s.fileTransferPath, cleanPath)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) FileTransferEnabled() bool {
|
func (s *State) FileTransferEnabled() bool {
|
||||||
return s.fileTransferEnabled
|
return s.fileTransferEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) UnprivFileTransferEnabled() bool {
|
|
||||||
return s.fileTransferUnprivEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) SetFileTransferState(admin bool, unpriv bool) {
|
|
||||||
s.fileTransferEnabled = admin
|
|
||||||
s.fileTransferUnprivEnabled = unpriv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) FileTransferPath() string {
|
|
||||||
return s.fileTransferPath
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -28,7 +27,7 @@ const CONTROL_PROTECTION_SESSION = "by_control_protection"
|
|||||||
func New(sessions types.SessionManager, desktop types.DesktopManager, capture types.CaptureManager, webrtc types.WebRTCManager, conf *config.WebSocket) *WebSocketHandler {
|
func New(sessions types.SessionManager, desktop types.DesktopManager, capture types.CaptureManager, webrtc types.WebRTCManager, conf *config.WebSocket) *WebSocketHandler {
|
||||||
logger := log.With().Str("module", "websocket").Logger()
|
logger := log.With().Str("module", "websocket").Logger()
|
||||||
|
|
||||||
state := state.New(conf.FileTransfer, conf.UnprivFileTransfer, conf.FileTransferPath)
|
state := state.New(conf.FileTransferEnabled, conf.FileTransferPath)
|
||||||
|
|
||||||
// if control protection is enabled
|
// if control protection is enabled
|
||||||
if conf.ControlProtection {
|
if conf.ControlProtection {
|
||||||
@ -36,9 +35,12 @@ func New(sessions types.SessionManager, desktop types.DesktopManager, capture ty
|
|||||||
logger.Info().Msgf("control locked on behalf of control protection")
|
logger.Info().Msgf("control locked on behalf of control protection")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(conf.FileTransferPath); os.IsNotExist(err) {
|
// create file transfer directory if not exists
|
||||||
err = os.Mkdir(conf.FileTransferPath, os.ModePerm)
|
if conf.FileTransferEnabled {
|
||||||
logger.Err(err).Msg("creating file transfer directory")
|
if _, err := os.Stat(conf.FileTransferPath); os.IsNotExist(err) {
|
||||||
|
err = os.Mkdir(conf.FileTransferPath, os.ModePerm)
|
||||||
|
logger.Err(err).Msg("creating file transfer directory")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply default locks
|
// apply default locks
|
||||||
@ -132,32 +134,6 @@ func (ws *WebSocketHandler) Start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send file list if necessary
|
|
||||||
if ws.state.FileTransferEnabled() && (session.Admin() || ws.state.UnprivFileTransferEnabled()) {
|
|
||||||
err := session.Send(
|
|
||||||
message.FileTransferStatus{
|
|
||||||
Event: event.FILETRANSFER_STATUS,
|
|
||||||
Admin: ws.state.FileTransferEnabled(),
|
|
||||||
Unpriv: ws.state.UnprivFileTransferEnabled(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
ws.logger.Warn().Err(err).Msgf("file transfer status event has failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := utils.ListFiles(ws.conf.FileTransferPath)
|
|
||||||
if err == nil {
|
|
||||||
if err := session.Send(
|
|
||||||
message.FileList{
|
|
||||||
Event: event.FILETRANSFER_LIST,
|
|
||||||
Cwd: ws.conf.FileTransferPath,
|
|
||||||
Files: files,
|
|
||||||
}); err != nil {
|
|
||||||
ws.logger.Warn().Err(err).Msg("file list event has failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove outdated stats
|
// remove outdated stats
|
||||||
if session.Admin() {
|
if session.Admin() {
|
||||||
ws.lastAdminLeftAt = nil
|
ws.lastAdminLeftAt = nil
|
||||||
@ -222,32 +198,35 @@ func (ws *WebSocketHandler) Start() {
|
|||||||
ws.logger.Err(err).Msg("sync clipboard")
|
ws.logger.Err(err).Msg("sync clipboard")
|
||||||
})
|
})
|
||||||
|
|
||||||
// watch for file changes
|
// watch for file changes and send file list if file transfer is enabled
|
||||||
watcher, err := fsnotify.NewWatcher()
|
if ws.conf.FileTransferEnabled {
|
||||||
if err != nil {
|
watcher, err := fsnotify.NewWatcher()
|
||||||
ws.logger.Err(err).Msg("unable to start file transfer dir watcher")
|
if err != nil {
|
||||||
return
|
ws.logger.Err(err).Msg("unable to start file transfer dir watcher")
|
||||||
}
|
return
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case e, ok := <-watcher.Events:
|
|
||||||
if !ok {
|
|
||||||
ws.logger.Info().Msg("file transfer dir watcher closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if e.Has(fsnotify.Create) || e.Has(fsnotify.Remove) || e.Has(fsnotify.Rename) {
|
|
||||||
ws.sendFileTransferUpdate()
|
|
||||||
}
|
|
||||||
case err := <-watcher.Errors:
|
|
||||||
ws.logger.Err(err).Msg("error in file transfer dir watcher")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
if err := watcher.Add(ws.conf.FileTransferPath); err != nil {
|
go func() {
|
||||||
ws.logger.Err(err).Msg("unable to add file transfer path to watcher")
|
for {
|
||||||
|
select {
|
||||||
|
case e, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
ws.logger.Info().Msg("file transfer dir watcher closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.Has(fsnotify.Create) || e.Has(fsnotify.Remove) || e.Has(fsnotify.Rename) {
|
||||||
|
ws.logger.Debug().Str("event", e.String()).Msg("file transfer dir watcher event")
|
||||||
|
ws.handler.FileTransferRefresh(nil)
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
ws.logger.Err(err).Msg("error in file transfer dir watcher")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := watcher.Add(ws.conf.FileTransferPath); err != nil {
|
||||||
|
ws.logger.Err(err).Msg("unable to add file transfer path to watcher")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,52 +355,6 @@ func (ws *WebSocketHandler) IsAdmin(password string) (bool, error) {
|
|||||||
return false, fmt.Errorf("invalid password")
|
return false, fmt.Errorf("invalid password")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *WebSocketHandler) CanTransferFiles(password string) (bool, error) {
|
|
||||||
if !ws.state.FileTransferEnabled() {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin, err := ws.IsAdmin(password)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return isAdmin || ws.state.UnprivFileTransferEnabled(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WebSocketHandler) MakeFilePath(filename string) string {
|
|
||||||
cleanPath := filepath.Clean(filename)
|
|
||||||
return filepath.Join(ws.conf.FileTransferPath, cleanPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WebSocketHandler) sendFileTransferUpdate() {
|
|
||||||
if !ws.state.FileTransferEnabled() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := utils.ListFiles(ws.conf.FileTransferPath)
|
|
||||||
if err != nil {
|
|
||||||
ws.logger.Err(err).Msg("unable to ls file transfer path")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
message := message.FileList{
|
|
||||||
Event: event.FILETRANSFER_LIST,
|
|
||||||
Cwd: ws.conf.FileTransferPath,
|
|
||||||
Files: files,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ws.state.UnprivFileTransferEnabled() {
|
|
||||||
err = ws.sessions.Broadcast(message, nil)
|
|
||||||
} else {
|
|
||||||
err = ws.sessions.AdminBroadcast(message, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ws.logger.Err(err).Msg("unable to broadcast file list")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WebSocketHandler) authenticate(r *http.Request) (bool, error) {
|
func (ws *WebSocketHandler) authenticate(r *http.Request) (bool, error) {
|
||||||
passwords, ok := r.URL.Query()["password"]
|
passwords, ok := r.URL.Query()["password"]
|
||||||
if !ok || len(passwords[0]) < 1 {
|
if !ok || len(passwords[0]) < 1 {
|
||||||
@ -492,3 +425,28 @@ func (ws *WebSocketHandler) handle(connection *websocket.Conn, id string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// File transfer
|
||||||
|
//
|
||||||
|
|
||||||
|
func (ws *WebSocketHandler) CanTransferFiles(password string) (bool, error) {
|
||||||
|
if !ws.conf.FileTransferEnabled {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdmin, err := ws.IsAdmin(password)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAdmin || !ws.state.IsLocked("file_transfer"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebSocketHandler) FileTransferPath(filename string) string {
|
||||||
|
return ws.state.FileTransferPath(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebSocketHandler) FileTransferEnabled() bool {
|
||||||
|
return ws.conf.FileTransferEnabled
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user