wip: bucket list
This commit is contained in:
parent
3c8c559ac7
commit
9079a82434
26
frontend/package-lock.json
generated
26
frontend/package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -1 +1 @@
|
|||||||
f23304e575da740e9b738508a43df31e
|
b20ef5a27687e07e09878451f9a2e1aa
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
import './home.css';
|
import './home.css';
|
||||||
import {
|
import {
|
||||||
Button, Dialog, DialogTrigger, makeStyles,mergeClasses, MenuItem, MenuList, tokens, Tooltip,
|
Button, Dialog, DialogTrigger, makeStyles, mergeClasses, MenuItem, MenuList, tokens, Tooltip,
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import {
|
import {
|
||||||
CloudAddFilled, DismissRegular
|
CloudAddFilled, DismissRegular
|
||||||
@ -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: {
|
||||||
@ -43,57 +44,43 @@ const useMenuListContainerStyles = makeStyles({
|
|||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
"&:hover" : {
|
"&:hover": {
|
||||||
color: tokens.colorNeutralForeground2BrandHover,
|
color: tokens.colorNeutralForeground2BrandHover,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
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
|
|
@ -7,3 +7,8 @@ export interface Connection {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Bucket {
|
||||||
|
name: string;
|
||||||
|
created_at: number;
|
||||||
|
}
|
@ -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')
|
||||||
|
32
frontend/src/store/bucket.tsx
Normal file
32
frontend/src/store/bucket.tsx
Normal 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]};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user