🚧 WIP: sftp manage

This commit is contained in:
萌萌哒赫萝 2023-08-19 21:06:28 -07:00
parent 1629dcaef0
commit e89b3ca6d1
7 changed files with 493 additions and 16 deletions

View File

@ -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,

View File

@ -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

View File

@ -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!)

View File

@ -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

View File

@ -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
} }

View File

@ -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
@ -739,6 +746,19 @@ async function transUpToManage (config: IUploaderConfigListItem, picBedName: str
Object.assign(resultMap, { Object.assign(resultMap, {
...commonConfig, ...commonConfig,
picBedName: 'sftp', picBedName: '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',
transformedConfig: JSON.stringify({
sftp: {
host: config.host, host: config.host,
port: config.port || 22, port: config.port || 22,
username: config.username, username: config.username,
@ -750,6 +770,8 @@ async function transUpToManage (config: IUploaderConfigListItem, picBedName: str
customUrl: config.customUrl || '', customUrl: config.customUrl || '',
fileMode: config.fileMode || '0664', fileMode: config.fileMode || '0664',
dirMode: config.dirMode || '0775' dirMode: config.dirMode || '0775'
}
})
}) })
delete resultMap.paging delete resultMap.paging
break break

View File

@ -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')