Compare commits

..

No commits in common. "3c8c559ac7662e83c226d86555acc05e0d460cd3" and "5e0885f22dc5ca4750128c3e8211b856aeafb6fc" have entirely different histories.

22 changed files with 263 additions and 584 deletions

View File

@ -10,11 +10,9 @@
"dependencies": {
"@fluentui/react-components": "^9.54.16",
"@fluentui/react-icons": "^2.0.258",
"jotai": "^2.10.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.26.2",
"zustand": "^5.0.0-rc.2"
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@types/react": "^18.0.17",
@ -3004,27 +3002,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/jotai": {
"version": "2.10.0",
"resolved": "https://registry.npmmirror.com/jotai/-/jotai-2.10.0.tgz",
"integrity": "sha512-8W4u0aRlOIwGlLQ0sqfl/c6+eExl5D8lZgAUolirZLktyaj4WnxO/8a0HEPmtriQAB6X5LMhXzZVmw02X0P0qQ==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=17.0.0",
"react": ">=17.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
@ -3525,35 +3502,6 @@
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"node_modules/zustand": {
"version": "5.0.0-rc.2",
"resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.0-rc.2.tgz",
"integrity": "sha512-o2Nwuvnk8vQBX7CcHL8WfFkZNJdxB/VKeWw0tNglw8p4cypsZ3tRT7rTRTDNeUPFS0qaMBRSKe+fVwL5xpcE3A==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
},
"dependencies": {
@ -5601,12 +5549,6 @@
"hasown": "^2.0.2"
}
},
"jotai": {
"version": "2.10.0",
"resolved": "https://registry.npmmirror.com/jotai/-/jotai-2.10.0.tgz",
"integrity": "sha512-8W4u0aRlOIwGlLQ0sqfl/c6+eExl5D8lZgAUolirZLktyaj4WnxO/8a0HEPmtriQAB6X5LMhXzZVmw02X0P0qQ==",
"requires": {}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
@ -5919,12 +5861,6 @@
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"zustand": {
"version": "5.0.0-rc.2",
"resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.0-rc.2.tgz",
"integrity": "sha512-o2Nwuvnk8vQBX7CcHL8WfFkZNJdxB/VKeWw0tNglw8p4cypsZ3tRT7rTRTDNeUPFS0qaMBRSKe+fVwL5xpcE3A==",
"requires": {}
}
}
}

View File

