wip: bucket list

This commit is contained in:
zhaoyupeng 2024-10-08 14:29:36 +08:00
parent 3c8c559ac7
commit 9079a82434
10 changed files with 147 additions and 53 deletions

View File

@ -11,12 +11,14 @@
"@fluentui/react-components": "^9.54.16", "@fluentui/react-components": "^9.54.16",
"@fluentui/react-icons": "^2.0.258", "@fluentui/react-icons": "^2.0.258",
"jotai": "^2.10.0", "jotai": "^2.10.0",
"lodash": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"zustand": "^5.0.0-rc.2" "zustand": "^5.0.0-rc.2"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.17.10",
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.0.1", "@vitejs/plugin-react": "^2.0.1",
@ -2372,6 +2374,13 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@types/lodash": {
"version": "4.17.10",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/prop-types": { "node_modules/@types/prop-types": {
"version": "15.7.13", "version": "15.7.13",
"resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.13.tgz", "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.13.tgz",
@ -3060,6 +3069,12 @@
"integrity": "sha512-o5kvLbuTF+o326CMVYpjlaykxqYP9DphFQZ2ZpgrvBouyvOxyEB7oqe8nOLFpiV5VCtz0D3pt8gXQYWpLpBnmA==", "integrity": "sha512-o5kvLbuTF+o326CMVYpjlaykxqYP9DphFQZ2ZpgrvBouyvOxyEB7oqe8nOLFpiV5VCtz0D3pt8gXQYWpLpBnmA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/loose-envify": { "node_modules/loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
@ -5245,6 +5260,12 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"@types/lodash": {
"version": "4.17.10",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==",
"dev": true
},
"@types/prop-types": { "@types/prop-types": {
"version": "15.7.13", "version": "15.7.13",
"resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.13.tgz", "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.13.tgz",
@ -5629,6 +5650,11 @@
"resolved": "https://registry.npmmirror.com/keyborg/-/keyborg-2.6.0.tgz", "resolved": "https://registry.npmmirror.com/keyborg/-/keyborg-2.6.0.tgz",
"integrity": "sha512-o5kvLbuTF+o326CMVYpjlaykxqYP9DphFQZ2ZpgrvBouyvOxyEB7oqe8nOLFpiV5VCtz0D3pt8gXQYWpLpBnmA==" "integrity": "sha512-o5kvLbuTF+o326CMVYpjlaykxqYP9DphFQZ2ZpgrvBouyvOxyEB7oqe8nOLFpiV5VCtz0D3pt8gXQYWpLpBnmA=="
}, },
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"loose-envify": { "loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",

View File

@ -12,12 +12,14 @@
"@fluentui/react-components": "^9.54.16", "@fluentui/react-components": "^9.54.16",
"@fluentui/react-icons": "^2.0.258", "@fluentui/react-icons": "^2.0.258",
"jotai": "^2.10.0", "jotai": "^2.10.0",
"lodash": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.26.2", "react-router-dom": "^6.26.2",
"zustand": "^5.0.0-rc.2" "zustand": "^5.0.0-rc.2"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.17.10",
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.0.1", "@vitejs/plugin-react": "^2.0.1",

View File

@ -1 +1 @@
f23304e575da740e9b738508a43df31e b20ef5a27687e07e09878451f9a2e1aa

View File

@ -25,11 +25,15 @@ const useActionStyle = makeStyles({
test: {} test: {}
}); });
export function ConnectionCreate(){ export interface ConnectionCreateProps {
openFn: (open: boolean) => void;
}
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<"initial" | "loading" | "success" | "error">("initial");
const {conn_list, get_conn} = useStoreConnection(); const {conn_get} = useStoreConnection();
const buttonIcon = const buttonIcon =
testLoading === "loading" ? ( testLoading === "loading" ? (
<Spinner size="tiny"/> <Spinner size="tiny"/>
@ -54,12 +58,19 @@ export function ConnectionCreate(){
} }
async function create() { async function create() {
// self
// qUvfW8xpOTc23O96 // qUvfW8xpOTc23O96
// eTcuc8BebHPVpZZwIaNmzfwxRxPYGfTj // eTcuc8BebHPVpZZwIaNmzfwxRxPYGfTj
// 48-dev
// OSIsqPrl0TkAUj3R
// FYF4BBzL2j2ObbVYH0FrvOZqJf1EACRy
let res = await Dial("/api/connection/create", value) 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) {
get_conn() dispatchMessage("新建连接成功", "success");
conn_get()
props.openFn(false)
} }
} }

