mirror of https://github.com/usememos/memos.git
Merge db412ae135 into 5f57f48673
This commit is contained in:
commit
fef0917f5a
|
|
@ -147,6 +147,9 @@ message WorkspaceSetting {
|
|||
}
|
||||
// The S3 config.
|
||||
S3Config s3_config = 4;
|
||||
// enable_s3_image_thumbnails enables thumbnail generation for images stored in S3.
|
||||
// When false, images stored in S3 will not have thumbnails generated.
|
||||
bool enable_s3_image_thumbnails = 5;
|
||||
}
|
||||
|
||||
// Memo-related workspace settings and policies.
|
||||
|
|
|
|||
|
|
@ -589,9 +589,12 @@ type WorkspaceSetting_StorageSetting struct {
|
|||
// The max upload size in megabytes.
|
||||
UploadSizeLimitMb int64 `protobuf:"varint,3,opt,name=upload_size_limit_mb,json=uploadSizeLimitMb,proto3" json:"upload_size_limit_mb,omitempty"`
|
||||
// The S3 config.
|
||||
S3Config *WorkspaceSetting_StorageSetting_S3Config `protobuf:"bytes,4,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
S3Config *WorkspaceSetting_StorageSetting_S3Config `protobuf:"bytes,4,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
|
||||
// enable_s3_image_thumbnails enables thumbnail generation for images stored in S3.
|
||||
// When false, images stored in S3 will not have thumbnails generated.
|
||||
EnableS3ImageThumbnails bool `protobuf:"varint,5,opt,name=enable_s3_image_thumbnails,json=enableS3ImageThumbnails,proto3" json:"enable_s3_image_thumbnails,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *WorkspaceSetting_StorageSetting) Reset() {
|
||||
|
|
@ -652,6 +655,13 @@ func (x *WorkspaceSetting_StorageSetting) GetS3Config() *WorkspaceSetting_Storag
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *WorkspaceSetting_StorageSetting) GetEnableS3ImageThumbnails() bool {
|
||||
if x != nil {
|
||||
return x.EnableS3ImageThumbnails
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Memo-related workspace settings and policies.
|
||||
type WorkspaceSetting_MemoRelatedSetting struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
|
|
@ -935,7 +945,7 @@ const file_api_v1_workspace_service_proto_rawDesc = "" +
|
|||
"\aversion\x18\x02 \x01(\tR\aversion\x12\x12\n" +
|
||||
"\x04mode\x18\x03 \x01(\tR\x04mode\x12!\n" +
|
||||
"\finstance_url\x18\x06 \x01(\tR\vinstanceUrl\"\x1c\n" +
|
||||
"\x1aGetWorkspaceProfileRequest\"\x97\x11\n" +
|
||||
"\x1aGetWorkspaceProfileRequest\"\xd4\x11\n" +
|
||||
"\x10WorkspaceSetting\x12\x17\n" +
|
||||
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12X\n" +
|
||||
"\x0fgeneral_setting\x18\x02 \x01(\v2-.memos.api.v1.WorkspaceSetting.GeneralSettingH\x00R\x0egeneralSetting\x12X\n" +
|
||||
|
|
@ -955,12 +965,13 @@ const file_api_v1_workspace_service_proto_rawDesc = "" +
|
|||
"\x05title\x18\x01 \x01(\tR\x05title\x12 \n" +
|
||||
"\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" +
|
||||
"\blogo_url\x18\x03 \x01(\tR\alogoUrl\x12\x16\n" +
|
||||
"\x06locale\x18\x04 \x01(\tR\x06locale\x1a\xbe\x04\n" +
|
||||
"\x06locale\x18\x04 \x01(\tR\x06locale\x1a\xfb\x04\n" +
|
||||
"\x0eStorageSetting\x12\\\n" +
|
||||
"\fstorage_type\x18\x01 \x01(\x0e29.memos.api.v1.WorkspaceSetting.StorageSetting.StorageTypeR\vstorageType\x12+\n" +
|
||||
"\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" +
|
||||
"\x14upload_size_limit_mb\x18\x03 \x01(\x03R\x11uploadSizeLimitMb\x12S\n" +
|
||||
"\ts3_config\x18\x04 \x01(\v26.memos.api.v1.WorkspaceSetting.StorageSetting.S3ConfigR\bs3Config\x1a\xcc\x01\n" +
|
||||
"\ts3_config\x18\x04 \x01(\v26.memos.api.v1.WorkspaceSetting.StorageSetting.S3ConfigR\bs3Config\x12;\n" +
|
||||
"\x1aenable_s3_image_thumbnails\x18\x05 \x01(\bR\x17enableS3ImageThumbnails\x1a\xcc\x01\n" +
|
||||
"\bS3Config\x12\"\n" +
|
||||
"\raccess_key_id\x18\x01 \x01(\tR\vaccessKeyId\x12*\n" +
|
||||
"\x11access_key_secret\x18\x02 \x01(\tR\x0faccessKeySecret\x12\x1a\n" +
|
||||
|
|
|
|||
|
|
@ -3188,6 +3188,11 @@ components:
|
|||
allOf:
|
||||
- $ref: '#/components/schemas/StorageSetting_S3Config'
|
||||
description: The S3 config.
|
||||
enableS3ImageThumbnails:
|
||||
type: boolean
|
||||
description: |-
|
||||
enable_s3_image_thumbnails enables thumbnail generation for images stored in S3.
|
||||
When false, images stored in S3 will not have thumbnails generated.
|
||||
description: Storage configuration settings for workspace attachments.
|
||||
tags:
|
||||
- name: ActivityService
|
||||
|
|
|
|||
|
|
@ -509,9 +509,12 @@ type WorkspaceStorageSetting struct {
|
|||
// The max upload size in megabytes.
|
||||
UploadSizeLimitMb int64 `protobuf:"varint,3,opt,name=upload_size_limit_mb,json=uploadSizeLimitMb,proto3" json:"upload_size_limit_mb,omitempty"`
|
||||
// The S3 config.
|
||||
S3Config *StorageS3Config `protobuf:"bytes,4,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
S3Config *StorageS3Config `protobuf:"bytes,4,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
|
||||
// enable_s3_image_thumbnails enables thumbnail generation for images stored in S3.
|
||||
// When false, images stored in S3 will not have thumbnails generated.
|
||||
EnableS3ImageThumbnails bool `protobuf:"varint,5,opt,name=enable_s3_image_thumbnails,json=enableS3ImageThumbnails,proto3" json:"enable_s3_image_thumbnails,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *WorkspaceStorageSetting) Reset() {
|
||||
|
|
@ -572,6 +575,13 @@ func (x *WorkspaceStorageSetting) GetS3Config() *StorageS3Config {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *WorkspaceStorageSetting) GetEnableS3ImageThumbnails() bool {
|
||||
if x != nil {
|
||||
return x.EnableS3ImageThumbnails
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
|
||||
type StorageS3Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
|
|
@ -804,12 +814,13 @@ const file_store_workspace_setting_proto_rawDesc = "" +
|
|||
"\x05title\x18\x01 \x01(\tR\x05title\x12 \n" +
|
||||
"\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" +
|
||||
"\blogo_url\x18\x03 \x01(\tR\alogoUrl\x12\x16\n" +
|
||||
"\x06locale\x18\x04 \x01(\tR\x06locale\"\xd5\x02\n" +
|
||||
"\x06locale\x18\x04 \x01(\tR\x06locale\"\x92\x03\n" +
|
||||
"\x17WorkspaceStorageSetting\x12S\n" +
|
||||
"\fstorage_type\x18\x01 \x01(\x0e20.memos.store.WorkspaceStorageSetting.StorageTypeR\vstorageType\x12+\n" +
|
||||
"\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" +
|
||||
"\x14upload_size_limit_mb\x18\x03 \x01(\x03R\x11uploadSizeLimitMb\x129\n" +
|
||||
"\ts3_config\x18\x04 \x01(\v2\x1c.memos.store.StorageS3ConfigR\bs3Config\"L\n" +
|
||||
"\ts3_config\x18\x04 \x01(\v2\x1c.memos.store.StorageS3ConfigR\bs3Config\x12;\n" +
|
||||
"\x1aenable_s3_image_thumbnails\x18\x05 \x01(\bR\x17enableS3ImageThumbnails\"L\n" +
|
||||
"\vStorageType\x12\x1c\n" +
|
||||
"\x18STORAGE_TYPE_UNSPECIFIED\x10\x00\x12\f\n" +
|
||||
"\bDATABASE\x10\x01\x12\t\n" +
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@ message WorkspaceStorageSetting {
|
|||
int64 upload_size_limit_mb = 3;
|
||||
// The S3 config.
|
||||
StorageS3Config s3_config = 4;
|
||||
// enable_s3_image_thumbnails enables thumbnail generation for images stored in S3.
|
||||
// When false, images stored in S3 will not have thumbnails generated.
|
||||
bool enable_s3_image_thumbnails = 5;
|
||||
}
|
||||
|
||||
// Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
|
||||
|
|
|
|||
|
|
@ -235,16 +235,29 @@ func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.Ge
|
|||
}
|
||||
|
||||
if request.Thumbnail && util.HasPrefixes(attachment.Type, SupportedThumbnailMimeTypes...) {
|
||||
thumbnailBlob, err := s.getOrGenerateThumbnail(attachment)
|
||||
if err != nil {
|
||||
// thumbnail failures are logged as warnings and not cosidered critical failures as
|
||||
// a attachment image can be used in its place.
|
||||
slog.Warn("failed to get attachment thumbnail image", slog.Any("error", err))
|
||||
} else {
|
||||
return &httpbody.HttpBody{
|
||||
ContentType: attachment.Type,
|
||||
Data: thumbnailBlob,
|
||||
}, nil
|
||||
// Check if we should generate thumbnails for S3 images
|
||||
shouldGenerateThumbnail := true
|
||||
if attachment.StorageType == storepb.AttachmentStorageType_S3 {
|
||||
storageSetting, err := s.Store.GetWorkspaceStorageSetting(ctx)
|
||||
if err != nil {
|
||||
slog.Warn("failed to get workspace storage setting", slog.Any("error", err))
|
||||
} else if !storageSetting.EnableS3ImageThumbnails {
|
||||
shouldGenerateThumbnail = false
|
||||
}
|
||||
}
|
||||
|
||||
if shouldGenerateThumbnail {
|
||||
thumbnailBlob, err := s.getOrGenerateThumbnail(attachment)
|
||||
if err != nil {
|
||||
// thumbnail failures are logged as warnings and not cosidered critical failures as
|
||||
// a attachment image can be used in its place.
|
||||
slog.Warn("failed to get attachment thumbnail image", slog.Any("error", err))
|
||||
} else {
|
||||
return &httpbody.HttpBody{
|
||||
ContentType: attachment.Type,
|
||||
Data: thumbnailBlob,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,14 +64,30 @@ func (s *APIV1Service) GetWorkspaceSetting(ctx context.Context, request *v1pb.Ge
|
|||
return nil, status.Errorf(codes.NotFound, "workspace setting not found")
|
||||
}
|
||||
|
||||
// For storage setting, only host can get it.
|
||||
// For storage setting, filter based on user role.
|
||||
if workspaceSetting.Key == storepb.WorkspaceSettingKey_STORAGE {
|
||||
user, err := s.GetCurrentUser(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
|
||||
}
|
||||
|
||||
// Host can see everything, regular users only see enable_s3_image_thumbnails.
|
||||
if user == nil || user.Role != store.RoleHost {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
// Convert and filter for non-host users.
|
||||
convertedSetting := convertWorkspaceStorageSettingFromStore(workspaceSetting.GetStorageSetting())
|
||||
// Clear sensitive fields.
|
||||
convertedSetting.StorageType = v1pb.WorkspaceSetting_StorageSetting_STORAGE_TYPE_UNSPECIFIED
|
||||
convertedSetting.FilepathTemplate = ""
|
||||
convertedSetting.UploadSizeLimitMb = 0
|
||||
convertedSetting.S3Config = nil
|
||||
// Keep only EnableS3ImageThumbnails.
|
||||
|
||||
return &v1pb.WorkspaceSetting{
|
||||
Name: fmt.Sprintf("workspace/settings/%s", workspaceSetting.Key.String()),
|
||||
Value: &v1pb.WorkspaceSetting_StorageSetting_{
|
||||
StorageSetting: convertedSetting,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -211,9 +227,10 @@ func convertWorkspaceStorageSettingFromStore(settingpb *storepb.WorkspaceStorage
|
|||
return nil
|
||||
}
|
||||
setting := &v1pb.WorkspaceSetting_StorageSetting{
|
||||
StorageType: v1pb.WorkspaceSetting_StorageSetting_StorageType(settingpb.StorageType),
|
||||
FilepathTemplate: settingpb.FilepathTemplate,
|
||||
UploadSizeLimitMb: settingpb.UploadSizeLimitMb,
|
||||
StorageType: v1pb.WorkspaceSetting_StorageSetting_StorageType(settingpb.StorageType),
|
||||
FilepathTemplate: settingpb.FilepathTemplate,
|
||||
UploadSizeLimitMb: settingpb.UploadSizeLimitMb,
|
||||
EnableS3ImageThumbnails: settingpb.EnableS3ImageThumbnails,
|
||||
}
|
||||
if settingpb.S3Config != nil {
|
||||
setting.S3Config = &v1pb.WorkspaceSetting_StorageSetting_S3Config{
|
||||
|
|
@ -233,9 +250,10 @@ func convertWorkspaceStorageSettingToStore(setting *v1pb.WorkspaceSetting_Storag
|
|||
return nil
|
||||
}
|
||||
settingpb := &storepb.WorkspaceStorageSetting{
|
||||
StorageType: storepb.WorkspaceStorageSetting_StorageType(setting.StorageType),
|
||||
FilepathTemplate: setting.FilepathTemplate,
|
||||
UploadSizeLimitMb: setting.UploadSizeLimitMb,
|
||||
StorageType: storepb.WorkspaceStorageSetting_StorageType(setting.StorageType),
|
||||
FilepathTemplate: setting.FilepathTemplate,
|
||||
UploadSizeLimitMb: setting.UploadSizeLimitMb,
|
||||
EnableS3ImageThumbnails: setting.EnableS3ImageThumbnails,
|
||||
}
|
||||
if setting.S3Config != nil {
|
||||
settingpb.S3Config = &storepb.StorageS3Config{
|
||||
|
|
|
|||
|
|
@ -240,6 +240,18 @@ const StorageSection = observer(() => {
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.storage-section.use-thumbnails-for-s3-images")}</span>
|
||||
<Switch
|
||||
checked={workspaceStorageSetting.enableS3ImageThumbnails}
|
||||
onCheckedChange={(checked) =>
|
||||
setWorkspaceStorageSetting({
|
||||
...workspaceStorageSetting,
|
||||
enableS3ImageThumbnails: checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}>
|
||||
{t("common.save")}
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@
|
|||
"url-prefix-placeholder": "Custom URL prefix, optional",
|
||||
"url-suffix": "URL suffix",
|
||||
"url-suffix-placeholder": "Custom URL suffix, optional",
|
||||
"use-thumbnails-for-s3-images": "Generate and serve thumbnails for images stored in S3",
|
||||
"warning-text": "Are you sure you want to delete storage service `{{name}}`? THIS ACTION IS IRREVERSIBLE"
|
||||
},
|
||||
"system": "System",
|
||||
|
|
|
|||
|
|
@ -247,6 +247,7 @@ export const initialWorkspaceStore = async (): Promise<void> => {
|
|||
await Promise.all([
|
||||
workspaceStore.fetchWorkspaceSetting(WorkspaceSetting_Key.GENERAL),
|
||||
workspaceStore.fetchWorkspaceSetting(WorkspaceSetting_Key.MEMO_RELATED),
|
||||
workspaceStore.fetchWorkspaceSetting(WorkspaceSetting_Key.STORAGE),
|
||||
]);
|
||||
|
||||
// Apply settings to state
|
||||
|
|
|
|||
|
|
@ -141,7 +141,14 @@ export interface WorkspaceSetting_StorageSetting {
|
|||
/** The max upload size in megabytes. */
|
||||
uploadSizeLimitMb: number;
|
||||
/** The S3 config. */
|
||||
s3Config?: WorkspaceSetting_StorageSetting_S3Config | undefined;
|
||||
s3Config?:
|
||||
| WorkspaceSetting_StorageSetting_S3Config
|
||||
| undefined;
|
||||
/**
|
||||
* enable_s3_image_thumbnails enables thumbnail generation for images stored in S3.
|
||||
* When false, images stored in S3 will not have thumbnails generated.
|
||||
*/
|
||||
enableS3ImageThumbnails: boolean;
|
||||
}
|
||||
|
||||
/** Storage type enumeration for different storage backends. */
|
||||
|
|
@ -705,6 +712,7 @@ function createBaseWorkspaceSetting_StorageSetting(): WorkspaceSetting_StorageSe
|
|||
filepathTemplate: "",
|
||||
uploadSizeLimitMb: 0,
|
||||
s3Config: undefined,
|
||||
enableS3ImageThumbnails: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -722,6 +730,9 @@ export const WorkspaceSetting_StorageSetting: MessageFns<WorkspaceSetting_Storag
|
|||
if (message.s3Config !== undefined) {
|
||||
WorkspaceSetting_StorageSetting_S3Config.encode(message.s3Config, writer.uint32(34).fork()).join();
|
||||
}
|
||||
if (message.enableS3ImageThumbnails !== false) {
|
||||
writer.uint32(40).bool(message.enableS3ImageThumbnails);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
|
|
@ -764,6 +775,14 @@ export const WorkspaceSetting_StorageSetting: MessageFns<WorkspaceSetting_Storag
|
|||
message.s3Config = WorkspaceSetting_StorageSetting_S3Config.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enableS3ImageThumbnails = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
|
@ -784,6 +803,7 @@ export const WorkspaceSetting_StorageSetting: MessageFns<WorkspaceSetting_Storag
|
|||
message.s3Config = (object.s3Config !== undefined && object.s3Config !== null)
|
||||
? WorkspaceSetting_StorageSetting_S3Config.fromPartial(object.s3Config)
|
||||
: undefined;
|
||||
message.enableS3ImageThumbnails = object.enableS3ImageThumbnails ?? false;
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import workspaceStore from "@/store/workspace";
|
||||
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
||||
import { WorkspaceSetting_Key } from "@/types/proto/api/v1/workspace_service";
|
||||
|
||||
export const getAttachmentUrl = (attachment: Attachment) => {
|
||||
if (attachment.externalLink) {
|
||||
|
|
@ -9,6 +11,14 @@ export const getAttachmentUrl = (attachment: Attachment) => {
|
|||
};
|
||||
|
||||
export const getAttachmentThumbnailUrl = (attachment: Attachment) => {
|
||||
// Don't request thumbnails for S3 images if the setting is disabled
|
||||
if (
|
||||
attachment.externalLink &&
|
||||
!(workspaceStore.getWorkspaceSettingByKey(WorkspaceSetting_Key.STORAGE).storageSetting?.enableS3ImageThumbnails ?? false)
|
||||
) {
|
||||
return getAttachmentUrl(attachment);
|
||||
}
|
||||
|
||||
return `${window.location.origin}/file/${attachment.name}/${attachment.filename}?thumbnail=true`;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue