Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
9c5b44d0
Unverified
Commit
9c5b44d0
authored
Feb 23, 2023
by
boojack
Committed by
GitHub
Feb 23, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: update storage schema (#1142)
parent
84fb8b22
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
271 additions
and
229 deletions
+271
-229
storage.go
api/storage.go
+26
-20
s3.go
plugin/storage/s3/s3.go
+24
-18
resource.go
server/resource.go
+26
-14
LATEST__SCHEMA.sql
store/db/migration/dev/LATEST__SCHEMA.sql
+4
-8
storage.go
store/storage.go
+73
-70
CreateStorageServiceDialog.tsx
web/src/components/CreateStorageServiceDialog.tsx
+89
-74
StorageSection.tsx
web/src/components/Settings/StorageSection.tsx
+7
-7
api.ts
web/src/helpers/api.ts
+3
-3
storage.d.ts
web/src/types/modules/storage.d.ts
+19
-15
No files found.
api/storage.go
View file @
9c5b44d0
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
{
...
...
plugin/storage/s3/s3.go
View file @
9c5b44d0
...
...
@@ -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
Config
struct
{
AccessKey
string
SecretKey
string
Bucket
string
EndPoint
string
Region
string
URLPrefix
string
}
type
Client
struct
{
Client
*
awss3
.
Client
BucketName
string
URLPrefix
string
Client
*
awss3
.
Client
Config
*
Config
}
func
NewClient
(
ctx
context
.
Context
,
storage
*
api
.
Storage
)
(
*
Client
,
error
)
{
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
:=
s3
config
.
LoadDefaultConfig
(
ctx
,
s3
config
.
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
}
server/resource.go
View file @
9c5b44d0
...
...
@@ -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
)
}
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
,
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
)
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"
)
}
}
...
...
store/db/migration/dev/LATEST__SCHEMA.sql
View file @
9c5b44d0
...
...
@@ -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
'{}'
...
...
store/storage.go
View file @
9c5b44d0
...
...
@@ -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
.
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
)
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
))
}
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
)
}
...
...
web/src/components/CreateStorageServiceDialog.tsx
View file @
9c5b44d0
...
...
@@ -7,15 +7,18 @@ import Icon from "./Icon";
import
toastHelper
from
"./Toast"
;
interface
Props
extends
DialogProps
{
storage
?:
Storage
;
storage
?:
Object
Storage
;
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
?:
Object
Storage
,
confirmCallback
?:
()
=>
void
)
{
generateDialog
(
{
className
:
"create-storage-service-dialog"
,
...
...
web/src/components/Settings/StorageSection.tsx
View file @
9c5b44d0
...
...
@@ -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
<
Object
Storage
[]
>
([]);
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
:
Object
Storage
)
=>
{
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"
>
...
...
web/src/helpers/api.ts
View file @
9c5b44d0
...
...
@@ -215,15 +215,15 @@ export function deleteTag(tagName: string) {
}
export
function
getStorageList
()
{
return
axios
.
get
<
ResponseObject
<
Storage
[]
>>
(
`/api/storage`
);
return
axios
.
get
<
ResponseObject
<
Object
Storage
[]
>>
(
`/api/storage`
);
}
export
function
createStorage
(
storageCreate
:
StorageCreate
)
{
return
axios
.
post
<
ResponseObject
<
Storage
>>
(
`/api/storage`
,
storageCreate
);
return
axios
.
post
<
ResponseObject
<
Object
Storage
>>
(
`/api/storage`
,
storageCreate
);
}
export
function
patchStorage
(
storagePatch
:
StoragePatch
)
{
return
axios
.
patch
<
ResponseObject
<
Storage
>>
(
`/api/storage/
${
storagePatch
.
id
}
`
,
storagePatch
);
return
axios
.
patch
<
ResponseObject
<
Object
Storage
>>
(
`/api/storage/
${
storagePatch
.
id
}
`
,
storagePatch
);
}
export
function
deleteStorage
(
storageId
:
StorageId
)
{
...
...
web/src/types/modules/storage.d.ts
View file @
9c5b44d0
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
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment