feat: configure separate remote object storage
This commit is contained in:
parent
eb7e96e8c9
commit
69294dc0a7
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class RemoteObjectStorage1729518620697 {
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "useRemoteObjectStorage" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageBucket" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStoragePrefix" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageBaseUrl" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageEndpoint" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageRegion" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageAccessKey" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageSecretKey" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStoragePort" integer`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageUseSSL" boolean DEFAULT true`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageUseProxy" boolean DEFAULT true`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageSetPublicRead" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "remoteObjectStorageS3ForcePathStyle" boolean NOT NULL DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageS3ForcePathStyle";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageSetPublicRead";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageUseProxy";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageUseSSL";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStoragePort";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageSecretKey";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageAccessKey";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageRegion";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageEndpoint";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageBaseUrl";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStoragePrefix";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "remoteObjectStorageBucket";`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useRemoteObjectStorage";`);
|
||||||
|
}
|
||||||
|
}
|
@ -144,9 +144,10 @@ export class DriveService {
|
|||||||
* @param type Content-Type for original
|
* @param type Content-Type for original
|
||||||
* @param hash Hash for original
|
* @param hash Hash for original
|
||||||
* @param size Size for original
|
* @param size Size for original
|
||||||
|
* @param isRemote If true, file is remote file
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number): Promise<MiDriveFile> {
|
private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number, isRemote = false): Promise<MiDriveFile> {
|
||||||
// thunbnail, webpublic を必要なら生成
|
// thunbnail, webpublic を必要なら生成
|
||||||
const alts = await this.generateAlts(path, type, !file.uri);
|
const alts = await this.generateAlts(path, type, !file.uri);
|
||||||
|
|
||||||
@ -169,11 +170,37 @@ export class DriveService {
|
|||||||
ext = '';
|
ext = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = this.meta.objectStorageBaseUrl
|
const useRemoteObjectStorage = isRemote && this.meta.useRemoteObjectStorage;
|
||||||
?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
|
|
||||||
|
const objectStorageBaseUrl = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStorageBaseUrl
|
||||||
|
: this.meta.objectStorageBaseUrl;
|
||||||
|
|
||||||
|
const objectStorageUseSSL = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStorageUseSSL
|
||||||
|
: this.meta.objectStorageUseSSL;
|
||||||
|
|
||||||
|
const objectStorageEndpoint = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStorageEndpoint
|
||||||
|
: this.meta.objectStorageEndpoint;
|
||||||
|
|
||||||
|
const objectStoragePort = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStoragePort
|
||||||
|
: this.meta.objectStoragePort;
|
||||||
|
|
||||||
|
const objectStorageBucket = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStorageBucket
|
||||||
|
: this.meta.objectStorageBucket;
|
||||||
|
|
||||||
|
const objectStoragePrefix = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStoragePrefix
|
||||||
|
: this.meta.objectStoragePrefix;
|
||||||
|
|
||||||
|
const baseUrl = objectStorageBaseUrl
|
||||||
|
?? `${objectStorageUseSSL ? 'https' : 'http'}://${objectStorageEndpoint}${objectStoragePort ? `:${objectStoragePort}` : ''}/${objectStorageBucket}`;
|
||||||
|
|
||||||
// for original
|
// for original
|
||||||
const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`;
|
const key = `${objectStoragePrefix}/${randomUUID()}${ext}`;
|
||||||
const url = `${ baseUrl }/${ key }`;
|
const url = `${ baseUrl }/${ key }`;
|
||||||
|
|
||||||
// for alts
|
// for alts
|
||||||
@ -186,7 +213,7 @@ export class DriveService {
|
|||||||
//#region Uploads
|
//#region Uploads
|
||||||
this.registerLogger.info(`uploading original: ${key}`);
|
this.registerLogger.info(`uploading original: ${key}`);
|
||||||
const uploads = [
|
const uploads = [
|
||||||
this.upload(key, fs.createReadStream(path), type, null, name),
|
this.upload(key, fs.createReadStream(path), type, isRemote, null, name),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (alts.webpublic) {
|
if (alts.webpublic) {
|
||||||
@ -194,7 +221,7 @@ export class DriveService {
|
|||||||
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
||||||
|
|
||||||
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
|
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
|
||||||
uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, alts.webpublic.ext, name));
|
uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, isRemote, alts.webpublic.ext, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alts.thumbnail) {
|
if (alts.thumbnail) {
|
||||||
@ -202,7 +229,7 @@ export class DriveService {
|
|||||||
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
|
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
|
||||||
|
|
||||||
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
|
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
|
||||||
uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext, `${name}.thumbnail`));
|
uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, isRemote, alts.thumbnail.ext, `${name}.thumbnail`));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(uploads);
|
await Promise.all(uploads);
|
||||||
@ -371,12 +398,22 @@ export class DriveService {
|
|||||||
* Upload to ObjectStorage
|
* Upload to ObjectStorage
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, ext?: string | null, filename?: string) {
|
private async upload(key: string, stream: fs.ReadStream | Buffer, type: string, isRemote = false, ext?: string | null, filename?: string) {
|
||||||
if (type === 'image/apng') type = 'image/png';
|
if (type === 'image/apng') type = 'image/png';
|
||||||
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
|
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
|
||||||
|
|
||||||
|
const useRemoteObjectStorage = isRemote && this.meta.useRemoteObjectStorage;
|
||||||
|
|
||||||
|
const objectStorageBucket = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStorageBucket
|
||||||
|
: this.meta.objectStorageBucket;
|
||||||
|
|
||||||
|
const objectStorageSetPublicRead = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStorageSetPublicRead
|
||||||
|
: this.meta.objectStorageSetPublicRead;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
Bucket: this.meta.objectStorageBucket,
|
Bucket: objectStorageBucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
Body: stream,
|
Body: stream,
|
||||||
ContentType: type,
|
ContentType: type,
|
||||||
@ -389,9 +426,9 @@ export class DriveService {
|
|||||||
// 許可されているファイル形式でしか拡張子をつけない
|
// 許可されているファイル形式でしか拡張子をつけない
|
||||||
ext ? correctFilename(filename, ext) : filename,
|
ext ? correctFilename(filename, ext) : filename,
|
||||||
);
|
);
|
||||||
if (this.meta.objectStorageSetPublicRead) params.ACL = 'public-read';
|
if (objectStorageSetPublicRead) params.ACL = 'public-read';
|
||||||
|
|
||||||
await this.s3Service.upload(this.meta, params)
|
await this.s3Service.upload(this.meta, params, isRemote)
|
||||||
.then(
|
.then(
|
||||||
result => {
|
result => {
|
||||||
if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput
|
if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput
|
||||||
@ -630,7 +667,8 @@ export class DriveService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
file = await (this.save(file, path, detectedName, info.type.mime, info.md5, info.size));
|
const isRemote = user ? this.userEntityService.isRemoteUser(user) : false;
|
||||||
|
file = await (this.save(file, path, detectedName, info.type.mime, info.md5, info.size, isRemote));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registerLogger.succ(`drive file has been created ${file.id}`);
|
this.registerLogger.succ(`drive file has been created ${file.id}`);
|
||||||
@ -740,7 +778,7 @@ export class DriveService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async deleteFileSync(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
|
public async deleteFileSync(file: MiDriveFile, isExpired = false, isRemote = false, deleter?: MiUser) {
|
||||||
if (file.storedInternal) {
|
if (file.storedInternal) {
|
||||||
this.internalStorageService.del(file.accessKey!);
|
this.internalStorageService.del(file.accessKey!);
|
||||||
|
|
||||||
@ -754,14 +792,14 @@ export class DriveService {
|
|||||||
} else if (!file.isLink) {
|
} else if (!file.isLink) {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
promises.push(this.deleteObjectStorageFile(file.accessKey!));
|
promises.push(this.deleteObjectStorageFile(file.accessKey!, isRemote));
|
||||||
|
|
||||||
if (file.thumbnailUrl) {
|
if (file.thumbnailUrl) {
|
||||||
promises.push(this.deleteObjectStorageFile(file.thumbnailAccessKey!));
|
promises.push(this.deleteObjectStorageFile(file.thumbnailAccessKey!, isRemote));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.webpublicUrl) {
|
if (file.webpublicUrl) {
|
||||||
promises.push(this.deleteObjectStorageFile(file.webpublicAccessKey!));
|
promises.push(this.deleteObjectStorageFile(file.webpublicAccessKey!, isRemote));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
@ -815,14 +853,19 @@ export class DriveService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async deleteObjectStorageFile(key: string) {
|
public async deleteObjectStorageFile(key: string, isRemote = false) {
|
||||||
|
const useRemoteObjectStorage = isRemote && this.meta.useRemoteObjectStorage;
|
||||||
|
const objectStorageBucket = useRemoteObjectStorage
|
||||||
|
? this.meta.remoteObjectStorageBucket
|
||||||
|
: this.meta.objectStorageBucket;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const param = {
|
const param = {
|
||||||
Bucket: this.meta.objectStorageBucket,
|
Bucket: objectStorageBucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
} as DeleteObjectCommandInput;
|
} as DeleteObjectCommandInput;
|
||||||
|
|
||||||
await this.s3Service.delete(this.meta, param);
|
await this.s3Service.delete(this.meta, param, isRemote);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.name === 'NoSuchKey') {
|
if (err.name === 'NoSuchKey') {
|
||||||
this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
|
this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
|
||||||
|
@ -19,39 +19,64 @@ import type { DeleteObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/c
|
|||||||
export class S3Service {
|
export class S3Service {
|
||||||
constructor(
|
constructor(
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getS3Client(meta: MiMeta): S3Client {
|
public getS3Client(meta: MiMeta, isRemote = false): S3Client {
|
||||||
const u = meta.objectStorageEndpoint
|
const useRemoteObjectStorage = isRemote && meta.useRemoteObjectStorage;
|
||||||
? `${meta.objectStorageUseSSL ? 'https' : 'http'}://${meta.objectStorageEndpoint}`
|
|
||||||
: `${meta.objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent
|
|
||||||
|
|
||||||
const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy);
|
const objectStorageEndpoint = useRemoteObjectStorage
|
||||||
|
? meta.remoteObjectStorageEndpoint
|
||||||
|
: meta.objectStorageEndpoint;
|
||||||
|
|
||||||
|
const objectStorageUseSSL = useRemoteObjectStorage
|
||||||
|
? meta.remoteObjectStorageUseSSL
|
||||||
|
: meta.objectStorageUseSSL;
|
||||||
|
|
||||||
|
const objectStorageAccessKey = useRemoteObjectStorage
|
||||||
|
? meta.remoteObjectStorageAccessKey
|
||||||
|
: meta.objectStorageAccessKey;
|
||||||
|
|
||||||
|
const objectStorageSecretKey = useRemoteObjectStorage
|
||||||
|
? meta.remoteObjectStorageSecretKey
|
||||||
|
: meta.objectStorageSecretKey;
|
||||||
|
|
||||||
|
const objectStorageRegion = useRemoteObjectStorage
|
||||||
|
? meta.remoteObjectStorageRegion
|
||||||
|
: meta.objectStorageRegion;
|
||||||
|
|
||||||
|
const objectStorageS3ForcePathStyle = useRemoteObjectStorage
|
||||||
|
? meta.remoteObjectStorageS3ForcePathStyle
|
||||||
|
: meta.objectStorageS3ForcePathStyle;
|
||||||
|
|
||||||
|
const u = objectStorageEndpoint
|
||||||
|
? `${objectStorageUseSSL ? 'https' : 'http'}://${objectStorageEndpoint}`
|
||||||
|
: `${objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent
|
||||||
|
|
||||||
|
const agent = this.httpRequestService.getAgentByUrl(new URL(u), !objectStorageUseSSL);
|
||||||
const handlerOption: NodeHttpHandlerOptions = {};
|
const handlerOption: NodeHttpHandlerOptions = {};
|
||||||
if (meta.objectStorageUseSSL) {
|
if (objectStorageUseSSL) {
|
||||||
handlerOption.httpsAgent = agent as https.Agent;
|
handlerOption.httpsAgent = agent as https.Agent;
|
||||||
} else {
|
} else {
|
||||||
handlerOption.httpAgent = agent as http.Agent;
|
handlerOption.httpAgent = agent as http.Agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new S3Client({
|
return new S3Client({
|
||||||
endpoint: meta.objectStorageEndpoint ? u : undefined,
|
endpoint: objectStorageEndpoint ? u : undefined,
|
||||||
credentials: (meta.objectStorageAccessKey !== null && meta.objectStorageSecretKey !== null) ? {
|
credentials: (objectStorageAccessKey !== null && objectStorageSecretKey !== null) ? {
|
||||||
accessKeyId: meta.objectStorageAccessKey,
|
accessKeyId: objectStorageAccessKey,
|
||||||
secretAccessKey: meta.objectStorageSecretKey,
|
secretAccessKey: objectStorageSecretKey,
|
||||||
} : undefined,
|
} : undefined,
|
||||||
region: meta.objectStorageRegion ? meta.objectStorageRegion : undefined, // 空文字列もundefinedにするため ?? は使わない
|
region: objectStorageRegion || undefined, // 空文字列もundefinedにするため ?? は使わない
|
||||||
tls: meta.objectStorageUseSSL,
|
tls: objectStorageUseSSL,
|
||||||
forcePathStyle: meta.objectStorageEndpoint ? meta.objectStorageS3ForcePathStyle : false, // AWS with endPoint omitted
|
forcePathStyle: objectStorageEndpoint ? objectStorageS3ForcePathStyle : false, // AWS with endPoint omitted
|
||||||
requestHandler: new NodeHttpHandler(handlerOption),
|
requestHandler: new NodeHttpHandler(handlerOption),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async upload(meta: MiMeta, input: PutObjectCommandInput) {
|
public async upload(meta: MiMeta, input: PutObjectCommandInput, isRemote = false) {
|
||||||
const client = this.getS3Client(meta);
|
const client = this.getS3Client(meta, isRemote);
|
||||||
return new Upload({
|
return new Upload({
|
||||||
client,
|
client,
|
||||||
params: input,
|
params: input,
|
||||||
@ -62,8 +87,8 @@ export class S3Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public delete(meta: MiMeta, input: DeleteObjectCommandInput) {
|
public delete(meta: MiMeta, input: DeleteObjectCommandInput, isRemote = false) {
|
||||||
const client = this.getS3Client(meta);
|
const client = this.getS3Client(meta, isRemote);
|
||||||
return client.send(new DeleteObjectCommand(input));
|
return client.send(new DeleteObjectCommand(input));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,6 +481,78 @@ export class MiMeta {
|
|||||||
})
|
})
|
||||||
public objectStorageS3ForcePathStyle: boolean;
|
public objectStorageS3ForcePathStyle: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public useRemoteObjectStorage: boolean;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageBucket: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStoragePrefix: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageBaseUrl: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageEndpoint: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageRegion: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageAccessKey: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageSecretKey: string | null;
|
||||||
|
|
||||||
|
@Column('integer', {
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStoragePort: number | null;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageUseSSL: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageUseProxy: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageSetPublicRead: boolean;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
public remoteObjectStorageS3ForcePathStyle: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
@ -54,7 +54,7 @@ export class CleanRemoteFilesProcessorService {
|
|||||||
|
|
||||||
cursor = files.at(-1)?.id ?? null;
|
cursor = files.at(-1)?.id ?? null;
|
||||||
|
|
||||||
await Promise.all(files.map(file => this.driveService.deleteFileSync(file, true)));
|
await Promise.all(files.map(file => this.driveService.deleteFileSync(file, true, true)));
|
||||||
|
|
||||||
deletedCount += 8;
|
deletedCount += 8;
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import { SearchService } from '@/core/SearchService.js';
|
|||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbUserDeleteJobData } from '../types.js';
|
import type { DbUserDeleteJobData } from '../types.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteAccountProcessorService {
|
export class DeleteAccountProcessorService {
|
||||||
@ -35,6 +36,7 @@ export class DeleteAccountProcessorService {
|
|||||||
@Inject(DI.driveFilesRepository)
|
@Inject(DI.driveFilesRepository)
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
@ -48,6 +50,7 @@ export class DeleteAccountProcessorService {
|
|||||||
this.logger.info(`Deleting account of ${job.data.user.id} ...`);
|
this.logger.info(`Deleting account of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
|
const isRemote = user ? this.userEntityService.isRemoteUser(user) : false;
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -105,7 +108,7 @@ export class DeleteAccountProcessorService {
|
|||||||
cursor = files.at(-1)?.id ?? null;
|
cursor = files.at(-1)?.id ?? null;
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
await this.driveService.deleteFileSync(file);
|
await this.driveService.deleteFileSync(file, undefined, isRemote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import { bindThis } from '@/decorators.js';
|
|||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbJobDataWithUser } from '../types.js';
|
import type { DbJobDataWithUser } from '../types.js';
|
||||||
|
import { UserEntityService } from "@/core/entities/UserEntityService.js";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteDriveFilesProcessorService {
|
export class DeleteDriveFilesProcessorService {
|
||||||
@ -25,6 +26,7 @@ export class DeleteDriveFilesProcessorService {
|
|||||||
@Inject(DI.driveFilesRepository)
|
@Inject(DI.driveFilesRepository)
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
@ -36,6 +38,7 @@ export class DeleteDriveFilesProcessorService {
|
|||||||
this.logger.info(`Deleting drive files of ${job.data.user.id} ...`);
|
this.logger.info(`Deleting drive files of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
|
const isRemote = user ? this.userEntityService.isRemoteUser(user) : false;
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -63,7 +66,7 @@ export class DeleteDriveFilesProcessorService {
|
|||||||
cursor = files.at(-1)?.id ?? null;
|
cursor = files.at(-1)?.id ?? null;
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
await this.driveService.deleteFileSync(file);
|
await this.driveService.deleteFileSync(file, undefined, isRemote);
|
||||||
deletedCount++;
|
deletedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,19 +3,26 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
import type { ObjectStorageFileJobData } from '../types.js';
|
import type { ObjectStorageFileJobData } from '../types.js';
|
||||||
|
import type { DriveFilesRepository } from "@/models/_.js";
|
||||||
|
import { UserEntityService } from "@/core/entities/UserEntityService.js";
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteFileProcessorService {
|
export class DeleteFileProcessorService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.driveFilesRepository)
|
||||||
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
@ -26,7 +33,14 @@ export class DeleteFileProcessorService {
|
|||||||
public async process(job: Bull.Job<ObjectStorageFileJobData>): Promise<string> {
|
public async process(job: Bull.Job<ObjectStorageFileJobData>): Promise<string> {
|
||||||
const key: string = job.data.key;
|
const key: string = job.data.key;
|
||||||
|
|
||||||
await this.driveService.deleteObjectStorageFile(key);
|
const file = await this.driveFilesRepository.createQueryBuilder('file')
|
||||||
|
.where('file.accessKey = :accessKey', { accessKey: key })
|
||||||
|
.orWhere('file.thumbnailAccessKey = :thumbnailAccessKey', { thumbnailAccessKey: key })
|
||||||
|
.orWhere('file.webpublicAccessKey = :webpublicAccessKey', { webpublicAccessKey: key })
|
||||||
|
.getOne();
|
||||||
|
const isRemote = file?.user ? this.userEntityService.isRemoteUser(file.user) : false;
|
||||||
|
|
||||||
|
await this.driveService.deleteObjectStorageFile(key, isRemote);
|
||||||
|
|
||||||
return 'Success';
|
return 'Success';
|
||||||
}
|
}
|
||||||
|
@ -312,6 +312,66 @@ export const meta = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
useRemoteObjectStorage: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
remoteObjectStorageBaseUrl: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false,
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
remoteObjectStorageBucket: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false,
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
remoteObjectStoragePrefix: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false,
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
remoteObjectStorageEndpoint: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false,
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
remoteObjectStorageRegion: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false,
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
remoteObjectStoragePort: {
|
||||||
|
type: 'number',
|
||||||
|
optional: false,
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
remoteObjectStorageAccessKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false,
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
remoteObjectStorageSecretKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false,
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
remoteObjectStorageUseSSL: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
remoteObjectStorageUseProxy: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
remoteObjectStorageSetPublicRead: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
enableIpLogging: {
|
enableIpLogging: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
@ -452,6 +512,10 @@ export const meta = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
remoteObjectStorageS3ForcePathStyle: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
privacyPolicyUrl: {
|
privacyPolicyUrl: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
@ -628,6 +692,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
objectStorageUseProxy: instance.objectStorageUseProxy,
|
objectStorageUseProxy: instance.objectStorageUseProxy,
|
||||||
objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
|
objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
|
||||||
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
|
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
|
||||||
|
useRemoteObjectStorage: instance.useRemoteObjectStorage,
|
||||||
|
remoteObjectStorageBaseUrl: instance.remoteObjectStorageBaseUrl,
|
||||||
|
remoteObjectStorageBucket: instance.remoteObjectStorageBucket,
|
||||||
|
remoteObjectStoragePrefix: instance.remoteObjectStoragePrefix,
|
||||||
|
remoteObjectStorageEndpoint: instance.remoteObjectStorageEndpoint,
|
||||||
|
remoteObjectStorageRegion: instance.remoteObjectStorageRegion,
|
||||||
|
remoteObjectStoragePort: instance.remoteObjectStoragePort,
|
||||||
|
remoteObjectStorageAccessKey: instance.remoteObjectStorageAccessKey,
|
||||||
|
remoteObjectStorageSecretKey: instance.remoteObjectStorageSecretKey,
|
||||||
|
remoteObjectStorageUseSSL: instance.remoteObjectStorageUseSSL,
|
||||||
|
remoteObjectStorageUseProxy: instance.remoteObjectStorageUseProxy,
|
||||||
|
remoteObjectStorageSetPublicRead: instance.remoteObjectStorageSetPublicRead,
|
||||||
|
remoteObjectStorageS3ForcePathStyle: instance.remoteObjectStorageS3ForcePathStyle,
|
||||||
deeplAuthKey: instance.deeplAuthKey,
|
deeplAuthKey: instance.deeplAuthKey,
|
||||||
deeplIsPro: instance.deeplIsPro,
|
deeplIsPro: instance.deeplIsPro,
|
||||||
enableIpLogging: instance.enableIpLogging,
|
enableIpLogging: instance.enableIpLogging,
|
||||||
|
@ -127,6 +127,19 @@ export const paramDef = {
|
|||||||
objectStorageUseProxy: { type: 'boolean' },
|
objectStorageUseProxy: { type: 'boolean' },
|
||||||
objectStorageSetPublicRead: { type: 'boolean' },
|
objectStorageSetPublicRead: { type: 'boolean' },
|
||||||
objectStorageS3ForcePathStyle: { type: 'boolean' },
|
objectStorageS3ForcePathStyle: { type: 'boolean' },
|
||||||
|
useRemoteObjectStorage: { type: 'boolean' },
|
||||||
|
remoteObjectStorageBaseUrl: { type: 'string', nullable: true },
|
||||||
|
remoteObjectStorageBucket: { type: 'string', nullable: true },
|
||||||
|
remoteObjectStoragePrefix: { type: 'string', nullable: true },
|
||||||
|
remoteObjectStorageEndpoint: { type: 'string', nullable: true },
|
||||||
|
remoteObjectStorageRegion: { type: 'string', nullable: true },
|
||||||
|
remoteObjectStoragePort: { type: 'integer', nullable: true },
|
||||||
|
remoteObjectStorageAccessKey: { type: 'string', nullable: true },
|
||||||
|
remoteObjectStorageSecretKey: { type: 'string', nullable: true },
|
||||||
|
remoteObjectStorageUseSSL: { type: 'boolean' },
|
||||||
|
remoteObjectStorageUseProxy: { type: 'boolean' },
|
||||||
|
remoteObjectStorageSetPublicRead: { type: 'boolean' },
|
||||||
|
remoteObjectStorageS3ForcePathStyle: { type: 'boolean' },
|
||||||
enableIpLogging: { type: 'boolean' },
|
enableIpLogging: { type: 'boolean' },
|
||||||
enableActiveEmailValidation: { type: 'boolean' },
|
enableActiveEmailValidation: { type: 'boolean' },
|
||||||
enableVerifymailApi: { type: 'boolean' },
|
enableVerifymailApi: { type: 'boolean' },
|
||||||
@ -519,6 +532,58 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
|
set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.useRemoteObjectStorage !== undefined) {
|
||||||
|
set.useRemoteObjectStorage = ps.useRemoteObjectStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageBaseUrl !== undefined) {
|
||||||
|
set.remoteObjectStorageBaseUrl = ps.remoteObjectStorageBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageBucket !== undefined) {
|
||||||
|
set.remoteObjectStorageBucket = ps.remoteObjectStorageBucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStoragePrefix !== undefined) {
|
||||||
|
set.remoteObjectStoragePrefix = ps.remoteObjectStoragePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageEndpoint !== undefined) {
|
||||||
|
set.remoteObjectStorageEndpoint = ps.remoteObjectStorageEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageRegion !== undefined) {
|
||||||
|
set.remoteObjectStorageRegion = ps.remoteObjectStorageRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStoragePort !== undefined) {
|
||||||
|
set.remoteObjectStoragePort = ps.remoteObjectStoragePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageAccessKey !== undefined) {
|
||||||
|
set.remoteObjectStorageAccessKey = ps.remoteObjectStorageAccessKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageSecretKey !== undefined) {
|
||||||
|
set.remoteObjectStorageSecretKey = ps.remoteObjectStorageSecretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageUseSSL !== undefined) {
|
||||||
|
set.remoteObjectStorageUseSSL = ps.remoteObjectStorageUseSSL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageUseProxy !== undefined) {
|
||||||
|
set.remoteObjectStorageUseProxy = ps.remoteObjectStorageUseProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageSetPublicRead !== undefined) {
|
||||||
|
set.remoteObjectStorageSetPublicRead = ps.remoteObjectStorageSetPublicRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.remoteObjectStorageS3ForcePathStyle !== undefined) {
|
||||||
|
set.remoteObjectStorageS3ForcePathStyle = ps.remoteObjectStorageS3ForcePathStyle;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.deeplAuthKey !== undefined) {
|
if (ps.deeplAuthKey !== undefined) {
|
||||||
if (ps.deeplAuthKey === '') {
|
if (ps.deeplAuthKey === '') {
|
||||||
set.deeplAuthKey = null;
|
set.deeplAuthKey = null;
|
||||||
|
@ -4,82 +4,164 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><XHeader :tabs="headerTabs"/></template>
|
<template #header><XHeader :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
|
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
|
||||||
<FormSuspense :p="init">
|
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<MkSwitch v-model="useObjectStorage">{{ i18n.ts.useObjectStorage }}</MkSwitch>
|
<MkFolder :defaultOpen="false">
|
||||||
|
<template #icon><i class="ti ti-cloud"></i></template>
|
||||||
|
<template #label>{{ i18n.ts.objectStorage }}</template>
|
||||||
|
<template v-if="objectStorageForm.savedState.useObjectStorage" #suffix>Enabled</template>
|
||||||
|
<template v-else #suffix>Disabled</template>
|
||||||
|
<template v-if="objectStorageForm.modified.value" #footer>
|
||||||
|
<MkFormFooter :form="objectStorageForm"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="useObjectStorage">
|
<div class="_gaps">
|
||||||
<MkInput v-model="objectStorageBaseUrl" :placeholder="'https://example.com'" type="url">
|
<MkSwitch v-model="objectStorageForm.state.useObjectStorage">
|
||||||
|
<template #label>{{ i18n.ts.useObjectStorage }}<span v-if="objectStorageForm.modifiedStates.useObjectStorage" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStorageEnableDesc }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
|
<template v-if="objectStorageForm.state.useObjectStorage">
|
||||||
|
<MkInput v-model="objectStorageForm.state.objectStorageBaseUrl" :placeholder="'https://example.com'" type="url">
|
||||||
<template #label>{{ i18n.ts.objectStorageBaseUrl }}</template>
|
<template #label>{{ i18n.ts.objectStorageBaseUrl }}</template>
|
||||||
<template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template>
|
<template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkInput v-model="objectStorageBucket">
|
<MkInput v-model="objectStorageForm.state.objectStorageBucket">
|
||||||
<template #label>{{ i18n.ts.objectStorageBucket }}</template>
|
<template #label>{{ i18n.ts.objectStorageBucket }}</template>
|
||||||
<template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template>
|
<template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkInput v-model="objectStoragePrefix">
|
<MkInput v-model="objectStorageForm.state.objectStoragePrefix">
|
||||||
<template #label>{{ i18n.ts.objectStoragePrefix }}</template>
|
<template #label>{{ i18n.ts.objectStoragePrefix }}</template>
|
||||||
<template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template>
|
<template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkInput v-model="objectStorageEndpoint" :placeholder="'example.com'">
|
<MkInput v-model="objectStorageForm.state.objectStorageEndpoint" :placeholder="'example.com'">
|
||||||
<template #label>{{ i18n.ts.objectStorageEndpoint }}</template>
|
<template #label>{{ i18n.ts.objectStorageEndpoint }}</template>
|
||||||
<template #prefix>https://</template>
|
<template #prefix>https://</template>
|
||||||
<template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template>
|
<template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkInput v-model="objectStorageRegion">
|
<MkInput v-model="objectStorageForm.state.objectStorageRegion">
|
||||||
<template #label>{{ i18n.ts.objectStorageRegion }}</template>
|
<template #label>{{ i18n.ts.objectStorageRegion }}</template>
|
||||||
<template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template>
|
<template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<FormSplit :minWidth="280">
|
<FormSplit :minWidth="280">
|
||||||
<MkInput v-model="objectStorageAccessKey">
|
<MkInput v-model="objectStorageForm.state.objectStorageAccessKey">
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>Access key</template>
|
<template #label>Access key<span v-if="objectStorageForm.modifiedStates.objectStorageAccessKey" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkInput v-model="objectStorageSecretKey" type="password">
|
<MkInput v-model="objectStorageForm.state.objectStorageSecretKey" type="password">
|
||||||
<template #prefix><i class="ti ti-key"></i></template>
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
<template #label>Secret key</template>
|
<template #label>Secret key<span v-if="objectStorageForm.modifiedStates.objectStorageSecretKey" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</FormSplit>
|
</FormSplit>
|
||||||
|
|
||||||
<MkSwitch v-model="objectStorageUseSSL">
|
<MkSwitch v-model="objectStorageForm.state.objectStorageUseSSL">
|
||||||
<template #label>{{ i18n.ts.objectStorageUseSSL }}</template>
|
<template #label>{{ i18n.ts.objectStorageUseSSL }}<span v-if="objectStorageForm.modifiedStates.objectStorageUseSSL" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
<template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template>
|
<template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
<MkSwitch v-model="objectStorageUseProxy">
|
<MkSwitch v-model="objectStorageForm.state.objectStorageUseProxy">
|
||||||
<template #label>{{ i18n.ts.objectStorageUseProxy }}</template>
|
<template #label>{{ i18n.ts.objectStorageUseProxy }}<span v-if="objectStorageForm.modifiedStates.objectStorageUseProxy" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
<template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template>
|
<template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
<MkSwitch v-model="objectStorageSetPublicRead">
|
<MkSwitch v-model="objectStorageForm.state.objectStorageSetPublicRead">
|
||||||
<template #label>{{ i18n.ts.objectStorageSetPublicRead }}</template>
|
<template #label>{{ i18n.ts.objectStorageSetPublicRead }}<span v-if="objectStorageForm.modifiedStates.objectStorageSetPublicRead" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
<MkSwitch v-model="objectStorageS3ForcePathStyle">
|
<MkSwitch v-model="objectStorageForm.state.objectStorageS3ForcePathStyle">
|
||||||
<template #label>s3ForcePathStyle</template>
|
<template #label>s3ForcePathStyle<span v-if="objectStorageForm.modifiedStates.objectStorageS3ForcePathStyle" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
<template #caption>{{ i18n.ts.s3ForcePathStyleDesc }}</template>
|
<template #caption>{{ i18n.ts.s3ForcePathStyleDesc }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</FormSuspense>
|
</MkFolder>
|
||||||
</MkSpacer>
|
|
||||||
<template #footer>
|
<MkFolder :defaultOpen="false">
|
||||||
<div :class="$style.footer">
|
<template #icon><i class="ti ti-cloud"></i></template>
|
||||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
|
<template #label>{{ i18n.ts.objectStorage }} ({{ i18n.ts.remote }})</template>
|
||||||
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
<template v-if="remoteObjectStorageForm.savedState.useRemoteObjectStorage" #suffix>Enabled</template>
|
||||||
</MkSpacer>
|
<template v-else #suffix>Disabled</template>
|
||||||
</div>
|
<template v-if="remoteObjectStorageForm.modified.value" #footer>
|
||||||
|
<MkFormFooter :form="remoteObjectStorageForm"/>
|
||||||
</template>
|
</template>
|
||||||
</MkStickyContainer>
|
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="remoteObjectStorageForm.state.useRemoteObjectStorage">
|
||||||
|
<template #label>{{ i18n.ts.useObjectStorage }} ({{ i18n.ts.remote }})<span v-if="remoteObjectStorageForm.modifiedStates.useRemoteObjectStorage" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStorageRemoteEnableDesc }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
|
<template v-if="remoteObjectStorageForm.state.useRemoteObjectStorage">
|
||||||
|
<MkInput v-model="remoteObjectStorageForm.state.remoteObjectStorageBaseUrl" :placeholder="'https://example.com'" type="url">
|
||||||
|
<template #label>{{ i18n.ts.objectStorageBaseUrl }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="remoteObjectStorageForm.state.remoteObjectStorageBucket">
|
||||||
|
<template #label>{{ i18n.ts.objectStorageBucket }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="remoteObjectStorageForm.state.remoteObjectStoragePrefix">
|
||||||
|
<template #label>{{ i18n.ts.objectStoragePrefix }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="remoteObjectStorageForm.state.remoteObjectStorageEndpoint" :placeholder="'example.com'">
|
||||||
|
<template #label>{{ i18n.ts.objectStorageEndpoint }}</template>
|
||||||
|
<template #prefix>https://</template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="remoteObjectStorageForm.state.remoteObjectStorageRegion">
|
||||||
|
<template #label>{{ i18n.ts.objectStorageRegion }}</template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<FormSplit :minWidth="280">
|
||||||
|
<MkInput v-model="remoteObjectStorageForm.state.remoteObjectStorageAccessKey">
|
||||||
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
|
<template #label>Access key<span v-if="remoteObjectStorageForm.modifiedStates.remoteObjectStorageAccessKey" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="remoteObjectStorageForm.state.remoteObjectStorageSecretKey" type="password">
|
||||||
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
|
<template #label>Secret key<span v-if="remoteObjectStorageForm.modifiedStates.remoteObjectStorageSecretKey" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
|
</MkInput>
|
||||||
|
</FormSplit>
|
||||||
|
|
||||||
|
<MkSwitch v-model="remoteObjectStorageForm.state.remoteObjectStorageUseSSL">
|
||||||
|
<template #label>{{ i18n.ts.objectStorageUseSSL }}<span v-if="remoteObjectStorageForm.modifiedStates.remoteObjectStorageUseSSL" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
|
<MkSwitch v-model="remoteObjectStorageForm.state.remoteObjectStorageUseProxy">
|
||||||
|
<template #label>{{ i18n.ts.objectStorageUseProxy }}<span v-if="remoteObjectStorageForm.modifiedStates.remoteObjectStorageUseProxy" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
|
<template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
|
<MkSwitch v-model="remoteObjectStorageForm.state.remoteObjectStorageSetPublicRead">
|
||||||
|
<template #label>{{ i18n.ts.objectStorageSetPublicRead }}<span v-if="remoteObjectStorageForm.modifiedStates.remoteObjectStorageSetPublicRead" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
|
</MkSwitch>
|
||||||
|
|
||||||
|
<MkSwitch v-model="remoteObjectStorageForm.state.remoteObjectStorageS3ForcePathStyle">
|
||||||
|
<template #label>s3ForcePathStyle<span v-if="remoteObjectStorageForm.modifiedStates.remoteObjectStorageS3ForcePathStyle" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
|
<template #caption>{{ i18n.ts.s3ForcePathStyleDesc }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -87,65 +169,79 @@ import { ref, computed } from 'vue';
|
|||||||
import XHeader from './_header_.vue';
|
import XHeader from './_header_.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import FormSuspense from '@/components/form/suspense.vue';
|
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { fetchInstance } from '@/instance.js';
|
import { fetchInstance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkFolder from "@/components/MkFolder.vue";
|
||||||
|
import MkFormFooter from '@/components/MkFormFooter.vue';
|
||||||
|
import { useForm } from '@/scripts/use-form.js';
|
||||||
|
|
||||||
const useObjectStorage = ref<boolean>(false);
|
const meta = await misskeyApi('admin/meta');
|
||||||
const objectStorageBaseUrl = ref<string | null>(null);
|
|
||||||
const objectStorageBucket = ref<string | null>(null);
|
|
||||||
const objectStoragePrefix = ref<string | null>(null);
|
|
||||||
const objectStorageEndpoint = ref<string | null>(null);
|
|
||||||
const objectStorageRegion = ref<string | null>(null);
|
|
||||||
const objectStoragePort = ref<number | null>(null);
|
|
||||||
const objectStorageAccessKey = ref<string | null>(null);
|
|
||||||
const objectStorageSecretKey = ref<string | null>(null);
|
|
||||||
const objectStorageUseSSL = ref<boolean>(false);
|
|
||||||
const objectStorageUseProxy = ref<boolean>(false);
|
|
||||||
const objectStorageSetPublicRead = ref<boolean>(false);
|
|
||||||
const objectStorageS3ForcePathStyle = ref<boolean>(true);
|
|
||||||
|
|
||||||
async function init() {
|
const objectStorageForm = useForm({
|
||||||
const meta = await misskeyApi('admin/meta');
|
useObjectStorage: meta.useObjectStorage,
|
||||||
useObjectStorage.value = meta.useObjectStorage;
|
objectStorageBaseUrl: meta.objectStorageBaseUrl,
|
||||||
objectStorageBaseUrl.value = meta.objectStorageBaseUrl;
|
objectStorageBucket: meta.objectStorageBucket,
|
||||||
objectStorageBucket.value = meta.objectStorageBucket;
|
objectStoragePrefix: meta.objectStoragePrefix,
|
||||||
objectStoragePrefix.value = meta.objectStoragePrefix;
|
objectStorageEndpoint: meta.objectStorageEndpoint,
|
||||||
objectStorageEndpoint.value = meta.objectStorageEndpoint;
|
objectStorageRegion: meta.objectStorageRegion,
|
||||||
objectStorageRegion.value = meta.objectStorageRegion;
|
objectStorageAccessKey: meta.objectStorageAccessKey,
|
||||||
objectStoragePort.value = meta.objectStoragePort;
|
objectStorageSecretKey: meta.objectStorageSecretKey,
|
||||||
objectStorageAccessKey.value = meta.objectStorageAccessKey;
|
objectStorageUseSSL: meta.objectStorageUseSSL,
|
||||||
objectStorageSecretKey.value = meta.objectStorageSecretKey;
|
objectStorageUseProxy: meta.objectStorageUseProxy,
|
||||||
objectStorageUseSSL.value = meta.objectStorageUseSSL;
|
objectStorageSetPublicRead: meta.objectStorageSetPublicRead,
|
||||||
objectStorageUseProxy.value = meta.objectStorageUseProxy;
|
objectStorageS3ForcePathStyle: meta.objectStorageS3ForcePathStyle,
|
||||||
objectStorageSetPublicRead.value = meta.objectStorageSetPublicRead;
|
}, async (state) => {
|
||||||
objectStorageS3ForcePathStyle.value = meta.objectStorageS3ForcePathStyle;
|
await os.apiWithDialog('admin/update-meta', {
|
||||||
}
|
useObjectStorage: state.useObjectStorage,
|
||||||
|
objectStorageBaseUrl: state.objectStorageBaseUrl,
|
||||||
function save() {
|
objectStorageBucket: state.objectStorageBucket,
|
||||||
os.apiWithDialog('admin/update-meta', {
|
objectStoragePrefix: state.objectStoragePrefix,
|
||||||
useObjectStorage: useObjectStorage.value,
|
objectStorageEndpoint: state.objectStorageEndpoint,
|
||||||
objectStorageBaseUrl: objectStorageBaseUrl.value,
|
objectStorageRegion: state.objectStorageRegion,
|
||||||
objectStorageBucket: objectStorageBucket.value,
|
objectStorageAccessKey: state.objectStorageAccessKey,
|
||||||
objectStoragePrefix: objectStoragePrefix.value,
|
objectStorageSecretKey: state.objectStorageSecretKey,
|
||||||
objectStorageEndpoint: objectStorageEndpoint.value,
|
objectStorageUseSSL: state.objectStorageUseSSL,
|
||||||
objectStorageRegion: objectStorageRegion.value,
|
objectStorageUseProxy: state.objectStorageUseProxy,
|
||||||
objectStoragePort: objectStoragePort.value,
|
objectStorageSetPublicRead: state.objectStorageSetPublicRead,
|
||||||
objectStorageAccessKey: objectStorageAccessKey.value,
|
objectStorageS3ForcePathStyle: state.objectStorageS3ForcePathStyle,
|
||||||
objectStorageSecretKey: objectStorageSecretKey.value,
|
|
||||||
objectStorageUseSSL: objectStorageUseSSL.value,
|
|
||||||
objectStorageUseProxy: objectStorageUseProxy.value,
|
|
||||||
objectStorageSetPublicRead: objectStorageSetPublicRead.value,
|
|
||||||
objectStorageS3ForcePathStyle: objectStorageS3ForcePathStyle.value,
|
|
||||||
}).then(() => {
|
|
||||||
fetchInstance(true);
|
|
||||||
});
|
});
|
||||||
}
|
fetchInstance(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const remoteObjectStorageForm = useForm({
|
||||||
|
useRemoteObjectStorage: meta.useRemoteObjectStorage,
|
||||||
|
remoteObjectStorageBaseUrl: meta.remoteObjectStorageBaseUrl,
|
||||||
|
remoteObjectStorageBucket: meta.remoteObjectStorageBucket,
|
||||||
|
remoteObjectStoragePrefix: meta.remoteObjectStoragePrefix,
|
||||||
|
remoteObjectStorageEndpoint: meta.remoteObjectStorageEndpoint,
|
||||||
|
remoteObjectStorageRegion: meta.remoteObjectStorageRegion,
|
||||||
|
remoteObjectStorageAccessKey: meta.remoteObjectStorageAccessKey,
|
||||||
|
remoteObjectStorageSecretKey: meta.remoteObjectStorageSecretKey,
|
||||||
|
remoteObjectStorageUseSSL: meta.remoteObjectStorageUseSSL,
|
||||||
|
remoteObjectStorageUseProxy: meta.remoteObjectStorageUseProxy,
|
||||||
|
remoteObjectStorageSetPublicRead: meta.remoteObjectStorageSetPublicRead,
|
||||||
|
remoteObjectStorageS3ForcePathStyle: meta.remoteObjectStorageS3ForcePathStyle,
|
||||||
|
}, async (state) => {
|
||||||
|
await os.apiWithDialog('admin/update-meta', {
|
||||||
|
useRemoteObjectStorage: state.useRemoteObjectStorage,
|
||||||
|
remoteObjectStorageBaseUrl: state.remoteObjectStorageBaseUrl,
|
||||||
|
remoteObjectStorageBucket: state.remoteObjectStorageBucket,
|
||||||
|
remoteObjectStoragePrefix: state.remoteObjectStoragePrefix,
|
||||||
|
remoteObjectStorageEndpoint: state.remoteObjectStorageEndpoint,
|
||||||
|
remoteObjectStorageRegion: state.remoteObjectStorageRegion,
|
||||||
|
remoteObjectStorageAccessKey: state.remoteObjectStorageAccessKey,
|
||||||
|
remoteObjectStorageSecretKey: state.remoteObjectStorageSecretKey,
|
||||||
|
remoteObjectStorageUseSSL: state.remoteObjectStorageUseSSL,
|
||||||
|
remoteObjectStorageUseProxy: state.remoteObjectStorageUseProxy,
|
||||||
|
remoteObjectStorageSetPublicRead: state.remoteObjectStorageSetPublicRead,
|
||||||
|
remoteObjectStorageS3ForcePathStyle: state.remoteObjectStorageS3ForcePathStyle,
|
||||||
|
});
|
||||||
|
fetchInstance(true);
|
||||||
|
});
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
|
@ -5168,6 +5168,18 @@ export type operations = {
|
|||||||
objectStorageUseSSL: boolean;
|
objectStorageUseSSL: boolean;
|
||||||
objectStorageUseProxy: boolean;
|
objectStorageUseProxy: boolean;
|
||||||
objectStorageSetPublicRead: boolean;
|
objectStorageSetPublicRead: boolean;
|
||||||
|
useRemoteObjectStorage: boolean;
|
||||||
|
remoteObjectStorageBaseUrl: string | null;
|
||||||
|
remoteObjectStorageBucket: string | null;
|
||||||
|
remoteObjectStoragePrefix: string | null;
|
||||||
|
remoteObjectStorageEndpoint: string | null;
|
||||||
|
remoteObjectStorageRegion: string | null;
|
||||||
|
remoteObjectStoragePort: number | null;
|
||||||
|
remoteObjectStorageAccessKey: string | null;
|
||||||
|
remoteObjectStorageSecretKey: string | null;
|
||||||
|
remoteObjectStorageUseSSL: boolean;
|
||||||
|
remoteObjectStorageUseProxy: boolean;
|
||||||
|
remoteObjectStorageSetPublicRead: boolean;
|
||||||
enableIpLogging: boolean;
|
enableIpLogging: boolean;
|
||||||
enableActiveEmailValidation: boolean;
|
enableActiveEmailValidation: boolean;
|
||||||
enableVerifymailApi: boolean;
|
enableVerifymailApi: boolean;
|
||||||
@ -5203,6 +5215,7 @@ export type operations = {
|
|||||||
name: string | null;
|
name: string | null;
|
||||||
shortName: string | null;
|
shortName: string | null;
|
||||||
objectStorageS3ForcePathStyle: boolean;
|
objectStorageS3ForcePathStyle: boolean;
|
||||||
|
remoteObjectStorageS3ForcePathStyle: boolean;
|
||||||
privacyPolicyUrl: string | null;
|
privacyPolicyUrl: string | null;
|
||||||
inquiryUrl: string | null;
|
inquiryUrl: string | null;
|
||||||
repositoryUrl: string | null;
|
repositoryUrl: string | null;
|
||||||
@ -9551,6 +9564,19 @@ export type operations = {
|
|||||||
objectStorageUseProxy?: boolean;
|
objectStorageUseProxy?: boolean;
|
||||||
objectStorageSetPublicRead?: boolean;
|
objectStorageSetPublicRead?: boolean;
|
||||||
objectStorageS3ForcePathStyle?: boolean;
|
objectStorageS3ForcePathStyle?: boolean;
|
||||||
|
useRemoteObjectStorage?: boolean;
|
||||||
|
remoteObjectStorageBaseUrl?: string | null;
|
||||||
|
remoteObjectStorageBucket?: string | null;
|
||||||
|
remoteObjectStoragePrefix?: string | null;
|
||||||
|
remoteObjectStorageEndpoint?: string | null;
|
||||||
|
remoteObjectStorageRegion?: string | null;
|
||||||
|
remoteObjectStoragePort?: number | null;
|
||||||
|
remoteObjectStorageAccessKey?: string | null;
|
||||||
|
remoteObjectStorageSecretKey?: string | null;
|
||||||
|
remoteObjectStorageUseSSL?: boolean;
|
||||||
|
remoteObjectStorageUseProxy?: boolean;
|
||||||
|
remoteObjectStorageSetPublicRead?: boolean;
|
||||||
|
remoteObjectStorageS3ForcePathStyle?: boolean;
|
||||||
enableIpLogging?: boolean;
|
enableIpLogging?: boolean;
|
||||||
enableActiveEmailValidation?: boolean;
|
enableActiveEmailValidation?: boolean;
|
||||||
enableVerifymailApi?: boolean;
|
enableVerifymailApi?: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user