mirror of
https://github.com/Kuingsmile/PicList.git
synced 2025-01-23 06:38:13 -05:00
🚧 WIP: sftp manage
This commit is contained in:
parent
1629dcaef0
commit
e89b3ca6d1
@ -4,6 +4,7 @@ import ImgurApi from './imgur'
|
|||||||
import LocalApi from './local'
|
import LocalApi from './local'
|
||||||
import QiniuApi from './qiniu'
|
import QiniuApi from './qiniu'
|
||||||
import S3plistApi from './s3plist'
|
import S3plistApi from './s3plist'
|
||||||
|
import SftpApi from './sftp'
|
||||||
import SmmsApi from './smms'
|
import SmmsApi from './smms'
|
||||||
import TcyunApi from './tcyun'
|
import TcyunApi from './tcyun'
|
||||||
import UpyunApi from './upyun'
|
import UpyunApi from './upyun'
|
||||||
@ -16,6 +17,7 @@ export default {
|
|||||||
LocalApi,
|
LocalApi,
|
||||||
QiniuApi,
|
QiniuApi,
|
||||||
S3plistApi,
|
S3plistApi,
|
||||||
|
SftpApi,
|
||||||
SmmsApi,
|
SmmsApi,
|
||||||
TcyunApi,
|
TcyunApi,
|
||||||
UpyunApi,
|
UpyunApi,
|
||||||
|
@ -0,0 +1,409 @@
|
|||||||
|
// 日志记录器
|
||||||
|
import ManageLogger from '../utils/logger'
|
||||||
|
|
||||||
|
// SSH 客户端
|
||||||
|
import SSHClient from '~/main/utils/sshClient'
|
||||||
|
|
||||||
|
// 错误格式化函数、新的下载器、并发异步任务池
|
||||||
|
import { formatError, ConcurrencyPromisePool } from '../utils/common'
|
||||||
|
|
||||||
|
// 是否为图片的判断函数
|
||||||
|
import { isImage } from '@/manage/utils/common'
|
||||||
|
|
||||||
|
// 窗口管理器
|
||||||
|
import windowManager from 'apis/app/window/windowManager'
|
||||||
|
|
||||||
|
// 枚举类型声明
|
||||||
|
import { IWindowList } from '#/types/enum'
|
||||||
|
|
||||||
|
// Electron 相关
|
||||||
|
import { ipcMain, IpcMainEvent } from 'electron'
|
||||||
|
|
||||||
|
// 上传下载任务队列
|
||||||
|
import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
|
||||||
|
|
||||||
|
// 路径处理库
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
// 取消下载任务的加载文件列表、刷新下载文件传输列表
|
||||||
|
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
|
||||||
|
import { Undefinable } from '~/universal/types/manage'
|
||||||
|
|
||||||
|
interface listDirResult {
|
||||||
|
permissions: string
|
||||||
|
isDir: boolean
|
||||||
|
owner: string
|
||||||
|
group: string
|
||||||
|
size: number
|
||||||
|
mtime: string
|
||||||
|
filename: string
|
||||||
|
key: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class SftpApi {
|
||||||
|
host: string
|
||||||
|
port: number
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
privateKey: string
|
||||||
|
passphrase: string
|
||||||
|
fileMode: string
|
||||||
|
dirMode: string
|
||||||
|
logger: ManageLogger
|
||||||
|
ctx: SSHClient
|
||||||
|
config: {
|
||||||
|
host: string
|
||||||
|
port: number
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
privateKey: string
|
||||||
|
passphrase: string
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
host: string,
|
||||||
|
port: Undefinable<number>,
|
||||||
|
username: Undefinable<string>,
|
||||||
|
password: Undefinable<string>,
|
||||||
|
privateKey: Undefinable<string>,
|
||||||
|
passphrase: Undefinable<string>,
|
||||||
|
fileMode: Undefinable<string>,
|
||||||
|
dirMode: Undefinable<string>,
|
||||||
|
logger: ManageLogger
|
||||||
|
) {
|
||||||
|
this.host = host
|
||||||
|
this.port = Number(port) || 22
|
||||||
|
this.username = username || ''
|
||||||
|
this.password = password || ''
|
||||||
|
this.privateKey = privateKey || ''
|
||||||
|
this.passphrase = passphrase || ''
|
||||||
|
this.fileMode = fileMode || '0664'
|
||||||
|
this.dirMode = dirMode || '0775'
|
||||||
|
this.logger = logger
|
||||||
|
this.ctx = SSHClient.instance
|
||||||
|
this.config = {
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
privateKey: this.privateKey,
|
||||||
|
passphrase: this.passphrase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logParam = (error:any, method: string) =>
|
||||||
|
this.logger.error(formatError(error, { class: 'SftpApi', method }))
|
||||||
|
|
||||||
|
transFormPermission = (permissionsStr: string) => {
|
||||||
|
const permissions = permissionsStr.length === 10 ? permissionsStr.slice(1) : permissionsStr
|
||||||
|
let result = ''
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const chunk = permissions.slice(i * 3, i * 3 + 3)
|
||||||
|
let value = 0
|
||||||
|
|
||||||
|
if (chunk[0] === 'r') value += 4
|
||||||
|
if (chunk[1] === 'w') value += 2
|
||||||
|
if (chunk[2] === 'x') value += 1
|
||||||
|
|
||||||
|
result += value
|
||||||
|
}
|
||||||
|
|
||||||
|
return `0${result}`
|
||||||
|
}
|
||||||
|
|
||||||
|
formatFolder (item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||||
|
const key = item.key
|
||||||
|
let url: string
|
||||||
|
if (isWebPath) {
|
||||||
|
url = urlPrefix
|
||||||
|
} else {
|
||||||
|
if (this.username && this.password) {
|
||||||
|
url = `sfpt://${this.username}:${this.password}@${urlPrefix}${item.filename}`
|
||||||
|
} else {
|
||||||
|
url = `${urlPrefix}${item.filename}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
key,
|
||||||
|
fileName: item.filename,
|
||||||
|
fileSize: 0,
|
||||||
|
Key: key,
|
||||||
|
formatedTime: '',
|
||||||
|
isDir: true,
|
||||||
|
checked: false,
|
||||||
|
isImage: false,
|
||||||
|
match: false,
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatFile (item: listDirResult, urlPrefix: string, isWebPath = false) {
|
||||||
|
const key = item.key
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
key,
|
||||||
|
fileName: item.filename,
|
||||||
|
fileSize: item.size,
|
||||||
|
Key: key,
|
||||||
|
formatedTime: new Date(item.mtime).toLocaleString(),
|
||||||
|
isDir: false,
|
||||||
|
checked: false,
|
||||||
|
match: false,
|
||||||
|
isImage: isImage(item.filename),
|
||||||
|
url: isWebPath ? urlPrefix : `${urlPrefix}${item.filename}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isRequestSuccess = (code: number | null) => code === 0
|
||||||
|
|
||||||
|
connectClient = async () => {
|
||||||
|
try {
|
||||||
|
await this.ctx.connect(this.config)
|
||||||
|
if (!this.ctx.isConnected) {
|
||||||
|
throw new Error('SSH 未连接')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'connectClient')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBucketListRecursively (configMap: IStringKeyMap): Promise<any> {
|
||||||
|
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||||
|
const { prefix, customUrl, cancelToken } = configMap
|
||||||
|
const urlPrefix = customUrl || `${this.host}:${this.port}`
|
||||||
|
const cancelTask = [false]
|
||||||
|
ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => {
|
||||||
|
if (token === cancelToken) {
|
||||||
|
cancelTask[0] = true
|
||||||
|
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let res = {} as any
|
||||||
|
const result = {
|
||||||
|
fullList: <any>[],
|
||||||
|
success: false,
|
||||||
|
finished: false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.connectClient()
|
||||||
|
res = await this.ctx.execCommand(`cd ${prefix} && ls -la --time-style=long-iso`)
|
||||||
|
this.ctx.close()
|
||||||
|
if (this.isRequestSuccess(res.code)) {
|
||||||
|
const formatedLSRes = this.formatLSResult(res.stdout, prefix)
|
||||||
|
if (formatedLSRes.length) {
|
||||||
|
formatedLSRes.forEach((item: listDirResult) => {
|
||||||
|
if (!item.isDir) {
|
||||||
|
result.fullList.push(this.formatFile(item, urlPrefix))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result.success = true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'getBucketListRecursively')
|
||||||
|
}
|
||||||
|
result.finished = true
|
||||||
|
window.webContents.send(refreshDownloadFileTransferList, result)
|
||||||
|
ipcMain.removeAllListeners(cancelDownloadLoadingFileList)
|
||||||
|
}
|
||||||
|
|
||||||
|
formatLSResult (res: string, cwd: string): listDirResult[] {
|
||||||
|
const result = [] as listDirResult[]
|
||||||
|
const resArray = res.trim().split('\n')
|
||||||
|
resArray.slice(resArray[0].startsWith('total') ? 1 : 0).forEach((item: string) => {
|
||||||
|
const [permissions, , owner, group, size, date, time, ...name] = item.trim().split(/\s+/)
|
||||||
|
const filename = name.join(' ')
|
||||||
|
if (filename === '.' || filename === '..') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const isDir = permissions.startsWith('d')
|
||||||
|
const mtime = `${date} ${time}`
|
||||||
|
const key = path.join(cwd, filename).replace(/\\/g, '/').replace(/^\/+/, '')
|
||||||
|
result.push({
|
||||||
|
permissions,
|
||||||
|
isDir,
|
||||||
|
owner,
|
||||||
|
group,
|
||||||
|
size: Number(size) || 0,
|
||||||
|
mtime,
|
||||||
|
filename,
|
||||||
|
key
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||||
|
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||||
|
const { prefix, customUrl, cancelToken, baseDir } = configMap
|
||||||
|
let urlPrefix = customUrl || `${this.host}:${this.port}`
|
||||||
|
urlPrefix = urlPrefix.replace(/\/+$/, '')
|
||||||
|
let webPath = configMap.webPath || ''
|
||||||
|
if (webPath && customUrl && webPath !== '/') {
|
||||||
|
webPath = webPath.replace(/^\/+|\/+$/, '')
|
||||||
|
}
|
||||||
|
const cancelTask = [false]
|
||||||
|
ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
|
||||||
|
if (token === cancelToken) {
|
||||||
|
cancelTask[0] = true
|
||||||
|
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let res = {} as any
|
||||||
|
const result = {
|
||||||
|
fullList: <any>[],
|
||||||
|
success: false,
|
||||||
|
finished: false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.connectClient()
|
||||||
|
res = await this.ctx.execCommand(`cd ${prefix} && ls -la --time-style=long-iso`)
|
||||||
|
this.ctx.close()
|
||||||
|
if (this.isRequestSuccess(res.code)) {
|
||||||
|
const formatedLSRes = this.formatLSResult(res.stdout, prefix)
|
||||||
|
console.log(formatedLSRes)
|
||||||
|
if (formatedLSRes.length) {
|
||||||
|
formatedLSRes.forEach((item: listDirResult) => {
|
||||||
|
const relativePath = path.relative(baseDir, item.key)
|
||||||
|
const relative = webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/')
|
||||||
|
if (item.isDir) {
|
||||||
|
result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath))
|
||||||
|
} else {
|
||||||
|
result.fullList.push(this.formatFile(item, webPath ? relative : urlPrefix, !!webPath))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.finished = true
|
||||||
|
window.webContents.send('refreshFileTransferList', result)
|
||||||
|
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'getBucketListBackstage')
|
||||||
|
result.finished = true
|
||||||
|
window.webContents.send('refreshFileTransferList', result)
|
||||||
|
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result.success = true
|
||||||
|
result.finished = true
|
||||||
|
window.webContents.send('refreshFileTransferList', result)
|
||||||
|
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||||
|
}
|
||||||
|
|
||||||
|
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { oldKey, newKey } = configMap
|
||||||
|
let result = false
|
||||||
|
try {
|
||||||
|
await this.connectClient()
|
||||||
|
const res = await this.ctx.execCommand(`mv -f ${oldKey} ${newKey}`)
|
||||||
|
this.ctx.close()
|
||||||
|
result = this.isRequestSuccess(res.code)
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'renameBucketFile')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { key } = configMap
|
||||||
|
let result = false
|
||||||
|
try {
|
||||||
|
await this.connectClient()
|
||||||
|
const res = await this.ctx.execCommand(`rm -f ${key}`)
|
||||||
|
this.ctx.close()
|
||||||
|
result = this.isRequestSuccess(res.code)
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'deleteBucketFile')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { key } = configMap
|
||||||
|
let result = false
|
||||||
|
try {
|
||||||
|
await this.connectClient()
|
||||||
|
const res = await this.ctx.execCommand(`rm -rf ${key}`)
|
||||||
|
this.ctx.close()
|
||||||
|
result = this.isRequestSuccess(res.code)
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'deleteBucketFolder')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { fileArray } = configMap
|
||||||
|
const instance = UpDownTaskQueue.getInstance()
|
||||||
|
for (const item of fileArray) {
|
||||||
|
const { alias, bucketName, region, key, filePath, fileName } = item
|
||||||
|
const id = `${alias}-${bucketName}-${key}-${filePath}`
|
||||||
|
if (instance.getUploadTask(id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
instance.addUploadTask({
|
||||||
|
id,
|
||||||
|
progress: 0,
|
||||||
|
status: commonTaskStatus.queuing,
|
||||||
|
sourceFileName: fileName,
|
||||||
|
sourceFilePath: filePath,
|
||||||
|
targetFilePath: key,
|
||||||
|
targetFileBucket: bucketName,
|
||||||
|
targetFileRegion: region,
|
||||||
|
noProgress: true
|
||||||
|
})
|
||||||
|
instance.updateUploadTask({
|
||||||
|
id,
|
||||||
|
progress: 0,
|
||||||
|
status: commonTaskStatus.failed,
|
||||||
|
finishTime: new Date().toLocaleString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { key } = configMap
|
||||||
|
let result = false
|
||||||
|
try {
|
||||||
|
await this.connectClient()
|
||||||
|
const res = await this.ctx.execCommand(`mkdir -p ${key}`)
|
||||||
|
this.ctx.close()
|
||||||
|
result = this.isRequestSuccess(res.code)
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'createBucketFolder')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { downloadPath, fileArray, maxDownloadFileCount } = configMap
|
||||||
|
const instance = UpDownTaskQueue.getInstance()
|
||||||
|
const promises = [] as any
|
||||||
|
for (const item of fileArray) {
|
||||||
|
const { alias, bucketName, region, key, fileName } = item
|
||||||
|
const savedFilePath = path.join(downloadPath, fileName)
|
||||||
|
const id = `${alias}-${bucketName}-${region}-${key}`
|
||||||
|
if (instance.getDownloadTask(id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
instance.addDownloadTask({
|
||||||
|
id,
|
||||||
|
progress: 0,
|
||||||
|
status: commonTaskStatus.queuing,
|
||||||
|
sourceFileName: fileName,
|
||||||
|
targetFilePath: savedFilePath
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
|
||||||
|
pool.all(promises).catch((error) => {
|
||||||
|
this.logParam(error, 'downloadBucketFile')
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SftpApi
|
@ -68,6 +68,8 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
return new API.SmmsApi(this.currentPicBedConfig.token, this.logger)
|
return new API.SmmsApi(this.currentPicBedConfig.token, this.logger)
|
||||||
case 's3plist':
|
case 's3plist':
|
||||||
return new API.S3plistApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.secretAccessKey, this.currentPicBedConfig.endpoint, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.s3ForcePathStyle, this.currentPicBedConfig.proxy, this.logger)
|
return new API.S3plistApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.secretAccessKey, this.currentPicBedConfig.endpoint, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.s3ForcePathStyle, this.currentPicBedConfig.proxy, this.logger)
|
||||||
|
case 'sftp':
|
||||||
|
return new API.SftpApi(this.currentPicBedConfig.host, this.currentPicBedConfig.port, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.privateKey, this.currentPicBedConfig.passphrase, this.currentPicBedConfig.fileMode, this.currentPicBedConfig.dirMode, this.logger)
|
||||||
case 'tcyun':
|
case 'tcyun':
|
||||||
return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger)
|
return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger)
|
||||||
case 'upyun':
|
case 'upyun':
|
||||||
@ -174,6 +176,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'smms':
|
case 'smms':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
return [{
|
return [{
|
||||||
Name: name,
|
Name: name,
|
||||||
Location: name,
|
Location: name,
|
||||||
@ -313,6 +316,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 's3plist':
|
case 's3plist':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.getBucketListRecursively(param!)
|
return await client.getBucketListRecursively(param!)
|
||||||
@ -357,6 +361,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 's3plist':
|
case 's3plist':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.getBucketListBackstage(param!)
|
return await client.getBucketListBackstage(param!)
|
||||||
@ -428,6 +433,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 's3plist':
|
case 's3plist':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
const res = await client.deleteBucketFile(param!)
|
const res = await client.deleteBucketFile(param!)
|
||||||
@ -454,6 +460,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 's3plist':
|
case 's3plist':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.deleteBucketFolder(param!)
|
return await client.deleteBucketFolder(param!)
|
||||||
@ -478,6 +485,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 's3plist':
|
case 's3plist':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.renameBucketFile(param!)
|
return await client.renameBucketFile(param!)
|
||||||
@ -505,6 +513,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 's3plist':
|
case 's3plist':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
const res = await client.downloadBucketFile(param!)
|
const res = await client.downloadBucketFile(param!)
|
||||||
@ -538,6 +547,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 's3plist':
|
case 's3plist':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.createBucketFolder(param!)
|
return await client.createBucketFolder(param!)
|
||||||
@ -565,6 +575,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 's3plist':
|
case 's3plist':
|
||||||
case 'webdavplist':
|
case 'webdavplist':
|
||||||
case 'local':
|
case 'local':
|
||||||
|
case 'sftp':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.uploadBucketFile(param!)
|
return await client.uploadBucketFile(param!)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { NodeSSH, Config } from 'node-ssh-no-cpu-features'
|
import { NodeSSH, Config, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
|
||||||
import { ISftpPlistConfig } from 'piclist/dist/types'
|
import { ISftpPlistConfig } from 'piclist/dist/types'
|
||||||
|
|
||||||
class SSHClient {
|
class SSHClient {
|
||||||
@ -55,6 +55,31 @@ class SSHClient {
|
|||||||
return execResult.code === 0
|
return execResult.code === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async execCommand (script: string): Promise<SSHExecCommandResponse> {
|
||||||
|
const execResult = await SSHClient.client.execCommand(script)
|
||||||
|
return execResult || { code: 1, stdout: '', stderr: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFile (local: string, remote: string): Promise<boolean> {
|
||||||
|
if (!this._isConnected) {
|
||||||
|
throw new Error('SSH 未连接')
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
remote = this.changeWinStylePathToUnix(remote)
|
||||||
|
local = this.changeWinStylePathToUnix(local)
|
||||||
|
console.log(`remote: ${remote}, local: ${local}`)
|
||||||
|
await SSHClient.client.getFile(local, remote)
|
||||||
|
return true
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isConnected (): boolean {
|
||||||
|
return SSHClient.client.isConnected()
|
||||||
|
}
|
||||||
|
|
||||||
public close (): void {
|
public close (): void {
|
||||||
SSHClient.client.dispose()
|
SSHClient.client.dispose()
|
||||||
this._isConnected = false
|
this._isConnected = false
|
||||||
|
@ -497,7 +497,7 @@ https://www.baidu.com/img/bd_logo1.png"
|
|||||||
shadow="hover"
|
shadow="hover"
|
||||||
>
|
>
|
||||||
<el-image
|
<el-image
|
||||||
v-if="!item.isDir && currentPicBedName !== 'webdavplist'"
|
v-if="!item.isDir && currentPicBedName !== 'webdavplist' && currentPicBedName !== 'sftp'"
|
||||||
:src="isShowThumbnail && item.isImage ?
|
:src="isShowThumbnail && item.isImage ?
|
||||||
item.url
|
item.url
|
||||||
: require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
|
: require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
|
||||||
@ -519,13 +519,20 @@ https://www.baidu.com/img/bd_logo1.png"
|
|||||||
</template>
|
</template>
|
||||||
</el-image>
|
</el-image>
|
||||||
<ImageWebdav
|
<ImageWebdav
|
||||||
v-else-if="!item.isDir && currentPicBedName === 'webdavplist'"
|
v-else-if="!item.isDir && currentPicBedName === 'webdavplist' && item.isImage"
|
||||||
:is-show-thumbnail="isShowThumbnail"
|
:is-show-thumbnail="isShowThumbnail"
|
||||||
:item="item"
|
:item="item"
|
||||||
:headers="getBase64ofWebdav()"
|
:headers="getBase64ofWebdav()"
|
||||||
:url="item.url"
|
:url="item.url"
|
||||||
@click="handleClickFile(item)"
|
@click="handleClickFile(item)"
|
||||||
/>
|
/>
|
||||||
|
<el-image
|
||||||
|
v-else-if="!item.isDir"
|
||||||
|
:src="require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
|
||||||
|
fit="contain"
|
||||||
|
style="height: 100px;width: 100%;margin: 0 auto;"
|
||||||
|
@click="handleClickFile(item)"
|
||||||
|
/>
|
||||||
<el-image
|
<el-image
|
||||||
v-else
|
v-else
|
||||||
:src="require('./assets/icons/folder.webp')"
|
:src="require('./assets/icons/folder.webp')"
|
||||||
@ -1578,7 +1585,7 @@ const lastChoosed = ref<number>(-1)
|
|||||||
const customDomainList = ref([] as any[])
|
const customDomainList = ref([] as any[])
|
||||||
const currentCustomDomain = ref('')
|
const currentCustomDomain = ref('')
|
||||||
const isShowCustomDomainSelectList = computed(() => ['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value))
|
const isShowCustomDomainSelectList = computed(() => ['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value))
|
||||||
const isShowCustomDomainInput = computed(() => ['aliyun', 'qiniu', 'tcyun', 's3plist', 'webdavplist', 'local'].includes(currentPicBedName.value))
|
const isShowCustomDomainInput = computed(() => ['aliyun', 'qiniu', 'tcyun', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value))
|
||||||
const isAutoCustomDomain = computed(() => manageStore.config.picBed[configMap.alias].isAutoCustomUrl === undefined ? true : manageStore.config.picBed[configMap.alias].isAutoCustomUrl)
|
const isAutoCustomDomain = computed(() => manageStore.config.picBed[configMap.alias].isAutoCustomUrl === undefined ? true : manageStore.config.picBed[configMap.alias].isAutoCustomUrl)
|
||||||
// 文件预览相关
|
// 文件预览相关
|
||||||
const isShowMarkDownDialog = ref(false)
|
const isShowMarkDownDialog = ref(false)
|
||||||
@ -1589,7 +1596,7 @@ const isShowVideoFileDialog = ref(false)
|
|||||||
const videoFileUrl = ref('')
|
const videoFileUrl = ref('')
|
||||||
const videoPlayerHeaders = ref({})
|
const videoPlayerHeaders = ref({})
|
||||||
// 重命名相关
|
// 重命名相关
|
||||||
const isShowRenameFileIcon = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun', 's3plist', 'webdavplist', 'local'].includes(currentPicBedName.value))
|
const isShowRenameFileIcon = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value))
|
||||||
const isShowBatchRenameDialog = ref(false)
|
const isShowBatchRenameDialog = ref(false)
|
||||||
const batchRenameMatch = ref('')
|
const batchRenameMatch = ref('')
|
||||||
const batchRenameReplace = ref('')
|
const batchRenameReplace = ref('')
|
||||||
@ -1607,9 +1614,9 @@ const isAutoRefresh = computed(() => manageStore.config.settings.isAutoRefresh ?
|
|||||||
const isIgnoreCase = computed(() => manageStore.config.settings.isIgnoreCase ?? false)
|
const isIgnoreCase = computed(() => manageStore.config.settings.isIgnoreCase ?? false)
|
||||||
|
|
||||||
// 新建文件夹相关
|
// 新建文件夹相关
|
||||||
const isShowCreateNewFolder = computed(() => ['aliyun', 'github', 'local', 'qiniu', 'tcyun', 's3plist', 'upyun', 'webdavplist'].includes(currentPicBedName.value))
|
const isShowCreateNewFolder = computed(() => ['aliyun', 'github', 'local', 'qiniu', 'tcyun', 's3plist', 'upyun', 'webdavplist', 'sftp'].includes(currentPicBedName.value))
|
||||||
|
|
||||||
const isShowPresignedUrl = computed(() => ['aliyun', 'github', 'qiniu', 's3plist', 'tcyun', 'webdavplist'].includes(currentPicBedName.value))
|
const isShowPresignedUrl = computed(() => ['aliyun', 'github', 'qiniu', 's3plist', 'tcyun', 'webdavplist', 'sftp'].includes(currentPicBedName.value))
|
||||||
|
|
||||||
// 上传相关函数
|
// 上传相关函数
|
||||||
|
|
||||||
@ -2000,7 +2007,7 @@ async function handleChangeCustomUrl () {
|
|||||||
isShowLoadingPage.value = true
|
isShowLoadingPage.value = true
|
||||||
await resetParam(true)
|
await resetParam(true)
|
||||||
isShowLoadingPage.value = false
|
isShowLoadingPage.value = false
|
||||||
} else if (['aliyun', 'tcyun', 'qiniu', 's3plist', 'webdavplist'].includes(currentPicBedName.value)) {
|
} else if (['aliyun', 'tcyun', 'qiniu', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value)) {
|
||||||
const currentConfigs = await getConfig<any>('picBed')
|
const currentConfigs = await getConfig<any>('picBed')
|
||||||
const currentConfig = currentConfigs[configMap.alias]
|
const currentConfig = currentConfigs[configMap.alias]
|
||||||
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
|
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
|
||||||
@ -2105,7 +2112,7 @@ async function initCustomDomainList () {
|
|||||||
currentCustomDomain.value = endpoint
|
currentCustomDomain.value = endpoint
|
||||||
}
|
}
|
||||||
handleChangeCustomUrl()
|
handleChangeCustomUrl()
|
||||||
} else if (currentPicBedName.value === 'local') {
|
} else if (currentPicBedName.value === 'local' || currentPicBedName.value === 'sftp') {
|
||||||
const currentConfigs = await getConfig<any>('picBed')
|
const currentConfigs = await getConfig<any>('picBed')
|
||||||
const currentConfig = currentConfigs[configMap.alias]
|
const currentConfig = currentConfigs[configMap.alias]
|
||||||
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
|
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
|
||||||
@ -2799,7 +2806,8 @@ async function getBucketFileListBackStage () {
|
|||||||
const fileTransferStore = useFileTransferStore()
|
const fileTransferStore = useFileTransferStore()
|
||||||
fileTransferStore.resetFileTransferList()
|
fileTransferStore.resetFileTransferList()
|
||||||
if (currentPicBedName.value === 'webdavplist' ||
|
if (currentPicBedName.value === 'webdavplist' ||
|
||||||
currentPicBedName.value === 'local') {
|
currentPicBedName.value === 'local' ||
|
||||||
|
currentPicBedName.value === 'sftp') {
|
||||||
param.baseDir = configMap.baseDir
|
param.baseDir = configMap.baseDir
|
||||||
param.webPath = configMap.webPath
|
param.webPath = configMap.webPath
|
||||||
}
|
}
|
||||||
|
@ -730,7 +730,14 @@ async function transUpToManage (config: IUploaderConfigListItem, picBedName: str
|
|||||||
...commonConfig,
|
...commonConfig,
|
||||||
baseDir: config.path,
|
baseDir: config.path,
|
||||||
webPath: config.webpath || '',
|
webPath: config.webpath || '',
|
||||||
customUrl: config.customUrl || ''
|
customUrl: config.customUrl || '',
|
||||||
|
transformedConfig: JSON.stringify({
|
||||||
|
local: {
|
||||||
|
customUrl: config.customUrl || '',
|
||||||
|
baseDir: config.path,
|
||||||
|
webPath: config.webpath || ''
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
delete resultMap.paging
|
delete resultMap.paging
|
||||||
break
|
break
|
||||||
@ -749,7 +756,22 @@ async function transUpToManage (config: IUploaderConfigListItem, picBedName: str
|
|||||||
webPath: config.webPath || '',
|
webPath: config.webPath || '',
|
||||||
customUrl: config.customUrl || '',
|
customUrl: config.customUrl || '',
|
||||||
fileMode: config.fileMode || '0664',
|
fileMode: config.fileMode || '0664',
|
||||||
dirMode: config.dirMode || '0775'
|
dirMode: config.dirMode || '0775',
|
||||||
|
transformedConfig: JSON.stringify({
|
||||||
|
sftp: {
|
||||||
|
host: config.host,
|
||||||
|
port: config.port || 22,
|
||||||
|
username: config.username,
|
||||||
|
password: config.password,
|
||||||
|
privateKey: config.privateKey,
|
||||||
|
passphrase: config.passphrase,
|
||||||
|
baseDir: config.uploadPath || '/',
|
||||||
|
webPath: config.webPath || '',
|
||||||
|
customUrl: config.customUrl || '',
|
||||||
|
fileMode: config.fileMode || '0664',
|
||||||
|
dirMode: config.dirMode || '0775'
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
delete resultMap.paging
|
delete resultMap.paging
|
||||||
break
|
break
|
||||||
|
@ -27,19 +27,19 @@ export class FileCacheDb extends Dexie {
|
|||||||
qiniu: Table<IFileCache, string>
|
qiniu: Table<IFileCache, string>
|
||||||
smms: Table<IFileCache, string>
|
smms: Table<IFileCache, string>
|
||||||
s3plist: Table<IFileCache, string>
|
s3plist: Table<IFileCache, string>
|
||||||
sftpplist: Table<IFileCache, string>
|
sftp: Table<IFileCache, string>
|
||||||
upyun: Table<IFileCache, string>
|
upyun: Table<IFileCache, string>
|
||||||
webdavplist: Table<IFileCache, string>
|
webdavplist: Table<IFileCache, string>
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
super('bucketFileDb')
|
super('bucketFileDb')
|
||||||
const tableNames = ['aliyun', 'github', 'imgur', 'local', 'qiniu', 's3plist', 'sftpplist', 'smms', 'tcyun', 'upyun', 'webdavplist']
|
const tableNames = ['aliyun', 'github', 'imgur', 'local', 'qiniu', 's3plist', 'sftp', 'smms', 'tcyun', 'upyun', 'webdavplist']
|
||||||
|
|
||||||
const tableNamesMap = tableNames.reduce((acc, cur) => {
|
const tableNamesMap = tableNames.reduce((acc, cur) => {
|
||||||
acc[cur] = '&key, value'
|
acc[cur] = '&key, value'
|
||||||
return acc
|
return acc
|
||||||
}, {} as IStringKeyMap)
|
}, {} as IStringKeyMap)
|
||||||
this.version(4).stores(tableNamesMap)
|
this.version(5).stores(tableNamesMap)
|
||||||
this.aliyun = this.table('aliyun')
|
this.aliyun = this.table('aliyun')
|
||||||
this.github = this.table('github')
|
this.github = this.table('github')
|
||||||
this.imgur = this.table('imgur')
|
this.imgur = this.table('imgur')
|
||||||
@ -47,7 +47,7 @@ export class FileCacheDb extends Dexie {
|
|||||||
this.qiniu = this.table('qiniu')
|
this.qiniu = this.table('qiniu')
|
||||||
this.tcyun = this.table('tcyun')
|
this.tcyun = this.table('tcyun')
|
||||||
this.s3plist = this.table('s3plist')
|
this.s3plist = this.table('s3plist')
|
||||||
this.sftpplist = this.table('sftpplist')
|
this.sftp = this.table('sftp')
|
||||||
this.smms = this.table('smms')
|
this.smms = this.table('smms')
|
||||||
this.upyun = this.table('upyun')
|
this.upyun = this.table('upyun')
|
||||||
this.webdavplist = this.table('webdavplist')
|
this.webdavplist = this.table('webdavplist')
|
||||||
|
Loading…
Reference in New Issue
Block a user