Compare commits
1 Commits
dev/zhaoyu
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
a0c3ee8ac0 |
39
.github/workflows/build.yml
vendored
Normal file
39
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
name: Auto Build Windows
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-job:
|
||||||
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
repository-projects: write
|
||||||
|
steps:
|
||||||
|
- name: install node
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: install golang
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '1.22'
|
||||||
|
|
||||||
|
- name: install wails
|
||||||
|
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
run: wails build -ldflags='-s -w'
|
||||||
|
|
||||||
|
- name: create release
|
||||||
|
id: create_release
|
||||||
|
uses: "marvinpinto/action-automatic-releases@latest"
|
||||||
|
with:
|
||||||
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
title: "Release_Windows_${{ github.ref_name }}"
|
||||||
|
files: |
|
||||||
|
build/bin/nf-disk.exe
|
@ -4,7 +4,7 @@ import {VirtualizerScrollView} from "@fluentui/react-components/unstable";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {useStoreBucket} from "../../store/bucket";
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
import {Bucket} from "../../interfaces/connection";
|
import {Bucket} from "../../interfaces/connection";
|
||||||
import {useStoreFile} from "../../store/file";
|
import {useStoreFile, useStoreFileFilter} from "../../store/file";
|
||||||
import {useStoreConnection} from "../../store/connection";
|
import {useStoreConnection} from "../../store/connection";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
@ -35,13 +35,13 @@ const useStyles = makeStyles({
|
|||||||
export function ListBucketComponent() {
|
export function ListBucketComponent() {
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const {conn_active} = useStoreConnection()
|
|
||||||
const {bucket_set, bucket_list} = useStoreBucket()
|
const {bucket_set, bucket_list} = useStoreBucket()
|
||||||
const {files_get} = useStoreFile()
|
const {filter_set, prefix_set} = useStoreFileFilter()
|
||||||
|
|
||||||
async function handleClick(item: Bucket) {
|
async function handleClick(item: Bucket) {
|
||||||
bucket_set(item)
|
await bucket_set(item)
|
||||||
files_get(conn_active!, item, "")
|
await filter_set('')
|
||||||
|
await prefix_set('')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: Bucket) {
|
function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: Bucket) {
|
||||||
@ -50,7 +50,7 @@ export function ListBucketComponent() {
|
|||||||
|
|
||||||
return <MenuList className={styles.container}>
|
return <MenuList className={styles.container}>
|
||||||
<VirtualizerScrollView
|
<VirtualizerScrollView
|
||||||
numItems={bucket_list?bucket_list.length:0}
|
numItems={bucket_list ? bucket_list.length : 0}
|
||||||
itemSize={32}
|
itemSize={32}
|
||||||
container={{role: 'list', style: {maxHeight: 'calc(100vh - 9rem)'}}}
|
container={{role: 'list', style: {maxHeight: 'calc(100vh - 9rem)'}}}
|
||||||
>
|
>
|
||||||
|
@ -2,26 +2,26 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
|
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
mergeClasses,
|
mergeClasses,
|
||||||
tokens,
|
tokens,
|
||||||
Tooltip
|
Tooltip,
|
||||||
} from "@fluentui/react-components"
|
} from "@fluentui/react-components";
|
||||||
import {
|
import {
|
||||||
DatabaseLinkRegular,
|
DatabaseLinkRegular,
|
||||||
DeleteRegular,
|
DeleteRegular,
|
||||||
DismissRegular,
|
DismissRegular,
|
||||||
PlugConnectedRegular, PlugDisconnectedRegular,
|
PlugConnectedRegular,
|
||||||
SettingsRegular
|
PlugDisconnectedRegular,
|
||||||
|
SettingsRegular,
|
||||||
} from "@fluentui/react-icons";
|
} 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";
|
||||||
import {Dial} from "../../api";
|
import { Dial } from "../../api";
|
||||||
import {useStoreConnection} from "../../store/connection";
|
import { useStoreConnection } from "../../store/connection";
|
||||||
import {useStoreBucket} from "../../store/bucket";
|
import { useStoreBucket } from "../../store/bucket";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
list: {
|
list: {
|
||||||
@ -77,87 +77,96 @@ const useStyles = makeStyles({
|
|||||||
marginLeft: "auto",
|
marginLeft: "auto",
|
||||||
},
|
},
|
||||||
slider: {
|
slider: {
|
||||||
height: '100%', width: '1px',
|
height: "100%",
|
||||||
|
width: "1px",
|
||||||
// todo: resize
|
// todo: resize
|
||||||
// cursor: 'ew-resize',
|
// cursor: 'ew-resize',
|
||||||
'& > div': {
|
"& > div": {
|
||||||
height: '100%', width: '1px',
|
height: "100%",
|
||||||
backgroundColor: 'lightgray',
|
width: "1px",
|
||||||
|
backgroundColor: "lightgray",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export function ConnectionList() {
|
export function ConnectionList() {
|
||||||
const styles = useStyles()
|
const styles = useStyles();
|
||||||
const {dispatchMessage} = useToast();
|
const { dispatchMessage } = useToast();
|
||||||
const {conn_list, conn_update} = useStoreConnection();
|
const { conn_get, conn_list, conn_set } = useStoreConnection();
|
||||||
const [conn_filter, set_conn_filter] = useState<string>('');
|
const [conn_filter, set_conn_filter] = useState<string>("");
|
||||||
const {bucket_get, bucket_set} = useStoreBucket()
|
const { bucket_get, bucket_set } = useStoreBucket();
|
||||||
const [ctx_menu, set_ctx_menu] = useState<{
|
const [ctx_menu, set_ctx_menu] = useState<{
|
||||||
x: number,
|
x: number;
|
||||||
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);
|
const [menu_conn, set_menu_conn] = useState<Connection | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
set_ctx_menu({x: 0, y: 0, display: 'none'});
|
set_ctx_menu({ x: 0, y: 0, display: "none" });
|
||||||
})
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
conn_get().then();
|
||||||
|
}, 1000);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("click", (e) => {
|
document.removeEventListener("click", (e) => {});
|
||||||
})
|
};
|
||||||
}
|
}, []);
|
||||||
}, [])
|
|
||||||
|
|
||||||
async function handleSelect(item: Connection) {
|
async function handleSelect(item: Connection) {
|
||||||
conn_list.map((one: Connection) => {
|
conn_list.map((one: Connection) => {
|
||||||
if (item.id === one.id && one.active) {
|
if (item.id === one.id && one.active) {
|
||||||
conn_update(one)
|
conn_set(one);
|
||||||
bucket_get(one, false)
|
bucket_get(one, false);
|
||||||
bucket_set(null)
|
bucket_set(null);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleConnect(item: Connection | null) {
|
async function handleConnect(item: Connection | null) {
|
||||||
if (!item) return;
|
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");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn_update({...item, active: true})
|
await conn_set({ ...item, active: true });
|
||||||
bucket_get(item, true)
|
bucket_get(item, true);
|
||||||
bucket_set(null)
|
await bucket_set(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDisconnect(item: Connection | null) {
|
async function handleDisconnect(item: Connection | null) {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
let res = await Dial('/api/connection/disconnect', {id: item.id})
|
let res = await Dial("/api/connection/disconnect", { id: item.id });
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
dispatchMessage(res.msg, "error")
|
dispatchMessage(res.msg, "error");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
conn_update({...item, active: false})
|
await conn_set({ ...item, active: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: Connection) {
|
async function handleRightClick(
|
||||||
e.preventDefault()
|
e: React.MouseEvent<HTMLDivElement>,
|
||||||
set_menu_conn(item)
|
item: Connection
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
set_menu_conn(item);
|
||||||
|
|
||||||
const ele = document.querySelector('#list-connection-container')
|
const ele = document.querySelector("#list-connection-container");
|
||||||
const eleX = ele ? ele.clientWidth : 0
|
const eleX = ele ? ele.clientWidth : 0;
|
||||||
const eleY = ele ? ele.clientHeight : 0
|
const eleY = ele ? ele.clientHeight : 0;
|
||||||
const positionX = (e.pageX + eleX > window.innerWidth) ? e.pageX - eleX : e.pageX
|
const positionX =
|
||||||
const positionY = (e.pageY + eleY > window.innerHeight) ? e.pageY - eleY : e.pageY
|
e.pageX + eleX > window.innerWidth ? e.pageX - eleX : e.pageX;
|
||||||
|
const positionY =
|
||||||
|
e.pageY + eleY > window.innerHeight ? e.pageY - eleY : e.pageY;
|
||||||
|
|
||||||
set_ctx_menu({
|
set_ctx_menu({
|
||||||
x: positionX,
|
x: positionX,
|
||||||
y: positionY,
|
y: positionY,
|
||||||
display: 'block',
|
display: "block",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -168,52 +177,90 @@ export function ConnectionList() {
|
|||||||
value={conn_filter}
|
value={conn_filter}
|
||||||
className={styles.filter_input}
|
className={styles.filter_input}
|
||||||
contentAfter={
|
contentAfter={
|
||||||
<Button appearance={'transparent'} onClick={async () => {
|
<Button
|
||||||
set_conn_filter('')
|
appearance={"transparent"}
|
||||||
}} size="small" icon={<DismissRegular/>}/>
|
onClick={async () => {
|
||||||
|
set_conn_filter("");
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
icon={<DismissRegular />}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
placeholder="搜索连接"
|
placeholder="搜索连接"
|
||||||
onChange={(e) => set_conn_filter(e.target.value)}
|
onChange={(e) => set_conn_filter(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id={'list-connection-container'}
|
id={"list-connection-container"}
|
||||||
className={styles.ctx_menu}
|
className={styles.ctx_menu}
|
||||||
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
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleConnect(menu_conn)
|
await handleConnect(menu_conn);
|
||||||
}}
|
}}
|
||||||
icon={<PlugConnectedRegular/>}>连接</MenuItem>
|
icon={<PlugConnectedRegular />}
|
||||||
|
>
|
||||||
|
连接
|
||||||
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleDisconnect(menu_conn)
|
await handleDisconnect(menu_conn);
|
||||||
}}
|
}}
|
||||||
icon={<PlugDisconnectedRegular/>}>断开</MenuItem>
|
icon={<PlugDisconnectedRegular />}
|
||||||
<MenuItem icon={<SettingsRegular/>}>设置</MenuItem>
|
>
|
||||||
<MenuItem icon={<DeleteRegular/>}>删除</MenuItem>
|
断开
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
dispatchMessage("暂未实现", "warning");
|
||||||
|
}}
|
||||||
|
icon={<SettingsRegular />}
|
||||||
|
>
|
||||||
|
设置
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
dispatchMessage("暂未实现", "warning");
|
||||||
|
}}
|
||||||
|
icon={<DeleteRegular />}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.items}>
|
<div className={styles.items}>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
{conn_list.filter(item => item.name.includes(conn_filter)).map(item => {
|
{conn_list
|
||||||
return <MenuItem
|
.filter((item) => item.name.includes(conn_filter))
|
||||||
className={item.active ? mergeClasses(styles.items_one, "active") : styles.items_one}
|
.map((item) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
className={
|
||||||
|
item.active
|
||||||
|
? mergeClasses(styles.items_one, "active")
|
||||||
|
: styles.items_one
|
||||||
|
}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleSelect(item)
|
await handleSelect(item);
|
||||||
}}
|
}}
|
||||||
onDoubleClick={async () => {
|
onDoubleClick={async () => {
|
||||||
await handleConnect(item)
|
await handleConnect(item);
|
||||||
}}
|
}}
|
||||||
onContextMenu={async (e) => {
|
onContextMenu={async (e) => {
|
||||||
await handleRightClick(e, item)
|
await handleRightClick(e, item);
|
||||||
}}
|
}}
|
||||||
icon={<DatabaseLinkRegular/>}
|
icon={<DatabaseLinkRegular />}
|
||||||
key={item.id}>
|
key={item.id}
|
||||||
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</div>
|
</div>
|
||||||
@ -222,5 +269,5 @@ export function ConnectionList() {
|
|||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
@ -5,13 +5,18 @@ import {
|
|||||||
DialogBody,
|
DialogBody,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Button, Spinner, Field, Input, makeStyles, tokens,
|
Button,
|
||||||
|
Spinner,
|
||||||
|
Field,
|
||||||
|
Input,
|
||||||
|
makeStyles,
|
||||||
|
tokens,
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import {useState} from "react";
|
import { useState } from "react";
|
||||||
import {CheckmarkFilled, DismissRegular} from "@fluentui/react-icons";
|
import { CheckmarkFilled, DismissRegular } from "@fluentui/react-icons";
|
||||||
import {useToast} from "../../message";
|
import { useToast } from "../../message";
|
||||||
import {Dial} from "../../api";
|
import { Dial } from "../../api";
|
||||||
import {useStoreConnection} from "../../store/connection";
|
import { useStoreConnection } from "../../store/connection";
|
||||||
|
|
||||||
const useActionStyle = makeStyles({
|
const useActionStyle = makeStyles({
|
||||||
container: {
|
container: {
|
||||||
@ -22,7 +27,7 @@ const useActionStyle = makeStyles({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
gridColumnStart: 0,
|
gridColumnStart: 0,
|
||||||
},
|
},
|
||||||
test: {}
|
test: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface ConnectionCreateProps {
|
export interface ConnectionCreateProps {
|
||||||
@ -31,117 +36,130 @@ export interface ConnectionCreateProps {
|
|||||||
|
|
||||||
export function ConnectionCreate(props: ConnectionCreateProps) {
|
export function ConnectionCreate(props: ConnectionCreateProps) {
|
||||||
const actionStyle = useActionStyle();
|
const actionStyle = useActionStyle();
|
||||||
const {dispatchMessage} = useToast();
|
const { dispatchMessage } = useToast();
|
||||||
const [testLoading, setTestLoading] = useState<"initial" | "loading" | "success" | "error">("initial");
|
const [testLoading, setTestLoading] = useState<
|
||||||
const {conn_get} = useStoreConnection();
|
"initial" | "loading" | "success" | "error"
|
||||||
|
>("initial");
|
||||||
|
const { conn_get } = useStoreConnection();
|
||||||
const buttonIcon =
|
const buttonIcon =
|
||||||
testLoading === "loading" ? (
|
testLoading === "loading" ? (
|
||||||
<Spinner size="tiny"/>
|
<Spinner size="tiny" />
|
||||||
) : testLoading === "success" ? (
|
) : testLoading === "success" ? (
|
||||||
<CheckmarkFilled/>
|
<CheckmarkFilled />
|
||||||
) : testLoading === "error" ? (
|
) : testLoading === "error" ? (
|
||||||
<DismissRegular/>
|
<DismissRegular />
|
||||||
) : null;
|
) : null;
|
||||||
const [value, setValue] = useState<{ name: string, endpoint: string, access: string, key: string }>({
|
const [value, setValue] = useState<{
|
||||||
name: '',
|
name: string;
|
||||||
endpoint: '',
|
endpoint: string;
|
||||||
access: '',
|
access: string;
|
||||||
key: ''
|
key: string;
|
||||||
})
|
}>({
|
||||||
|
name: "",
|
||||||
|
endpoint: "",
|
||||||
|
access: "",
|
||||||
|
key: "",
|
||||||
|
});
|
||||||
|
|
||||||
async function test() {
|
async function test() {
|
||||||
setTestLoading("loading")
|
setTestLoading("loading");
|
||||||
let res = await Dial<string>("/api/connection/test", value)
|
let res = await Dial<string>("/api/connection/test", value);
|
||||||
const status = res.status === 200 ? "success" : "error"
|
const status = res.status === 200 ? "success" : "error";
|
||||||
setTestLoading(status);
|
setTestLoading(status);
|
||||||
dispatchMessage(res.msg, status)
|
dispatchMessage(res.msg, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
// self
|
let res = await Dial("/api/connection/create", value);
|
||||||
// qUvfW8xpOTc23O96
|
|
||||||
// eTcuc8BebHPVpZZwIaNmzfwxRxPYGfTj
|
|
||||||
|
|
||||||
// 48-dev
|
|
||||||
// OSIsqPrl0TkAUj3R
|
|
||||||
// FYF4BBzL2j2ObbVYH0FrvOZqJf1EACRy
|
|
||||||
let res = await Dial("/api/connection/create", value)
|
|
||||||
dispatchMessage(res.msg, res.status === 200 ? "success" : "error");
|
dispatchMessage(res.msg, res.status === 200 ? "success" : "error");
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
dispatchMessage("新建连接成功", "success");
|
dispatchMessage("新建连接成功", "success");
|
||||||
conn_get()
|
await conn_get();
|
||||||
props.openFn(false)
|
props.openFn(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return (
|
||||||
|
<>
|
||||||
<DialogSurface>
|
<DialogSurface>
|
||||||
<DialogBody>
|
<DialogBody>
|
||||||
<DialogTitle>新建S3连接</DialogTitle>
|
<DialogTitle>新建S3连接</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div className='connection-container'>
|
<div className="connection-container">
|
||||||
<div className='connection-form'>
|
<div className="connection-form">
|
||||||
<div className='connection-form-field'>
|
<div className="connection-form-field">
|
||||||
<Field
|
<Field label="name">
|
||||||
label="name"
|
<Input
|
||||||
>
|
placeholder="名称 (example: 测试S3-minio)"
|
||||||
<Input placeholder='名称 (example: 测试S3-minio)' value={value.name}
|
value={value.name}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setValue({...value, name: e.target.value});
|
setValue({ ...value, name: e.target.value });
|
||||||
}}/>
|
}}
|
||||||
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
<div className='connection-form-field'>
|
<div className="connection-form-field">
|
||||||
<Field
|
<Field label="endpoint" required>
|
||||||
label="endpoint"
|
<Input
|
||||||
required
|
placeholder="地址 (example: https://ip_or_server-name:port)"
|
||||||
>
|
|
||||||
<Input placeholder='地址 (example: https://ip_or_server-name:port)'
|
|
||||||
value={value.endpoint}
|
value={value.endpoint}
|
||||||
required
|
required
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setValue({...value, endpoint: e.target.value});
|
setValue({ ...value, endpoint: e.target.value });
|
||||||
}}/>
|
}}
|
||||||
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
<div className='connection-form-field'>
|
<div className="connection-form-field">
|
||||||
<Field
|
<Field label="secret access" required>
|
||||||
label="secret access"
|
<Input
|
||||||
|
placeholder=""
|
||||||
required
|
required
|
||||||
>
|
value={value.access}
|
||||||
<Input placeholder=''
|
onChange={(e) => {
|
||||||
required
|
setValue({ ...value, access: e.target.value });
|
||||||
value={value.access} onChange={(e) => {
|
}}
|
||||||
setValue({...value, access: e.target.value});
|
/>
|
||||||
}}/>
|
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
<div className='connection-form-field'>
|
<div className="connection-form-field">
|
||||||
<Field
|
<Field label="secret key" required>
|
||||||
label="secret key"
|
<Input
|
||||||
|
placeholder=""
|
||||||
required
|
required
|
||||||
>
|
value={value.key}
|
||||||
<Input placeholder=''
|
onChange={(e) => {
|
||||||
required
|
setValue({ ...value, key: e.target.value });
|
||||||
value={value.key} onChange={(e) => {
|
}}
|
||||||
setValue({...value, key: e.target.value});
|
/>
|
||||||
}}/>
|
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions className={actionStyle.container}>
|
<DialogActions className={actionStyle.container}>
|
||||||
<Button className={actionStyle.test} appearance='transparent' icon={buttonIcon}
|
<Button
|
||||||
onClick={async () => await test()}>测试连接</Button>
|
className={actionStyle.test}
|
||||||
|
appearance="transparent"
|
||||||
|
icon={buttonIcon}
|
||||||
|
onClick={async () => await test()}
|
||||||
|
>
|
||||||
|
测试连接
|
||||||
|
</Button>
|
||||||
<DialogTrigger disableButtonEnhancement>
|
<DialogTrigger disableButtonEnhancement>
|
||||||
<Button appearance="secondary">取消</Button>
|
<Button appearance="secondary">取消</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<Button onClick={async () => {
|
<Button
|
||||||
await create()
|
onClick={async () => {
|
||||||
}} appearance="primary">新建</Button>
|
await create();
|
||||||
|
}}
|
||||||
|
appearance="primary"
|
||||||
|
>
|
||||||
|
新建
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
</DialogSurface>
|
</DialogSurface>
|
||||||
</>
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
@ -3,6 +3,8 @@ import {ListBucketComponent} from "../bucket/list_bucket";
|
|||||||
import {makeStyles} from "@fluentui/react-components";
|
import {makeStyles} from "@fluentui/react-components";
|
||||||
import {useStoreBucket} from "../../store/bucket";
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
import {ListFileComponent} from "./list_file";
|
import {ListFileComponent} from "./list_file";
|
||||||
|
import {useState} from "react";
|
||||||
|
import {PreviewFile} from "../preview/preview";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
content: {
|
content: {
|
||||||
@ -17,13 +19,21 @@ const useStyles = makeStyles({
|
|||||||
export function Content() {
|
export function Content() {
|
||||||
|
|
||||||
const styles = useStyles()
|
const styles = useStyles()
|
||||||
const {bucket_active } = useStoreBucket()
|
const [preview, set_preview] = useState<{ url: string, content_type: string }>({url: '', content_type: ''})
|
||||||
|
const {bucket_active} = useStoreBucket()
|
||||||
|
|
||||||
|
const closeFn = () => {
|
||||||
|
set_preview({url: '', content_type: ''})
|
||||||
|
}
|
||||||
|
|
||||||
return <div className={styles.content}>
|
return <div className={styles.content}>
|
||||||
<Path/>
|
<Path/>
|
||||||
{
|
{
|
||||||
|
preview.url ? <PreviewFile url={preview.url} content_type={preview.content_type} close={closeFn}/> :
|
||||||
|
(
|
||||||
bucket_active ?
|
bucket_active ?
|
||||||
<ListFileComponent/> :
|
<ListFileComponent set_preview_fn={set_preview}/> : <ListBucketComponent/>
|
||||||
<ListBucketComponent/>
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
@ -1,4 +1,11 @@
|
|||||||
import {makeStyles, MenuItem, MenuList, Text, tokens} from "@fluentui/react-components";
|
import {
|
||||||
|
makeStyles,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
Spinner,
|
||||||
|
Text,
|
||||||
|
tokens,
|
||||||
|
} from "@fluentui/react-components";
|
||||||
import {
|
import {
|
||||||
ArrowDownloadFilled,
|
ArrowDownloadFilled,
|
||||||
DeleteRegular,
|
DeleteRegular,
|
||||||
@ -12,52 +19,69 @@ import {
|
|||||||
DocumentPdfRegular,
|
DocumentPdfRegular,
|
||||||
DocumentYmlRegular,
|
DocumentYmlRegular,
|
||||||
FolderRegular,
|
FolderRegular,
|
||||||
PreviewLinkRegular
|
PreviewLinkRegular,
|
||||||
} from "@fluentui/react-icons";
|
} from "@fluentui/react-icons";
|
||||||
import {VirtualizerScrollView} from "@fluentui/react-components/unstable";
|
import { VirtualizerScrollView } from "@fluentui/react-components/unstable";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {useStoreBucket} from "../../store/bucket";
|
import { useStoreBucket } from "../../store/bucket";
|
||||||
import {S3File} from "../../interfaces/connection";
|
import { S3File } from "../../interfaces/connection";
|
||||||
import {useStoreFile} from "../../store/file";
|
import { useStoreFile, useStoreFileFilter } from "../../store/file";
|
||||||
import {useStoreConnection} from "../../store/connection";
|
import { useStoreConnection } from "../../store/connection";
|
||||||
import {TrimSuffix} from "../../hook/strings";
|
import { TrimSuffix } from "../../hook/strings";
|
||||||
import {Dial} from "../../api";
|
import { Dial } from "../../api";
|
||||||
import {useToast} from "../../message";
|
import { useToast } from "../../message";
|
||||||
|
import { CanPreview } from "../../hook/preview";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
container: {
|
container: {
|
||||||
marginTop: '0.5rem',
|
marginTop: "0.5rem",
|
||||||
maxWidth: 'calc(100vw - 25rem - 1px)',
|
maxWidth: "calc(100vw - 25.2rem)",
|
||||||
width: 'calc(100vw - 25rem - 1px)',
|
width: "calc(100vw - 25.2rem)",
|
||||||
height: 'calc(100vh - 9rem)',
|
maxHeight: "calc(100vh - 10rem)",
|
||||||
|
height: "calc(100vh - 10rem)",
|
||||||
},
|
},
|
||||||
row: {
|
loading: {
|
||||||
height: '32px',
|
flex: "1",
|
||||||
display: 'flex',
|
width: "100%",
|
||||||
marginLeft: '0.5rem',
|
height: "100%",
|
||||||
marginRight: '0.5rem',
|
display: "flex",
|
||||||
},
|
justifyContent: "center",
|
||||||
item: {
|
alignItems: "center",
|
||||||
width: '100%',
|
flexDirection: "column",
|
||||||
maxWidth: '100%',
|
|
||||||
"&:hover": {
|
|
||||||
color: tokens.colorNeutralForeground2BrandPressed,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
overflow: 'hidden',
|
|
||||||
width: 'calc(100vw - 32rem)',
|
|
||||||
display: "block",
|
|
||||||
},
|
},
|
||||||
no_data: {
|
no_data: {
|
||||||
flex: "1",
|
flex: "1",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
fontSize: '8rem',
|
fontSize: "8rem",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
flex: "1",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
height: "32px",
|
||||||
|
lineHeight: "32px",
|
||||||
|
display: "flex",
|
||||||
|
marginLeft: "0.5rem",
|
||||||
|
marginRight: "0.5rem",
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "100%",
|
||||||
|
"&:hover": {
|
||||||
|
color: tokens.colorNeutralForeground2BrandPressed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
overflow: "hidden",
|
||||||
|
width: "calc(100vw - 32rem)",
|
||||||
|
display: "block",
|
||||||
},
|
},
|
||||||
ctx_menu: {
|
ctx_menu: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@ -68,177 +92,249 @@ const useStyles = makeStyles({
|
|||||||
paddingTop: "4px",
|
paddingTop: "4px",
|
||||||
paddingBottom: "4px",
|
paddingBottom: "4px",
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export function ListFileComponent() {
|
export interface ListFileComponentProps {
|
||||||
|
set_preview_fn: React.Dispatch<
|
||||||
|
React.SetStateAction<{ url: string; content_type: string }>
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListFileComponent(props: ListFileComponentProps) {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const {dispatchMessage} = useToast();
|
const { dispatchMessage } = useToast();
|
||||||
const {conn_active} = useStoreConnection();
|
const { conn_active } = useStoreConnection();
|
||||||
const {bucket_active} = useStoreBucket()
|
const { bucket_active } = useStoreBucket();
|
||||||
const {file_active, files_get, file_set, files_list} = useStoreFile()
|
const { file_active, files_get, file_set, files_list } = useStoreFile();
|
||||||
|
const { prefix, filter, prefix_set } = useStoreFileFilter();
|
||||||
|
const [preview_content_type, set_preview_content_type] = useState("");
|
||||||
const [ctx_menu, set_ctx_menu] = useState<{
|
const [ctx_menu, set_ctx_menu] = useState<{
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
display: 'none' | 'block'
|
display: "none" | "block";
|
||||||
}>({x: 0, y: 0, display: 'none'});
|
}>({ x: 0, y: 0, display: "none" });
|
||||||
|
const [loading, set_loading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
set_ctx_menu({x: 0, y: 0, display: 'none'});
|
set_ctx_menu({ x: 0, y: 0, display: "none" });
|
||||||
})
|
});
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("click", (e) => {
|
document.removeEventListener("click", (e) => {});
|
||||||
})
|
};
|
||||||
}
|
}, []);
|
||||||
}, [])
|
|
||||||
|
useEffect(() => {
|
||||||
|
set_loading(true);
|
||||||
|
files_get(conn_active!, bucket_active!, prefix, filter).then(() => {
|
||||||
|
set_loading(false);
|
||||||
|
});
|
||||||
|
}, [conn_active, bucket_active, prefix, filter]);
|
||||||
|
|
||||||
const filename = (key: string) => {
|
const filename = (key: string) => {
|
||||||
let strs = TrimSuffix(key, "/").split("/")
|
let strs = TrimSuffix(key, "/").split("/");
|
||||||
return strs[strs.length - 1]
|
return strs[strs.length - 1];
|
||||||
}
|
};
|
||||||
|
|
||||||
async function handleClick(item: S3File) {
|
async function handleClick(item: S3File) {
|
||||||
if (item.type === 1) {
|
if (item.type === 1) {
|
||||||
files_get(conn_active!, bucket_active!, item.key)
|
await prefix_set(item.key);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: S3File) {
|
async function handleRightClick(
|
||||||
e.preventDefault()
|
e: React.MouseEvent<HTMLDivElement>,
|
||||||
await file_set(item.key)
|
item: S3File
|
||||||
const ele = document.querySelector('#list-file-container')
|
) {
|
||||||
const eleX = ele ? ele.clientWidth : 0
|
e.preventDefault();
|
||||||
const eleY = ele ? ele.clientHeight : 0
|
await file_set(item.key);
|
||||||
const positionX = (e.pageX + eleX > window.innerWidth) ? e.pageX - eleX : e.pageX
|
set_preview_content_type(CanPreview(item.name));
|
||||||
const positionY = (e.pageY + eleY > window.innerHeight) ? e.pageY - eleY : e.pageY
|
const ele = document.querySelector("#list-file-container");
|
||||||
|
let eleX = ele ? ele.clientWidth : 0;
|
||||||
|
let eleY = ele ? ele.clientHeight : 0;
|
||||||
|
const positionX =
|
||||||
|
e.pageX + eleX > window.innerWidth ? e.pageX - eleX : e.pageX;
|
||||||
|
const positionY =
|
||||||
|
e.pageY + eleY > window.innerHeight ? e.pageY - eleY : e.pageY;
|
||||||
set_ctx_menu({
|
set_ctx_menu({
|
||||||
x: positionX,
|
x: positionX,
|
||||||
y: positionY,
|
y: positionY,
|
||||||
display: 'block',
|
display: "block",
|
||||||
})
|
});
|
||||||
// const res = await Dial('/api/file/info', {conn_id: conn_active?.id, bucket: bucket_active?.name, key: item.key})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDownload(file: string | null) {
|
async function handleDownload(file: string | null) {
|
||||||
if (!file) return
|
if (!file) return;
|
||||||
const res1 = await Dial<{result:string}>("/runtime/dialog/save", {
|
const res1 = await Dial<{ result: string }>("/runtime/dialog/save", {
|
||||||
default_filename: file,
|
default_filename: file,
|
||||||
})
|
});
|
||||||
if (res1.status !== 200) {
|
if (res1.status !== 200) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (!res1.data) return
|
if (!res1.data) return;
|
||||||
const res2 = await Dial('/api/file/download', {
|
const res2 = await Dial("/api/file/download", {
|
||||||
conn_id: conn_active?.id,
|
conn_id: conn_active?.id,
|
||||||
bucket: bucket_active?.name,
|
bucket: bucket_active?.name,
|
||||||
key: file,
|
key: file,
|
||||||
location: res1.data.result,
|
location: res1.data.result,
|
||||||
})
|
});
|
||||||
if (res2.status === 200) {
|
if (res2.status === 200) {
|
||||||
dispatchMessage('保存文件成功', 'success')
|
dispatchMessage("保存文件成功", "success");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
async function handlePreview() {
|
||||||
|
const res = await Dial<{ url: string; method: string }>("/api/file/get", {
|
||||||
|
conn_id: conn_active?.id,
|
||||||
|
bucket: bucket_active?.name,
|
||||||
|
key: file_active ?? "",
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
dispatchMessage("预览失败", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
props.set_preview_fn({
|
||||||
|
url: res.data.url,
|
||||||
|
content_type: preview_content_type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
<div
|
<div
|
||||||
id={'list-file-container'}
|
id={"list-file-container"}
|
||||||
className={styles.ctx_menu}
|
className={styles.ctx_menu}
|
||||||
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
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleDownload(file_active)
|
await handleDownload(file_active);
|
||||||
}}
|
}}
|
||||||
icon={<ArrowDownloadFilled/>}>下载</MenuItem>
|
icon={<ArrowDownloadFilled />}
|
||||||
|
>
|
||||||
|
下载
|
||||||
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled
|
disabled={!preview_content_type}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
// await handleDisconnect(menu_conn)
|
await handlePreview();
|
||||||
}}
|
}}
|
||||||
icon={<PreviewLinkRegular/>}>预览</MenuItem>
|
icon={<PreviewLinkRegular />}
|
||||||
<MenuItem icon={<DeleteRegular/>}>删除</MenuItem>
|
>
|
||||||
|
预览
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
dispatchMessage("敬请期待...", "warning");
|
||||||
|
}}
|
||||||
|
icon={<DeleteRegular />}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</div>
|
</div>
|
||||||
<MenuList className={styles.container}>
|
<div
|
||||||
{files_list.length ?
|
className={styles.loading}
|
||||||
|
style={{ display: loading ? "flex" : "none" }}
|
||||||
|
>
|
||||||
|
<Spinner appearance="primary" label="加载中..." />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={styles.no_data}
|
||||||
|
style={{ display: !loading && !files_list.length ? "flex" : "none" }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<DocumentDismissRegular />
|
||||||
|
</div>
|
||||||
|
<Text size={900}>没有文件</Text>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ display: !loading && files_list.length ? "block" : "none" }}
|
||||||
|
>
|
||||||
<VirtualizerScrollView
|
<VirtualizerScrollView
|
||||||
numItems={files_list.length}
|
numItems={files_list.length}
|
||||||
itemSize={32}
|
itemSize={32}
|
||||||
container={{role: 'list', style: {maxHeight: 'calc(100vh - 9rem)'}}}
|
container={{
|
||||||
|
role: "list",
|
||||||
|
style: { maxHeight: "calc(100vh - 10rem)" },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{(idx) => {
|
{(idx) => {
|
||||||
return <div
|
return (
|
||||||
className={styles.row} key={idx}
|
<div
|
||||||
|
className={styles.row}
|
||||||
|
key={idx}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleClick(files_list[idx])
|
await handleClick(files_list[idx]);
|
||||||
}}
|
}}
|
||||||
onContextMenu={async (e) => {
|
onContextMenu={async (e) => {
|
||||||
await handleRightClick(e, files_list[idx])
|
await handleRightClick(e, files_list[idx]);
|
||||||
}}>
|
}}
|
||||||
<MenuItem className={styles.item}
|
>
|
||||||
icon={files_list[idx].type ? <FolderRegular/> :
|
<MenuItem
|
||||||
<FileIcon name={files_list[idx].name}/>}>
|
className={styles.item}
|
||||||
|
icon={
|
||||||
|
files_list[idx].type ? (
|
||||||
|
<FolderRegular />
|
||||||
|
) : (
|
||||||
|
<FileIcon name={files_list[idx].name} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
<Text truncate wrap={false} className={styles.text}>
|
<Text truncate wrap={false} className={styles.text}>
|
||||||
{filename(files_list[idx].key)}
|
{filename(files_list[idx].key)}
|
||||||
</Text>
|
</Text>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
</VirtualizerScrollView> : <div className={styles.no_data}>
|
</VirtualizerScrollView>
|
||||||
<div>
|
|
||||||
<DocumentDismissRegular/>
|
|
||||||
</div>
|
</div>
|
||||||
<Text size={900}>
|
|
||||||
没有文件
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
);
|
||||||
</MenuList>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileIconProps = {
|
type FileIconProps = {
|
||||||
name: string
|
name: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
function FileIcon(props: FileIconProps) {
|
function FileIcon(props: FileIconProps) {
|
||||||
const strings = props.name.split(".")
|
const strings = props.name.split(".");
|
||||||
const suffix = strings[strings.length - 1]
|
const suffix = strings[strings.length - 1];
|
||||||
switch (suffix) {
|
switch (suffix.toLowerCase()) {
|
||||||
case "png":
|
case "png":
|
||||||
return <DocumentImageRegular/>
|
return <DocumentImageRegular />;
|
||||||
case "jpg":
|
case "jpg":
|
||||||
return <DocumentImageRegular/>
|
return <DocumentImageRegular />;
|
||||||
case "jpeg":
|
case "jpeg":
|
||||||
return <DocumentImageRegular/>
|
return <DocumentImageRegular />;
|
||||||
case "gif":
|
case "gif":
|
||||||
return <DocumentImageRegular/>
|
return <DocumentImageRegular />;
|
||||||
case "db":
|
case "db":
|
||||||
return <DocumentDatabaseRegular/>
|
return <DocumentDatabaseRegular />;
|
||||||
case "sqlite":
|
case "sqlite":
|
||||||
return <DocumentDatabaseRegular/>
|
return <DocumentDatabaseRegular />;
|
||||||
case "sqlite3":
|
case "sqlite3":
|
||||||
return <DocumentDatabaseRegular/>
|
return <DocumentDatabaseRegular />;
|
||||||
case "pdf":
|
case "pdf":
|
||||||
return <DocumentPdfRegular/>
|
return <DocumentPdfRegular />;
|
||||||
case "css":
|
case "css":
|
||||||
return <DocumentCssRegular/>
|
return <DocumentCssRegular />;
|
||||||
case "js":
|
case "js":
|
||||||
return <DocumentJavascriptRegular/>
|
return <DocumentJavascriptRegular />;
|
||||||
case "yaml":
|
case "yaml":
|
||||||
return <DocumentYmlRegular/>
|
return <DocumentYmlRegular />;
|
||||||
case "yml":
|
case "yml":
|
||||||
return <DocumentYmlRegular/>
|
return <DocumentYmlRegular />;
|
||||||
case "html":
|
case "html":
|
||||||
return <DocumentChevronDoubleRegular/>
|
return <DocumentChevronDoubleRegular />;
|
||||||
case "json":
|
case "json":
|
||||||
return <DocumentChevronDoubleRegular/>
|
return <DocumentChevronDoubleRegular />;
|
||||||
case "go":
|
case "go":
|
||||||
return <DocumentChevronDoubleRegular/>
|
return <DocumentChevronDoubleRegular />;
|
||||||
default:
|
default:
|
||||||
return <DocumentBulletListRegular/>
|
return <DocumentBulletListRegular />;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,12 @@
|
|||||||
import {Button, Input, makeStyles, Text, tokens, Tooltip} from "@fluentui/react-components";
|
import {Button, Input, makeStyles, Text, tokens, Tooltip} from "@fluentui/react-components";
|
||||||
import {useStoreBucket} from "../../store/bucket";
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
import {ArchiveRegular, ArrowCurveUpLeftFilled} from "@fluentui/react-icons";
|
import {ArchiveRegular, ArrowCurveUpLeftFilled} from "@fluentui/react-icons";
|
||||||
import {useStoreFile} from "../../store/file";
|
import {useStoreFile, useStoreFileFilter} from "../../store/file";
|
||||||
import React from "react";
|
import React, {useState} from "react";
|
||||||
import {debounce} from 'lodash'
|
import {debounce} from 'lodash'
|
||||||
import {useStoreConnection} from "../../store/connection";
|
import {useStoreConnection} from "../../store/connection";
|
||||||
|
import {ListFileComponent} from "./list_file";
|
||||||
|
import {ListBucketComponent} from "../bucket/list_bucket";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
container: {
|
container: {
|
||||||
@ -51,17 +53,18 @@ const useStyles = makeStyles({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
export function Path() {
|
export function Path() {
|
||||||
const styles = useStyles()
|
const styles = useStyles()
|
||||||
const {conn_active} = useStoreConnection()
|
const {conn_active} = useStoreConnection()
|
||||||
const {bucket_active, bucket_get, bucket_set} = useStoreBucket()
|
const {bucket_active, bucket_get, bucket_set} = useStoreBucket()
|
||||||
const {prefix, files_get} = useStoreFile()
|
const {prefix, filter, prefix_set, filter_set} = useStoreFileFilter()
|
||||||
|
|
||||||
async function handleClickUp() {
|
async function handleClickUp() {
|
||||||
const dirs = prefix.split('/').filter((item => item))
|
const dirs = prefix.split('/').filter((item => item))
|
||||||
if (dirs.length > 0) {
|
if (dirs.length > 0) {
|
||||||
dirs.pop()
|
dirs.pop()
|
||||||
files_get(conn_active!, bucket_active!, dirs.join("/"))
|
await prefix_set(dirs.join('/'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,8 +73,8 @@ export function Path() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const handleFilterChange = debounce((e) => {
|
const handleFilterChange = debounce(async (e) => {
|
||||||
files_get(conn_active!, bucket_active!, prefix, e.target.value)
|
await filter_set(e.target.value)
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
return <div className={styles.container}>
|
return <div className={styles.container}>
|
||||||
|
@ -12,7 +12,7 @@ import {useToast} from "../../message";
|
|||||||
import {Dial} from "../../api";
|
import {Dial} from "../../api";
|
||||||
import {useStoreConnection} from "../../store/connection";
|
import {useStoreConnection} from "../../store/connection";
|
||||||
import {useStoreBucket} from "../../store/bucket";
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
import {useStoreFile} from "../../store/file";
|
import {useStoreFile, useStoreFileFilter} from "../../store/file";
|
||||||
import {MoreHorizontalRegular} from "@fluentui/react-icons";
|
import {MoreHorizontalRegular} from "@fluentui/react-icons";
|
||||||
|
|
||||||
const useStyle = makeStyles({
|
const useStyle = makeStyles({
|
||||||
@ -43,7 +43,8 @@ export function UploadFiles(props: UploadFilesProps) {
|
|||||||
|
|
||||||
const { conn_active} = useStoreConnection();
|
const { conn_active} = useStoreConnection();
|
||||||
const {bucket_active} = useStoreBucket();
|
const {bucket_active} = useStoreBucket();
|
||||||
const {prefix, files_get} = useStoreFile()
|
const {files_get} = useStoreFile()
|
||||||
|
const {prefix } = useStoreFileFilter()
|
||||||
|
|
||||||
const [selected, set_selected] = useState<string[]>([]);
|
const [selected, set_selected] = useState<string[]>([]);
|
||||||
|
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {Button, Input, makeStyles, MenuItem, MenuList, mergeClasses, tokens, Tooltip} from "@fluentui/react-components";
|
import {makeStyles} from "@fluentui/react-components";
|
||||||
import {DismissRegular} from "@fluentui/react-icons";
|
|
||||||
import {useEffect, useState} from "react";
|
|
||||||
import {Connection} from "../../interfaces/connection";
|
|
||||||
import {useStoreBucket} from "../../store/bucket";
|
|
||||||
import {useStoreConnection} from "../../store/connection";
|
|
||||||
import {Dial} from "../../api";
|
|
||||||
import {useToast} from "../../message";
|
|
||||||
import {ConnectionList} from "../connection/list";
|
import {ConnectionList} from "../connection/list";
|
||||||
import {Content} from "../file/content";
|
import {Content} from "../file/content";
|
||||||
|
|
||||||
@ -20,15 +13,9 @@ const useStyles = makeStyles({
|
|||||||
|
|
||||||
export function Body() {
|
export function Body() {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const {conn_get} = useStoreConnection();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
conn_get()
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
return <div className={styles.body}>
|
return <div className={styles.body}>
|
||||||
<ConnectionList/>
|
<ConnectionList/>
|
||||||
<Content />
|
<Content/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import {UploadFiles} from "../file/upload_files";
|
|||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
header: {
|
header: {
|
||||||
height: "5rem",
|
height: "5rem",
|
||||||
|
minHeight: '5rem',
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@ -64,7 +65,7 @@ export function Header() {
|
|||||||
open={open_upload}
|
open={open_upload}
|
||||||
onOpenChange={(event, data) => set_open_upload(data.open)}>
|
onOpenChange={(event, data) => set_open_upload(data.open)}>
|
||||||
<DialogTrigger disableButtonEnhancement>
|
<DialogTrigger disableButtonEnhancement>
|
||||||
<Button appearance="primary" icon={<DocumentArrowUpRegular />}>
|
<Button appearance="primary" icon={<DocumentArrowUpRegular/>}>
|
||||||
上传
|
上传
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
@ -1,3 +1,81 @@
|
|||||||
export function PreviewFile() {
|
import {Button, makeStyles,tokens, Text } from "@fluentui/react-components";
|
||||||
|
import { DismissRegular } from "@fluentui/react-icons";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const useStyle = makeStyles({
|
||||||
|
container: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100vw',
|
||||||
|
maxWidth: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
maxHeight: '100vh',
|
||||||
|
zIndex: 100,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: tokens.colorNeutralBackground1,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
height: '4rem',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
header_close_button: {
|
||||||
|
marginLeft: 'auto',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
height: '100%',
|
||||||
|
maxHeight: '100%',
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems:'center',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function PreviewFile(props: { url: string, content_type: string, close: () => void }) {
|
||||||
|
const styles = useStyle()
|
||||||
|
|
||||||
|
const category = props.content_type.split('/')[0]
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('keyup', (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
props.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keyup', () => { })
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
switch (category) {
|
||||||
|
case "image":
|
||||||
|
return <div className={styles.container}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
appearance="transparent"
|
||||||
|
className={styles.header_close_button}
|
||||||
|
onClick={() => { props.close() }}>
|
||||||
|
<DismissRegular />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.body}>
|
||||||
|
<img src={props.url}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
default:
|
||||||
|
return <div className={styles.container}>
|
||||||
|
<Text>该文件无法预览</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
13
frontend/src/hook/preview.ts
Normal file
13
frontend/src/hook/preview.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export function CanPreview(filename: string) {
|
||||||
|
const fs = filename.split(".")
|
||||||
|
switch (fs[fs.length - 1].toLowerCase()) {
|
||||||
|
case "jpg":
|
||||||
|
return "image/jpg"
|
||||||
|
case "jpeg":
|
||||||
|
return "image/jpg"
|
||||||
|
case "png":
|
||||||
|
return "image/png"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,8 @@ import {Dial} from "../api";
|
|||||||
interface StoreConnection {
|
interface StoreConnection {
|
||||||
conn_active: Connection | null;
|
conn_active: Connection | null;
|
||||||
conn_list: Connection[];
|
conn_list: Connection[];
|
||||||
conn_get: () => void;
|
conn_get: () => Promise<void>;
|
||||||
conn_update: (connection: Connection) => Promise<void>;
|
conn_set: (connection: Connection) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStoreConnection = create<StoreConnection>()((set) => ({
|
export const useStoreConnection = create<StoreConnection>()((set) => ({
|
||||||
@ -21,7 +21,7 @@ export const useStoreConnection = create<StoreConnection>()((set) => ({
|
|||||||
set({conn_list: res.data.list})
|
set({conn_list: res.data.list})
|
||||||
},
|
},
|
||||||
|
|
||||||
conn_update: async (connection: Connection) => {
|
conn_set: async (connection: Connection) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
return {
|
return {
|
||||||
conn_active: connection.active? connection: null,
|
conn_active: connection.active? connection: null,
|
||||||
|
@ -5,10 +5,8 @@ import {Dial} from "../api";
|
|||||||
interface StoreFile {
|
interface StoreFile {
|
||||||
file_active: string | null,
|
file_active: string | null,
|
||||||
file_set: (key: string) => Promise<void>,
|
file_set: (key: string) => Promise<void>,
|
||||||
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) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStoreFile = create<StoreFile>()((set) => ({
|
export const useStoreFile = create<StoreFile>()((set) => ({
|
||||||
@ -18,8 +16,6 @@ export const useStoreFile = create<StoreFile>()((set) => ({
|
|||||||
return {file_active: key}
|
return {file_active: key}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
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', {
|
||||||
@ -33,7 +29,25 @@ export const useStoreFile = create<StoreFile>()((set) => ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
return {files_list: res.data.list, prefix: prefix, filter: filter}
|
return {files_list: res.data.list}
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
interface StoreFileFilter {
|
||||||
|
prefix: string;
|
||||||
|
filter: string;
|
||||||
|
prefix_set: (prefix: string) => Promise<void>;
|
||||||
|
filter_set: (filter: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStoreFileFilter = create<StoreFileFilter>()((set) => ({
|
||||||
|
prefix: '',
|
||||||
|
filter: '',
|
||||||
|
prefix_set: async (keyword: string) => {
|
||||||
|
set(state => {return {prefix: keyword}})
|
||||||
|
},
|
||||||
|
filter_set: async (keyword: string) => {
|
||||||
|
set(state => {return {filter: keyword}})
|
||||||
|
},
|
||||||
}))
|
}))
|
@ -1,18 +1,33 @@
|
|||||||
import {create} from 'zustand'
|
import {create} from 'zustand'
|
||||||
|
import {Dial} from "../api";
|
||||||
import {Bucket, Connection} from "../interfaces/connection";
|
import {Bucket, Connection} from "../interfaces/connection";
|
||||||
|
|
||||||
interface StorePreview {
|
interface StorePreview {
|
||||||
preview_key: string;
|
|
||||||
preview_url: string;
|
preview_url: string;
|
||||||
preview_content_type: string;
|
preview_content_type: string;
|
||||||
preview_set: (key: string) => void;
|
preview_get: (conn:Connection,bucket: Bucket,key: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStorePreview = create<StorePreview>()((set) => ({
|
export const useStorePreview = create<StorePreview>()((set) => ({
|
||||||
preview_key: '',
|
|
||||||
preview_url: '',
|
preview_url: '',
|
||||||
preview_content_type: '',
|
preview_content_type: '',
|
||||||
preview_set: async (key: string) => set(state => {
|
preview_get: async (conn: Connection, bucket: Bucket,key: string) => {
|
||||||
return {preview_key: key}
|
if (key === '') {
|
||||||
}),
|
return set(()=>{return {preview_url: ''}})
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await Dial<{url:string,method:string}>('/api/file/get', {
|
||||||
|
conn_id: conn.id,
|
||||||
|
bucket: bucket.name,
|
||||||
|
key: key
|
||||||
|
})
|
||||||
|
|
||||||
|
if(res.status!=200) {
|
||||||
|
return set(()=>{return {preview_url: ''}})
|
||||||
|
}
|
||||||
|
|
||||||
|
set(()=>{
|
||||||
|
return {preview_url:res.data.url}
|
||||||
|
})
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -2,10 +2,12 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"github.com/loveuer/nf-disk/internal/api"
|
"github.com/loveuer/nf-disk/internal/api"
|
||||||
"github.com/loveuer/nf-disk/internal/db"
|
"github.com/loveuer/nf-disk/internal/db"
|
||||||
"github.com/loveuer/nf-disk/internal/manager"
|
"github.com/loveuer/nf-disk/internal/manager"
|
||||||
"github.com/loveuer/nf-disk/internal/model"
|
"github.com/loveuer/nf-disk/internal/model"
|
||||||
|
"github.com/loveuer/nf-disk/internal/opt"
|
||||||
"github.com/loveuer/nf-disk/internal/tool"
|
"github.com/loveuer/nf-disk/internal/tool"
|
||||||
"github.com/loveuer/nf-disk/ndh"
|
"github.com/loveuer/nf-disk/ndh"
|
||||||
"github.com/loveuer/nf/nft/log"
|
"github.com/loveuer/nf/nft/log"
|
||||||
@ -37,7 +39,8 @@ func NewApp(gctx context.Context) *App {
|
|||||||
func (a *App) Startup(ctx context.Context) {
|
func (a *App) Startup(ctx context.Context) {
|
||||||
log.Info("app startup!!!")
|
log.Info("app startup!!!")
|
||||||
a.ctx = ctx
|
a.ctx = ctx
|
||||||
tool.Must(db.Init(ctx, "sqlite::memory", db.OptSqliteByMem(nil)))
|
tool.Must(opt.Init())
|
||||||
|
tool.Must(db.Init(ctx, fmt.Sprintf("sqlite::%s", opt.ConfigFile)))
|
||||||
tool.Must(model.Init(db.Default.Session()))
|
tool.Must(model.Init(db.Default.Session()))
|
||||||
tool.Must(manager.Init(ctx))
|
tool.Must(manager.Init(ctx))
|
||||||
tool.Must(api.Init(ctx))
|
tool.Must(api.Init(ctx))
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/loveuer/nf-disk/internal/s3"
|
"github.com/loveuer/nf-disk/internal/s3"
|
||||||
"github.com/loveuer/nf-disk/ndh"
|
"github.com/loveuer/nf-disk/ndh"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConnectionTest(c *ndh.Ctx) error {
|
func ConnectionTest(c *ndh.Ctx) error {
|
||||||
@ -210,11 +209,5 @@ func ConnectionBuckets(c *ndh.Ctx) error {
|
|||||||
return c.Send500(err.Error())
|
return c.Send500(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: for frontend test
|
|
||||||
buckets = append(buckets, &s3.ListBucketRes{
|
|
||||||
Name: "这是一个非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长的名字",
|
|
||||||
CreatedAt: time.Now().UnixMilli(),
|
|
||||||
})
|
|
||||||
|
|
||||||
return c.Send200(map[string]any{"list": buckets})
|
return c.Send200(map[string]any{"list": buckets})
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/loveuer/nf-disk/internal/opt"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(tx *gorm.DB) (err error) {
|
func Init(tx *gorm.DB) (err error) {
|
||||||
@ -11,24 +9,5 @@ func Init(tx *gorm.DB) (err error) {
|
|||||||
&Connection{},
|
&Connection{},
|
||||||
)
|
)
|
||||||
|
|
||||||
if opt.Debug {
|
|
||||||
err = tx.Create([]*Connection{
|
|
||||||
{
|
|
||||||
Name: "dev-minio",
|
|
||||||
Endpoint: "http://10.220.10.15:9000",
|
|
||||||
Access: "8ALV3DUZI31YG4BDRJ0Z",
|
|
||||||
Key: "CRqwS1MsiUj27TbRK+3T2n+LpKWd07VvaDKuzU0H",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "test",
|
|
||||||
Endpoint: "http://10.220.10.14:19000",
|
|
||||||
Access: "5VCR05L4BSGNCTCD8DXP",
|
|
||||||
Key: "FPTMYBEiHhWLJ05C3aGXW8bjFXXNmghc8Za3Fo2u",
|
|
||||||
},
|
|
||||||
}).Clauses(clause.OnConflict{
|
|
||||||
DoNothing: true,
|
|
||||||
}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
24
internal/opt/init.go
Normal file
24
internal/opt/init.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package opt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if ConfigDir, err = os.UserConfigDir(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.MkdirAll(filepath.Join(ConfigDir, "nf-disk"), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigFile = filepath.Join(ConfigDir, "nf-disk", "config.db")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -8,4 +8,6 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Debug bool = false
|
Debug bool = false
|
||||||
|
ConfigDir string
|
||||||
|
ConfigFile string
|
||||||
)
|
)
|
||||||
|
@ -12,10 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ObjectInfo struct {
|
type ObjectInfo struct {
|
||||||
Bucket string
|
Bucket string `json:"bucket"`
|
||||||
Key string
|
Key string `json:"key"`
|
||||||
ContentType string
|
ContentType string `json:"content_type"`
|
||||||
Expire int64
|
Expire int64 `json:"expire"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetObjectInfo(ctx context.Context, bucket string, key string) (*ObjectInfo, error) {
|
func (c *Client) GetObjectInfo(ctx context.Context, bucket string, key string) (*ObjectInfo, error) {
|
||||||
@ -62,9 +62,9 @@ func (presigner *Presigner) GetObject(ctx context.Context, bucketName string, ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ObjectEntry struct {
|
type ObjectEntry struct {
|
||||||
URL string
|
URL string `json:"url"`
|
||||||
Method string
|
Method string `json:"method"`
|
||||||
Header http.Header
|
Header http.Header `json:"header"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetObjectEntry(ctx context.Context, bucket string, key string, lifetimes ...int64) (*ObjectEntry, error) {
|
func (c *Client) GetObjectEntry(ctx context.Context, bucket string, key string, lifetimes ...int64) (*ObjectEntry, error) {
|
||||||
@ -92,7 +92,7 @@ func (c *Client) GetObjectEntry(ctx context.Context, bucket string, key string,
|
|||||||
|
|
||||||
type ObjectEntity struct {
|
type ObjectEntity struct {
|
||||||
ObjectInfo
|
ObjectInfo
|
||||||
Body io.ReadCloser
|
Body io.ReadCloser `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetObject(ctx context.Context, bucket string, key string) (*ObjectEntity, error) {
|
func (c *Client) GetObject(ctx context.Context, bucket string, key string) (*ObjectEntity, error) {
|
||||||
|
2
main.go
2
main.go
@ -22,7 +22,7 @@ func main() {
|
|||||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
flag.BoolVar(&opt.Debug, "debug", true, "debug mode")
|
flag.BoolVar(&opt.Debug, "debug", false, "debug mode")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if opt.Debug {
|
if opt.Debug {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user