Compare commits
1 Commits
6f15f82122
...
0b58c3347b
Author | SHA1 | Date | |
---|---|---|---|
|
0b58c3347b |
@ -9,7 +9,13 @@ import {
|
|||||||
tokens,
|
tokens,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@fluentui/react-components"
|
} from "@fluentui/react-components"
|
||||||
import {DatabaseLinkRegular, DismissRegular} from "@fluentui/react-icons";
|
import {
|
||||||
|
DatabaseLinkRegular,
|
||||||
|
DeleteRegular,
|
||||||
|
DismissRegular,
|
||||||
|
PlugConnectedRegular,
|
||||||
|
SettingsRegular
|
||||||
|
} from "@fluentui/react-icons";
|
||||||
import React, { useEffect, useState} from "react";
|
import React, { useEffect, useState} from "react";
|
||||||
import { Connection} from "../../interfaces/connection";
|
import { Connection} from "../../interfaces/connection";
|
||||||
import {useToast} from "../../message";
|
import {useToast} from "../../message";
|
||||||
@ -92,6 +98,7 @@ export function ConnectionList() {
|
|||||||
y: number,
|
y: number,
|
||||||
display: 'none' | 'block'
|
display: 'none' | 'block'
|
||||||
}>({x: 0, y: 0, display: 'none'});
|
}>({x: 0, y: 0, display: 'none'});
|
||||||
|
const [menu_conn, set_menu_conn] = useState<Connection | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
@ -112,7 +119,8 @@ export function ConnectionList() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleConnect(item: Connection) {
|
async function handleConnect(item: Connection|null) {
|
||||||
|
if (!item) return;
|
||||||
let res = await Dial('/api/connection/connect', {id: item.id});
|
let res = await Dial('/api/connection/connect', {id: item.id});
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
dispatchMessage(res.msg, "error")
|
dispatchMessage(res.msg, "error")
|
||||||
@ -137,6 +145,7 @@ export function ConnectionList() {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
console.log('[DEBUG] right click connection =', item, 'event =', e)
|
console.log('[DEBUG] right click connection =', item, 'event =', e)
|
||||||
console.log(`[DEBUG] click position: [${e.pageX}, ${e.pageY}]`)
|
console.log(`[DEBUG] click position: [${e.pageX}, ${e.pageY}]`)
|
||||||
|
set_menu_conn(item)
|
||||||
set_ctx_menu({
|
set_ctx_menu({
|
||||||
x: e.pageX,
|
x: e.pageX,
|
||||||
y: e.pageY,
|
y: e.pageY,
|
||||||
@ -164,9 +173,11 @@ export function ConnectionList() {
|
|||||||
style={{left: ctx_menu.x, top: ctx_menu.y, display: ctx_menu.display}}
|
style={{left: ctx_menu.x, top: ctx_menu.y, display: ctx_menu.display}}
|
||||||
>
|
>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem>连接</MenuItem>
|
<MenuItem
|
||||||
<MenuItem>设置</MenuItem>
|
onClick={async () => {await handleConnect(menu_conn)}}
|
||||||
<MenuItem>删除</MenuItem>
|
icon={<PlugConnectedRegular />}>连接</MenuItem>
|
||||||
|
<MenuItem icon={<SettingsRegular />}>设置</MenuItem>
|
||||||
|
<MenuItem icon={<DeleteRegular />}>删除</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.items}>
|
<div className={styles.items}>
|
||||||
|
3
frontend/src/component/preview/preview.tsx
Normal file
3
frontend/src/component/preview/preview.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function PreviewFile() {
|
||||||
|
|
||||||
|
}
|
@ -4,12 +4,14 @@ import {Dial} from "../api";
|
|||||||
|
|
||||||
interface StoreFile {
|
interface StoreFile {
|
||||||
prefix: string;
|
prefix: string;
|
||||||
|
filter:string;
|
||||||
files_list: S3File[];
|
files_list: S3File[];
|
||||||
files_get: (conn: Connection, bucket: Bucket, prefix?: string, filter?: string) => void;
|
files_get: (conn: Connection, bucket: Bucket, prefix?: string, filter?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStoreFile = create<StoreFile>()((set) => ({
|
export const useStoreFile = create<StoreFile>()((set) => ({
|
||||||
prefix: "",
|
prefix: "",
|
||||||
|
filter: "",
|
||||||
files_list: [],
|
files_list: [],
|
||||||
files_get: async (conn: Connection, bucket: Bucket, prefix = '', filter = '') => {
|
files_get: async (conn: Connection, bucket: Bucket, prefix = '', filter = '') => {
|
||||||
const res = await Dial<{ list: S3File[] }>('/api/bucket/files', {
|
const res = await Dial<{ list: S3File[] }>('/api/bucket/files', {
|
||||||
@ -23,7 +25,7 @@ export const useStoreFile = create<StoreFile>()((set) => ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
return {files_list: res.data.list, prefix: prefix}
|
return {files_list: res.data.list, prefix: prefix, filter: filter}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
18
frontend/src/store/preview.tsx
Normal file
18
frontend/src/store/preview.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
import {create} from 'zustand'
|
||||||
|
import {Bucket, Connection} from "../interfaces/connection";
|
||||||
|
|
||||||
|
interface StorePreview {
|
||||||
|
preview_key: string;
|
||||||
|
preview_url: string;
|
||||||
|
preview_content_type: string;
|
||||||
|
preview_set: (conn:Connection,bucket: Bucket,key: string) => void;
|
||||||
|
}
|
||||||
|
export const useStoreFile = create<StorePreview>()((set) => ({
|
||||||
|
preview_key: '',
|
||||||
|
preview_url: '',
|
||||||
|
preview_content_type: '',
|
||||||
|
preview_set: (conn:Connection,bucket: Bucket,key: string) => set(state => {
|
||||||
|
return {preview_key: key}
|
||||||
|
}),
|
||||||
|
}))
|
@ -34,6 +34,8 @@ func Init(ctx context.Context) error {
|
|||||||
register("/api/bucket/files", handler.BucketFiles)
|
register("/api/bucket/files", handler.BucketFiles)
|
||||||
register("/api/bucket/create", handler.BucketCreate)
|
register("/api/bucket/create", handler.BucketCreate)
|
||||||
register("/api/file/upload", handler.FileUpload)
|
register("/api/file/upload", handler.FileUpload)
|
||||||
|
register("/api/file/info", handler.FileInfo)
|
||||||
|
register("/api/file/get", handler.FileGet)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -86,3 +86,61 @@ func FileUpload(c *ndh.Ctx) error {
|
|||||||
|
|
||||||
return c.Send200(req)
|
return c.Send200(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FileInfo(c *ndh.Ctx) error {
|
||||||
|
type Req struct {
|
||||||
|
ConnId uint64 `json:"conn_id"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
req = new(Req)
|
||||||
|
client *s3.Client
|
||||||
|
info *s3.ObjectInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = c.ReqParse(req); err != nil {
|
||||||
|
return c.Send400(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, client, err = manager.Manager.Use(req.ConnId); err != nil {
|
||||||
|
return c.Send500(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if info, err = client.GetObjectInfo(c.Context(), req.Bucket, req.Key); err != nil {
|
||||||
|
return c.Send500(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send200(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FileGet(c *ndh.Ctx) error {
|
||||||
|
type Req struct {
|
||||||
|
ConnId uint64 `json:"conn_id"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
req = new(Req)
|
||||||
|
client *s3.Client
|
||||||
|
link *s3.ObjectEntry
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = c.ReqParse(req); err != nil {
|
||||||
|
return c.Send400(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, client, err = manager.Manager.Use(req.ConnId); err != nil {
|
||||||
|
return c.Send500(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if link, err = client.GetObject(c.Context(), req.Bucket, req.Key); err != nil {
|
||||||
|
return c.Send500(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send200(link)
|
||||||
|
}
|
||||||
|
90
internal/s3/get.go
Normal file
90
internal/s3/get.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/labstack/gommon/log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ObjectInfo struct {
|
||||||
|
Bucket string
|
||||||
|
Key string
|
||||||
|
ContentType string
|
||||||
|
Expire int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetObjectInfo(ctx context.Context, bucket string, key string) (*ObjectInfo, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
input = &s3.HeadObjectInput{
|
||||||
|
Bucket: aws.String(bucket),
|
||||||
|
Key: aws.String(key),
|
||||||
|
}
|
||||||
|
output *s3.HeadObjectOutput
|
||||||
|
)
|
||||||
|
|
||||||
|
if output, err = c.client.HeadObject(ctx, input); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ObjectInfo{
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: key,
|
||||||
|
ContentType: aws.ToString(output.ContentType),
|
||||||
|
Expire: aws.ToTime(output.Expires).UnixMilli(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetObject
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/example_s3_Scenario_PresignedUrl_section.html
|
||||||
|
|
||||||
|
type Presigner struct {
|
||||||
|
PresignClient *s3.PresignClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (presigner *Presigner) GetObject(ctx context.Context, bucketName string, objectKey string, lifetimeSecs int64) (*v4.PresignedHTTPRequest, error) {
|
||||||
|
request, err := presigner.PresignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String(bucketName),
|
||||||
|
Key: aws.String(objectKey),
|
||||||
|
}, func(opts *s3.PresignOptions) {
|
||||||
|
opts.Expires = time.Duration(lifetimeSecs * int64(time.Second))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Presigner: couldn't get a presigned request to get %v:%v. Here's why: %v\n",
|
||||||
|
bucketName, objectKey, err)
|
||||||
|
}
|
||||||
|
return request, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjectEntry struct {
|
||||||
|
URL string
|
||||||
|
Method string
|
||||||
|
Header http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetObject(ctx context.Context, bucket string, key string, lifetimes ...int64) (*ObjectEntry, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
lifetime int64 = 5 * 60
|
||||||
|
pc = &Presigner{PresignClient: s3.NewPresignClient(c.client)}
|
||||||
|
output *v4.PresignedHTTPRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(lifetimes) > 0 && lifetimes[0] > 0 {
|
||||||
|
lifetime = lifetimes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if output, err = pc.GetObject(ctx, bucket, key, lifetime); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ObjectEntry{
|
||||||
|
URL: output.URL,
|
||||||
|
Method: output.Method,
|
||||||
|
Header: output.SignedHeader,
|
||||||
|
}, nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user