mirror of
https://github.com/usememos/memos.git
synced 2024-12-25 04:13:07 +03:00
feat: update storage schema (#1142)
This commit is contained in:
parent
84fb8b2288
commit
9c5b44d070
@ -1,8 +1,16 @@
|
||||
package api
|
||||
|
||||
type Storage struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
type StorageType string
|
||||
|
||||
const (
|
||||
StorageS3 StorageType = "S3"
|
||||
)
|
||||
|
||||
type StorageConfig struct {
|
||||
S3Config *StorageS3Config `json:"s3Config"`
|
||||
}
|
||||
|
||||
type StorageS3Config struct {
|
||||
EndPoint string `json:"endPoint"`
|
||||
Region string `json:"region"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
@ -11,30 +19,28 @@ type Storage struct {
|
||||
URLPrefix string `json:"urlPrefix"`
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type StorageType `json:"type"`
|
||||
Config *StorageConfig `json:"config"`
|
||||
}
|
||||
|
||||
type StorageCreate struct {
|
||||
Name string `json:"name"`
|
||||
EndPoint string `json:"endPoint"`
|
||||
Region string `json:"region"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Bucket string `json:"bucket"`
|
||||
URLPrefix string `json:"urlPrefix"`
|
||||
Name string `json:"name"`
|
||||
Type StorageType `json:"type"`
|
||||
Config *StorageConfig `json:"config"`
|
||||
}
|
||||
|
||||
type StoragePatch struct {
|
||||
ID int `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
EndPoint *string `json:"endPoint"`
|
||||
Region *string `json:"region"`
|
||||
AccessKey *string `json:"accessKey"`
|
||||
SecretKey *string `json:"secretKey"`
|
||||
Bucket *string `json:"bucket"`
|
||||
URLPrefix *string `json:"urlPrefix"`
|
||||
ID int `json:"id"`
|
||||
Type StorageType `json:"type"`
|
||||
Name *string `json:"name"`
|
||||
Config *StorageConfig `json:"config"`
|
||||
}
|
||||
|
||||
type StorageFind struct {
|
||||
ID *int `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
ID *int `json:"id"`
|
||||
}
|
||||
|
||||
type StorageDelete struct {
|
||||
|
@ -6,31 +6,38 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
s3config "github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
||||
awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/usememos/memos/api"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Client *awss3.Client
|
||||
BucketName string
|
||||
URLPrefix string
|
||||
type Config struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Bucket string
|
||||
EndPoint string
|
||||
Region string
|
||||
URLPrefix string
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, storage *api.Storage) (*Client, error) {
|
||||
type Client struct {
|
||||
Client *awss3.Client
|
||||
Config *Config
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, config *Config) (*Client, error) {
|
||||
resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
||||
return aws.Endpoint{
|
||||
URL: storage.EndPoint,
|
||||
SigningRegion: storage.Region,
|
||||
URL: config.EndPoint,
|
||||
SigningRegion: config.Region,
|
||||
}, nil
|
||||
})
|
||||
|
||||
cfg, err := config.LoadDefaultConfig(ctx,
|
||||
config.WithEndpointResolverWithOptions(resolver),
|
||||
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(storage.AccessKey, storage.SecretKey, "")),
|
||||
cfg, err := s3config.LoadDefaultConfig(ctx,
|
||||
s3config.WithEndpointResolverWithOptions(resolver),
|
||||
s3config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(config.AccessKey, config.SecretKey, "")),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -39,16 +46,15 @@ func NewClient(ctx context.Context, storage *api.Storage) (*Client, error) {
|
||||
client := awss3.NewFromConfig(cfg)
|
||||
|
||||
return &Client{
|
||||
Client: client,
|
||||
BucketName: storage.Bucket,
|
||||
URLPrefix: storage.URLPrefix,
|
||||
Client: client,
|
||||
Config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (client *Client) UploadFile(ctx context.Context, filename string, fileType string, src io.Reader, storage *api.Storage) (string, error) {
|
||||
func (client *Client) UploadFile(ctx context.Context, filename string, fileType string, src io.Reader) (string, error) {
|
||||
uploader := manager.NewUploader(client.Client)
|
||||
resp, err := uploader.Upload(ctx, &awss3.PutObjectInput{
|
||||
Bucket: aws.String(client.BucketName),
|
||||
Bucket: aws.String(client.Config.Bucket),
|
||||
Key: aws.String(filename),
|
||||
Body: src,
|
||||
ContentType: aws.String(fileType),
|
||||
@ -58,10 +64,10 @@ func (client *Client) UploadFile(ctx context.Context, filename string, fileType
|
||||
return "", err
|
||||
}
|
||||
var link string
|
||||
if storage.URLPrefix == "" {
|
||||
if client.Config.URLPrefix == "" {
|
||||
link = resp.Location
|
||||
} else {
|
||||
link = fmt.Sprintf("%s/%s", storage.URLPrefix, filename)
|
||||
link = fmt.Sprintf("%s/%s", client.Config.URLPrefix, filename)
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
@ -111,20 +111,32 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
|
||||
}
|
||||
|
||||
s3client, err := s3.NewClient(ctx, storage)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to new s3 client").SetInternal(err)
|
||||
}
|
||||
if storage.Type == api.StorageS3 {
|
||||
s3Config := storage.Config.S3Config
|
||||
s3client, err := s3.NewClient(ctx, &s3.Config{
|
||||
AccessKey: s3Config.AccessKey,
|
||||
SecretKey: s3Config.SecretKey,
|
||||
EndPoint: s3Config.EndPoint,
|
||||
Region: s3Config.Region,
|
||||
Bucket: s3Config.Bucket,
|
||||
URLPrefix: s3Config.URLPrefix,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to new s3 client").SetInternal(err)
|
||||
}
|
||||
|
||||
link, err := s3client.UploadFile(ctx, filename, filetype, src, storage)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upload via s3 client").SetInternal(err)
|
||||
}
|
||||
resourceCreate = &api.ResourceCreate{
|
||||
CreatorID: userID,
|
||||
Filename: filename,
|
||||
Type: filetype,
|
||||
ExternalLink: link,
|
||||
link, err := s3client.UploadFile(ctx, filename, filetype, src)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upload via s3 client").SetInternal(err)
|
||||
}
|
||||
resourceCreate = &api.ResourceCreate{
|
||||
CreatorID: userID,
|
||||
Filename: filename,
|
||||
Type: filetype,
|
||||
ExternalLink: link,
|
||||
}
|
||||
} else {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unsupported storage type")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,19 +108,15 @@ CREATE TABLE activity (
|
||||
-- storage
|
||||
CREATE TABLE storage (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL DEFAULT '' UNIQUE,
|
||||
end_point TEXT NOT NULL DEFAULT '',
|
||||
region TEXT NOT NULL DEFAULT '',
|
||||
access_key TEXT NOT NULL DEFAULT '',
|
||||
secret_key TEXT NOT NULL DEFAULT '',
|
||||
bucket TEXT NOT NULL DEFAULT '',
|
||||
url_prefix TEXT NOT NULL DEFAULT ''
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
config TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- idp
|
||||
CREATE TABLE idp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
identifier_filter TEXT NOT NULL DEFAULT '',
|
||||
config TEXT NOT NULL DEFAULT '{}'
|
||||
|
143
store/storage.go
143
store/storage.go
@ -3,6 +3,7 @@ package store
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -11,26 +12,18 @@ import (
|
||||
)
|
||||
|
||||
type storageRaw struct {
|
||||
ID int
|
||||
Name string
|
||||
EndPoint string
|
||||
Region string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Bucket string
|
||||
URLPrefix string
|
||||
ID int
|
||||
Name string
|
||||
Type api.StorageType
|
||||
Config *api.StorageConfig
|
||||
}
|
||||
|
||||
func (raw *storageRaw) toStorage() *api.Storage {
|
||||
return &api.Storage{
|
||||
ID: raw.ID,
|
||||
Name: raw.Name,
|
||||
EndPoint: raw.EndPoint,
|
||||
Region: raw.Region,
|
||||
AccessKey: raw.AccessKey,
|
||||
SecretKey: raw.SecretKey,
|
||||
Bucket: raw.Bucket,
|
||||
URLPrefix: raw.URLPrefix,
|
||||
ID: raw.ID,
|
||||
Name: raw.Name,
|
||||
Type: raw.Type,
|
||||
Config: raw.Config,
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,27 +124,36 @@ func (s *Store) DeleteStorage(ctx context.Context, delete *api.StorageDelete) er
|
||||
}
|
||||
|
||||
func createStorageRaw(ctx context.Context, tx *sql.Tx, create *api.StorageCreate) (*storageRaw, error) {
|
||||
set := []string{"name", "end_point", "region", "access_key", "secret_key", "bucket", "url_prefix"}
|
||||
args := []interface{}{create.Name, create.EndPoint, create.Region, create.AccessKey, create.SecretKey, create.Bucket, create.URLPrefix}
|
||||
placeholder := []string{"?", "?", "?", "?", "?", "?", "?"}
|
||||
set := []string{"name", "type", "config"}
|
||||
args := []interface{}{create.Name, create.Type}
|
||||
placeholder := []string{"?", "?", "?"}
|
||||
|
||||
var configBytes []byte
|
||||
var err error
|
||||
if create.Type == api.StorageS3 {
|
||||
configBytes, err = json.Marshal(create.Config.S3Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported storage type %s", string(create.Type))
|
||||
}
|
||||
args = append(args, string(configBytes))
|
||||
|
||||
query := `
|
||||
INSERT INTO storage (
|
||||
` + strings.Join(set, ", ") + `
|
||||
)
|
||||
VALUES (` + strings.Join(placeholder, ",") + `)
|
||||
RETURNING id, name, end_point, region, access_key, secret_key, bucket, url_prefix
|
||||
RETURNING id
|
||||
`
|
||||
var storageRaw storageRaw
|
||||
storageRaw := storageRaw{
|
||||
Name: create.Name,
|
||||
Type: create.Type,
|
||||
Config: create.Config,
|
||||
}
|
||||
if err := tx.QueryRowContext(ctx, query, args...).Scan(
|
||||
&storageRaw.ID,
|
||||
&storageRaw.Name,
|
||||
&storageRaw.EndPoint,
|
||||
&storageRaw.Region,
|
||||
&storageRaw.AccessKey,
|
||||
&storageRaw.SecretKey,
|
||||
&storageRaw.Bucket,
|
||||
&storageRaw.URLPrefix,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
@ -164,47 +166,48 @@ func patchStorageRaw(ctx context.Context, tx *sql.Tx, patch *api.StoragePatch) (
|
||||
if v := patch.Name; v != nil {
|
||||
set, args = append(set, "name = ?"), append(args, *v)
|
||||
}
|
||||
if v := patch.EndPoint; v != nil {
|
||||
set, args = append(set, "end_point = ?"), append(args, *v)
|
||||
if v := patch.Config; v != nil {
|
||||
var configBytes []byte
|
||||
var err error
|
||||
if patch.Type == api.StorageS3 {
|
||||
configBytes, err = json.Marshal(patch.Config.S3Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported storage type %s", string(patch.Type))
|
||||
}
|
||||
set, args = append(set, "config = ?"), append(args, string(configBytes))
|
||||
}
|
||||
if v := patch.Region; v != nil {
|
||||
set, args = append(set, "region = ?"), append(args, *v)
|
||||
}
|
||||
if v := patch.AccessKey; v != nil {
|
||||
set, args = append(set, "access_key = ?"), append(args, *v)
|
||||
}
|
||||
if v := patch.SecretKey; v != nil {
|
||||
set, args = append(set, "secret_key = ?"), append(args, *v)
|
||||
}
|
||||
if v := patch.Bucket; v != nil {
|
||||
set, args = append(set, "bucket = ?"), append(args, *v)
|
||||
}
|
||||
if v := patch.URLPrefix; v != nil {
|
||||
set, args = append(set, "url_prefix = ?"), append(args, *v)
|
||||
}
|
||||
|
||||
args = append(args, patch.ID)
|
||||
|
||||
query := `
|
||||
UPDATE storage
|
||||
SET ` + strings.Join(set, ", ") + `
|
||||
WHERE id = ?
|
||||
RETURNING id, name, end_point, region, access_key, secret_key, bucket, url_prefix
|
||||
RETURNING id, name, type, config
|
||||
`
|
||||
|
||||
var storageRaw storageRaw
|
||||
var storageConfig string
|
||||
if err := tx.QueryRowContext(ctx, query, args...).Scan(
|
||||
&storageRaw.ID,
|
||||
&storageRaw.Name,
|
||||
&storageRaw.EndPoint,
|
||||
&storageRaw.Region,
|
||||
&storageRaw.AccessKey,
|
||||
&storageRaw.SecretKey,
|
||||
&storageRaw.Bucket,
|
||||
&storageRaw.URLPrefix,
|
||||
&storageRaw.Type,
|
||||
&storageConfig,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
if storageRaw.Type == api.StorageS3 {
|
||||
s3Config := &api.StorageS3Config{}
|
||||
if err := json.Unmarshal([]byte(storageConfig), s3Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storageRaw.Config = &api.StorageConfig{
|
||||
S3Config: s3Config,
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported storage type %s", string(storageRaw.Type))
|
||||
}
|
||||
|
||||
return &storageRaw, nil
|
||||
}
|
||||
@ -215,20 +218,13 @@ func findStorageRawList(ctx context.Context, tx *sql.Tx, find *api.StorageFind)
|
||||
if v := find.ID; v != nil {
|
||||
where, args = append(where, "id = ?"), append(args, *v)
|
||||
}
|
||||
if v := find.Name; v != nil {
|
||||
where, args = append(where, "name = ?"), append(args, *v)
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
end_point,
|
||||
region,
|
||||
access_key,
|
||||
secret_key,
|
||||
bucket,
|
||||
url_prefix
|
||||
type,
|
||||
config
|
||||
FROM storage
|
||||
WHERE ` + strings.Join(where, " AND ") + `
|
||||
ORDER BY id DESC
|
||||
@ -242,19 +238,26 @@ func findStorageRawList(ctx context.Context, tx *sql.Tx, find *api.StorageFind)
|
||||
storageRawList := make([]*storageRaw, 0)
|
||||
for rows.Next() {
|
||||
var storageRaw storageRaw
|
||||
var storageConfig string
|
||||
if err := rows.Scan(
|
||||
&storageRaw.ID,
|
||||
&storageRaw.Name,
|
||||
&storageRaw.EndPoint,
|
||||
&storageRaw.Region,
|
||||
&storageRaw.AccessKey,
|
||||
&storageRaw.SecretKey,
|
||||
&storageRaw.Bucket,
|
||||
&storageRaw.URLPrefix,
|
||||
&storageRaw.Type,
|
||||
&storageConfig,
|
||||
); err != nil {
|
||||
return nil, FormatError(err)
|
||||
}
|
||||
|
||||
if storageRaw.Type == api.StorageS3 {
|
||||
s3Config := &api.StorageS3Config{}
|
||||
if err := json.Unmarshal([]byte(storageConfig), s3Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storageRaw.Config = &api.StorageConfig{
|
||||
S3Config: s3Config,
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported storage type %s", string(storageRaw.Type))
|
||||
}
|
||||
storageRawList = append(storageRawList, &storageRaw)
|
||||
}
|
||||
|
||||
|
@ -7,15 +7,18 @@ import Icon from "./Icon";
|
||||
import toastHelper from "./Toast";
|
||||
|
||||
interface Props extends DialogProps {
|
||||
storage?: Storage;
|
||||
storage?: ObjectStorage;
|
||||
confirmCallback?: () => void;
|
||||
}
|
||||
|
||||
const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy, storage, confirmCallback } = props;
|
||||
const { t } = useTranslation();
|
||||
const [storageCreate, setStorageCreate] = useState<StorageCreate>({
|
||||
const [basicInfo, setBasicInfo] = useState({
|
||||
name: "",
|
||||
});
|
||||
const [type, setType] = useState<StorageType>("S3");
|
||||
const [s3Config, setS3Config] = useState<StorageS3Config>({
|
||||
endPoint: "",
|
||||
region: "",
|
||||
accessKey: "",
|
||||
@ -27,7 +30,13 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (storage) {
|
||||
setStorageCreate({ ...storage });
|
||||
setBasicInfo({
|
||||
name: storage.name,
|
||||
});
|
||||
setType(storage.type);
|
||||
if (storage.type === "S3") {
|
||||
setS3Config(storage.config.s3Config);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
@ -36,27 +45,35 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
const allowConfirmAction = () => {
|
||||
if (
|
||||
storageCreate.name === "" ||
|
||||
storageCreate.endPoint === "" ||
|
||||
storageCreate.region === "" ||
|
||||
storageCreate.accessKey === "" ||
|
||||
storageCreate.bucket === "" ||
|
||||
storageCreate.bucket === ""
|
||||
) {
|
||||
if (basicInfo.name === "") {
|
||||
return false;
|
||||
}
|
||||
if (type === "S3") {
|
||||
if (s3Config.endPoint === "" || s3Config.region === "" || s3Config.accessKey === "" || s3Config.bucket === "") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleConfirmBtnClick = async () => {
|
||||
try {
|
||||
if (isCreating) {
|
||||
await api.createStorage(storageCreate);
|
||||
await api.createStorage({
|
||||
...basicInfo,
|
||||
type: type,
|
||||
config: {
|
||||
s3Config: s3Config,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await api.patchStorage({
|
||||
id: storage.id,
|
||||
...storageCreate,
|
||||
type: type,
|
||||
...basicInfo,
|
||||
config: {
|
||||
s3Config: s3Config,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
@ -69,59 +86,10 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
|
||||
destroy();
|
||||
};
|
||||
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const name = event.target.value;
|
||||
setStorageCreate({
|
||||
...storageCreate,
|
||||
name,
|
||||
});
|
||||
};
|
||||
|
||||
const handleEndPointChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const endPoint = event.target.value;
|
||||
setStorageCreate({
|
||||
...storageCreate,
|
||||
endPoint,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRegionChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const region = event.target.value;
|
||||
setStorageCreate({
|
||||
...storageCreate,
|
||||
region,
|
||||
});
|
||||
};
|
||||
|
||||
const handleAccessKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const accessKey = event.target.value;
|
||||
setStorageCreate({
|
||||
...storageCreate,
|
||||
accessKey,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSecretKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const secretKey = event.target.value;
|
||||
setStorageCreate({
|
||||
...storageCreate,
|
||||
secretKey,
|
||||
});
|
||||
};
|
||||
|
||||
const handleBucketChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const bucket = event.target.value;
|
||||
setStorageCreate({
|
||||
...storageCreate,
|
||||
bucket,
|
||||
});
|
||||
};
|
||||
|
||||
const handleURLPrefixChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const urlPrefix = event.target.value;
|
||||
setStorageCreate({
|
||||
...storageCreate,
|
||||
urlPrefix,
|
||||
const setPartialS3Config = (state: Partial<StorageS3Config>) => {
|
||||
setS3Config({
|
||||
...s3Config,
|
||||
...state,
|
||||
});
|
||||
};
|
||||
|
||||
@ -140,37 +108,84 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
|
||||
Name
|
||||
<span className="text-sm text-gray-400 ml-1">(Unique identifier)</span>
|
||||
</Typography>
|
||||
<Input className="mb-2" placeholder="Name" value={storageCreate.name} onChange={handleNameChange} fullWidth />
|
||||
<Input
|
||||
className="mb-2"
|
||||
placeholder="Name"
|
||||
value={basicInfo.name}
|
||||
onChange={(e) =>
|
||||
setBasicInfo({
|
||||
...basicInfo,
|
||||
name: e.target.value,
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
<Typography className="!mb-1" level="body2">
|
||||
EndPoint
|
||||
<span className="text-sm text-gray-400 ml-1">(S3-compatible server URL)</span>
|
||||
</Typography>
|
||||
<Input className="mb-2" placeholder="EndPoint" value={storageCreate.endPoint} onChange={handleEndPointChange} fullWidth />
|
||||
<Input
|
||||
className="mb-2"
|
||||
placeholder="EndPoint"
|
||||
value={s3Config.endPoint}
|
||||
onChange={(e) => setPartialS3Config({ endPoint: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<Typography className="!mb-1" level="body2">
|
||||
Region
|
||||
<span className="text-sm text-gray-400 ml-1">(Region name)</span>
|
||||
</Typography>
|
||||
<Input className="mb-2" placeholder="Region" value={storageCreate.region} onChange={handleRegionChange} fullWidth />
|
||||
<Input
|
||||
className="mb-2"
|
||||
placeholder="Region"
|
||||
value={s3Config.region}
|
||||
onChange={(e) => setPartialS3Config({ region: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<Typography className="!mb-1" level="body2">
|
||||
AccessKey
|
||||
<span className="text-sm text-gray-400 ml-1">(Access Key / Access ID)</span>
|
||||
</Typography>
|
||||
<Input className="mb-2" placeholder="AccessKey" value={storageCreate.accessKey} onChange={handleAccessKeyChange} fullWidth />
|
||||
<Input
|
||||
className="mb-2"
|
||||
placeholder="AccessKey"
|
||||
value={s3Config.accessKey}
|
||||
onChange={(e) => setPartialS3Config({ accessKey: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<Typography className="!mb-1" level="body2">
|
||||
SecretKey
|
||||
<span className="text-sm text-gray-400 ml-1">(Secret Key / Secret Access Key)</span>
|
||||
</Typography>
|
||||
<Input className="mb-2" placeholder="SecretKey" value={storageCreate.secretKey} onChange={handleSecretKeyChange} fullWidth />
|
||||
<Input
|
||||
className="mb-2"
|
||||
placeholder="SecretKey"
|
||||
value={s3Config.secretKey}
|
||||
onChange={(e) => setPartialS3Config({ secretKey: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<Typography className="!mb-1" level="body2">
|
||||
Bucket
|
||||
<span className="text-sm text-gray-400 ml-1">(Bucket name)</span>
|
||||
</Typography>
|
||||
<Input className="mb-2" placeholder="Bucket" value={storageCreate.bucket} onChange={handleBucketChange} fullWidth />
|
||||
<Input
|
||||
className="mb-2"
|
||||
placeholder="Bucket"
|
||||
value={s3Config.bucket}
|
||||
onChange={(e) => setPartialS3Config({ bucket: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<Typography className="!mb-1" level="body2">
|
||||
URLPrefix
|
||||
<span className="text-sm text-gray-400 ml-1">(Custom URL prefix; Optional)</span>
|
||||
</Typography>
|
||||
<Input className="mb-2" placeholder="URLPrefix" value={storageCreate.urlPrefix} onChange={handleURLPrefixChange} fullWidth />
|
||||
<Input
|
||||
className="mb-2"
|
||||
placeholder="URLPrefix"
|
||||
value={s3Config.urlPrefix}
|
||||
onChange={(e) => setPartialS3Config({ urlPrefix: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<div className="mt-2 w-full flex flex-row justify-end items-center space-x-1">
|
||||
<Button variant="plain" color="neutral" onClick={handleCloseBtnClick}>
|
||||
Cancel
|
||||
@ -184,7 +199,7 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
function showCreateStorageServiceDialog(storage?: Storage, confirmCallback?: () => void) {
|
||||
function showCreateStorageServiceDialog(storage?: ObjectStorage, confirmCallback?: () => void) {
|
||||
generateDialog(
|
||||
{
|
||||
className: "create-storage-service-dialog",
|
||||
|
@ -13,16 +13,12 @@ const StorageSection = () => {
|
||||
const globalStore = useGlobalStore();
|
||||
const systemStatus = globalStore.state.systemStatus;
|
||||
const [storageServiceId, setStorageServiceId] = useState(systemStatus.storageServiceId);
|
||||
const [storageList, setStorageList] = useState<Storage[]>([]);
|
||||
const [storageList, setStorageList] = useState<ObjectStorage[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStorageList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setStorageServiceId(systemStatus.storageServiceId);
|
||||
}, [systemStatus]);
|
||||
|
||||
const fetchStorageList = async () => {
|
||||
const {
|
||||
data: { data: storageList },
|
||||
@ -31,6 +27,10 @@ const StorageSection = () => {
|
||||
};
|
||||
|
||||
const handleActiveStorageServiceChanged = async (storageId: StorageId) => {
|
||||
if (storageList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await api.upsertSystemSetting({
|
||||
name: "storageServiceId",
|
||||
value: JSON.stringify(storageId),
|
||||
@ -38,7 +38,7 @@ const StorageSection = () => {
|
||||
setStorageServiceId(storageId);
|
||||
};
|
||||
|
||||
const handleDeleteStorage = (storage: Storage) => {
|
||||
const handleDeleteStorage = (storage: ObjectStorage) => {
|
||||
showCommonDialog({
|
||||
title: t("setting.storage-section.delete-storage"),
|
||||
content: t("setting.storage-section.warning-text"),
|
||||
@ -68,12 +68,12 @@ const StorageSection = () => {
|
||||
handleActiveStorageServiceChanged(storageId || 0);
|
||||
}}
|
||||
>
|
||||
<Option value={0}>Database</Option>
|
||||
{storageList.map((storage) => (
|
||||
<Option key={storage.id} value={storage.id}>
|
||||
{storage.name}
|
||||
</Option>
|
||||
))}
|
||||
<Option value={0}>Database</Option>
|
||||
</Select>
|
||||
<Divider />
|
||||
<div className="mt-4 mb-2 w-full flex flex-row justify-start items-center">
|
||||
|
@ -215,15 +215,15 @@ export function deleteTag(tagName: string) {
|
||||
}
|
||||
|
||||
export function getStorageList() {
|
||||
return axios.get<ResponseObject<Storage[]>>(`/api/storage`);
|
||||
return axios.get<ResponseObject<ObjectStorage[]>>(`/api/storage`);
|
||||
}
|
||||
|
||||
export function createStorage(storageCreate: StorageCreate) {
|
||||
return axios.post<ResponseObject<Storage>>(`/api/storage`, storageCreate);
|
||||
return axios.post<ResponseObject<ObjectStorage>>(`/api/storage`, storageCreate);
|
||||
}
|
||||
|
||||
export function patchStorage(storagePatch: StoragePatch) {
|
||||
return axios.patch<ResponseObject<Storage>>(`/api/storage/${storagePatch.id}`, storagePatch);
|
||||
return axios.patch<ResponseObject<ObjectStorage>>(`/api/storage/${storagePatch.id}`, storagePatch);
|
||||
}
|
||||
|
||||
export function deleteStorage(storageId: StorageId) {
|
||||
|
34
web/src/types/modules/storage.d.ts
vendored
34
web/src/types/modules/storage.d.ts
vendored
@ -1,8 +1,8 @@
|
||||
type StorageId = number;
|
||||
|
||||
interface Storage {
|
||||
id: StorageId;
|
||||
name: string;
|
||||
type StorageType = "S3";
|
||||
|
||||
interface StorageS3Config {
|
||||
endPoint: string;
|
||||
region: string;
|
||||
accessKey: string;
|
||||
@ -11,23 +11,27 @@ interface Storage {
|
||||
urlPrefix: string;
|
||||
}
|
||||
|
||||
interface StorageConfig {
|
||||
s3Config: StorageS3Config;
|
||||
}
|
||||
|
||||
// Note: Storage is a reserved word in TypeScript. So we use ObjectStorage instead.
|
||||
interface ObjectStorage {
|
||||
id: StorageId;
|
||||
name: string;
|
||||
type: StorageType;
|
||||
config: StorageConfig;
|
||||
}
|
||||
|
||||
interface StorageCreate {
|
||||
name: string;
|
||||
endPoint: string;
|
||||
region: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
bucket: string;
|
||||
urlPrefix: string;
|
||||
type: StorageType;
|
||||
config: StorageConfig;
|
||||
}
|
||||
|
||||
interface StoragePatch {
|
||||
id: StorageId;
|
||||
name: string;
|
||||
endPoint: string;
|
||||
region: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
bucket: string;
|
||||
urlPrefix: string;
|
||||
type: StorageType;
|
||||
config: StorageConfig;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user