View File

@ -11,6 +11,7 @@ import {useToast} from "../../message";
import {Connection} from "../../interfaces/connection"; import {Connection} from "../../interfaces/connection";
import {ConnectionCreate} from "../connection/new"; import {ConnectionCreate} from "../connection/new";
import {useStoreConnection} from "../../store/connection"; import {useStoreConnection} from "../../store/connection";
import {useStoreBucket} from "../../store/bucket";
const useMenuListContainerStyles = makeStyles({ const useMenuListContainerStyles = makeStyles({
container: { container: {
@ -50,50 +51,36 @@ const useMenuListContainerStyles = makeStyles({
}); });
function Home() { export function Home() {
const styles = useMenuListContainerStyles(); const styles = useMenuListContainerStyles();
const {dispatchMessage} = useToast(); const {dispatchMessage} = useToast();
const [openCreate, setOpenCreate] = useState(false); const [openCreate, setOpenCreate] = useState(false);
const [connectionFilterKeywords, setConnectionFilterKeywords] = useState<string>(''); const [conn_filter, set_conn_filter] = useState<string>('');
const [connections, setConnections] = useState<Connection[]>([]); const {conn_list, conn_get, conn_update} = useStoreConnection();
const {conn_list, get_conn} = useStoreConnection(); const {bucket_list, bucket_get} = useStoreBucket()
useEffect(() => { useEffect(() => {
get_conn() conn_get()
}, []); }, []);
async function handleConnect(item: Connection) { async function handleConnect(item: Connection) {
console.log('[DEBUG] db click item =', item) let res = await Dial('/api/connection/connect', {id: item.id});
for (const c of connections) { if (res.status !== 200) {
if (item.id === c.id && c.active) { dispatchMessage(res.msg, "error")
console.log('[DEBUG] conn is already connected:', c)
return return
} }
}
let res = await Dial("/api/connection/connect", {id: item.id}) conn_update({...item, active: true})
if (res.status === 200) { bucket_get(item, true)
dispatchMessage("连接成功", "success")
setConnections(connections.map(one => {
if (one.id === item.id) {
one.active = true
}
return one
}))
}
} }
async function handleDisconnect(item: Connection) { async function handleDisconnect(item: Connection) {
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) {
setConnections(connections.map(c => { dispatchMessage(res.msg, "error")
if (item.id === c.id) { return
c.active = false
}
return c
}))
} }
conn_update({...item, active: false})
} }
return ( return (
@ -107,17 +94,17 @@ function Home() {
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<ConnectionCreate /> <ConnectionCreate openFn={setOpenCreate}/>
</Dialog> </Dialog>
</div> </div>
<div className="body"> <div className="body">
<div className="body-connections"> <div className="body-connections">
<div className="body-connections-search"> <div className="body-connections-search">
<input className="body-connections-search-input" type={"text"} placeholder="搜索连接" <input className="body-connections-search-input" type={"text"} placeholder="搜索连接"
value={connectionFilterKeywords} value={conn_filter}
onChange={(e) => setConnectionFilterKeywords(e.target.value)}/> onChange={(e) => set_conn_filter(e.target.value)}/>
<div className="body-connections-search-dismiss" onClick={() => { <div className="body-connections-search-dismiss" onClick={() => {
setConnectionFilterKeywords('') set_conn_filter('')
}}> }}>
<DismissRegular/> <DismissRegular/>
</div> </div>
@ -125,7 +112,7 @@ function Home() {
<div className="body-connections-list"> <div className="body-connections-list">
<div className={styles.container}> <div className={styles.container}>
<MenuList> <MenuList>
{conn_list.map(item => { {conn_list.filter(item => item.name.includes(conn_filter)).map(item => {
return <MenuItem return <MenuItem
onDoubleClick={async () => { onDoubleClick={async () => {
await handleConnect(item) await handleConnect(item)
@ -134,7 +121,9 @@ function Home() {
key={item.id}> key={item.id}>
{item.name} {item.name}
<Tooltip content="断开连接" relationship="label"> <Tooltip content="断开连接" relationship="label">
<Button onClick={async () => {await handleDisconnect(item)}} size="small" className={styles.item_icon} icon={<DismissRegular />} /> <Button onClick={async () => {
await handleDisconnect(item)
}} size="small" className={styles.item_icon} icon={<DismissRegular/>}/>
</Tooltip> </Tooltip>
</MenuItem> </MenuItem>
})} })}
@ -142,11 +131,18 @@ function Home() {
</div> </div>
</div> </div>
</div> </div>
<div className="body-content"></div> <div className="body-content" style={{width:'100%'}}>
<div></div>
<div style={{width:'100%'}}>
<MenuList style={{width:'100%'}}>
{bucket_list.map(((item, idx) => {
return <MenuItem style={{width:'100%'}} key={idx}>{item.name}</MenuItem>
}))}
</MenuList>
</div>
</div>
</div> </div>
<div className="footer"></div> <div className="footer"></div>
</div> </div>
) )
} }
export default Home

View File

@ -7,3 +7,8 @@ export interface Connection {
endpoint: string; endpoint: string;
active: boolean; active: boolean;
} }
export interface Bucket {
name: string;
created_at: number;
}

View File

@ -3,7 +3,7 @@ import {createRoot} from 'react-dom/client'
import './style.css' import './style.css'
import {FluentProvider, webLightTheme} from '@fluentui/react-components'; import {FluentProvider, webLightTheme} from '@fluentui/react-components';
import {createBrowserRouter, RouterProvider} from "react-router-dom"; import {createBrowserRouter, RouterProvider} from "react-router-dom";
import Home from "./component/home/home"; import {Home} from "./component/home/home";
import {ToastProvider} from "./message"; import {ToastProvider} from "./message";
const container = document.getElementById('root') const container = document.getElementById('root')

View File

@ -0,0 +1,32 @@
import {create} from 'zustand'
import {Bucket, Connection} from "../interfaces/connection";
import {Dial, Resp} from "../api";
interface StoreBucket {
bucket_list: Bucket[];
bucket_get: (conn: Connection, refresh: boolean) => void;
}
let bucket_map: { [id: number]: Bucket[] };
export const useStoreBucket = create<StoreBucket>()((set) => ({
bucket_list: [],
bucket_get: async (conn: Connection, refresh: boolean) => {
let res: Resp<{ list: Bucket[]; }>;
if (refresh) {
res = await Dial<{list: Bucket[]}>('/api/connection/buckets', {id: conn.id});
if (res.status !== 200) {
return
}
}
set((state) => {
if (refresh) {
bucket_map = {...bucket_map, [conn.id]: res.data.list}
return {bucket_list: res.data.list};
}
return {bucket_list: bucket_map[conn.id]};
})
}
}))

View File

@ -2,15 +2,37 @@ import {create} from 'zustand'
import {Connection} from "../interfaces/connection"; import {Connection} from "../interfaces/connection";
import {Dial} from "../api"; import {Dial} from "../api";
type StoreConnection = { interface StoreConnection {
conn_list: Connection[] conn_active: Connection | null;
get_conn: () => void conn_list: Connection[];
conn_get: () => void;
conn_update: (connection: Connection) => void;
} }
export const useStoreConnection = create<StoreConnection>()((set) => ({ export const useStoreConnection = create<StoreConnection>()((set) => ({
conn_active: null,
conn_list: [], conn_list: [],
get_conn: async () => { conn_get: async () => {
const res = await Dial<{ list: Connection[] }>('/api/connection/list'); const res = await Dial<{ list: Connection[] }>('/api/connection/list');
if (res.status !== 200) {
return
}
set({conn_list: res.data.list}) set({conn_list: res.data.list})
},
conn_update: async (connection: Connection) => {
set((state) => {
return {
conn_active: connection.active? connection: null,
conn_list: state.conn_list.map(item => {
if (item.id === connection.id) {
return connection
}
return item
})
}
})
} }
})) }))

View File

@ -9,8 +9,8 @@ import (
) )
type ListBucketRes struct { type ListBucketRes struct {
CreatedAt int64 CreatedAt int64 `json:"created_at"`
Name string Name string `json:"name"`
} }
func (c *Client) ListBucket(ctx context.Context) ([]*ListBucketRes, error) { func (c *Client) ListBucket(ctx context.Context) ([]*ListBucketRes, error) {