diff --git a/frontend/src/api.ts b/frontend/src/api.tsx similarity index 55% rename from frontend/src/api.ts rename to frontend/src/api.tsx index a772a6c..a0d9f97 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.tsx @@ -7,6 +7,7 @@ export interface Resp { data: T; } + // 类型保护函数 function isResp(obj: any): obj is Resp { return ( @@ -18,23 +19,32 @@ function isResp(obj: any): obj is Resp { ); } -const invoke = async (path: string, req: any): Promise> => { +export async function Dial(path: string, req: any = null): Promise> { const bs = JSON.stringify(req) console.log(`[DEBUG] invoke req: path = ${path}, req =`, req) - const res = await Invoke(path, bs) - console.log(`[DEBUG] invoke res: path = ${path}, res =`, res) + + let result: Resp; + let ok = false; + try { + const res = await Invoke(path, bs) const parsed = JSON.parse(res); if (isResp(parsed)) { - return parsed; + result = parsed; + ok = true } else { console.error('[ERROR] invoke: resp not valid =', res) - throw new Error('Parsed response does not match Resp structure'); + result = {status: 500, msg: "发生错误(0)", err: res} as Resp; } } catch (error) { - console.error(`[ERROR] invoke: resp parse err, err = ${error}, res =`, res); - throw new Error('Invalid response format'); + result = {status: 500, msg: "发生错误(-1)", err: "backend method(Invoke) not found in window"} as Resp; } -} -export const Dial = invoke; \ No newline at end of file + if (ok) { + console.log(`[DEBUG] invoke res: path = ${path}, res =`, result) + } else { + console.error(`[ERROR] invoke res: path = ${path}, res =`, result) + } + + return result +} diff --git a/frontend/src/interfaces/connection.ts b/frontend/src/interfaces/connection.ts new file mode 100644 index 0000000..26c6a82 --- /dev/null +++ b/frontend/src/interfaces/connection.ts @@ -0,0 +1,9 @@ +export interface Connection { + id: number; + created_at: number; + updated_at: number; + deleted_at: number; + name: string; + endpoint: string; + active: boolean; +} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 048c76a..ff77e3c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,22 +1,25 @@ import React from 'react' import {createRoot} from 'react-dom/client' import './style.css' -import { FluentProvider, webLightTheme } from '@fluentui/react-components'; -import {createBrowserRouter,RouterProvider} from "react-router-dom"; +import {FluentProvider, webLightTheme} from '@fluentui/react-components'; +import {createBrowserRouter, RouterProvider} from "react-router-dom"; import Home from "./page/home/home"; import Connection from "./page/connection/connection"; +import {ToastProvider} from "./message"; const container = document.getElementById('root') const root = createRoot(container!) const router = createBrowserRouter([ - {path:'/', element: }, - {path:'/connection', element: }, + {path: '/', element: }, + {path: '/connection', element: }, ]) root.render( - + + + , ); diff --git a/frontend/src/message.tsx b/frontend/src/message.tsx new file mode 100644 index 0000000..4a8adb3 --- /dev/null +++ b/frontend/src/message.tsx @@ -0,0 +1,37 @@ +import {createContext, FC, ReactNode, useContext} from "react"; +import {Toast, Toaster, ToastTitle, useId, useToastController} from "@fluentui/react-components"; + + +interface ToastContextType { + dispatchMessage: (content: string, type: "success" | "error" | "warning" | "info") => void; +} + +const ToastContext = createContext(undefined); + +export const ToastProvider: FC<{ children: ReactNode }> = ({children}) => { + + const toasterId = useId("toaster"); + const {dispatchToast} = useToastController(toasterId); + + const dispatchMessage = (content: string, type: "success" | "error" | "warning" | "info" = "info") => { + dispatchToast( + + {content} + , + {position: "top-end", intent: type} + ); + }; + + return + {children} + + +} + +export const useToast = () => { + const context = useContext(ToastContext); + if (!context) { + throw new Error("useToast must be used within a ToastProvider"); + } + return context; +}; \ No newline at end of file diff --git a/frontend/src/page/connection/connection.tsx b/frontend/src/page/connection/connection.tsx index 6f4bc49..36f82e0 100644 --- a/frontend/src/page/connection/connection.tsx +++ b/frontend/src/page/connection/connection.tsx @@ -3,23 +3,28 @@ import { useId, Button, FieldProps, - useToastController, - Toast, - ToastTitle, - ToastIntent, - Toaster + 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) => { - - const toasterId = useId("toaster"); - const {dispatchToast} = useToastController(toasterId); + const { dispatchMessage } = useToast(); const navigate = useNavigate(); + const [testLoading, setTestLoading] = useState<"initial" | "loading" | "success" | "error">("initial"); + const buttonIcon = + testLoading === "loading" ? ( + + ) : testLoading === "success" ? ( + + ) : testLoading === "error" ? ( + + ) : null; const [value, setValue] = useState<{ name: string, endpoint: string, access: string, key: string }>({ name: '', endpoint: '', @@ -28,16 +33,24 @@ const Connection = (props: Partial) => { }) function test() { - const val = JSON.stringify(value); - console.log('[DEBUG] connection.test: value =', val) + setTestLoading("loading") Dial("/api/connection/test", value).then(res => { + let status: "success" | "error" = "error" if (res.status === 200) { - dispatchToast( - - 连接成功! - , - {position: "top-end", intent: "success"} - ) + status = "success" + } + + setTestLoading(status); + + dispatchMessage(res.msg, status) + }) + } + + function create() { + Dial("/api/connection/create", value).then(res => { + dispatchMessage(res.msg, res.status === 200?"success":"error"); + if (res.status === 200) { + navigate("/"); } }) } @@ -94,17 +107,17 @@ const Connection = (props: Partial) => {
- +
- +
- } diff --git a/frontend/src/page/home/home.tsx b/frontend/src/page/home/home.tsx index d8d1dd5..1692641 100644 --- a/frontend/src/page/home/home.tsx +++ b/frontend/src/page/home/home.tsx @@ -1,4 +1,4 @@ -import {useState} from 'react'; +import {useEffect, useState} from 'react'; import './home.css'; import { Button, @@ -6,25 +6,45 @@ import { import { CloudAddFilled, DismissRegular } from "@fluentui/react-icons"; -import { useNavigate } from "react-router-dom"; +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(''); const navigate = useNavigate(); + const [connectionList, setConnectionList] = useState([]); + + 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) + } + }) + }, []); return (
- +
- setConnectionFilterKeywords(e.target.value)} /> -
{setConnectionFilterKeywords('')}}> - + setConnectionFilterKeywords(e.target.value)}/> +
{ + setConnectionFilterKeywords('') + }}> +
diff --git a/go.mod b/go.mod index 0d85bf4..2502815 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module nf-disk +module github.com/loveuer/nf-disk go 1.21 @@ -9,8 +9,15 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.27.38 github.com/aws/aws-sdk-go-v2/credentials v1.17.36 github.com/aws/aws-sdk-go-v2/service/s3 v1.63.2 + github.com/aws/smithy-go v1.21.0 + github.com/loveuer/go-sqlite3 v1.0.2 github.com/loveuer/nf v0.2.11 + github.com/ncruces/go-sqlite3/gormlite v0.18.4 + github.com/psanford/httpreadat v0.1.0 github.com/wailsapp/wails/v2 v2.9.2 + gorm.io/driver/mysql v1.5.7 + gorm.io/driver/postgres v1.5.9 + gorm.io/gorm v1.25.12 ) require ( @@ -27,13 +34,19 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.23.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.31.2 // indirect - github.com/aws/smithy-go v1.21.0 // indirect github.com/bep/debounce v1.2.1 // indirect github.com/fatih/color v1.17.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/labstack/echo/v4 v4.10.2 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leaanthony/go-ansi-parser v1.6.0 // indirect @@ -42,20 +55,24 @@ require ( github.com/leaanthony/u v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-sqlite3 v0.18.4 // indirect + github.com/ncruces/julianday v1.0.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/samber/lo v1.38.1 // indirect + github.com/tetratelabs/wazero v1.8.0 // indirect github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/wailsapp/go-webview2 v1.0.16 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/crypto v0.23.0 // indirect + golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect ) // replace github.com/wailsapp/wails/v2 v2.9.2 => C:\Users\loveuer\go\pkg\mod diff --git a/go.sum b/go.sum index afc9447..bd84fb9 100644 --- a/go.sum +++ b/go.sum @@ -43,12 +43,26 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= @@ -64,6 +78,8 @@ github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/ github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/loveuer/go-sqlite3 v1.0.2 h1:kcENqm6mt0wPH/N9Sw+6UC74qtU8o+aMEO04I62pjDE= +github.com/loveuer/go-sqlite3 v1.0.2/go.mod h1:8+45etSlBYCtYP/ThX/e1wLgG+x6G6oXck2FhjC57tA= github.com/loveuer/nf v0.2.11 h1:W775exDO8eNAHT45WDhXekMYCuWahOW9t1aVmGh3u1o= github.com/loveuer/nf v0.2.11/go.mod h1:M6reF17/kJBis30H4DxR5hrtgo/oJL4AV4cBe4HzJLw= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= @@ -75,21 +91,32 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-sqlite3 v0.18.4 h1:Je8o3y33MDwPYY/Cacas8yCsuoUzpNY/AgoSlN2ekyE= +github.com/ncruces/go-sqlite3 v0.18.4/go.mod h1:4HLag13gq1k10s4dfGBhMfRVsssJRT9/5hYqVM9RUYo= +github.com/ncruces/go-sqlite3/gormlite v0.18.4 h1:NdZkzS7SkcGlUafCmF6/fpqS/JkhxXP/DRPDYmSVdL4= +github.com/ncruces/go-sqlite3/gormlite v0.18.4/go.mod h1:laAntS4laxUO47GmxhIhSeJPrRSPF9TdsOQhaqlIifI= +github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= +github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE= +github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= +github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -103,13 +130,15 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k= github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -120,15 +149,22 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/internal/api/api.go b/internal/api/api.go new file mode 100644 index 0000000..d519d4f --- /dev/null +++ b/internal/api/api.go @@ -0,0 +1,32 @@ +package api + +import ( + "context" + "github.com/loveuer/nf-disk/internal/handler" + "github.com/loveuer/nf-disk/ndh" + "github.com/loveuer/nf/nft/log" + "reflect" +) + +var ( + apis = make(map[string]ndh.Handler) +) + +func register(path string, h ndh.Handler) { + name := reflect.ValueOf(h).String() + log.Info("app register: path = %s, name = %s", path, name) + apis[path] = h +} + +func Resolve(path string) (ndh.Handler, bool) { + h, ok := apis[path] + return h, ok +} + +func Init(ctx context.Context) error { + register("/api/connection/test", handler.ConnectionTest) + register("/api/connection/create", handler.ConnectionCreate) + register("/api/connection/list", handler.ConnectionList) + + return nil +} diff --git a/internal/controller/api.go b/internal/controller/api.go deleted file mode 100644 index 08ec5c2..0000000 --- a/internal/controller/api.go +++ /dev/null @@ -1,18 +0,0 @@ -package controller - -import ( - "github.com/loveuer/nf/nft/log" - "nf-disk/internal/handler" - "nf-disk/internal/ndh" - "reflect" -) - -func (a *App) register(path string, handler ndh.Handler) { - name := reflect.ValueOf(handler).String() - log.Info("app register: path = %s, name = %s", path, name) - a.handlers[path] = handler -} - -func initApi(a *App) { - a.register("/api/connection/test", handler.ConnectionTest) -} diff --git a/internal/controller/app.go b/internal/controller/app.go index 69bac0b..e53ebc4 100644 --- a/internal/controller/app.go +++ b/internal/controller/app.go @@ -1,13 +1,13 @@ package controller import ( - "bytes" "context" - "encoding/json" + "github.com/loveuer/nf-disk/internal/api" + "github.com/loveuer/nf-disk/internal/db" + "github.com/loveuer/nf-disk/internal/model" + "github.com/loveuer/nf-disk/internal/tool" + "github.com/loveuer/nf-disk/ndh" "github.com/loveuer/nf/nft/log" - "nf-disk/internal/ndh" - "nf-disk/internal/tool" - "strings" ) type App struct { @@ -22,24 +22,11 @@ func NewApp() *App { } func (a *App) Startup(ctx context.Context) { - a.ctx = ctx log.Info("app startup!!!") - initApi(a) -} - -func (a *App) Invoke(path string, req string) (res string) { - log.Info("app invoke: path = %s, req = %s", path, req) - handler, ok := a.handlers[path] - if !ok { - return `{"err": "handler not found", "status": 404}` - } - - var buf bytes.Buffer - ctx := ndh.NewCtx(tool.Timeout(), json.NewDecoder(strings.NewReader(req)), &buf) - - if err := handler(ctx); err != nil { - return err.Error() - } - - return buf.String() + + a.ctx = ctx + + tool.Must(db.Init(ctx, "sqlite::memory", db.OptSqliteByMem(nil))) + tool.Must(model.Init(db.Default.Session())) + tool.Must(api.Init(ctx)) } diff --git a/internal/controller/invoke.go b/internal/controller/invoke.go new file mode 100644 index 0000000..be2607d --- /dev/null +++ b/internal/controller/invoke.go @@ -0,0 +1,51 @@ +package controller + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/loveuer/nf-disk/internal/api" + "github.com/loveuer/nf-disk/internal/opt" + "github.com/loveuer/nf-disk/internal/tool" + "github.com/loveuer/nf-disk/ndh" + "github.com/loveuer/nf/nft/log" + "strings" +) + +func handleError(err error) string { + bs, _ := json.Marshal(map[string]any{ + "err": err.Error(), + "msg": opt.Msg500, + "status": 500, + }) + + return string(bs) +} + +func handleNotFound(path string) string { + bs, _ := json.Marshal(map[string]any{ + "err": fmt.Sprintf("path not found, path: %s", path), + "msg": opt.Msg500, + "status": 404, + }) + + return string(bs) +} + +func (a *App) Invoke(path string, req string) (res string) { + log.Info("app invoke: path = %s, req = %s", path, req) + handler, ok := api.Resolve(path) + if !ok { + log.Warn("app invoke: path not found, path = %s", path) + return handleNotFound(path) + } + + var buf bytes.Buffer + ctx := ndh.NewCtx(tool.TimeoutCtx(a.ctx), strings.NewReader(req), &buf) + + if err := handler(ctx); err != nil { + return handleError(err) + } + + return buf.String() +} diff --git a/internal/db/client.go b/internal/db/client.go new file mode 100644 index 0000000..f4a6459 --- /dev/null +++ b/internal/db/client.go @@ -0,0 +1,61 @@ +package db + +import ( + "context" + "github.com/loveuer/nf-disk/internal/opt" + "github.com/loveuer/nf-disk/internal/tool" + "io" + + "gorm.io/gorm" +) + +var ( + Default *Client +) + +type Client struct { + ctx context.Context + cli *gorm.DB + ttype string + cfgSqlite *cfgSqlite +} + +func (c *Client) Type() string { + return c.ttype +} + +func (c *Client) Session(ctxs ...context.Context) *gorm.DB { + var ctx context.Context + if len(ctxs) > 0 && ctxs[0] != nil { + ctx = ctxs[0] + } else { + ctx = tool.Timeout(30) + } + + session := c.cli.Session(&gorm.Session{Context: ctx}) + + if opt.Debug { + session = session.Debug() + } + + return session +} + +func (c *Client) Close() { + d, _ := c.cli.DB() + d.Close() +} + +// Dump +// Only for sqlite with mem mode to dump data to bytes(io.Reader) +func (c *Client) Dump() (reader io.ReadSeekCloser, ok bool) { + if c.ttype != "sqlite" { + return nil, false + } + + if c.cfgSqlite.fsType != "mem" { + return nil, false + } + + return c.cfgSqlite.memDump.Dump(), true +} diff --git a/internal/db/db_test.go b/internal/db/db_test.go new file mode 100644 index 0000000..2fc6a57 --- /dev/null +++ b/internal/db/db_test.go @@ -0,0 +1,44 @@ +package db + +import ( + "context" + "io" + "os" + "testing" +) + +func TestOpen(t *testing.T) { + myClient, err := New(context.TODO(), "sqlite::", OptSqliteByMem()) + if err != nil { + t.Fatalf("TestOpen: New err = %v", err) + } + + type Start struct { + Id int `json:"id" gorm:"column:id;primaryKey"` + Name string `json:"name" gorm:"column:name"` + Dis float64 `json:"dis" gorm:"column:dis"` + } + + if err = myClient.Session().AutoMigrate(&Start{}); err != nil { + t.Fatalf("TestOpen: AutoMigrate err = %v", err) + } + + if err = myClient.Session().Create(&Start{Name: "sun", Dis: 6631.76}).Error; err != nil { + t.Fatalf("TestOpen: Create err = %v", err) + } + + if err = myClient.Session().Create(&Start{Name: "mar", Dis: 786.35}).Error; err != nil { + t.Fatalf("TestOpen: Create err = %v", err) + } + + if reader, ok := myClient.Dump(); ok { + bs, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("TestOpen: ReadAll err = %v", err) + } + + os.WriteFile("dump.db", bs, 0644) + } + + myClient.Close() +} diff --git a/internal/db/init.go b/internal/db/init.go new file mode 100644 index 0000000..01f3fb1 --- /dev/null +++ b/internal/db/init.go @@ -0,0 +1,54 @@ +package db + +import ( + "context" + "fmt" + "strings" + + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +func New(ctx context.Context, uri string, opts ...Option) (*Client, error) { + strs := strings.Split(uri, "::") + + if len(strs) != 2 { + return nil, fmt.Errorf("db.Init: opt db uri invalid: %s", uri) + } + + c := &Client{ttype: strs[0], cfgSqlite: &cfgSqlite{fsType: "file"}} + for _, f := range opts { + f(c) + } + + var ( + err error + dsn = strs[1] + ) + + switch strs[0] { + case "sqlite": + err = openSqlite(c, dsn) + case "mysql": + c.cli, err = gorm.Open(mysql.Open(dsn)) + case "postgres": + c.cli, err = gorm.Open(postgres.Open(dsn)) + default: + return nil, fmt.Errorf("db type only support: [sqlite, mysql, postgres], unsupported db type: %s", strs[0]) + } + + if err != nil { + return nil, fmt.Errorf("db.Init: open %s with dsn:%s, err: %w", strs[0], dsn, err) + } + + return c, nil +} + +func Init(ctx context.Context, uri string, opts ...Option) (err error) { + if Default, err = New(ctx, uri, opts...); err != nil { + return err + } + + return nil +} diff --git a/internal/db/option.go b/internal/db/option.go new file mode 100644 index 0000000..3fa25d8 --- /dev/null +++ b/internal/db/option.go @@ -0,0 +1,27 @@ +package db + +import ( + _ "github.com/loveuer/go-sqlite3/embed" + "io" +) + +type Option func(c *Client) + +func OptSqliteByUrl(address string) Option { + return func(c *Client) { + c.cfgSqlite.fsType = "url" + } +} + +type SqliteMemDumper interface { + Dump() io.ReadSeekCloser +} + +// 如果传 nil 则表示新生成一个 mem 的 sqlite +// 如果传了一个合法的 reader 则会从这个 reader 初始化 database +func OptSqliteByMem(reader io.ReadCloser) Option { + return func(c *Client) { + c.cfgSqlite.memReader = reader + c.cfgSqlite.fsType = "mem" + } +} diff --git a/internal/db/sqlite.go b/internal/db/sqlite.go new file mode 100644 index 0000000..1e7b342 --- /dev/null +++ b/internal/db/sqlite.go @@ -0,0 +1,63 @@ +package db + +import ( + "fmt" + "io" + "time" + + _ "github.com/loveuer/go-sqlite3/embed" + "github.com/loveuer/go-sqlite3/vfs/memdb" + "github.com/loveuer/go-sqlite3/vfs/readervfs" + "github.com/ncruces/go-sqlite3/gormlite" + "github.com/psanford/httpreadat" + "gorm.io/gorm" +) + +type cfgSqlite struct { + fsType string // file, mem(bytes), url + memDump *memdb.MemDB + memReader io.ReadCloser +} + +func openSqlite(c *Client, dsn string) error { + var ( + db gorm.Dialector + err error + ) + + switch c.cfgSqlite.fsType { + case "file": + db = gormlite.Open("file:" + dsn) + case "url": + name := fmt.Sprintf("%d.db", time.Now().UnixNano()) + readervfs.Create(name, httpreadat.New(dsn)) + uri := fmt.Sprintf("file:%s?vfs=reader", name) + db = gormlite.Open(uri) + case "mem": + var ( + bs []byte + name = fmt.Sprintf("%d.db", time.Now().UnixNano()) + ) + + if c.cfgSqlite.memReader == nil { + bs = make([]byte, 0) + } else { + if bs, err = io.ReadAll(c.cfgSqlite.memReader); err != nil { + return err + } + } + + memDump := memdb.Create(name, bs) + c.cfgSqlite.memDump = memDump + uri := fmt.Sprintf("file:/%s?vfs=memdb", name) + db = gormlite.Open(uri) + default: + return fmt.Errorf("unsupported sqlite fs type: %s", c.cfgSqlite.fsType) + } + + if c.cli, err = gorm.Open(db); err != nil { + return err + } + + return nil +} diff --git a/internal/handler/connection.go b/internal/handler/connection.go index 991da3c..92ca3d9 100644 --- a/internal/handler/connection.go +++ b/internal/handler/connection.go @@ -1,8 +1,11 @@ package handler import ( - "nf-disk/internal/ndh" - "nf-disk/internal/s3" + "github.com/loveuer/nf-disk/internal/db" + "github.com/loveuer/nf-disk/internal/manager" + "github.com/loveuer/nf-disk/internal/model" + "github.com/loveuer/nf-disk/internal/s3" + "github.com/loveuer/nf-disk/ndh" ) func ConnectionTest(c *ndh.Ctx) error { @@ -30,5 +33,69 @@ func ConnectionTest(c *ndh.Ctx) error { return c.Send500(err.Error(), "连接失败") } - return c.Send200("test success") + return c.Send200("连接测试成功") +} + +func ConnectionCreate(c *ndh.Ctx) error { + type Req struct { + Name string `json:"name"` + Endpoint string `json:"endpoint"` + Access string `json:"access"` + Key string `json:"key"` + Force bool `json:"force"` + } + + var ( + err error + req = new(Req) + client *s3.Client + ) + + if err = c.ReqParse(req); err != nil { + return err + } + + if req.Endpoint == "" || req.Access == "" || req.Key == "" { + return c.Send400(nil, "endpoint, secret_access, secret_key 是必填项") + } + + if client, err = s3.New(c.Context(), req.Endpoint, req.Access, req.Key); err != nil { + return c.Send500(err.Error(), "连接失败") + } + + if req.Name == "" { + req.Name = req.Endpoint + } + + connection := &model.Connection{ + Name: req.Name, + Endpoint: req.Endpoint, + Access: req.Access, + Key: req.Key, + } + + if err = connection.Create(db.Default.Session()); err != nil { + return c.Send500(err.Error(), "创建连接失败(1)") + } + + if err = manager.Register(connection, client); err != nil { + return c.Send500(err.Error(), "创建连接失败(2)") + } + + return c.Send200(connection, "创建连接成功") +} + +func ConnectionList(c *ndh.Ctx) error { + var ( + err error + list = make([]*model.Connection, 0) + ) + + if err = db.Default.Session().Model(&model.Connection{}). + Find(&list). + Error; err != nil { + return err + } + + return c.Send200(map[string]any{"list": list}) } diff --git a/internal/manager/manager.go b/internal/manager/manager.go new file mode 100644 index 0000000..ff17cd1 --- /dev/null +++ b/internal/manager/manager.go @@ -0,0 +1,18 @@ +package manager + +import ( + "context" + "github.com/loveuer/nf-disk/internal/model" + "github.com/loveuer/nf-disk/internal/s3" + "github.com/loveuer/nf/nft/log" +) + +func Init(ctx context.Context) error { + return nil +} + +func Register(m *model.Connection, c *s3.Client) error { + log.Debug("manager: register connection-client: id = %d, name = %s", m.Id, m.Name) + + return nil +} diff --git a/internal/model/init.go b/internal/model/init.go new file mode 100644 index 0000000..43cd790 --- /dev/null +++ b/internal/model/init.go @@ -0,0 +1,9 @@ +package model + +import "gorm.io/gorm" + +func Init(tx *gorm.DB) error { + return tx.AutoMigrate( + &Connection{}, + ) +} diff --git a/internal/model/s3.go b/internal/model/s3.go new file mode 100644 index 0000000..e6754a1 --- /dev/null +++ b/internal/model/s3.go @@ -0,0 +1,18 @@ +package model + +import "gorm.io/gorm" + +type Connection struct { + Id uint64 `json:"id" gorm:"primaryKey;column:id"` + CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` + UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` + DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` + Name string `json:"name" gorm:"unique;column:name"` + Endpoint string `json:"endpoint" gorm:"column:endpoint"` + Access string `json:"access" gorm:"column:access"` + Key string `json:"key" gorm:"column:key"` +} + +func (c *Connection) Create(tx *gorm.DB) error { + return tx.Create(c).Error +} diff --git a/internal/opt/var.go b/internal/opt/var.go index a3e7ac4..df1d844 100644 --- a/internal/opt/var.go +++ b/internal/opt/var.go @@ -1,5 +1,11 @@ package opt +const ( + Msg200 = "操作成功" + Msg400 = "输入不正确" + Msg500 = "发生错误" +) + var ( Debug bool ) diff --git a/internal/s3/new.go b/internal/s3/new.go index ad23f1b..ae8dd38 100644 --- a/internal/s3/new.go +++ b/internal/s3/new.go @@ -7,9 +7,9 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" smithyendpoints "github.com/aws/smithy-go/endpoints" + "github.com/loveuer/nf-disk/internal/tool" "github.com/loveuer/nf/nft/log" "net/url" - "nf-disk/internal/tool" ) type resolverV2 struct{} diff --git a/internal/tool/must.go b/internal/tool/must.go new file mode 100644 index 0000000..0615f8d --- /dev/null +++ b/internal/tool/must.go @@ -0,0 +1,11 @@ +package tool + +import "github.com/loveuer/nf/nft/log" + +func Must(errs ...error) { + for _, err := range errs { + if err != nil { + log.Panic(err.Error()) + } + } +} diff --git a/main.go b/main.go index 6908565..5d25765 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,8 @@ package main import ( "embed" "flag" + "github.com/loveuer/nf-disk/internal/controller" "github.com/loveuer/nf/nft/nfctl/opt" - "nf-disk/internal/controller" "github.com/loveuer/nf/nft/log" "github.com/wailsapp/wails/v2" diff --git a/internal/ndh/ctx.go b/ndh/ctx.go similarity index 89% rename from internal/ndh/ctx.go rename to ndh/ctx.go index 55547e0..33b9b42 100644 --- a/internal/ndh/ctx.go +++ b/ndh/ctx.go @@ -8,11 +8,11 @@ import ( type Ctx struct { ctx context.Context - req *json.Decoder + req io.Reader res io.Writer } -func NewCtx(ctx context.Context, req *json.Decoder, res io.Writer) *Ctx { +func NewCtx(ctx context.Context, req io.Reader, res io.Writer) *Ctx { return &Ctx{ ctx: ctx, req: req, @@ -29,7 +29,7 @@ func (c *Ctx) Write(bs []byte) (int, error) { } func (c *Ctx) ReqParse(req any) error { - return c.req.Decode(req) + return json.NewDecoder(c.req).Decode(req) } func (c *Ctx) Send200(data any, msg ...string) error { diff --git a/internal/ndh/handler.go b/ndh/handler.go similarity index 100% rename from internal/ndh/handler.go rename to ndh/handler.go