tabby/tabby-ssh/src/components/sftpPanel.component.ts
2022-04-30 10:10:10 -07:00

189 lines
6.1 KiB
TypeScript

import * as C from 'constants'
import { posix as path } from 'path'
import { Component, Input, Output, EventEmitter, Inject, Optional } from '@angular/core'
import { FileUpload, MenuItemOptions, NotificationsService, PlatformService } from 'tabby-core'
import { SFTPSession, SFTPFile } from '../session/sftp'
import { SSHSession } from '../session/ssh'
import { SFTPContextMenuItemProvider } from '../api'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { SFTPCreateDirectoryModalComponent } from './sftpCreateDirectoryModal.component'
interface PathSegment {
name: string
path: string
}
@Component({
selector: 'sftp-panel',
template: require('./sftpPanel.component.pug'),
styles: [require('./sftpPanel.component.scss')],
})
export class SFTPPanelComponent {
@Input() session: SSHSession
@Output() closed = new EventEmitter<void>()
sftp: SFTPSession
fileList: SFTPFile[]|null = null
@Input() path = '/'
@Output() pathChange = new EventEmitter<string>()
pathSegments: PathSegment[] = []
@Input() cwdDetectionAvailable = false
constructor (
private ngbModal: NgbModal,
private notifications: NotificationsService,
public platform: PlatformService,
@Optional() @Inject(SFTPContextMenuItemProvider) protected contextMenuProviders: SFTPContextMenuItemProvider[],
) {
this.contextMenuProviders.sort((a, b) => a.weight - b.weight)
}
async ngOnInit (): Promise<void> {
this.sftp = await this.session.openSFTP()
try {
await this.navigate(this.path)
} catch (error) {
console.warn('Could not navigate to', this.path, ':', error)
this.notifications.error(error.message)
await this.navigate('/')
}
}
async navigate (newPath: string, fallbackOnError = true): Promise<void> {
const previousPath = this.path
this.path = newPath
this.pathChange.next(this.path)
let p = newPath
this.pathSegments = []
while (p !== '/') {
this.pathSegments.unshift({
name: path.basename(p),
path: p,
})
p = path.dirname(p)
}
this.fileList = null
try {
this.fileList = await this.sftp.readdir(this.path)
} catch (error) {
this.notifications.error(error.message)
if (previousPath && fallbackOnError) {
this.navigate(previousPath, false)
}
return
}
const dirKey = a => a.isDirectory ? 1 : 0
this.fileList.sort((a, b) =>
dirKey(b) - dirKey(a) ||
a.name.localeCompare(b.name))
}
getIcon (item: SFTPFile): string {
if (item.isDirectory) {
return 'fas fa-folder text-info'
}
if (item.isSymlink) {
return 'fas fa-link text-warning'
}
return 'fas fa-file'
}
goUp (): void {
this.navigate(path.dirname(this.path))
}
async open (item: SFTPFile): Promise<void> {
if (item.isDirectory) {
await this.navigate(item.fullPath)
} else if (item.isSymlink) {
const target = path.resolve(this.path, await this.sftp.readlink(item.fullPath))
const stat = await this.sftp.stat(target)
if (stat.isDirectory) {
await this.navigate(item.fullPath)
} else {
await this.download(item.fullPath, stat.mode, stat.size)
}
} else {
await this.download(item.fullPath, item.mode, item.size)
}
}
async openCreateDirectoryModal (): Promise<void> {
const modal = this.ngbModal.open(SFTPCreateDirectoryModalComponent)
const directoryName = await modal.result
if (directoryName !== '') {
this.sftp.mkdir(path.join(this.path, directoryName)).then(() => {
this.notifications.notice('The directory was created successfully')
this.navigate(path.join(this.path, directoryName))
}).catch(() => {
this.notifications.error('The directory could not be created')
})
}
}
async upload (): Promise<void> {
const transfers = await this.platform.startUpload({ multiple: true })
await Promise.all(transfers.map(t => this.uploadOne(t)))
}
async uploadOne (transfer: FileUpload): Promise<void> {
const savedPath = this.path
await this.sftp.upload(path.join(this.path, transfer.getName()), transfer)
if (this.path === savedPath) {
await this.navigate(this.path)
}
}
async download (itemPath: string, mode: number, size: number): Promise<void> {
const transfer = await this.platform.startDownload(path.basename(itemPath), mode, size)
if (!transfer) {
return
}
this.sftp.download(itemPath, transfer)
}
getModeString (item: SFTPFile): string {
const s = 'SGdrwxrwxrwx'
const e = ' ---------'
const c = [
0o4000, 0o2000, C.S_IFDIR,
C.S_IRUSR, C.S_IWUSR, C.S_IXUSR,
C.S_IRGRP, C.S_IWGRP, C.S_IXGRP,
C.S_IROTH, C.S_IWOTH, C.S_IXOTH,
]
let result = ''
for (let i = 0; i < c.length; i++) {
result += item.mode & c[i] ? s[i] : e[i]
}
return result
}
async buildContextMenu (item: SFTPFile): Promise<MenuItemOptions[]> {
let items: MenuItemOptions[] = []
for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(item, this)))) {
items.push({ type: 'separator' })
items = items.concat(section)
}
return items.slice(1)
}
async showContextMenu (item: SFTPFile, event: MouseEvent): Promise<void> {
event.preventDefault()
this.platform.popupContextMenu(await this.buildContextMenu(item), event)
}
get shouldShowCWDTip (): boolean {
return !window.localStorage.sshCWDTipDismissed
}
dismissCWDTip (): void {
window.localStorage.sshCWDTipDismissed = 'true'
}
close (): void {
this.closed.emit()
}
}