mirror of
https://github.com/Kuingsmile/PicList.git
synced 2025-01-23 06:38:13 -05:00
✨ Feature: s3-compatible storage is supported now
This commit is contained in:
parent
7f7f400ce9
commit
176bdac993
@ -35,6 +35,7 @@
|
|||||||
| 阿里云OSS | :heavy_check_mark: | :heavy_check_mark: |
|
| 阿里云OSS | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| 又拍云 | :heavy_check_mark: | :heavy_check_mark: |
|
| 又拍云 | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| 七牛云 | :heavy_check_mark: | :heavy_check_mark: |
|
| 七牛云 | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
|
| S3 API兼容平台 | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
|
|
||||||
| 插件 | 相册云删除 |
|
| 插件 | 相册云删除 |
|
||||||
| :--: | :--: |
|
| :--: | :--: |
|
||||||
@ -48,13 +49,7 @@
|
|||||||
|
|
||||||
### CDN加速下载地址
|
### CDN加速下载地址
|
||||||
|
|
||||||
- [PicList-1.0.1-arm64.dmg](https://release.piclist.cn/1.0.1/PicList-1.0.1-arm64.dmg)
|
[https://github.com/Kuingsmile/PicList/releases/latest](https://github.com/Kuingsmile/PicList/releases/latest)
|
||||||
- [PicList-1.0.1-x64.dmg](https://release.piclist.cn/1.0.1/PicList-1.0.1-x64.dmg)
|
|
||||||
- [PicList-1.0.1.AppImage](https://release.piclist.cn/1.0.1/PicList-1.0.1.AppImage)
|
|
||||||
- [PicList-Setup-1.0.1-ia32.exe](https://release.piclist.cn/1.0.1/PicList-Setup-1.0.1-ia32.exe)
|
|
||||||
- [PicList-Setup-1.0.1-x64.exe](https://release.piclist.cn/1.0.1/PicList-Setup-1.0.1-x64.exe)
|
|
||||||
- [PicList-Setup-1.0.1.exe](https://release.piclist.cn/1.0.1/PicList-Setup-1.0.1.exe)
|
|
||||||
- [piclist_1.0.1_amd64.snap](https://release.piclist.cn/1.0.1/piclist_1.0.1_amd64.snap)
|
|
||||||
|
|
||||||
## 应用截图
|
## 应用截图
|
||||||
|
|
||||||
|
@ -19,6 +19,9 @@
|
|||||||
"link": "node ./scripts/cos-link.js"
|
"link": "node ./scripts/cos-link.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.272.0",
|
||||||
|
"@aws-sdk/lib-storage": "^3.272.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.272.0",
|
||||||
"@element-plus/icons-vue": "^2.0.10",
|
"@element-plus/icons-vue": "^2.0.10",
|
||||||
"@imengyu/vue3-context-menu": "^1.2.2",
|
"@imengyu/vue3-context-menu": "^1.2.2",
|
||||||
"@octokit/rest": "^19.0.7",
|
"@octokit/rest": "^19.0.7",
|
||||||
|
@ -20,7 +20,7 @@ class AliyunApi {
|
|||||||
ctx: OSS
|
ctx: OSS
|
||||||
accessKeyId: string
|
accessKeyId: string
|
||||||
accessKeySecret: string
|
accessKeySecret: string
|
||||||
timeOut = 60000
|
timeOut = 30000
|
||||||
logger: ManageLogger
|
logger: ManageLogger
|
||||||
|
|
||||||
constructor (accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
|
constructor (accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
|
||||||
@ -308,12 +308,10 @@ class AliyunApi {
|
|||||||
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
|
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
|
||||||
})
|
})
|
||||||
result.isTruncated = res.isTruncated
|
result.isTruncated = res.isTruncated
|
||||||
result.nextMarker = res.nextContinuationToken === null ? '' : res.nextContinuationToken
|
result.nextMarker = res.nextContinuationToken || ''
|
||||||
result.success = true
|
result.success = true
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -375,7 +373,7 @@ class AliyunApi {
|
|||||||
delimiter: '/',
|
delimiter: '/',
|
||||||
'max-keys': '1000'
|
'max-keys': '1000'
|
||||||
}, {
|
}, {
|
||||||
timeout: 60000
|
timeout: this.timeOut
|
||||||
}) as any
|
}) as any
|
||||||
if (res && res.res.statusCode === 200) {
|
if (res && res.res.statusCode === 200) {
|
||||||
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
|
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
|
||||||
@ -498,8 +496,7 @@ class AliyunApi {
|
|||||||
progress: Math.floor(p * 100),
|
progress: Math.floor(p * 100),
|
||||||
status: uploadTaskSpecialStatus.uploading
|
status: uploadTaskSpecialStatus.uploading
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
timeout: 60000
|
|
||||||
}
|
}
|
||||||
).then((res: any) => {
|
).then((res: any) => {
|
||||||
const id = `${bucketName}-${region}-${key}-${filePath}`
|
const id = `${bucketName}-${region}-${key}-${filePath}`
|
||||||
|
@ -5,6 +5,7 @@ import UpyunApi from './upyun'
|
|||||||
import SmmsApi from './smms'
|
import SmmsApi from './smms'
|
||||||
import GithubApi from './github'
|
import GithubApi from './github'
|
||||||
import ImgurApi from './imgur'
|
import ImgurApi from './imgur'
|
||||||
|
import S3plistApi from './s3plist'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
TcyunApi,
|
TcyunApi,
|
||||||
@ -13,5 +14,6 @@ export default {
|
|||||||
UpyunApi,
|
UpyunApi,
|
||||||
SmmsApi,
|
SmmsApi,
|
||||||
GithubApi,
|
GithubApi,
|
||||||
ImgurApi
|
ImgurApi,
|
||||||
|
S3plistApi
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ class QiniuApi {
|
|||||||
commonType = 'application/x-www-form-urlencoded'
|
commonType = 'application/x-www-form-urlencoded'
|
||||||
host = 'uc.qiniuapi.com'
|
host = 'uc.qiniuapi.com'
|
||||||
logger: ManageLogger
|
logger: ManageLogger
|
||||||
|
timeout = 30000
|
||||||
|
|
||||||
hostList = {
|
hostList = {
|
||||||
getBucketList: 'https://uc.qiniuapi.com/buckets',
|
getBucketList: 'https://uc.qiniuapi.com/buckets',
|
||||||
@ -100,7 +101,7 @@ class QiniuApi {
|
|||||||
Authorization: authorization,
|
Authorization: authorization,
|
||||||
'Content-Type': this.commonType
|
'Content-Type': this.commonType
|
||||||
},
|
},
|
||||||
timeout: 10000
|
timeout: this.timeout
|
||||||
})
|
})
|
||||||
if (res && res.status === 200) {
|
if (res && res.status === 200) {
|
||||||
if (res.data && res.data.length) {
|
if (res.data && res.data.length) {
|
||||||
@ -145,7 +146,7 @@ class QiniuApi {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Host: this.host
|
Host: this.host
|
||||||
},
|
},
|
||||||
timeout: 10000
|
timeout: this.timeout
|
||||||
})
|
})
|
||||||
if (res && res.status === 200) {
|
if (res && res.status === 200) {
|
||||||
return {
|
return {
|
||||||
@ -175,7 +176,7 @@ class QiniuApi {
|
|||||||
Authorization: authorization,
|
Authorization: authorization,
|
||||||
'Content-Type': this.commonType
|
'Content-Type': this.commonType
|
||||||
},
|
},
|
||||||
timeout: 10000
|
timeout: this.timeout
|
||||||
})
|
})
|
||||||
if (res && res.status === 200) {
|
if (res && res.status === 200) {
|
||||||
return res.data && res.data.length ? res.data : []
|
return res.data && res.data.length ? res.data : []
|
||||||
@ -206,7 +207,7 @@ class QiniuApi {
|
|||||||
'Content-Type': this.commonType,
|
'Content-Type': this.commonType,
|
||||||
Host: this.host
|
Host: this.host
|
||||||
},
|
},
|
||||||
timeout: 10000
|
timeout: this.timeout
|
||||||
})
|
})
|
||||||
return res && res.status === 200
|
return res && res.status === 200
|
||||||
}
|
}
|
||||||
@ -233,7 +234,7 @@ class QiniuApi {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Host: this.host
|
Host: this.host
|
||||||
},
|
},
|
||||||
timeout: 10000
|
timeout: this.timeout
|
||||||
})
|
})
|
||||||
if (res && res.status === 200) {
|
if (res && res.status === 200) {
|
||||||
const changeAclRes = await this.setBucketAclPolicy({
|
const changeAclRes = await this.setBucketAclPolicy({
|
||||||
@ -364,10 +365,8 @@ class QiniuApi {
|
|||||||
result.isTruncated = !!(res.respBody && res.respBody.marker)
|
result.isTruncated = !!(res.respBody && res.respBody.marker)
|
||||||
result.nextMarker = res.respBody && res.respBody.marker ? res.respBody.marker : ''
|
result.nextMarker = res.respBody && res.respBody.marker ? res.respBody.marker : ''
|
||||||
result.success = true
|
result.success = true
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
615
src/main/manage/apis/s3plist.ts
Normal file
615
src/main/manage/apis/s3plist.ts
Normal file
@ -0,0 +1,615 @@
|
|||||||
|
import {
|
||||||
|
S3Client,
|
||||||
|
ListBucketsCommand,
|
||||||
|
ListObjectsV2Command,
|
||||||
|
GetBucketLocationCommand,
|
||||||
|
S3ClientConfig,
|
||||||
|
_Object,
|
||||||
|
CommonPrefix,
|
||||||
|
ListObjectsV2CommandOutput,
|
||||||
|
CopyObjectCommand,
|
||||||
|
GetObjectCommand,
|
||||||
|
DeleteObjectCommand,
|
||||||
|
DeleteObjectsCommand,
|
||||||
|
PutObjectCommand
|
||||||
|
} from '@aws-sdk/client-s3'
|
||||||
|
import { Upload, Progress } from '@aws-sdk/lib-storage'
|
||||||
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
||||||
|
import https from 'https'
|
||||||
|
import http from 'http'
|
||||||
|
import { ManageLogger } from '../utils/logger'
|
||||||
|
import { formatError, getAgent, getFileMimeType, gotDownload } from '../utils/common'
|
||||||
|
import { isImage } from '@/manage/utils/common'
|
||||||
|
import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent'
|
||||||
|
import windowManager from 'apis/app/window/windowManager'
|
||||||
|
import { IWindowList } from '#/types/enum'
|
||||||
|
import { ipcMain, IpcMainEvent } from 'electron'
|
||||||
|
import UpDownTaskQueue,
|
||||||
|
{
|
||||||
|
uploadTaskSpecialStatus,
|
||||||
|
commonTaskStatus
|
||||||
|
} from '../datastore/upDownTaskQueue'
|
||||||
|
import fs from 'fs-extra'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
interface S3plistApiOptions {
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: string
|
||||||
|
secretAccessKey: string
|
||||||
|
}
|
||||||
|
endpoint?: string
|
||||||
|
sslEnabled: boolean
|
||||||
|
s3ForcePathStyle: boolean
|
||||||
|
httpOptions?: {
|
||||||
|
agent: https.Agent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class S3plistApi {
|
||||||
|
baseOptions: S3plistApiOptions
|
||||||
|
logger: ManageLogger
|
||||||
|
agent: any
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
accessKeyId: string,
|
||||||
|
secretAccessKey: string,
|
||||||
|
endpoint: string | undefined,
|
||||||
|
sslEnabled: boolean,
|
||||||
|
s3ForcePathStyle: boolean,
|
||||||
|
proxy: string | undefined,
|
||||||
|
logger: ManageLogger
|
||||||
|
) {
|
||||||
|
this.baseOptions = {
|
||||||
|
credentials: {
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey
|
||||||
|
},
|
||||||
|
endpoint: endpoint ? this.formatEndpoint(endpoint, sslEnabled) : undefined,
|
||||||
|
sslEnabled,
|
||||||
|
s3ForcePathStyle,
|
||||||
|
httpOptions: {
|
||||||
|
agent: this.setAgent(proxy, sslEnabled)
|
||||||
|
}
|
||||||
|
} as S3plistApiOptions
|
||||||
|
this.logger = logger
|
||||||
|
this.agent = this.setAgent(proxy, sslEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
formatEndpoint = (endpoint: string, sslEnabled: boolean): string =>
|
||||||
|
!/^https?:\/\//.test(endpoint) ? `${sslEnabled ? 'https' : 'http'}://${endpoint}` : endpoint
|
||||||
|
|
||||||
|
setAgent (proxy: string | undefined, sslEnabled: boolean) : HttpProxyAgent | HttpsProxyAgent | undefined {
|
||||||
|
if (sslEnabled) {
|
||||||
|
const agent = getAgent(proxy, true).https
|
||||||
|
return agent ?? new https.Agent({
|
||||||
|
keepAlive: true,
|
||||||
|
rejectUnauthorized: false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const agent = getAgent(proxy, false).http
|
||||||
|
return agent ?? new http.Agent({
|
||||||
|
keepAlive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logParam = (error:any, method: string) =>
|
||||||
|
this.logger.error(formatError(error, { class: 'S3plistApi', method }))
|
||||||
|
|
||||||
|
formatFolder (item: CommonPrefix, slicedPrefix: string): any {
|
||||||
|
return {
|
||||||
|
Key: item.Prefix,
|
||||||
|
fileSize: 0,
|
||||||
|
formatedTime: '',
|
||||||
|
fileName: item.Prefix?.replace(slicedPrefix, '').replace('/', ''),
|
||||||
|
isDir: true,
|
||||||
|
checked: false,
|
||||||
|
isImage: false,
|
||||||
|
match: false,
|
||||||
|
key: item.Prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatFile (item: _Object, slicedPrefix: string, urlPrefix: string): any {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
key: item.Key,
|
||||||
|
url: `${urlPrefix}/${item.Key}`,
|
||||||
|
fileName: item.Key?.replace(slicedPrefix, ''),
|
||||||
|
fileSize: item.Size,
|
||||||
|
formatedTime: new Date(item.LastModified!).toLocaleString(),
|
||||||
|
isDir: false,
|
||||||
|
checked: false,
|
||||||
|
match: false,
|
||||||
|
isImage: isImage(item.Key?.replace(slicedPrefix, '') || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取存储桶列表
|
||||||
|
*/
|
||||||
|
async getBucketList (): Promise<any> {
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = 'us-east-1'
|
||||||
|
const result = [] as IStringKeyMap[]
|
||||||
|
try {
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const command = new ListBucketsCommand({})
|
||||||
|
const data = await client.send(command)
|
||||||
|
if (data.$metadata.httpStatusCode === 200) {
|
||||||
|
if (data.Buckets) {
|
||||||
|
for (let i = 0; i < data.Buckets.length; i++) {
|
||||||
|
const bucket = data.Buckets[i]
|
||||||
|
const bucketName = bucket.Name
|
||||||
|
const command = new GetBucketLocationCommand({
|
||||||
|
Bucket: bucketName
|
||||||
|
})
|
||||||
|
const bucketConfig = await client.send(command)
|
||||||
|
if (bucketConfig.$metadata.httpStatusCode === 200) {
|
||||||
|
result.push({
|
||||||
|
Name: bucketName,
|
||||||
|
CreationDate: bucket.CreationDate,
|
||||||
|
Location: bucketConfig.LocationConstraint || 'us-east-1'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.logParam(bucketConfig, 'getBucketList')
|
||||||
|
result.push({
|
||||||
|
Name: bucketName,
|
||||||
|
CreationDate: bucket.CreationDate,
|
||||||
|
Location: 'us-east-1'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.logParam(data, 'getBucketList')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'getBucketList')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBucketListBackstage (configMap: IStringKeyMap): Promise<any> {
|
||||||
|
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
|
||||||
|
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
|
||||||
|
const slicedPrefix = prefix.slice(1)
|
||||||
|
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
|
||||||
|
let marker
|
||||||
|
const cancelTask = [false]
|
||||||
|
ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
|
||||||
|
if (token === cancelToken) {
|
||||||
|
cancelTask[0] = true
|
||||||
|
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let res = {} as ListObjectsV2CommandOutput
|
||||||
|
const result = {
|
||||||
|
fullList: <any>[],
|
||||||
|
success: false,
|
||||||
|
finished: false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const command = new ListObjectsV2Command({
|
||||||
|
Bucket: bucket,
|
||||||
|
Prefix: slicedPrefix === '' ? undefined : slicedPrefix,
|
||||||
|
MaxKeys: 1000,
|
||||||
|
ContinuationToken: marker,
|
||||||
|
Delimiter: '/'
|
||||||
|
})
|
||||||
|
res = await client.send(command)
|
||||||
|
if (res.$metadata.httpStatusCode === 200) {
|
||||||
|
res.CommonPrefixes && res.CommonPrefixes.forEach((item: CommonPrefix) => {
|
||||||
|
result.fullList.push(this.formatFolder(item, slicedPrefix))
|
||||||
|
})
|
||||||
|
res.Contents && res.Contents.forEach((item: _Object) => {
|
||||||
|
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
|
||||||
|
})
|
||||||
|
window.webContents.send('refreshFileTransferList', result)
|
||||||
|
} else {
|
||||||
|
this.logParam(res, 'getBucketFileList')
|
||||||
|
result.finished = true
|
||||||
|
window.webContents.send('refreshFileTransferList', result)
|
||||||
|
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
marker = res.NextContinuationToken
|
||||||
|
} while (res.IsTruncated && !cancelTask[0])
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'getBucketFileList')
|
||||||
|
result.finished = true
|
||||||
|
window.webContents.send('refreshFileTransferList', result)
|
||||||
|
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||||
|
}
|
||||||
|
result.success = true
|
||||||
|
result.finished = true
|
||||||
|
window.webContents.send('refreshFileTransferList', result)
|
||||||
|
ipcMain.removeAllListeners('cancelLoadingFileList')
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBucketFileList (configMap: IStringKeyMap): Promise<any> {
|
||||||
|
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap
|
||||||
|
const slicedPrefix = prefix.slice(1)
|
||||||
|
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
|
||||||
|
const result = {
|
||||||
|
fullList: <any>[],
|
||||||
|
isTruncated: false,
|
||||||
|
nextMarker: '',
|
||||||
|
success: false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const command = new ListObjectsV2Command({
|
||||||
|
Bucket: bucket,
|
||||||
|
Prefix: slicedPrefix,
|
||||||
|
ContinuationToken: marker === '' ? undefined : marker,
|
||||||
|
Delimiter: '/',
|
||||||
|
MaxKeys: itemsPerPage
|
||||||
|
})
|
||||||
|
const data = await client.send(command)
|
||||||
|
if (data.$metadata.httpStatusCode === 200) {
|
||||||
|
data.CommonPrefixes && data.CommonPrefixes.forEach((item: CommonPrefix) => {
|
||||||
|
result.fullList.push(this.formatFolder(item, slicedPrefix))
|
||||||
|
})
|
||||||
|
data.Contents && data.Contents.forEach((item: _Object) => {
|
||||||
|
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
|
||||||
|
})
|
||||||
|
result.isTruncated = data.IsTruncated || false
|
||||||
|
result.nextMarker = data.NextContinuationToken || ''
|
||||||
|
result.success = true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'getBucketFileList')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重命名文件
|
||||||
|
* @param configMap
|
||||||
|
* configMap = {
|
||||||
|
* bucketName: string,
|
||||||
|
* region: string,
|
||||||
|
* oldKey: string,
|
||||||
|
* newKey: string
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
async renameBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { bucketName, region, oldKey, newKey } = configMap
|
||||||
|
let result = false
|
||||||
|
try {
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const command = new CopyObjectCommand({
|
||||||
|
Bucket: bucketName,
|
||||||
|
CopySource: encodeURI(`${bucketName}/${oldKey}`),
|
||||||
|
Key: newKey
|
||||||
|
})
|
||||||
|
const data = await client.send(command)
|
||||||
|
if (data.$metadata.httpStatusCode === 200) {
|
||||||
|
const deleteCommand = new DeleteObjectCommand({
|
||||||
|
Bucket: bucketName,
|
||||||
|
Key: oldKey
|
||||||
|
})
|
||||||
|
const deleteData = await client.send(deleteCommand)
|
||||||
|
if (deleteData.$metadata.httpStatusCode === 204) {
|
||||||
|
result = true
|
||||||
|
} else {
|
||||||
|
this.logParam(deleteData, 'renameBucketFile')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.logParam(data, 'renameBucketFile')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'renameBucketFile')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
* @param configMap
|
||||||
|
* configMap = {
|
||||||
|
* bucketName: string,
|
||||||
|
* region: string,
|
||||||
|
* key: string
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
async deleteBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { bucketName, region, key } = configMap
|
||||||
|
let result = false
|
||||||
|
try {
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const command = new DeleteObjectCommand({
|
||||||
|
Bucket: bucketName,
|
||||||
|
Key: key
|
||||||
|
})
|
||||||
|
const data = await client.send(command)
|
||||||
|
if (data.$metadata.httpStatusCode === 204) {
|
||||||
|
result = true
|
||||||
|
} else {
|
||||||
|
this.logParam(data, 'deleteBucketFile')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'deleteBucketFile')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件夹
|
||||||
|
* @param configMap
|
||||||
|
*/
|
||||||
|
async deleteBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { bucketName, region, key } = configMap
|
||||||
|
let marker
|
||||||
|
let result = false
|
||||||
|
let IsTruncated
|
||||||
|
let res
|
||||||
|
const allFileList = {
|
||||||
|
CommonPrefixes: [] as any[],
|
||||||
|
Contents: [] as any[]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const command = new ListObjectsV2Command({
|
||||||
|
Bucket: bucketName,
|
||||||
|
Prefix: key,
|
||||||
|
ContinuationToken: marker === '' ? undefined : marker,
|
||||||
|
Delimiter: '/',
|
||||||
|
MaxKeys: 1000
|
||||||
|
})
|
||||||
|
res = await client.send(command) as ListObjectsV2CommandOutput
|
||||||
|
if (res.$metadata.httpStatusCode === 200) {
|
||||||
|
res.CommonPrefixes && allFileList.CommonPrefixes.push(...res.CommonPrefixes)
|
||||||
|
res.Contents && allFileList.Contents.push(...res.Contents)
|
||||||
|
IsTruncated = res.IsTruncated || false
|
||||||
|
marker = res.NextContinuationToken || ''
|
||||||
|
} else {
|
||||||
|
this.logParam(res, 'deleteBucketFolder')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} while (IsTruncated)
|
||||||
|
if (allFileList.CommonPrefixes.length > 0) {
|
||||||
|
for (const item of allFileList.CommonPrefixes) {
|
||||||
|
res = await this.deleteBucketFolder({
|
||||||
|
bucketName,
|
||||||
|
region,
|
||||||
|
key: item.Prefix
|
||||||
|
})
|
||||||
|
if (!res) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allFileList.Contents.length > 0) {
|
||||||
|
const cycle = Math.ceil(allFileList.Contents.length / 1000)
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
for (let i = 0; i < cycle; i++) {
|
||||||
|
const deleteList = allFileList.Contents.slice(i * 1000, (i + 1) * 1000)
|
||||||
|
const deleteCommand = new DeleteObjectsCommand({
|
||||||
|
Bucket: bucketName,
|
||||||
|
Delete: {
|
||||||
|
Objects: deleteList.map((item) => {
|
||||||
|
return {
|
||||||
|
Key: item.Key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
res = await client.send(deleteCommand)
|
||||||
|
if (res.$metadata.httpStatusCode !== 200) {
|
||||||
|
this.logParam(res, 'deleteBucketFolder')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = true
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'deleteBucketFolder')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取预签名url
|
||||||
|
* @param configMap
|
||||||
|
* configMap = {
|
||||||
|
* bucketName: string,
|
||||||
|
* region: string,
|
||||||
|
* key: string,
|
||||||
|
* expires: number,
|
||||||
|
* customUrl: string
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
async getPreSignedUrl (configMap: IStringKeyMap): Promise<string> {
|
||||||
|
const { bucketName, region, key, expires } = configMap
|
||||||
|
try {
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const signedUrl = await getSignedUrl(client, new GetObjectCommand({
|
||||||
|
Bucket: bucketName,
|
||||||
|
Key: key
|
||||||
|
}), {
|
||||||
|
expiresIn: expires || 3600
|
||||||
|
})
|
||||||
|
return signedUrl
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'getPreSignedUrl')
|
||||||
|
return 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建文件夹
|
||||||
|
* @param configMap
|
||||||
|
*/
|
||||||
|
async createBucketFolder (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { bucketName, region, key } = configMap
|
||||||
|
let result = false
|
||||||
|
try {
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const command = new PutObjectCommand({
|
||||||
|
Bucket: bucketName,
|
||||||
|
Key: key
|
||||||
|
})
|
||||||
|
const data = await client.send(command)
|
||||||
|
if (data.$metadata.httpStatusCode === 200) {
|
||||||
|
result = true
|
||||||
|
} else {
|
||||||
|
this.logParam(data, 'createBucketFolder')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logParam(error, 'createBucketFolder')
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* upload file
|
||||||
|
* @param configMap
|
||||||
|
*/
|
||||||
|
async uploadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { fileArray } = configMap
|
||||||
|
// fileArray = [{
|
||||||
|
// bucketName: string,
|
||||||
|
// region: string,
|
||||||
|
// key: string,
|
||||||
|
// filePath: string
|
||||||
|
// fileSize: number
|
||||||
|
// }]
|
||||||
|
const instance = UpDownTaskQueue.getInstance()
|
||||||
|
fileArray.forEach((item: any) => {
|
||||||
|
item.key.startsWith('/') && (item.key = item.key.slice(1))
|
||||||
|
})
|
||||||
|
const allowedAcl = ['private', 'public-read', 'public-read-write', 'aws-exec-read', 'authenticated-read', 'bucket-owner-read', 'bucket-owner-full-control']
|
||||||
|
for (const item of fileArray) {
|
||||||
|
const { bucketName, region, key, filePath, fileName, aclForUpload } = item
|
||||||
|
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
|
||||||
|
options.region = region || 'us-east-1'
|
||||||
|
const client = new S3Client(options)
|
||||||
|
const id = `${bucketName}-${region}-${key}-${filePath}`
|
||||||
|
if (instance.getUploadTask(id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const fileStream = fs.createReadStream(filePath)
|
||||||
|
instance.addUploadTask({
|
||||||
|
id,
|
||||||
|
progress: 0,
|
||||||
|
status: commonTaskStatus.queuing,
|
||||||
|
sourceFileName: fileName,
|
||||||
|
sourceFilePath: filePath,
|
||||||
|
targetFilePath: key,
|
||||||
|
targetFileBucket: bucketName,
|
||||||
|
targetFileRegion: region
|
||||||
|
})
|
||||||
|
const parallelUploads3 = new Upload({
|
||||||
|
client,
|
||||||
|
params: {
|
||||||
|
Bucket: bucketName,
|
||||||
|
Key: key,
|
||||||
|
Body: fileStream,
|
||||||
|
ContentType: getFileMimeType(fileName),
|
||||||
|
ACL: allowedAcl.includes(aclForUpload) ? aclForUpload : 'private',
|
||||||
|
Metadata: {
|
||||||
|
description: 'uploaded by PicList'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
parallelUploads3.on('httpUploadProgress', (progress: Progress) => {
|
||||||
|
instance.updateUploadTask({
|
||||||
|
id,
|
||||||
|
progress: progress.loaded && progress.total ? Math.floor(progress.loaded / progress.total * 100) : 0,
|
||||||
|
status: uploadTaskSpecialStatus.uploading
|
||||||
|
})
|
||||||
|
})
|
||||||
|
parallelUploads3.done().then((data) => {
|
||||||
|
if (data.$metadata.httpStatusCode === 200) {
|
||||||
|
instance.updateUploadTask({
|
||||||
|
id,
|
||||||
|
progress: 100,
|
||||||
|
status: uploadTaskSpecialStatus.uploaded,
|
||||||
|
finishTime: new Date().toLocaleString()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
instance.updateUploadTask({
|
||||||
|
id,
|
||||||
|
progress: 0,
|
||||||
|
status: commonTaskStatus.failed,
|
||||||
|
finishTime: new Date().toLocaleString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
this.logParam(error, 'uploadBucketFile')
|
||||||
|
instance.updateUploadTask({
|
||||||
|
id,
|
||||||
|
progress: 0,
|
||||||
|
status: commonTaskStatus.failed,
|
||||||
|
response: JSON.stringify(error),
|
||||||
|
finishTime: new Date().toLocaleString()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
* @param configMap
|
||||||
|
*/
|
||||||
|
async downloadBucketFile (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
|
const { downloadPath, fileArray } = configMap
|
||||||
|
// fileArray = [{
|
||||||
|
// bucketName: string,
|
||||||
|
// region: string,
|
||||||
|
// key: string,
|
||||||
|
// fileName: string
|
||||||
|
// }]
|
||||||
|
const instance = UpDownTaskQueue.getInstance()
|
||||||
|
for (const item of fileArray) {
|
||||||
|
const { bucketName, region, key, fileName, customUrl } = item
|
||||||
|
const savedFilePath = path.join(downloadPath, fileName)
|
||||||
|
const fileStream = fs.createWriteStream(savedFilePath)
|
||||||
|
const id = `${bucketName}-${region}-${key}-${savedFilePath}`
|
||||||
|
if (instance.getDownloadTask(id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
instance.addDownloadTask({
|
||||||
|
id,
|
||||||
|
progress: 0,
|
||||||
|
status: commonTaskStatus.queuing,
|
||||||
|
sourceFileName: fileName,
|
||||||
|
targetFilePath: savedFilePath
|
||||||
|
})
|
||||||
|
const preSignedUrl = await this.getPreSignedUrl({
|
||||||
|
bucketName,
|
||||||
|
region,
|
||||||
|
key,
|
||||||
|
expires: 36000,
|
||||||
|
customUrl
|
||||||
|
})
|
||||||
|
gotDownload(instance, preSignedUrl, fileStream, id, savedFilePath, this.logger)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default S3plistApi
|
@ -15,15 +15,20 @@ class SmmsApi {
|
|||||||
token: string
|
token: string
|
||||||
axiosInstance: AxiosInstance
|
axiosInstance: AxiosInstance
|
||||||
logger: ManageLogger
|
logger: ManageLogger
|
||||||
|
timeout = 30000
|
||||||
|
|
||||||
constructor (token: string, logger: ManageLogger) {
|
constructor (token: string, logger: ManageLogger) {
|
||||||
this.token = token
|
this.token = token
|
||||||
this.axiosInstance = axios.create({
|
this.axiosInstance = axios.create({
|
||||||
baseURL: this.baseUrl,
|
baseURL: this.baseUrl,
|
||||||
timeout: 30000,
|
timeout: this.timeout,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: this.token
|
Authorization: this.token
|
||||||
}
|
},
|
||||||
|
httpsAgent: new (require('https').Agent)({
|
||||||
|
keepAlive: true,
|
||||||
|
timeout: this.timeout
|
||||||
|
})
|
||||||
})
|
})
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
}
|
}
|
||||||
|
@ -211,10 +211,8 @@ class TcyunApi {
|
|||||||
result.isTruncated = res.IsTruncated === 'true'
|
result.isTruncated = res.IsTruncated === 'true'
|
||||||
result.nextMarker = res.NextMarker || ''
|
result.nextMarker = res.NextMarker || ''
|
||||||
result.success = true
|
result.success = true
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,10 +173,8 @@ class UpyunApi {
|
|||||||
result.isTruncated = res.next !== this.stopMarker
|
result.isTruncated = res.next !== this.stopMarker
|
||||||
result.nextMarker = res.next
|
result.nextMarker = res.next
|
||||||
result.success = true
|
result.success = true
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,6 +67,8 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
return new API.GithubApi(this.currentPicBedConfig.token, this.currentPicBedConfig.githubUsername, this.currentPicBedConfig.proxy, this.logger)
|
return new API.GithubApi(this.currentPicBedConfig.token, this.currentPicBedConfig.githubUsername, this.currentPicBedConfig.proxy, this.logger)
|
||||||
case 'imgur':
|
case 'imgur':
|
||||||
return new API.ImgurApi(this.currentPicBedConfig.imgurUserName, this.currentPicBedConfig.accessToken, this.currentPicBedConfig.proxy, this.logger)
|
return new API.ImgurApi(this.currentPicBedConfig.imgurUserName, this.currentPicBedConfig.accessToken, this.currentPicBedConfig.proxy, this.logger)
|
||||||
|
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)
|
||||||
default:
|
default:
|
||||||
return {} as any
|
return {} as any
|
||||||
}
|
}
|
||||||
@ -150,6 +152,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'qiniu':
|
case 'qiniu':
|
||||||
case 'github':
|
case 'github':
|
||||||
case 'imgur':
|
case 'imgur':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient()
|
client = this.createClient()
|
||||||
return await client.getBucketList()
|
return await client.getBucketList()
|
||||||
@ -305,6 +308,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'smms':
|
case 'smms':
|
||||||
case 'github':
|
case 'github':
|
||||||
case 'imgur':
|
case 'imgur':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.getBucketListBackstage(param!)
|
return await client.getBucketListBackstage(param!)
|
||||||
@ -348,6 +352,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'qiniu':
|
case 'qiniu':
|
||||||
case 'upyun':
|
case 'upyun':
|
||||||
case 'smms':
|
case 'smms':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient()
|
client = this.createClient()
|
||||||
return await client.getBucketFileList(param!)
|
return await client.getBucketFileList(param!)
|
||||||
@ -372,6 +377,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'smms':
|
case 'smms':
|
||||||
case 'github':
|
case 'github':
|
||||||
case 'imgur':
|
case 'imgur':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
const res = await client.deleteBucketFile(param!)
|
const res = await client.deleteBucketFile(param!)
|
||||||
@ -395,6 +401,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'qiniu':
|
case 'qiniu':
|
||||||
case 'upyun':
|
case 'upyun':
|
||||||
case 'github':
|
case 'github':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.deleteBucketFolder(param!)
|
return await client.deleteBucketFolder(param!)
|
||||||
@ -416,6 +423,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'aliyun':
|
case 'aliyun':
|
||||||
case 'qiniu':
|
case 'qiniu':
|
||||||
case 'upyun':
|
case 'upyun':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.renameBucketFile(param!)
|
return await client.renameBucketFile(param!)
|
||||||
@ -440,6 +448,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'smms':
|
case 'smms':
|
||||||
case 'github':
|
case 'github':
|
||||||
case 'imgur':
|
case 'imgur':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
const res = await client.downloadBucketFile(param!)
|
const res = await client.downloadBucketFile(param!)
|
||||||
@ -470,6 +479,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'qiniu':
|
case 'qiniu':
|
||||||
case 'upyun':
|
case 'upyun':
|
||||||
case 'github':
|
case 'github':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.createBucketFolder(param!)
|
return await client.createBucketFolder(param!)
|
||||||
@ -494,6 +504,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'smms':
|
case 'smms':
|
||||||
case 'github':
|
case 'github':
|
||||||
case 'imgur':
|
case 'imgur':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.uploadBucketFile(param!)
|
return await client.uploadBucketFile(param!)
|
||||||
@ -515,6 +526,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
|||||||
case 'aliyun':
|
case 'aliyun':
|
||||||
case 'qiniu':
|
case 'qiniu':
|
||||||
case 'github':
|
case 'github':
|
||||||
|
case 's3plist':
|
||||||
try {
|
try {
|
||||||
client = this.createClient() as any
|
client = this.createClient() as any
|
||||||
return await client.getPreSignedUrl(param!)
|
return await client.getPreSignedUrl(param!)
|
||||||
|
@ -101,13 +101,10 @@ export const gotDownload = async (
|
|||||||
got(
|
got(
|
||||||
preSignedUrl,
|
preSignedUrl,
|
||||||
{
|
{
|
||||||
timeout: {
|
|
||||||
request: 30000
|
|
||||||
},
|
|
||||||
isStream: true,
|
isStream: true,
|
||||||
throwHttpErrors: false,
|
throwHttpErrors: false,
|
||||||
searchParams: param,
|
searchParams: param,
|
||||||
agent
|
agent: agent || {}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.on('downloadProgress', (progress: any) => {
|
.on('downloadProgress', (progress: any) => {
|
||||||
@ -118,7 +115,7 @@ export const gotDownload = async (
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.pipe(fileStream)
|
.pipe(fileStream)
|
||||||
.on('finish', () => {
|
.on('close', () => {
|
||||||
instance.updateDownloadTask({
|
instance.updateDownloadTask({
|
||||||
id,
|
id,
|
||||||
progress: 100,
|
progress: 100,
|
||||||
@ -158,7 +155,7 @@ export const gotUpload = async (
|
|||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
timeout: {
|
timeout: {
|
||||||
request: timeout
|
lookup: timeout
|
||||||
},
|
},
|
||||||
throwHttpErrors,
|
throwHttpErrors,
|
||||||
agent
|
agent
|
||||||
@ -227,8 +224,7 @@ export const getAgent = (proxy:any, https: boolean = true) => {
|
|||||||
https: new HttpsProxyAgent({
|
https: new HttpsProxyAgent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 1000,
|
keepAliveMsecs: 1000,
|
||||||
maxSockets: 256,
|
rejectUnauthorized: false,
|
||||||
maxFreeSockets: 256,
|
|
||||||
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined,
|
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined,
|
||||||
proxy: formatProxy.replace('127.0.0.1', 'localhost')
|
proxy: formatProxy.replace('127.0.0.1', 'localhost')
|
||||||
})
|
})
|
||||||
@ -240,8 +236,6 @@ export const getAgent = (proxy:any, https: boolean = true) => {
|
|||||||
http: new HttpProxyAgent({
|
http: new HttpProxyAgent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 1000,
|
keepAliveMsecs: 1000,
|
||||||
maxSockets: 256,
|
|
||||||
maxFreeSockets: 256,
|
|
||||||
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined,
|
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined,
|
||||||
proxy: formatProxy.replace('127.0.0.1', 'localhost')
|
proxy: formatProxy.replace('127.0.0.1', 'localhost')
|
||||||
})
|
})
|
||||||
|
@ -4,11 +4,11 @@ export default class AwsS3Api {
|
|||||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||||
const { imgUrl, config: { accessKeyID, secretAccessKey, bucketName, region, endpoint, pathStyleAccess, bucketEndpoint, rejectUnauthorized } } = configMap
|
const { imgUrl, config: { accessKeyID, secretAccessKey, bucketName, region, endpoint, pathStyleAccess, bucketEndpoint, rejectUnauthorized } } = configMap
|
||||||
try {
|
try {
|
||||||
const url = new URL((!imgUrl.startsWith('http') && !imgUrl.startsWith('https')) ? `http://${imgUrl}` : imgUrl)
|
const url = new URL(!/^https?:\/\//.test(imgUrl) ? `http://${imgUrl}` : imgUrl)
|
||||||
const fileKey = url.pathname
|
const fileKey = url.pathname
|
||||||
let endpointUrl
|
let endpointUrl
|
||||||
if (endpoint) {
|
if (endpoint) {
|
||||||
if (!endpoint.startsWith('http') && !endpoint.startsWith('https')) {
|
if (!/^https?:\/\//.test(endpoint)) {
|
||||||
endpointUrl = `http://${endpoint}`
|
endpointUrl = `http://${endpoint}`
|
||||||
} else {
|
} else {
|
||||||
endpointUrl = endpoint
|
endpointUrl = endpoint
|
||||||
@ -29,7 +29,8 @@ export default class AwsS3Api {
|
|||||||
s3BucketEndpoint: bucketEndpoint,
|
s3BucketEndpoint: bucketEndpoint,
|
||||||
httpOptions: {
|
httpOptions: {
|
||||||
agent: new http.Agent({
|
agent: new http.Agent({
|
||||||
rejectUnauthorized
|
rejectUnauthorized,
|
||||||
|
timeout: 30000
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ export default class ImgurApi {
|
|||||||
try {
|
try {
|
||||||
const res = await axios.delete(fullUrl, {
|
const res = await axios.delete(fullUrl, {
|
||||||
headers,
|
headers,
|
||||||
timeout: 10000
|
timeout: 30000
|
||||||
})
|
})
|
||||||
return res.status === 200
|
return res.status === 200
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -15,7 +15,7 @@ export default class SmmsApi {
|
|||||||
hash,
|
hash,
|
||||||
format: 'json'
|
format: 'json'
|
||||||
},
|
},
|
||||||
timeout: 10000
|
timeout: 30000
|
||||||
})
|
})
|
||||||
return res.status === 200
|
return res.status === 200
|
||||||
}
|
}
|
||||||
|
BIN
src/renderer/manage/pages/assets/s3plist.png
Normal file
BIN
src/renderer/manage/pages/assets/s3plist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -15,7 +15,7 @@
|
|||||||
style="flex-grow: 1;margin-left: 16px"
|
style="flex-grow: 1;margin-left: 16px"
|
||||||
>
|
>
|
||||||
<el-select
|
<el-select
|
||||||
v-if="showCustomUrlSelectList && customUrlList.length > 1"
|
v-if="showCustomUrlSelectList && customUrlList.length > 1 && isAutoCustomUrl"
|
||||||
v-model="currentCustomUrl"
|
v-model="currentCustomUrl"
|
||||||
placeholder="请选择自定义域名"
|
placeholder="请选择自定义域名"
|
||||||
style="width: 200px;"
|
style="width: 200px;"
|
||||||
@ -28,6 +28,13 @@
|
|||||||
:value="item.value"
|
:value="item.value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-else-if="['aliyun', 'qiniu', 'tcyun', 's3plist'].includes(currentPicBedName)"
|
||||||
|
v-model="currentCustomUrl"
|
||||||
|
placeholder="请输入自定义域名"
|
||||||
|
style="width: 200px;"
|
||||||
|
@blur="handelChangeCustomUrl"
|
||||||
|
/>
|
||||||
<el-link
|
<el-link
|
||||||
v-else
|
v-else
|
||||||
:underline="false"
|
:underline="false"
|
||||||
@ -140,7 +147,7 @@
|
|||||||
<Link />
|
<Link />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<template v-if="['tcyun', 'qiniu', 'aliyun', 'github'].includes(currentPicBedName)">
|
<template v-if="showPresignedUrl">
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
v-for="i in [...linkArray, { key: '预签名链接', value: 'preSignedUrl' }]"
|
v-for="i in [...linkArray, { key: '预签名链接', value: 'preSignedUrl' }]"
|
||||||
:key="i.key"
|
:key="i.key"
|
||||||
@ -790,6 +797,7 @@ import { v4 as uuidv4 } from 'uuid'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { IUploadTask, IDownloadTask } from '~/main/manage/datastore/upDownTaskQueue'
|
import { IUploadTask, IDownloadTask } from '~/main/manage/datastore/upDownTaskQueue'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
|
import { getConfig, saveConfig } from '../utils/dataSender'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
configMap:{
|
configMap:{
|
||||||
@ -866,9 +874,11 @@ const currentCustomUrl = ref('')
|
|||||||
|
|
||||||
const showCustomUrlSelectList = computed(() => ['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value))
|
const showCustomUrlSelectList = computed(() => ['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value))
|
||||||
|
|
||||||
const showCreateNewFolder = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun', 'github'].includes(currentPicBedName.value))
|
const showCreateNewFolder = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun', 'github', 's3plist'].includes(currentPicBedName.value))
|
||||||
|
|
||||||
const showRenameFileIcon = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun'].includes(currentPicBedName.value))
|
const showRenameFileIcon = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun', 's3plist'].includes(currentPicBedName.value))
|
||||||
|
|
||||||
|
const showPresignedUrl = computed(() => ['tcyun', 'aliyun', 'qiniu', 'github', 's3plist'].includes(currentPicBedName.value))
|
||||||
|
|
||||||
const uploadingTaskList = computed(() => uploadTaskList.value.filter(item => ['uploading', 'queuing', 'paused'].includes(item.status)))
|
const uploadingTaskList = computed(() => uploadTaskList.value.filter(item => ['uploading', 'queuing', 'paused'].includes(item.status)))
|
||||||
|
|
||||||
@ -878,6 +888,8 @@ const downloadingTaskList = computed(() => downloadTaskList.value.filter(item =>
|
|||||||
|
|
||||||
const downloadedTaskList = computed(() => downloadTaskList.value.filter(item => ['downloaded', 'failed', 'canceled'].includes(item.status)))
|
const downloadedTaskList = computed(() => downloadTaskList.value.filter(item => ['downloaded', 'failed', 'canceled'].includes(item.status)))
|
||||||
|
|
||||||
|
const isAutoCustomUrl = computed(() => manageStore.config.picBed[configMap.alias].isAutoCustomUrl === undefined ? true : manageStore.config.picBed[configMap.alias].isAutoCustomUrl)
|
||||||
|
|
||||||
function startRefreshUploadTask () {
|
function startRefreshUploadTask () {
|
||||||
refreshUploadTaskId.value = setInterval(() => {
|
refreshUploadTaskId.value = setInterval(() => {
|
||||||
ipcRenderer.invoke('getUploadTaskList').then((res: any) => {
|
ipcRenderer.invoke('getUploadTaskList').then((res: any) => {
|
||||||
@ -1096,7 +1108,8 @@ function uploadFiles () {
|
|||||||
filePath: item.path,
|
filePath: item.path,
|
||||||
fileSize: item.size,
|
fileSize: item.size,
|
||||||
fileName: item.rawName,
|
fileName: item.rawName,
|
||||||
githubBranch: currentCustomUrl.value
|
githubBranch: currentCustomUrl.value,
|
||||||
|
aclForUpload: manageStore.config.picBed[configMap.alias].aclForUpload
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
ipcRenderer.send('uploadBucketFile', configMap.alias, param)
|
ipcRenderer.send('uploadBucketFile', configMap.alias, param)
|
||||||
@ -1193,49 +1206,97 @@ async function handelChangeCustomUrl () {
|
|||||||
showLoadingPage.value = true
|
showLoadingPage.value = true
|
||||||
await resetParam(true)
|
await resetParam(true)
|
||||||
showLoadingPage.value = false
|
showLoadingPage.value = false
|
||||||
|
} else if (['aliyun', 'tcyun', 'qiniu', 's3plist'].includes(currentPicBedName.value)) {
|
||||||
|
const currentConfigs = await getConfig<any>('picBed')
|
||||||
|
const currentConfig = currentConfigs[configMap.alias]
|
||||||
|
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
|
||||||
|
if (currentTransformedConfig[configMap.bucketName]) {
|
||||||
|
currentTransformedConfig[configMap.bucketName].customUrl = currentCustomUrl.value
|
||||||
|
} else {
|
||||||
|
currentTransformedConfig[configMap.bucketName] = {
|
||||||
|
customUrl: currentCustomUrl.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentConfig.transformedConfig = JSON.stringify(currentTransformedConfig)
|
||||||
|
saveConfig(`picBed.${configMap.alias}`, currentConfig)
|
||||||
|
await manageStore.refreshConfig()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the current picBed is github, the customUrlList is used to store the github repo branches
|
// when the current picBed is github, the customUrlList is used to store the github repo branches
|
||||||
async function initCustomUrlList () {
|
async function initCustomUrlList () {
|
||||||
const param = {
|
if ((['aliyun', 'tcyun', 'qiniu'].includes(currentPicBedName.value) &&
|
||||||
bucketName: configMap.bucketName,
|
(manageStore.config.picBed[configMap.alias].isAutoCustomUrl === undefined || manageStore.config.picBed[configMap.alias].isAutoCustomUrl === true)) ||
|
||||||
region: configMap.bucketConfig.Location
|
['github', 'smms', 'upyun', 'imgur'].includes(currentPicBedName.value)) {
|
||||||
}
|
const param = {
|
||||||
let defaultUrl = ''
|
bucketName: configMap.bucketName,
|
||||||
if (currentPicBedName.value === 'tcyun') {
|
region: configMap.bucketConfig.Location
|
||||||
defaultUrl = `https://${configMap.bucketName}.cos.${configMap.bucketConfig.Location}.myqcloud.com`
|
}
|
||||||
} else if (currentPicBedName.value === 'aliyun') {
|
let defaultUrl = ''
|
||||||
defaultUrl = `https://${configMap.bucketName}.${configMap.bucketConfig.Location}.aliyuncs.com`
|
if (currentPicBedName.value === 'tcyun') {
|
||||||
} else if (currentPicBedName.value === 'github') {
|
defaultUrl = `https://${configMap.bucketName}.cos.${configMap.bucketConfig.Location}.myqcloud.com`
|
||||||
defaultUrl = 'main'
|
} else if (currentPicBedName.value === 'aliyun') {
|
||||||
}
|
defaultUrl = `https://${configMap.bucketName}.${configMap.bucketConfig.Location}.aliyuncs.com`
|
||||||
const res = await ipcRenderer.invoke('getBucketDomain', configMap.alias, param)
|
} else if (currentPicBedName.value === 'github') {
|
||||||
if (res.length > 0) {
|
defaultUrl = 'main'
|
||||||
customUrlList.value.length = 0
|
}
|
||||||
res.forEach((item: any) => {
|
const res = await ipcRenderer.invoke('getBucketDomain', configMap.alias, param)
|
||||||
if (!item.startsWith('http://') && !item.startsWith('https://') && currentPicBedName.value !== 'github') {
|
if (res.length > 0) {
|
||||||
item = manageStore.config.settings.isForceCustomUrlHttps ? `https://${item}` : `http://${item}`
|
customUrlList.value.length = 0
|
||||||
}
|
res.forEach((item: any) => {
|
||||||
customUrlList.value.push({
|
if (!/^https?:\/\//.test(item) && currentPicBedName.value !== 'github') {
|
||||||
label: item,
|
item = manageStore.config.settings.isForceCustomUrlHttps ? `https://${item}` : `http://${item}`
|
||||||
value: item
|
}
|
||||||
|
customUrlList.value.push({
|
||||||
|
label: item,
|
||||||
|
value: item
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
defaultUrl !== '' && currentPicBedName.value !== 'github' && customUrlList.value.push({
|
||||||
defaultUrl !== '' && currentPicBedName.value !== 'github' && customUrlList.value.push({
|
|
||||||
label: defaultUrl,
|
|
||||||
value: defaultUrl
|
|
||||||
})
|
|
||||||
currentCustomUrl.value = customUrlList.value[0].value
|
|
||||||
} else {
|
|
||||||
customUrlList.value.length = 0
|
|
||||||
customUrlList.value = [
|
|
||||||
{
|
|
||||||
label: defaultUrl,
|
label: defaultUrl,
|
||||||
value: defaultUrl
|
value: defaultUrl
|
||||||
|
})
|
||||||
|
currentCustomUrl.value = customUrlList.value[0].value
|
||||||
|
} else {
|
||||||
|
customUrlList.value.length = 0
|
||||||
|
customUrlList.value = [
|
||||||
|
{
|
||||||
|
label: defaultUrl,
|
||||||
|
value: defaultUrl
|
||||||
|
}
|
||||||
|
]
|
||||||
|
currentCustomUrl.value = defaultUrl
|
||||||
|
}
|
||||||
|
} else if (['aliyun', 'tcyun', 'qiniu'].includes(currentPicBedName.value)) {
|
||||||
|
const currentConfigs = await getConfig<any>('picBed')
|
||||||
|
const currentConfig = currentConfigs[configMap.alias]
|
||||||
|
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
|
||||||
|
if (currentTransformedConfig[configMap.bucketName]) {
|
||||||
|
currentCustomUrl.value = currentTransformedConfig[configMap.bucketName].customUrl ?? ''
|
||||||
|
} else {
|
||||||
|
currentCustomUrl.value = ''
|
||||||
|
}
|
||||||
|
} else if (currentPicBedName.value === 's3plist') {
|
||||||
|
const currentConfigs = await getConfig<any>('picBed')
|
||||||
|
const currentConfig = currentConfigs[configMap.alias]
|
||||||
|
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
|
||||||
|
if (currentTransformedConfig[configMap.bucketName]) {
|
||||||
|
currentCustomUrl.value = currentTransformedConfig[configMap.bucketName].customUrl ?? ''
|
||||||
|
} else {
|
||||||
|
if (manageStore.config.picBed[configMap.alias].endpoint) {
|
||||||
|
const endpoint = manageStore.config.picBed[configMap.alias].endpoint
|
||||||
|
let url
|
||||||
|
if (/^https?:\/\//.test(endpoint)) {
|
||||||
|
url = new URL(endpoint)
|
||||||
|
} else {
|
||||||
|
url = new URL(manageStore.config.picBed[configMap.alias].sslEnabled ? 'https://' + endpoint : 'http://' + endpoint)
|
||||||
|
}
|
||||||
|
currentCustomUrl.value = `${url.protocol}//${configMap.bucketName}.${url.hostname}`
|
||||||
|
} else {
|
||||||
|
currentCustomUrl.value = `https://${configMap.bucketName}.s3.amazonaws.com`
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
currentCustomUrl.value = defaultUrl
|
handelChangeCustomUrl()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2484,7 +2545,7 @@ const columns: Column<any>[] = [
|
|||||||
>
|
>
|
||||||
自定义
|
自定义
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
{['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value)
|
{ showPresignedUrl.value
|
||||||
? <ElDropdownItem
|
? <ElDropdownItem
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const res = await getPreSignedUrl(item)
|
const res = await getPreSignedUrl(item)
|
||||||
|
@ -133,22 +133,50 @@
|
|||||||
v-for="option in supportedPicBedList[item.icon].options"
|
v-for="option in supportedPicBedList[item.icon].options"
|
||||||
:key="option"
|
:key="option"
|
||||||
:prop="item.icon + '.' + option"
|
:prop="item.icon + '.' + option"
|
||||||
:label="supportedPicBedList[item.icon].configOptions[option].description"
|
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
{{ supportedPicBedList[item.icon].configOptions[option].description }}
|
||||||
|
<el-tooltip
|
||||||
|
v-if="!!supportedPicBedList[item.icon].configOptions[option].tooltip"
|
||||||
|
effect="dark"
|
||||||
|
:content="supportedPicBedList[item.icon].configOptions[option].tooltip"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<el-icon
|
||||||
|
color="#409EFF"
|
||||||
|
>
|
||||||
|
<InfoFilled />
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="option !== 'paging' && option !== 'itemsPerPage'"
|
v-if="supportedPicBedList[item.icon].configOptions[option].type === 'string'"
|
||||||
v-model.trim="configResult[item.icon + '.' + option]"
|
v-model.trim="configResult[item.icon + '.' + option]"
|
||||||
:placeholder="supportedPicBedList[item.icon].configOptions[option].placeholder"
|
:placeholder="supportedPicBedList[item.icon].configOptions[option].placeholder"
|
||||||
/>
|
/>
|
||||||
<el-switch
|
<el-switch
|
||||||
v-else-if="option === 'paging'"
|
v-else-if="supportedPicBedList[item.icon].configOptions[option].type === 'boolean'"
|
||||||
v-model="configResult[item.icon + '.' + option]"
|
v-model="configResult[item.icon + '.' + option]"
|
||||||
|
active-color="#13ce66"
|
||||||
|
inactive-color="#ff4949"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else
|
v-else-if="supportedPicBedList[item.icon].configOptions[option].type === 'number'"
|
||||||
v-model.number="configResult[item.icon + '.' + option]"
|
v-model.number="configResult[item.icon + '.' + option]"
|
||||||
:placeholder="supportedPicBedList[item.icon].configOptions[option].placeholder"
|
:placeholder="supportedPicBedList[item.icon].configOptions[option].placeholder"
|
||||||
/>
|
/>
|
||||||
|
<el-select
|
||||||
|
v-else-if="supportedPicBedList[item.icon].configOptions[option].type === 'select'"
|
||||||
|
v-model="configResult[item.icon + '.' + option]"
|
||||||
|
placeholder="请选择"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="i in Object.entries(supportedPicBedList[item.icon].configOptions[option].selectOptions)"
|
||||||
|
:key="i[0]"
|
||||||
|
:label="i[1] as string"
|
||||||
|
:value="i[0]"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div style="margin: 0 auto;position: relative;left: 10%;right: 50%;">
|
<div style="margin: 0 auto;position: relative;left: 10%;right: 50%;">
|
||||||
@ -220,7 +248,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, onBeforeMount, computed } from 'vue'
|
import { reactive, ref, onBeforeMount, computed } from 'vue'
|
||||||
import { supportedPicBedList } from '../utils/constants'
|
import { supportedPicBedList } from '../utils/constants'
|
||||||
import { Delete, Edit, Pointer } from '@element-plus/icons-vue'
|
import { Delete, Edit, Pointer, InfoFilled } from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElNotification } from 'element-plus'
|
import { ElMessage, ElNotification } from 'element-plus'
|
||||||
import { getConfig, saveConfig, removeConfig } from '../utils/dataSender'
|
import { getConfig, saveConfig, removeConfig } from '../utils/dataSender'
|
||||||
import { shell } from 'electron'
|
import { shell } from 'electron'
|
||||||
@ -314,7 +342,7 @@ const handleConfigChange = async (name: string) => {
|
|||||||
for (const key of allKeys) {
|
for (const key of allKeys) {
|
||||||
const resultKey = name + '.' + key
|
const resultKey = name + '.' + key
|
||||||
if (supportedPicBedList[name].configOptions[key].required) {
|
if (supportedPicBedList[name].configOptions[key].required) {
|
||||||
if (key !== 'paging' && !configResult[resultKey]) {
|
if (supportedPicBedList[name].configOptions[key].type !== 'boolean' && !configResult[resultKey]) {
|
||||||
ElMessage.error(`请填写 ${supportedPicBedList[name].configOptions[key].description}`)
|
ElMessage.error(`请填写 ${supportedPicBedList[name].configOptions[key].description}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -329,7 +357,7 @@ const handleConfigChange = async (name: string) => {
|
|||||||
}
|
}
|
||||||
if ((key === 'customUrl') && configResult[resultKey] !== undefined && configResult[resultKey] !== '') {
|
if ((key === 'customUrl') && configResult[resultKey] !== undefined && configResult[resultKey] !== '') {
|
||||||
if (name !== 'upyun') {
|
if (name !== 'upyun') {
|
||||||
if (!configResult[resultKey].startsWith('http://') && !configResult[resultKey].startsWith('https://')) {
|
if (!/^https?:\/\//.test(configResult[resultKey])) {
|
||||||
ElMessage.error('自定义域名必须以http://或https://开头')
|
ElMessage.error('自定义域名必须以http://或https://开头')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -363,7 +391,7 @@ const handleConfigChange = async (name: string) => {
|
|||||||
[bucketName[i]]: {
|
[bucketName[i]]: {
|
||||||
baseDir: baseDir && baseDir[i] ? baseDir[i] : '/',
|
baseDir: baseDir && baseDir[i] ? baseDir[i] : '/',
|
||||||
area: area && area[i] ? area[i] : '',
|
area: area && area[i] ? area[i] : '',
|
||||||
customUrl: customUrl && customUrl[i] ? customUrl[i].startsWith('http') || customUrl[i].startsWith('https') ? customUrl[i] : 'http://' + customUrl[i] : '',
|
customUrl: customUrl && customUrl[i] ? /^https?:\/\//.test(customUrl[i]) ? customUrl[i] : 'http://' + customUrl[i] : '',
|
||||||
operator: operator && operator[i] ? operator[i] : '',
|
operator: operator && operator[i] ? operator[i] : '',
|
||||||
password: password && password[i] ? password[i] : ''
|
password: password && password[i] ? password[i] : ''
|
||||||
}
|
}
|
||||||
@ -489,10 +517,12 @@ function handleConfigImport (alias: string) {
|
|||||||
const selectedConfig = existingConfiguration[alias]
|
const selectedConfig = existingConfiguration[alias]
|
||||||
if (selectedConfig) {
|
if (selectedConfig) {
|
||||||
supportedPicBedList[selectedConfig.picBedName].options.forEach((option: any) => {
|
supportedPicBedList[selectedConfig.picBedName].options.forEach((option: any) => {
|
||||||
if (selectedConfig[option]) {
|
if (selectedConfig[option] !== undefined) {
|
||||||
|
configResult[selectedConfig.picBedName + '.' + option] = selectedConfig[option]
|
||||||
|
}
|
||||||
|
if (typeof selectedConfig[option] === 'boolean') {
|
||||||
configResult[selectedConfig.picBedName + '.' + option] = selectedConfig[option]
|
configResult[selectedConfig.picBedName + '.' + option] = selectedConfig[option]
|
||||||
}
|
}
|
||||||
configResult[selectedConfig.picBedName + '.paging'] = selectedConfig.paging
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,7 +315,8 @@ const urlMap : IStringKeyMap = {
|
|||||||
aliyun: 'https://oss.console.aliyun.com',
|
aliyun: 'https://oss.console.aliyun.com',
|
||||||
qiniu: 'https://portal.qiniu.com',
|
qiniu: 'https://portal.qiniu.com',
|
||||||
tcyun: 'https://console.cloud.tencent.com/cos',
|
tcyun: 'https://console.cloud.tencent.com/cos',
|
||||||
upyun: 'https://console.upyun.com'
|
upyun: 'https://console.upyun.com',
|
||||||
|
s3plist: 'https://aws.amazon.com/cn/s3/'
|
||||||
}
|
}
|
||||||
|
|
||||||
const openPicBedUrl = () => shell.openExternal(urlMap[currentPagePicBedConfig.picBedName])
|
const openPicBedUrl = () => shell.openExternal(urlMap[currentPagePicBedConfig.picBedName])
|
||||||
@ -438,6 +439,7 @@ const menuTitleMap:IStringKeyMap = {
|
|||||||
qiniu: '存储桶',
|
qiniu: '存储桶',
|
||||||
tcyun: '存储桶',
|
tcyun: '存储桶',
|
||||||
upyun: '存储桶',
|
upyun: '存储桶',
|
||||||
|
s3plist: '存储桶',
|
||||||
smms: '相册',
|
smms: '相册',
|
||||||
imgur: '相册',
|
imgur: '相册',
|
||||||
github: '仓库'
|
github: '仓库'
|
||||||
|
@ -89,13 +89,10 @@ export function formatFileName (fileName: string) {
|
|||||||
return name.length > 20 ? `${name.slice(0, 20)}...${ext}` : fileName
|
return name.length > 20 ? `${name.slice(0, 20)}...${ext}` : fileName
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExtension (fileName: string) {
|
export const getExtension = (fileName: string) => path.extname(fileName).slice(1)
|
||||||
return path.extname(fileName).slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isImage (fileName: string) {
|
export const isImage = (fileName: string) =>
|
||||||
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico'].includes(getExtension(fileName))
|
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico'].includes(getExtension(fileName))
|
||||||
}
|
|
||||||
|
|
||||||
export function formObjToTableData (obj: any) {
|
export function formObjToTableData (obj: any) {
|
||||||
const exclude = [undefined, null, '', 'transformedConfig']
|
const exclude = [undefined, null, '', 'transformedConfig']
|
||||||
@ -126,21 +123,23 @@ export interface IHTTPProxy {
|
|||||||
|
|
||||||
export const formatHttpProxy = (proxy: string | undefined, type: 'object' | 'string'): IHTTPProxy | undefined | string => {
|
export const formatHttpProxy = (proxy: string | undefined, type: 'object' | 'string'): IHTTPProxy | undefined | string => {
|
||||||
if (proxy === undefined || proxy === '') return undefined
|
if (proxy === undefined || proxy === '') return undefined
|
||||||
if (proxy.startsWith('http://') || proxy.startsWith('https://')) {
|
if (/^https?:\/\//.test(proxy)) {
|
||||||
const { protocol, hostname, port } = new URL(proxy)
|
const { protocol, hostname, port } = new URL(proxy)
|
||||||
if (type === 'string') return `${protocol}//${hostname}:${port}`
|
return type === 'string'
|
||||||
return {
|
? `${protocol}//${hostname}:${port}`
|
||||||
host: hostname,
|
: {
|
||||||
port: Number(port),
|
host: hostname,
|
||||||
protocol: protocol.slice(0, -1)
|
port: Number(port),
|
||||||
}
|
protocol: protocol.slice(0, -1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const [host, port] = proxy.split(':')
|
const [host, port] = proxy.split(':')
|
||||||
if (type === 'string') return `http://${host}:${port}`
|
return type === 'string'
|
||||||
return {
|
? `http://${host}:${port}`
|
||||||
host,
|
: {
|
||||||
port: port ? Number(port) : 80,
|
host,
|
||||||
protocol: 'http'
|
port: port ? Number(port) : 80,
|
||||||
}
|
protocol: 'http'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,13 @@ const aliasRule = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const aliasTooltip = '配置别名只能包含中文、英文、数字和下划线,且不能超过15个字符'
|
||||||
|
const itemsPerPageTooltip = '每页显示数量必须在20-1000之间'
|
||||||
|
const pagingTooltip = '关闭分页时,目录列表将使用数据库缓存以优化性能'
|
||||||
|
const bucketNameTooltip = '英文逗号分隔,如:bucket1,bucket2,bucket3,和起始目录顺序一一对应'
|
||||||
|
const baseDirTooltip = '英文逗号分隔,如:/dir1,/dir2,/dir3,和存储桶顺序一一对应'
|
||||||
|
const isAutoCustomUrlTooltip = '开启时,将自动获取存储桶绑定的域名,关闭时可手动填写域名'
|
||||||
|
|
||||||
export const supportedPicBedList: IStringKeyMap = {
|
export const supportedPicBedList: IStringKeyMap = {
|
||||||
smms: {
|
smms: {
|
||||||
name: 'SM.MS',
|
name: 'SM.MS',
|
||||||
@ -62,7 +69,8 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
placeholder: '该配置的唯一标识',
|
placeholder: '该配置的唯一标识',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: aliasRule,
|
rule: aliasRule,
|
||||||
default: 'smms-A'
|
default: 'smms-A',
|
||||||
|
tooltip: aliasTooltip
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
required: true,
|
required: true,
|
||||||
@ -75,7 +83,8 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
required: true,
|
required: true,
|
||||||
description: '是否分页',
|
description: '是否分页',
|
||||||
default: true,
|
default: true,
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
|
tooltip: pagingTooltip
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
explain: '大陆地区请访问备用域名https://smms.app, 请勿大批量上传图片,否则API接口会被限制',
|
explain: '大陆地区请访问备用域名https://smms.app, 请勿大批量上传图片,否则API接口会被限制',
|
||||||
@ -93,7 +102,8 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
placeholder: '该配置的唯一标识',
|
placeholder: '该配置的唯一标识',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: aliasRule,
|
rule: aliasRule,
|
||||||
default: 'qiniu-A'
|
default: 'qiniu-A',
|
||||||
|
tooltip: aliasTooltip
|
||||||
},
|
},
|
||||||
accessKey: {
|
accessKey: {
|
||||||
required: true,
|
required: true,
|
||||||
@ -113,31 +123,42 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
required: false,
|
required: false,
|
||||||
description: '空间名-可选',
|
description: '空间名-可选',
|
||||||
placeholder: '英文逗号分隔,例如:bucket1,bucket2',
|
placeholder: '英文逗号分隔,例如:bucket1,bucket2',
|
||||||
type: 'string'
|
type: 'string',
|
||||||
|
tooltip: bucketNameTooltip
|
||||||
},
|
},
|
||||||
baseDir: {
|
baseDir: {
|
||||||
required: false,
|
required: false,
|
||||||
description: '起始目录-可选',
|
description: '起始目录-可选',
|
||||||
placeholder: '英文逗号分隔,例如:/test1,/test2',
|
placeholder: '英文逗号分隔,例如:/test1,/test2',
|
||||||
default: '/',
|
default: '/',
|
||||||
type: 'string'
|
type: 'string',
|
||||||
|
tooltip: baseDirTooltip
|
||||||
|
},
|
||||||
|
isAutoCustomUrl: {
|
||||||
|
required: true,
|
||||||
|
description: '是否自动获取绑定域名',
|
||||||
|
default: true,
|
||||||
|
type: 'boolean',
|
||||||
|
tooltip: isAutoCustomUrlTooltip
|
||||||
},
|
},
|
||||||
paging: {
|
paging: {
|
||||||
required: true,
|
required: true,
|
||||||
description: '是否分页',
|
description: '是否分页',
|
||||||
default: true,
|
default: true,
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
|
tooltip: pagingTooltip
|
||||||
},
|
},
|
||||||
itemsPerPage: {
|
itemsPerPage: {
|
||||||
required: true,
|
required: true,
|
||||||
description: '每页显示数量',
|
description: '每页显示数量',
|
||||||
default: 50,
|
default: 50,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
rule: itemsPerPageRule
|
rule: itemsPerPageRule,
|
||||||
|
tooltip: itemsPerPageTooltip
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
explain: '空间名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
|
explain: '空间名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
|
||||||
options: ['alias', 'accessKey', 'secretKey', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
|
options: ['alias', 'accessKey', 'secretKey', 'bucketName', 'baseDir', 'isAutoCustomUrl', 'paging', 'itemsPerPage'],
|
||||||
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-3',
|
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-3',
|
||||||
referenceText: '配置教程请参考:'
|
referenceText: '配置教程请参考:'
|
||||||
},
|
},
|
||||||
@ -151,14 +172,16 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
placeholder: '该配置的唯一标识',
|
placeholder: '该配置的唯一标识',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: aliasRule,
|
rule: aliasRule,
|
||||||
default: 'github-A'
|
default: 'github-A',
|
||||||
|
tooltip: aliasTooltip
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
required: true,
|
required: true,
|
||||||
description: 'token-必需',
|
description: 'token-必需',
|
||||||
placeholder: '请输入token',
|
placeholder: '请输入token',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: defaultBaseRule('token')
|
rule: defaultBaseRule('token'),
|
||||||
|
tooltip: '请提供具有完整repo权限的token,否则部分功能可能无法使用'
|
||||||
},
|
},
|
||||||
githubUsername: {
|
githubUsername: {
|
||||||
required: true,
|
required: true,
|
||||||
@ -171,19 +194,22 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
required: false,
|
required: false,
|
||||||
description: '代理-可选',
|
description: '代理-可选',
|
||||||
placeholder: '例如:http://127.0.0.1:1080',
|
placeholder: '例如:http://127.0.0.1:1080',
|
||||||
type: 'string'
|
type: 'string',
|
||||||
|
tooltip: '如果您的网络环境需要使用代理才能访问GitHub,请在此处填写代理地址'
|
||||||
},
|
},
|
||||||
paging: {
|
paging: {
|
||||||
required: true,
|
required: true,
|
||||||
description: '是否分页',
|
description: '是否分页',
|
||||||
default: false,
|
default: false,
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
|
tooltip: pagingTooltip
|
||||||
},
|
},
|
||||||
customUrl: {
|
customUrl: {
|
||||||
required: false,
|
required: false,
|
||||||
description: 'CDN加速域名-可选;例如: https://cdn.staticaly.com/gh/{username}/{repo}@{branch}/{path}',
|
description: 'CDN加速域名-可选',
|
||||||
placeholder: '支持使用{username}、{repo}、{branch}和{path}作为替换占位符,用于适配不同仓库和分支',
|
placeholder: '支持使用{username}、{repo}、{branch}和{path}作为替换占位符,用于适配不同仓库和分支',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
tooltip: '例如: https://cdn.staticaly.com/gh/{username}/{repo}@{branch}/{path}',
|
||||||
rule: [
|
rule: [
|
||||||
{
|
{
|
||||||
validator: (_rule: any, value: any, callback: any) => {
|
validator: (_rule: any, value: any, callback: any) => {
|
||||||
@ -251,7 +277,8 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
placeholder: '该配置的唯一标识',
|
placeholder: '该配置的唯一标识',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: aliasRule,
|
rule: aliasRule,
|
||||||
default: 'aliyun-A'
|
default: 'aliyun-A',
|
||||||
|
tooltip: aliasTooltip
|
||||||
},
|
},
|
||||||
accessKeyId: {
|
accessKeyId: {
|
||||||
required: true,
|
required: true,
|
||||||
@ -271,31 +298,42 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
required: false,
|
required: false,
|
||||||
description: '存储桶名-可选',
|
description: '存储桶名-可选',
|
||||||
placeholder: '英文逗号分隔,例如:bucket1,bucket2',
|
placeholder: '英文逗号分隔,例如:bucket1,bucket2',
|
||||||
type: 'string'
|
type: 'string',
|
||||||
|
tooltip: bucketNameTooltip
|
||||||
},
|
},
|
||||||
baseDir: {
|
baseDir: {
|
||||||
required: false,
|
required: false,
|
||||||
description: '起始目录-可选',
|
description: '起始目录-可选',
|
||||||
placeholder: '英文逗号分隔,例如:/test1,/test2',
|
placeholder: '英文逗号分隔,例如:/test1,/test2',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: '/'
|
default: '/',
|
||||||
|
tooltip: baseDirTooltip
|
||||||
|
},
|
||||||
|
isAutoCustomUrl: {
|
||||||
|
required: true,
|
||||||
|
description: '是否自动获取绑定域名',
|
||||||
|
default: true,
|
||||||
|
type: 'boolean',
|
||||||
|
tooltip: isAutoCustomUrlTooltip
|
||||||
},
|
},
|
||||||
paging: {
|
paging: {
|
||||||
required: true,
|
required: true,
|
||||||
description: '是否分页',
|
description: '是否分页',
|
||||||
default: true,
|
default: true,
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
|
tooltip: pagingTooltip
|
||||||
},
|
},
|
||||||
itemsPerPage: {
|
itemsPerPage: {
|
||||||
required: true,
|
required: true,
|
||||||
description: '每页显示数量',
|
description: '每页显示数量',
|
||||||
default: 50,
|
default: 50,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
rule: itemsPerPageRule
|
rule: itemsPerPageRule,
|
||||||
|
tooltip: itemsPerPageTooltip
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
|
explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
|
||||||
options: ['alias', 'accessKeyId', 'accessKeySecret', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
|
options: ['alias', 'accessKeyId', 'accessKeySecret', 'bucketName', 'baseDir', 'isAutoCustomUrl', 'paging', 'itemsPerPage'],
|
||||||
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-1',
|
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-1',
|
||||||
referenceText: '配置教程请参考:'
|
referenceText: '配置教程请参考:'
|
||||||
},
|
},
|
||||||
@ -309,7 +347,8 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
placeholder: '该配置的唯一标识',
|
placeholder: '该配置的唯一标识',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: aliasRule,
|
rule: aliasRule,
|
||||||
default: 'tcyun-A'
|
default: 'tcyun-A',
|
||||||
|
tooltip: aliasTooltip
|
||||||
},
|
},
|
||||||
secretId: {
|
secretId: {
|
||||||
required: true,
|
required: true,
|
||||||
@ -330,37 +369,49 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
description: 'appId-必需',
|
description: 'appId-必需',
|
||||||
placeholder: '请输入appId',
|
placeholder: '请输入appId',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: defaultBaseRule('appId')
|
rule: defaultBaseRule('appId'),
|
||||||
|
tooltip: '例如:1250000000'
|
||||||
},
|
},
|
||||||
bucketName: {
|
bucketName: {
|
||||||
required: false,
|
required: false,
|
||||||
description: '存储桶名-可选(注意包含AppId)',
|
description: '存储桶名-可选(注意包含AppId)',
|
||||||
placeholder: '英文逗号分隔,例如:bucket1-1250000000,bucket2-1250000000',
|
placeholder: '英文逗号分隔,例如:bucket1-1250000000,bucket2-1250000000',
|
||||||
type: 'string'
|
type: 'string',
|
||||||
|
tooltip: bucketNameTooltip
|
||||||
},
|
},
|
||||||
baseDir: {
|
baseDir: {
|
||||||
required: false,
|
required: false,
|
||||||
description: '起始目录-可选',
|
description: '起始目录-可选',
|
||||||
placeholder: '英文逗号分隔,例如:/test1,/test2',
|
placeholder: '英文逗号分隔,例如:/test1,/test2',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: '/'
|
default: '/',
|
||||||
|
tooltip: baseDirTooltip
|
||||||
|
},
|
||||||
|
isAutoCustomUrl: {
|
||||||
|
required: true,
|
||||||
|
description: '是否自动获取绑定域名',
|
||||||
|
default: true,
|
||||||
|
type: 'boolean',
|
||||||
|
tooltip: isAutoCustomUrlTooltip
|
||||||
},
|
},
|
||||||
paging: {
|
paging: {
|
||||||
required: true,
|
required: true,
|
||||||
description: '是否分页',
|
description: '是否分页',
|
||||||
default: true,
|
default: true,
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
|
tooltip: pagingTooltip
|
||||||
},
|
},
|
||||||
itemsPerPage: {
|
itemsPerPage: {
|
||||||
required: true,
|
required: true,
|
||||||
description: '每页显示数量',
|
description: '每页显示数量',
|
||||||
default: 50,
|
default: 50,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
rule: itemsPerPageRule
|
rule: itemsPerPageRule,
|
||||||
|
tooltip: itemsPerPageTooltip
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
|
explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
|
||||||
options: ['alias', 'secretId', 'secretKey', 'appId', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
|
options: ['alias', 'secretId', 'secretKey', 'appId', 'bucketName', 'baseDir', 'isAutoCustomUrl', 'paging', 'itemsPerPage'],
|
||||||
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-2',
|
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-2',
|
||||||
referenceText: '配置教程请参考:'
|
referenceText: '配置教程请参考:'
|
||||||
},
|
},
|
||||||
@ -374,7 +425,8 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
placeholder: '该配置的唯一标识',
|
placeholder: '该配置的唯一标识',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: aliasRule,
|
rule: aliasRule,
|
||||||
default: 'upyun-A'
|
default: 'upyun-A',
|
||||||
|
tooltip: aliasTooltip
|
||||||
},
|
},
|
||||||
bucketName: {
|
bucketName: {
|
||||||
required: true,
|
required: true,
|
||||||
@ -445,14 +497,16 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
required: true,
|
required: true,
|
||||||
description: '是否分页',
|
description: '是否分页',
|
||||||
default: true,
|
default: true,
|
||||||
type: 'boolean'
|
type: 'boolean',
|
||||||
|
tooltip: pagingTooltip
|
||||||
},
|
},
|
||||||
itemsPerPage: {
|
itemsPerPage: {
|
||||||
required: true,
|
required: true,
|
||||||
description: '每页显示数量',
|
description: '每页显示数量',
|
||||||
default: 50,
|
default: 50,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
rule: itemsPerPageRule
|
rule: itemsPerPageRule,
|
||||||
|
tooltip: itemsPerPageTooltip
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
explain: '又拍云图床务必填写加速域名,否则无法正常使用',
|
explain: '又拍云图床务必填写加速域名,否则无法正常使用',
|
||||||
@ -481,21 +535,131 @@ export const supportedPicBedList: IStringKeyMap = {
|
|||||||
},
|
},
|
||||||
accessToken: {
|
accessToken: {
|
||||||
required: true,
|
required: true,
|
||||||
description: 'accessToken-必需(不是clientID,请参考配置教程)',
|
description: 'accessToken-必需',
|
||||||
placeholder: '请输入accessToken',
|
placeholder: '请输入accessToken',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rule: defaultBaseRule('accessToken')
|
rule: defaultBaseRule('accessToken'),
|
||||||
|
tooltip: '不是clientID,请参考配置教程'
|
||||||
},
|
},
|
||||||
proxy: {
|
proxy: {
|
||||||
required: false,
|
required: false,
|
||||||
description: '代理-可选',
|
description: '代理-可选',
|
||||||
placeholder: '例如:http://127.0.0.1:1080',
|
placeholder: '例如:http://127.0.0.1:1080',
|
||||||
type: 'string'
|
type: 'string',
|
||||||
|
tooltip: '大陆地区请使用代理,否则无法正常使用'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
explain: '大陆地区请使用代理,API调用存在限制,请注意使用频率',
|
explain: '大陆地区请使用代理,API调用存在限制,请注意使用频率',
|
||||||
options: ['alias', 'imgurUserName', 'accessToken', 'proxy'],
|
options: ['alias', 'imgurUserName', 'accessToken', 'proxy'],
|
||||||
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=imgur%e5%9b%be%e5%ba%8a-1',
|
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=imgur%e5%9b%be%e5%ba%8a-1',
|
||||||
referenceText: '配置教程请参考:'
|
referenceText: '配置教程请参考:'
|
||||||
|
},
|
||||||
|
s3plist: {
|
||||||
|
name: 'S3兼容云',
|
||||||
|
icon: 's3plist',
|
||||||
|
configOptions: {
|
||||||
|
alias: {
|
||||||
|
required: true,
|
||||||
|
description: '配置别名-必需',
|
||||||
|
placeholder: '该配置的唯一标识',
|
||||||
|
type: 'string',
|
||||||
|
rule: aliasRule,
|
||||||
|
default: 's3plist-A',
|
||||||
|
tooltip: aliasTooltip
|
||||||
|
},
|
||||||
|
accessKeyId: {
|
||||||
|
required: true,
|
||||||
|
description: 'accessKeyId-必需',
|
||||||
|
placeholder: '请输入accessKeyId',
|
||||||
|
type: 'string',
|
||||||
|
rule: defaultBaseRule('accessKeyId')
|
||||||
|
},
|
||||||
|
secretAccessKey: {
|
||||||
|
required: true,
|
||||||
|
description: 'secretAccessKey-必需',
|
||||||
|
placeholder: '请输入secretAccessKey',
|
||||||
|
type: 'string',
|
||||||
|
rule: defaultBaseRule('secretAccessKey')
|
||||||
|
},
|
||||||
|
endpoint: {
|
||||||
|
required: false,
|
||||||
|
description: 'endpoint-可选',
|
||||||
|
placeholder: '例如:s3.us-east-1.amazonaws.com',
|
||||||
|
type: 'string',
|
||||||
|
tooltip: '如果不填写,默认访问 AWS S3,请提供根API endpoint'
|
||||||
|
},
|
||||||
|
sslEnabled: {
|
||||||
|
required: true,
|
||||||
|
description: '使用HTTPS连接',
|
||||||
|
default: true,
|
||||||
|
type: 'boolean',
|
||||||
|
tooltip: '大部分平台都支持HTTPS连接,如果您的平台不支持,请关闭该选项'
|
||||||
|
},
|
||||||
|
s3ForcePathStyle: {
|
||||||
|
required: true,
|
||||||
|
description: '启用 S3 Path style',
|
||||||
|
default: false,
|
||||||
|
type: 'boolean',
|
||||||
|
tooltip: '例如使用 minio 时需要启用'
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
required: false,
|
||||||
|
description: '代理-可选',
|
||||||
|
placeholder: '例如:http://127.0.0.1:1080',
|
||||||
|
type: 'string',
|
||||||
|
tooltip: '如果部分平台大陆地区无法访问,请使用代理'
|
||||||
|
},
|
||||||
|
aclForUpload: {
|
||||||
|
required: true,
|
||||||
|
description: '上传文件的权限',
|
||||||
|
rule: defaultBaseRule('aclForUpload'),
|
||||||
|
default: 'public-read',
|
||||||
|
type: 'select',
|
||||||
|
selectOptions: {
|
||||||
|
private: '私有',
|
||||||
|
'public-read': '公共读',
|
||||||
|
'public-read-write': '公共读写',
|
||||||
|
'authenticated-read': '授权读',
|
||||||
|
'bucket-owner-read': '桶所有者读',
|
||||||
|
'bucket-owner-full-control': '桶所有者完全控制',
|
||||||
|
'aws-exec-read': 'aws执行读'
|
||||||
|
},
|
||||||
|
tooltip: '上传文件的权限,可选值:private、public-read、public-read-write、authenticated-read、bucket-owner-read、bucket-owner-full-control、aws-exec-read'
|
||||||
|
},
|
||||||
|
bucketName: {
|
||||||
|
required: false,
|
||||||
|
description: '存储桶名-可选',
|
||||||
|
placeholder: '英文逗号分隔,例如:bucket1,bucket2',
|
||||||
|
type: 'string',
|
||||||
|
tooltip: bucketNameTooltip
|
||||||
|
},
|
||||||
|
baseDir: {
|
||||||
|
required: false,
|
||||||
|
description: '起始目录-可选',
|
||||||
|
placeholder: '英文逗号分隔,例如:/test1,/test2',
|
||||||
|
type: 'string',
|
||||||
|
default: '/',
|
||||||
|
tooltip: baseDirTooltip
|
||||||
|
},
|
||||||
|
paging: {
|
||||||
|
required: true,
|
||||||
|
description: '是否分页',
|
||||||
|
default: true,
|
||||||
|
type: 'boolean',
|
||||||
|
tooltip: pagingTooltip
|
||||||
|
},
|
||||||
|
itemsPerPage: {
|
||||||
|
required: true,
|
||||||
|
description: '每页显示数量',
|
||||||
|
default: 50,
|
||||||
|
type: 'number',
|
||||||
|
rule: itemsPerPageRule,
|
||||||
|
tooltip: itemsPerPageTooltip
|
||||||
|
}
|
||||||
|
},
|
||||||
|
explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
|
||||||
|
options: ['alias', 'accessKeyId', 'secretAccessKey', 'endpoint', 'sslEnabled', 's3ForcePathStyle', 'proxy', 'aclForUpload', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
|
||||||
|
refLink: 'https://github.com/wayjam/picgo-plugin-s3',
|
||||||
|
referenceText: '配置教程请参考:'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,7 +446,6 @@ function remove (item: ImgInfo) {
|
|||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(file)
|
|
||||||
sendToMain('removeFiles', [file])
|
sendToMain('removeFiles', [file])
|
||||||
const obj = {
|
const obj = {
|
||||||
title: $T('OPERATION_SUCCEED'),
|
title: $T('OPERATION_SUCCEED'),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const isUrl = (url: string): boolean => (url.startsWith('http://') || url.startsWith('https://'))
|
export const isUrl = (url: string): boolean => (/^https?:\/\//.test(url))
|
||||||
export const isUrlEncode = (url: string): boolean => {
|
export const isUrlEncode = (url: string): boolean => {
|
||||||
url = url || ''
|
url = url || ''
|
||||||
try {
|
try {
|
||||||
|
Loading…
Reference in New Issue
Block a user