@ -11,11 +11,9 @@
"dependencies": {
"@fluentui/react-components": "^9.54.16",
"@fluentui/react-icons": "^2.0.258",
"jotai": "^2.10.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.26.2",
"zustand": "^5.0.0-rc.2"
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@types/react": "^18.0.17",

View File

@ -1 +1 @@
f23304e575da740e9b738508a43df31e
b35fc08c84ef0c2b0c3e1bf37916ac94

View File

@ -19,7 +19,7 @@ function isResp<T>(obj: any): obj is Resp<T> {
);
}
export async function Dial<T=any>(path: string, req: any = null): Promise<Resp<T>> {
export async function Dial<T>(path: string, req: any = null): Promise<Resp<T>> {
const bs = JSON.stringify(req)
console.log(`[DEBUG] invoke req: path = ${path}, req =`, req)

View File

@ -1,136 +0,0 @@
import {
DialogTrigger,
DialogSurface,
DialogTitle,
DialogBody,
DialogActions,
DialogContent,
Button, Spinner, Field, Input, FieldProps, makeStyles, tokens,
} from "@fluentui/react-components";
import {useState} from "react";
import {CheckmarkFilled, DismissRegular} from "@fluentui/react-icons";
import {useToast} from "../../message";
import {Dial} from "../../api";
import {useStoreConnection} from "../../store/connection";
const useActionStyle = makeStyles({
container: {
backgroundColor: tokens.colorNeutralBackground1,
display: "flex",
flexDirection: "row",
height: "100%",
width: "100%",
gridColumnStart: 0,
},
test: {}
});
export function ConnectionCreate(){
const actionStyle = useActionStyle();
const {dispatchMessage} = useToast();
const [testLoading, setTestLoading] = useState<"initial" | "loading" | "success" | "error">("initial");
const {conn_list, get_conn} = useStoreConnection();
const buttonIcon =
testLoading === "loading" ? (
<Spinner size="tiny"/>
) : testLoading === "success" ? (
<CheckmarkFilled/>
) : testLoading === "error" ? (
<DismissRegular/>
) : null;
const [value, setValue] = useState<{ name: string, endpoint: string, access: string, key: string }>({
name: '',
endpoint: '',
access: '',
key: ''
})
async function test() {
setTestLoading("loading")
let res = await Dial<string>("/api/connection/test", value)
const status = res.status === 200 ? "success" : "error"
setTestLoading(status);
dispatchMessage(res.msg, status)
}
async function create() {
// qUvfW8xpOTc23O96
// eTcuc8BebHPVpZZwIaNmzfwxRxPYGfTj
let res = await Dial("/api/connection/create", value)
dispatchMessage(res.msg, res.status === 200 ? "success" : "error");
if (res.status === 200) {
get_conn()
}
}
return <>
<DialogSurface>
<DialogBody>
<DialogTitle>S3连接</DialogTitle>
<DialogContent>
<div className='connection-container'>
<div className='connection-form'>
<div className='connection-form-field'>
<Field
label="name"
validationState="success"
validationMessage="This is a success message."
>
<Input placeholder='名称 (example: 测试S3-minio)' value={value.name}
onChange={(e) => {
setValue({...value, name: e.target.value});
}}/>
</Field>
</div>
<div className='connection-form-field'>
<Field
label="endpoint"
validationState="success"
validationMessage="This is a success message."
>
<Input placeholder='地址 (example: https://ip_or_server-name:port)'
value={value.endpoint}
onChange={(e) => {
setValue({...value, endpoint: e.target.value});
}}/>
</Field>
</div>
<div className='connection-form-field'>
<Field
label="secret access"
validationState="success"
validationMessage="This is a success message."
>
<Input placeholder='' value={value.access} onChange={(e) => {
setValue({...value, access: e.target.value});
}}/>
</Field>
</div>
<div className='connection-form-field'>
<Field
label="secret key"
validationState="success"
validationMessage="This is a success message."
>
<Input placeholder='' value={value.key} onChange={(e) => {
setValue({...value, key: e.target.value});
}}/>
</Field>
</div>
</div>
</div>
</DialogContent>
<DialogActions className={actionStyle.container}>
<Button className={actionStyle.test} appearance='transparent' icon={buttonIcon}
onClick={async () => await test()}></Button>
<DialogTrigger disableButtonEnhancement>
<Button appearance="secondary"></Button>
</DialogTrigger>
<Button onClick={async () => {
await create()
}} appearance="primary"></Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</>
}

View File

@ -1,152 +0,0 @@
import {useEffect, useState} from 'react';
import './home.css';
import {
Button, Dialog, DialogTrigger, makeStyles,mergeClasses, MenuItem, MenuList, tokens, Tooltip,
} from "@fluentui/react-components";
import {
CloudAddFilled, DismissRegular
} from "@fluentui/react-icons";
import {Dial} from "../../api";
import {useToast} from "../../message";
import {Connection} from "../../interfaces/connection";
import {ConnectionCreate} from "../connection/new";
import {useStoreConnection} from "../../store/connection";
const useMenuListContainerStyles = makeStyles({
container: {
backgroundColor: tokens.colorNeutralBackground1,
flex: 1,
width: "100%",
paddingTop: "4px",
paddingBottom: "4px",
},
item: {
display: 'flex',
height: '100%',
alignItems: 'center',
flexDirection: 'row',
fontSize: '15px',
'& span': {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
'&.active': {
color: tokens.colorNeutralForeground2BrandHover,
}
},
item_icon: {
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginLeft: 'auto',
border: 'none',
background: 'transparent',
"&:hover" : {
color: tokens.colorNeutralForeground2BrandHover,
}
}
});
function Home() {
const styles = useMenuListContainerStyles();
const {dispatchMessage} = useToast();
const [openCreate, setOpenCreate] = useState(false);
const [connectionFilterKeywords, setConnectionFilterKeywords] = useState<string>('');
const [connections, setConnections] = useState<Connection[]>([]);
const {conn_list, get_conn} = useStoreConnection();
useEffect(() => {
get_conn()
}, []);
async function handleConnect(item: Connection) {
console.log('[DEBUG] db click item =', item)
for (const c of connections) {
if (item.id === c.id && c.active) {
console.log('[DEBUG] conn is already connected:', c)
return
}
}
let res = await Dial("/api/connection/connect", {id: item.id})
if (res.status === 200) {
dispatchMessage("连接成功", "success")
setConnections(connections.map(one => {
if (one.id === item.id) {
one.active = true
}
return one
}))
}
}
async function handleDisconnect(item: Connection) {
let res = await Dial('/api/connection/disconnect', {id: item.id})
if (res.status === 200) {
setConnections(connections.map(c => {
if (item.id === c.id) {
c.active = false
}
return c
}))
}
}
return (
<div className="container">
<div className="header">
<Dialog
open={openCreate}
onOpenChange={(event, data) => setOpenCreate(data.open)}>
<DialogTrigger disableButtonEnhancement>
<Button appearance="primary" icon={<CloudAddFilled/>}>
</Button>
</DialogTrigger>
<ConnectionCreate />
</Dialog>
</div>
<div className="body">
<div className="body-connections">
<div className="body-connections-search">
<input className="body-connections-search-input" type={"text"} placeholder="搜索连接"
value={connectionFilterKeywords}
onChange={(e) => setConnectionFilterKeywords(e.target.value)}/>
<div className="body-connections-search-dismiss" onClick={() => {
setConnectionFilterKeywords('')
}}>
<DismissRegular/>
</div>
</div>
<div className="body-connections-list">
<div className={styles.container}>
<MenuList>
{conn_list.map(item => {
return <MenuItem
onDoubleClick={async () => {
await handleConnect(item)
}}
className={item.active ? mergeClasses(styles.item, 'active') : styles.item}
key={item.id}>
{item.name}
<Tooltip content="断开连接" relationship="label">
<Button onClick={async () => {await handleDisconnect(item)}} size="small" className={styles.item_icon} icon={<DismissRegular />} />
</Tooltip>
</MenuItem>
})}
</MenuList>
</div>
</div>
</div>
<div className="body-content"></div>
</div>
<div className="footer"></div>
</div>
)
}
export default Home

View File

@ -6,4 +6,4 @@ export interface Connection {
name: string;
endpoint: string;
active: boolean;
}
}

View File

@ -3,7 +3,8 @@ import {createRoot} from 'react-dom/client'
import './style.css'
import {FluentProvider, webLightTheme} from '@fluentui/react-components';
import {createBrowserRouter, RouterProvider} from "react-router-dom";
import Home from "./component/home/home";
import Home from "./page/home/home";
import Connection from "./page/connection/connection";
import {ToastProvider} from "./message";
const container = document.getElementById('root')
@ -12,6 +13,7 @@ const root = createRoot(container!)
const router = createBrowserRouter([
{path: '/', element: <Home/>},
{path: '/connection', element: <Connection/>},
])
root.render(

View File

@ -0,0 +1,124 @@
import './connection.css'
import {
useId,
Button,
FieldProps,
Spinner
} from "@fluentui/react-components";
import {Field, Input} from "@fluentui/react-components";
import {useNavigate} from "react-router-dom";
import {useState} from "react";
import {Dial} from "../../api";
import {useToast} from "../../message";
import {CheckmarkFilled, DismissRegular} from "@fluentui/react-icons";
const Connection = (props: Partial<FieldProps>) => {
const { dispatchMessage } = useToast();
const navigate = useNavigate();
const [testLoading, setTestLoading] = useState<"initial" | "loading" | "success" | "error">("initial");
const buttonIcon =
testLoading === "loading" ? (
<Spinner size="tiny"/>
) : testLoading === "success" ? (
<CheckmarkFilled/>
) : testLoading === "error" ? (
<DismissRegular/>
) : null;
const [value, setValue] = useState<{ name: string, endpoint: string, access: string, key: string }>({
name: '',
endpoint: '',
access: '',
key: ''
})
function test() {
setTestLoading("loading")
Dial<string>("/api/connection/test", value).then(res => {
let status: "success" | "error" = "error"
if (res.status === 200) {
status = "success"
}
setTestLoading(status);
dispatchMessage(res.msg, status)
})
}
function create() {
Dial<unknown>("/api/connection/create", value).then(res => {
dispatchMessage(res.msg, res.status === 200?"success":"error");
if (res.status === 200) {
navigate("/");
}
})
}
return <div className='connection-container'>
<div className='connection-form'>
<div className='connection-form-field'>
<Field
label="name"
validationState="success"
validationMessage="This is a success message."
{...props}
>
<Input placeholder='名称 (example: 测试S3-minio)' value={value.name} onChange={(e) => {
setValue({...value, name: e.target.value});
}}/>
</Field>
</div>
<div className='connection-form-field'>
<Field
label="endpoint"
validationState="success"
validationMessage="This is a success message."
{...props}
>
<Input placeholder='地址 (example: https://ip_or_server-name:port)' value={value.endpoint}
onChange={(e) => {
setValue({...value, endpoint: e.target.value});
}}/>
</Field>
</div>
<div className='connection-form-field'>
<Field
label="secret access"
validationState="success"
validationMessage="This is a success message."
{...props}
>
<Input placeholder='' value={value.access} onChange={(e) => {
setValue({...value, access: e.target.value});
}}/>
</Field>
</div>
<div className='connection-form-field'>
<Field
label="secret key"
validationState="success"
validationMessage="This is a success message."
{...props}
>
<Input placeholder='' value={value.key} onChange={(e) => {
setValue({...value, key: e.target.value});
}}/>
</Field>
</div>
<div className='connection-form-field connection-form-field-actions'>
<Button appearance='transparent' icon={buttonIcon} onClick={() => test()}></Button>
<div style={{marginLeft: 'auto'}}>
<Button style={{marginRight: '20px'}} className='connection-form-field-actions-cancel'
onClick={() => {
navigate("/")
}}></Button>
<Button className='connection-form-field-actions-confirm' appearance='primary'
onClick={() => create()}></Button>
</div>
</div>
</div>
</div>
}
export default Connection;

View File

@ -17,13 +17,11 @@ div.body {
display: flex;
flex: 1;
width: 100%;
height: 100%;
}
div.body div.body-connections {
width: 200px;
border-right: 1px solid lightgray;
height: 100%;
}
div.body-connections-search {
@ -52,9 +50,34 @@ div.body-connections-search-dismiss {
align-items: center;
}
div.body-connections-list {
height: 100%;
div.body-connections-list-item {
height: 36px;
}
div.body-connections-list-item.active {
color: var(--colorNeutralForeground2BrandSelected);
background: aquamarine;
}
div.body-connections-list-item:first-child {
margin-top: 2px;
}
button.body-connections-list-item-button {
background: none;
border: none;
height: 100%;
width: 100%;
padding: 0 6px;
display: flex;
justify-content: left;
align-items: center;
cursor: pointer;
font-size: 14px;
}
button.body-connections-list-item-button:hover {
background-color: var(--colorBrandBackground);
color: rgba(245, 245, 245, 1);
}

View File

@ -0,0 +1,83 @@
import {useEffect, useState} from 'react';
import './home.css';
import {
Button,
} from "@fluentui/react-components";
import {
CloudAddFilled, DismissRegular
} from "@fluentui/react-icons";
import {useNavigate} from "react-router-dom";
import {Dial} from "../../api";
import {useToast} from "../../message";
import {Connection} from "../../interfaces/connection";
function Home() {
const {dispatchMessage} = useToast();
const [connectionFilterKeywords, setConnectionFilterKeywords] = useState<string>('');
const navigate = useNavigate();
const [connectionList, setConnectionList] = useState<Connection[]>([]);
useEffect(() => {
Dial<{ list: Connection[] }>("/api/connection/list").then(res => {
dispatchMessage(res.msg, res.status === 200 ? "success" : "error");
if (res.status === 200) {
setConnectionList(res.data.list)
}
})
}, []);
async function handleConnect(item: Connection) {
console.log('[DEBUG] db click item =', item)
for (const c of connectionList) {
if(c.active) {
return
}
}
let res = await Dial("/api/connection/connect", {id: item.id})
if (res.status === 200) {
dispatchMessage("连接成功", "success")
for (const c of connectionList) {
c.active = c.id === item.id;
}
}
}
return (
<div className="container">
<div className="header">
<Button appearance="primary" icon={<CloudAddFilled/>} onClick={() => {
navigate("/connection")
}}>
</Button>
</div>
<div className="body">
<div className="body-connections">
<div className="body-connections-search">
<input className="body-connections-search-input" type={"text"} placeholder="搜索连接"
value={connectionFilterKeywords}
onChange={(e) => setConnectionFilterKeywords(e.target.value)}/>
<div className="body-connections-search-dismiss" onClick={() => {
setConnectionFilterKeywords('')
}}>
<DismissRegular/>
</div>
</div>
<div className="body-connections-list">
{connectionList.map(item => {
return <div key={item.id} className={item.active?'body-connections-list-item active':'body-connections-list-item'}>
<button onDoubleClick={() => handleConnect(item)}
className='body-connections-list-item-button'>{item.name}</button>
</div>
})}
</div>
</div>
<div className="body-content"></div>
</div>
<div className="footer"></div>
</div>
)
}
export default Home

View File

@ -1,16 +0,0 @@
import {create} from 'zustand'
import {Connection} from "../interfaces/connection";
import {Dial} from "../api";
type StoreConnection = {
conn_list: Connection[]
get_conn: () => void
}
export const useStoreConnection = create<StoreConnection>()((set) => ({
conn_list: [],
get_conn: async () => {
const res = await Dial<{list: Connection[]}>('/api/connection/list');
set({conn_list: res.data.list})
}
}))

View File

@ -28,8 +28,6 @@ func Init(ctx context.Context) error {
register("/api/connection/create", handler.ConnectionCreate)
register("/api/connection/list", handler.ConnectionList)
register("/api/connection/connect", handler.ConnectionConnect)
register("/api/connection/disconnect", handler.ConnectionDisconnect)
register("/api/connection/buckets", handler.ConnectionBuckets)
return nil
}

View File

@ -8,6 +8,7 @@ import (
"github.com/loveuer/nf-disk/internal/s3"
"github.com/loveuer/nf-disk/ndh"
"github.com/samber/lo"
"gorm.io/gorm"
)
func ConnectionTest(c *ndh.Ctx) error {
@ -88,20 +89,11 @@ func ConnectionCreate(c *ndh.Ctx) error {
}
func ConnectionList(c *ndh.Ctx) error {
type Req struct {
Keyword string `json:"keyword"`
}
var (
err error
list = make([]*model.Connection, 0)
req = new(Req)
)
if err = c.ReqParse(req); err != nil {
return c.Send400(nil, "参数错误")
}
if err = db.Default.Session().Model(&model.Connection{}).
Find(&list).
Error; err != nil {
@ -131,6 +123,7 @@ func ConnectionConnect(c *ndh.Ctx) error {
var (
err error
req = new(Req)
conn = new(model.Connection)
client *s3.Client
)
@ -138,9 +131,12 @@ func ConnectionConnect(c *ndh.Ctx) error {
return c.Send400(req)
}
conn := &model.Connection{Id: req.Id}
if err = conn.Get(db.Default.Session(), c); err != nil {
return err
if err = db.Default.Session().Take(conn, req.Id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return c.Send400(err.Error(), "连接不存在")
}
return c.Send500(err.Error())
}
if client, err = s3.New(c.Context(), conn.Endpoint, conn.Access, conn.Key); err != nil {
@ -153,61 +149,3 @@ func ConnectionConnect(c *ndh.Ctx) error {
return c.Send200(conn, "连接成功")
}
func ConnectionDisconnect(c *ndh.Ctx) error {
type Req struct {
Id uint64 `json:"id"`
}
var (
err error
req = new(Req)
)
if err = c.ReqParse(req); err != nil {
return c.Send400(req)
}
conn := &model.Connection{Id: req.Id}
if err = conn.Get(db.Default.Session(), c); err != nil {
return err
}
if err = manager.Manager.UnRegister(conn.Id); err != nil {
return c.Send500(err.Error())
}
return c.Send200(conn)
}
func ConnectionBuckets(c *ndh.Ctx) error {
type Req struct {
Id uint64 `json:"id"`
Keyword string `json:"keyword"`
}
var (
err error
req = new(Req)
client *s3.Client
buckets []*s3.ListBucketRes
)
if err = c.ReqParse(req); err != nil {
return c.Send400(nil, "参数错误")
}
if _, client, err = manager.Manager.Use(req.Id); err != nil {
if errors.Is(err, manager.ErrNotFound) {
return c.Send400(nil, "所选连接未激活")
}
return c.Send500(err.Error())
}
if buckets, err = client.ListBucket(c.Context()); err != nil {
return c.Send500(err.Error())
}
return c.Send200(map[string]any{"list": buckets})
}

View File

@ -1,21 +0,0 @@
package handler
import "github.com/loveuer/nf-disk/ndh"
func ListItem(c *ndh.Ctx) error {
type Req struct {
Id uint64 `json:"id"`
Bucket string `json:"bucket"`
}
var (
err error
req = new(Req)
)
if err = c.ReqParse(req); err != nil {
return c.Send400(err.Error())
}
panic("implement me!!!")
}

View File

@ -3,16 +3,17 @@ package model
import (
"github.com/loveuer/nf-disk/internal/opt"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func Init(tx *gorm.DB) (err error) {
err = tx.AutoMigrate(
if err = tx.AutoMigrate(
&Connection{},
)
); err != nil {
return err
}
if opt.Debug {
err = tx.Create([]*Connection{
if err = tx.Create([]*Connection{
{
Name: "dev-minio",
Endpoint: "http://10.220.10.15:9000",
@ -22,12 +23,12 @@ func Init(tx *gorm.DB) (err error) {
{
Name: "test",
Endpoint: "http://10.220.10.14:19000",
Access: "5VCR05L4BSGNCTCD8DXP",
Key: "FPTMYBEiHhWLJ05C3aGXW8bjFXXNmghc8Za3Fo2u",
Access: "",
Key: "",
},
}).Clauses(clause.OnConflict{
DoNothing: true,
}).Error
}).Error; err != nil {
return err
}
}
return

View File

@ -1,10 +1,6 @@
package model
import (
"errors"
"github.com/loveuer/nf-disk/ndh"
"gorm.io/gorm"
)
import "gorm.io/gorm"
type Connection struct {
Id uint64 `json:"id" gorm:"primaryKey;column:id"`
@ -22,15 +18,3 @@ type Connection struct {
func (c *Connection) Create(tx *gorm.DB) error {
return tx.Create(c).Error
}
func (c *Connection) Get(tx *gorm.DB, ctx *ndh.Ctx) error {
if err := tx.Take(c, c.Id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ctx.Send400(err.Error())
}
return ctx.Send500(err.Error())
}
return nil
}

View File

@ -7,5 +7,5 @@ const (
)
var (
Debug bool = false
Debug bool
)

View File

@ -1,37 +0,0 @@
package s3
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/samber/lo"
)
type ListBucketRes struct {
CreatedAt int64
Name string
}
func (c *Client) ListBucket(ctx context.Context) ([]*ListBucketRes, error) {
var (
err error
input = &s3.ListBucketsInput{
MaxBuckets: aws.Int32(100),
}
output *s3.ListBucketsOutput
)
if output, err = c.client.ListBuckets(ctx, input); err != nil {
return nil, err
}
res := lo.Map(
output.Buckets,
func(item types.Bucket, index int) *ListBucketRes {
return &ListBucketRes{CreatedAt: item.CreationDate.UnixMilli(), Name: *item.Name}
},
)
return res, nil
}

41
package-lock.json generated
View File

@ -1,41 +0,0 @@
{
"name": "nf-disk",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"zustand": "^5.0.0-rc.2"
}
},
"node_modules/zustand": {
"version": "5.0.0-rc.2",
"resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.0-rc.2.tgz",
"integrity": "sha512-o2Nwuvnk8vQBX7CcHL8WfFkZNJdxB/VKeWw0tNglw8p4cypsZ3tRT7rTRTDNeUPFS0qaMBRSKe+fVwL5xpcE3A==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"zustand": "^5.0.0-rc.2"
}
}