🎉 开始项目
feat: 完成基础界面; 列表展示 todo: uplevel button function todo: download/upload
This commit is contained in:
commit
1c818daf16
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
build/bin
|
||||||
|
node_modules
|
||||||
|
frontend/dist
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.DS_Store
|
35
build/README.md
Normal file
35
build/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Build Directory
|
||||||
|
|
||||||
|
The build directory is used to house all the build files and assets for your application.
|
||||||
|
|
||||||
|
The structure is:
|
||||||
|
|
||||||
|
* bin - Output directory
|
||||||
|
* darwin - macOS specific files
|
||||||
|
* windows - Windows specific files
|
||||||
|
|
||||||
|
## Mac
|
||||||
|
|
||||||
|
The `darwin` directory holds files specific to Mac builds.
|
||||||
|
These may be customised and used as part of the build. To return these files to the default state, simply delete them
|
||||||
|
and
|
||||||
|
build with `wails build`.
|
||||||
|
|
||||||
|
The directory contains the following files:
|
||||||
|
|
||||||
|
- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
|
||||||
|
- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
The `windows` directory contains the manifest and rc files used when building with `wails build`.
|
||||||
|
These may be customised for your application. To return these files to the default state, simply delete them and
|
||||||
|
build with `wails build`.
|
||||||
|
|
||||||
|
- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
|
||||||
|
use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
|
||||||
|
will be created using the `appicon.png` file in the build directory.
|
||||||
|
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
|
||||||
|
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
|
||||||
|
as well as the application itself (right click the exe -> properties -> details)
|
||||||
|
- `wails.exe.manifest` - The main application manifest file.
|
BIN
build/appicon.png
Normal file
BIN
build/appicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
68
build/darwin/Info.dev.plist
Normal file
68
build/darwin/Info.dev.plist
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>{{.Info.ProductName}}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.{{.Name}}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{{.Info.Comments}}</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>iconfile</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
{{if .Info.FileAssociations}}
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Ext}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>{{.IconName}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
|
{{if .Info.Protocols}}
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.Protocols}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>com.wails.{{.Scheme}}</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Scheme}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
63
build/darwin/Info.plist
Normal file
63
build/darwin/Info.plist
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>{{.Info.ProductName}}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.{{.Name}}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{{.Info.Comments}}</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>iconfile</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
{{if .Info.FileAssociations}}
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Ext}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>{{.IconName}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
|
{{if .Info.Protocols}}
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.Protocols}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>com.wails.{{.Scheme}}</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Scheme}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
|
</dict>
|
||||||
|
</plist>
|
BIN
build/windows/icon.ico
Normal file
BIN
build/windows/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
15
build/windows/info.json
Normal file
15
build/windows/info.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"fixed": {
|
||||||
|
"file_version": "{{.Info.ProductVersion}}"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"0000": {
|
||||||
|
"ProductVersion": "{{.Info.ProductVersion}}",
|
||||||
|
"CompanyName": "{{.Info.CompanyName}}",
|
||||||
|
"FileDescription": "{{.Info.ProductName}}",
|
||||||
|
"LegalCopyright": "{{.Info.Copyright}}",
|
||||||
|
"ProductName": "{{.Info.ProductName}}",
|
||||||
|
"Comments": "{{.Info.Comments}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
build/windows/installer/project.nsi
Normal file
114
build/windows/installer/project.nsi
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
Unicode true
|
||||||
|
|
||||||
|
####
|
||||||
|
## Please note: Template replacements don't work in this file. They are provided with default defines like
|
||||||
|
## mentioned underneath.
|
||||||
|
## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
|
||||||
|
## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
|
||||||
|
## from outside of Wails for debugging and development of the installer.
|
||||||
|
##
|
||||||
|
## For development first make a wails nsis build to populate the "wails_tools.nsh":
|
||||||
|
## > wails build --target windows/amd64 --nsis
|
||||||
|
## Then you can call makensis on this file with specifying the path to your binary:
|
||||||
|
## For a AMD64 only installer:
|
||||||
|
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
|
||||||
|
## For a ARM64 only installer:
|
||||||
|
## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
|
||||||
|
## For a installer with both architectures:
|
||||||
|
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
|
||||||
|
####
|
||||||
|
## The following information is taken from the ProjectInfo file, but they can be overwritten here.
|
||||||
|
####
|
||||||
|
## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
|
||||||
|
## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
|
||||||
|
## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}"
|
||||||
|
## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}"
|
||||||
|
## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}"
|
||||||
|
###
|
||||||
|
## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
|
||||||
|
## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
|
||||||
|
####
|
||||||
|
## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
|
||||||
|
####
|
||||||
|
## Include the wails tools
|
||||||
|
####
|
||||||
|
!include "wails_tools.nsh"
|
||||||
|
|
||||||
|
# The version information for this two must consist of 4 parts
|
||||||
|
VIProductVersion "${INFO_PRODUCTVERSION}.0"
|
||||||
|
VIFileVersion "${INFO_PRODUCTVERSION}.0"
|
||||||
|
|
||||||
|
VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
|
||||||
|
VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
|
||||||
|
VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
|
||||||
|
VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
|
||||||
|
VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
|
||||||
|
VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
|
||||||
|
|
||||||
|
# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware
|
||||||
|
ManifestDPIAware true
|
||||||
|
|
||||||
|
!include "MUI.nsh"
|
||||||
|
|
||||||
|
!define MUI_ICON "..\icon.ico"
|
||||||
|
!define MUI_UNICON "..\icon.ico"
|
||||||
|
# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
|
||||||
|
!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
|
||||||
|
!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
|
||||||
|
# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES # Installing page.
|
||||||
|
!insertmacro MUI_PAGE_FINISH # Finished installation page.
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
|
||||||
|
|
||||||
|
## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
|
||||||
|
#!uninstfinalize 'signtool --file "%1"'
|
||||||
|
#!finalize 'signtool --file "%1"'
|
||||||
|
|
||||||
|
Name "${INFO_PRODUCTNAME}"
|
||||||
|
OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
|
||||||
|
InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
|
||||||
|
ShowInstDetails show # This will always show the installation details.
|
||||||
|
|
||||||
|
Function .onInit
|
||||||
|
!insertmacro wails.checkArchitecture
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Section
|
||||||
|
!insertmacro wails.setShellContext
|
||||||
|
|
||||||
|
!insertmacro wails.webview2runtime
|
||||||
|
|
||||||
|
SetOutPath $INSTDIR
|
||||||
|
|
||||||
|
!insertmacro wails.files
|
||||||
|
|
||||||
|
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||||
|
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||||
|
|
||||||
|
!insertmacro wails.associateFiles
|
||||||
|
!insertmacro wails.associateCustomProtocols
|
||||||
|
|
||||||
|
!insertmacro wails.writeUninstaller
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "uninstall"
|
||||||
|
!insertmacro wails.setShellContext
|
||||||
|
|
||||||
|
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
|
||||||
|
|
||||||
|
RMDir /r $INSTDIR
|
||||||
|
|
||||||
|
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
|
||||||
|
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
|
||||||
|
|
||||||
|
!insertmacro wails.unassociateFiles
|
||||||
|
!insertmacro wails.unassociateCustomProtocols
|
||||||
|
|
||||||
|
!insertmacro wails.deleteUninstaller
|
||||||
|
SectionEnd
|
249
build/windows/installer/wails_tools.nsh
Normal file
249
build/windows/installer/wails_tools.nsh
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
# DO NOT EDIT - Generated automatically by `wails build`
|
||||||
|
|
||||||
|
!include "x64.nsh"
|
||||||
|
!include "WinVer.nsh"
|
||||||
|
!include "FileFunc.nsh"
|
||||||
|
|
||||||
|
!ifndef INFO_PROJECTNAME
|
||||||
|
!define INFO_PROJECTNAME "{{.Name}}"
|
||||||
|
!endif
|
||||||
|
!ifndef INFO_COMPANYNAME
|
||||||
|
!define INFO_COMPANYNAME "{{.Info.CompanyName}}"
|
||||||
|
!endif
|
||||||
|
!ifndef INFO_PRODUCTNAME
|
||||||
|
!define INFO_PRODUCTNAME "{{.Info.ProductName}}"
|
||||||
|
!endif
|
||||||
|
!ifndef INFO_PRODUCTVERSION
|
||||||
|
!define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}"
|
||||||
|
!endif
|
||||||
|
!ifndef INFO_COPYRIGHT
|
||||||
|
!define INFO_COPYRIGHT "{{.Info.Copyright}}"
|
||||||
|
!endif
|
||||||
|
!ifndef PRODUCT_EXECUTABLE
|
||||||
|
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
|
||||||
|
!endif
|
||||||
|
!ifndef UNINST_KEY_NAME
|
||||||
|
!define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
|
||||||
|
!endif
|
||||||
|
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
|
||||||
|
|
||||||
|
!ifndef REQUEST_EXECUTION_LEVEL
|
||||||
|
!define REQUEST_EXECUTION_LEVEL "admin"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
||||||
|
|
||||||
|
!ifdef ARG_WAILS_AMD64_BINARY
|
||||||
|
!define SUPPORTS_AMD64
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef ARG_WAILS_ARM64_BINARY
|
||||||
|
!define SUPPORTS_ARM64
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef SUPPORTS_AMD64
|
||||||
|
!ifdef SUPPORTS_ARM64
|
||||||
|
!define ARCH "amd64_arm64"
|
||||||
|
!else
|
||||||
|
!define ARCH "amd64"
|
||||||
|
!endif
|
||||||
|
!else
|
||||||
|
!ifdef SUPPORTS_ARM64
|
||||||
|
!define ARCH "arm64"
|
||||||
|
!else
|
||||||
|
!error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
|
||||||
|
!endif
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!macro wails.checkArchitecture
|
||||||
|
!ifndef WAILS_WIN10_REQUIRED
|
||||||
|
!define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
|
||||||
|
!define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
${If} ${AtLeastWin10}
|
||||||
|
!ifdef SUPPORTS_AMD64
|
||||||
|
${if} ${IsNativeAMD64}
|
||||||
|
Goto ok
|
||||||
|
${EndIf}
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef SUPPORTS_ARM64
|
||||||
|
${if} ${IsNativeARM64}
|
||||||
|
Goto ok
|
||||||
|
${EndIf}
|
||||||
|
!endif
|
||||||
|
|
||||||
|
IfSilent silentArch notSilentArch
|
||||||
|
silentArch:
|
||||||
|
SetErrorLevel 65
|
||||||
|
Abort
|
||||||
|
notSilentArch:
|
||||||
|
MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
|
||||||
|
Quit
|
||||||
|
${else}
|
||||||
|
IfSilent silentWin notSilentWin
|
||||||
|
silentWin:
|
||||||
|
SetErrorLevel 64
|
||||||
|
Abort
|
||||||
|
notSilentWin:
|
||||||
|
MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
|
||||||
|
Quit
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ok:
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.files
|
||||||
|
!ifdef SUPPORTS_AMD64
|
||||||
|
${if} ${IsNativeAMD64}
|
||||||
|
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
|
||||||
|
${EndIf}
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef SUPPORTS_ARM64
|
||||||
|
${if} ${IsNativeARM64}
|
||||||
|
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
|
||||||
|
${EndIf}
|
||||||
|
!endif
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.writeUninstaller
|
||||||
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
SetRegView 64
|
||||||
|
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
||||||
|
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
||||||
|
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
||||||
|
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||||
|
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||||
|
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||||
|
|
||||||
|
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||||
|
IntFmt $0 "0x%08X" $0
|
||||||
|
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.deleteUninstaller
|
||||||
|
Delete "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
SetRegView 64
|
||||||
|
DeleteRegKey HKLM "${UNINST_KEY}"
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.setShellContext
|
||||||
|
${If} ${REQUEST_EXECUTION_LEVEL} == "admin"
|
||||||
|
SetShellVarContext all
|
||||||
|
${else}
|
||||||
|
SetShellVarContext current
|
||||||
|
${EndIf}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
# Install webview2 by launching the bootstrapper
|
||||||
|
# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
|
||||||
|
!macro wails.webview2runtime
|
||||||
|
!ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
|
||||||
|
!define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
SetRegView 64
|
||||||
|
# If the admin key exists and is not empty then webview2 is already installed
|
||||||
|
ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||||
|
${If} $0 != ""
|
||||||
|
Goto ok
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${If} ${REQUEST_EXECUTION_LEVEL} == "user"
|
||||||
|
# If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
|
||||||
|
ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||||
|
${If} $0 != ""
|
||||||
|
Goto ok
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
SetDetailsPrint both
|
||||||
|
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
|
||||||
|
SetDetailsPrint listonly
|
||||||
|
|
||||||
|
InitPluginsDir
|
||||||
|
CreateDirectory "$pluginsdir\webview2bootstrapper"
|
||||||
|
SetOutPath "$pluginsdir\webview2bootstrapper"
|
||||||
|
File "tmp\MicrosoftEdgeWebview2Setup.exe"
|
||||||
|
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
|
||||||
|
|
||||||
|
SetDetailsPrint both
|
||||||
|
ok:
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b
|
||||||
|
!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
|
||||||
|
; Backup the previously associated file class
|
||||||
|
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
|
||||||
|
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
|
||||||
|
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro APP_UNASSOCIATE EXT FILECLASS
|
||||||
|
; Backup the previously associated file class
|
||||||
|
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
|
||||||
|
|
||||||
|
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.associateFiles
|
||||||
|
; Create file associations
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
!insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
|
||||||
|
|
||||||
|
File "..\{{.IconName}}.ico"
|
||||||
|
{{end}}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.unassociateFiles
|
||||||
|
; Delete app associations
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
!insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}"
|
||||||
|
|
||||||
|
Delete "$INSTDIR\{{.IconName}}.ico"
|
||||||
|
{{end}}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND
|
||||||
|
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}"
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" ""
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}"
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" ""
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" ""
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}"
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL
|
||||||
|
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.associateCustomProtocols
|
||||||
|
; Create custom protocols associations
|
||||||
|
{{range .Info.Protocols}}
|
||||||
|
!insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.unassociateCustomProtocols
|
||||||
|
; Delete app custom protocol associations
|
||||||
|
{{range .Info.Protocols}}
|
||||||
|
!insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}"
|
||||||
|
{{end}}
|
||||||
|
!macroend
|
15
build/windows/wails.exe.manifest
Normal file
15
build/windows/wails.exe.manifest
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity type="win32" name="com.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
</assembly>
|
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>nf-disk</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root" style="height: 100%"></div>
|
||||||
|
<script src="./src/main.tsx" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
5975
frontend/package-lock.json
generated
Normal file
5975
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
frontend/package.json
Normal file
30
frontend/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fluentui/react-components": "^9.54.16",
|
||||||
|
"@fluentui/react-icons": "^2.0.258",
|
||||||
|
"jotai": "^2.10.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.26.2",
|
||||||
|
"use-debounce": "^10.0.3",
|
||||||
|
"zustand": "^5.0.0-rc.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/lodash": "^4.17.10",
|
||||||
|
"@types/react": "^18.0.17",
|
||||||
|
"@types/react-dom": "^18.0.6",
|
||||||
|
"@vitejs/plugin-react": "^2.0.1",
|
||||||
|
"typescript": "^4.6.4",
|
||||||
|
"vite": "^3.0.7"
|
||||||
|
}
|
||||||
|
}
|
1
frontend/package.json.md5
Normal file
1
frontend/package.json.md5
Normal file
@ -0,0 +1 @@
|
|||||||
|
b20ef5a27687e07e09878451f9a2e1aa
|
2723
frontend/pnpm-lock.yaml
generated
Normal file
2723
frontend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
frontend/src/api.tsx
Normal file
50
frontend/src/api.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import {Invoke} from "../wailsjs/go/controller/App";
|
||||||
|
|
||||||
|
export interface Resp<T> {
|
||||||
|
status: number;
|
||||||
|
msg: string;
|
||||||
|
err: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 类型保护函数
|
||||||
|
function isResp<T>(obj: any): obj is Resp<T> {
|
||||||
|
return (
|
||||||
|
typeof obj === 'object' &&
|
||||||
|
obj !== null &&
|
||||||
|
typeof obj.status === 'number' &&
|
||||||
|
(typeof obj.msg === 'string' || typeof obj.msg === null) &&
|
||||||
|
(typeof obj.err === 'string' || typeof obj.err === null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function Dial<T=any>(path: string, req: any = null): Promise<Resp<T>> {
|
||||||
|
const bs = JSON.stringify(req)
|
||||||
|
console.log(`[DEBUG] invoke req: path = ${path}, req =`, req)
|
||||||
|
|
||||||
|
let result: Resp<T>;
|
||||||
|
let ok = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await Invoke(path, bs)
|
||||||
|
const parsed = JSON.parse(res);
|
||||||
|
if (isResp<T>(parsed)) {
|
||||||
|
result = parsed;
|
||||||
|
ok = true
|
||||||
|
} else {
|
||||||
|
console.error('[ERROR] invoke: resp not valid =', res)
|
||||||
|
result = {status: 500, msg: "发生错误(0)", err: res} as Resp<T>;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
result = {status: 500, msg: "发生错误(-1)", err: "backend method(Invoke) not found in window"} as Resp<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
console.log(`[DEBUG] invoke res: path = ${path}, res =`, result)
|
||||||
|
} else {
|
||||||
|
console.error(`[ERROR] invoke res: path = ${path}, res =`, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
93
frontend/src/assets/fonts/OFL.txt
Normal file
93
frontend/src/assets/fonts/OFL.txt
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
frontend/src/assets/fonts/nunito-v16-latin-regular.woff2
Normal file
BIN
frontend/src/assets/fonts/nunito-v16-latin-regular.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/images/logo-universal.png
Normal file
BIN
frontend/src/assets/images/logo-universal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
174
frontend/src/component/connection/list.tsx
Normal file
174
frontend/src/component/connection/list.tsx
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
makeStyles,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuList, MenuPopover, MenuProps,
|
||||||
|
mergeClasses, PositioningImperativeRef,
|
||||||
|
tokens,
|
||||||
|
Tooltip
|
||||||
|
} from "@fluentui/react-components"
|
||||||
|
import {DatabaseLinkRegular, DismissRegular} from "@fluentui/react-icons";
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import {Bucket, Connection} from "../../interfaces/connection";
|
||||||
|
import {useToast} from "../../message";
|
||||||
|
import {Dial} from "../../api";
|
||||||
|
import {useStoreConnection} from "../../store/connection";
|
||||||
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
list: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
height: "100%",
|
||||||
|
width: "25rem",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
height: "4rem",
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
filter_input: {
|
||||||
|
width: "100%",
|
||||||
|
marginLeft: "0.5rem",
|
||||||
|
marginRight: "0.5rem",
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
items_one: {
|
||||||
|
marginLeft: "0.5rem",
|
||||||
|
marginRight: "0.5rem",
|
||||||
|
"&:hover": {
|
||||||
|
color: tokens.colorNeutralForeground2BrandPressed,
|
||||||
|
},
|
||||||
|
"&.active": {
|
||||||
|
color: tokens.colorNeutralForeground2BrandPressed,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
"& > span": {
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items_disconn: {
|
||||||
|
marginLeft: "auto",
|
||||||
|
},
|
||||||
|
slider: {
|
||||||
|
height: '100%', width: '1px',
|
||||||
|
// todo: resize
|
||||||
|
// cursor: 'ew-resize',
|
||||||
|
'& > div': {
|
||||||
|
height: '100%', width: '1px',
|
||||||
|
backgroundColor: 'lightgray',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function ConnectionList() {
|
||||||
|
const styles = useStyles()
|
||||||
|
const {dispatchMessage} = useToast();
|
||||||
|
const {conn_list, conn_update} = useStoreConnection();
|
||||||
|
const [conn_filter, set_conn_filter] = useState<string>('');
|
||||||
|
const {bucket_get, bucket_set} = useStoreBucket()
|
||||||
|
|
||||||
|
async function handleSelect(item: Connection) {
|
||||||
|
conn_list.map((one: Connection) => {
|
||||||
|
if (item.id === one.id && one.active) {
|
||||||
|
conn_update(one)
|
||||||
|
bucket_get(one, false)
|
||||||
|
bucket_set(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleConnect(item: Connection) {
|
||||||
|
let res = await Dial('/api/connection/connect', {id: item.id});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
dispatchMessage(res.msg, "error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_update({...item, active: true})
|
||||||
|
bucket_get(item, true)
|
||||||
|
bucket_set(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDisconnect(item: Connection) {
|
||||||
|
let res = await Dial('/api/connection/disconnect', {id: item.id})
|
||||||
|
if (res.status !== 200) {
|
||||||
|
dispatchMessage(res.msg, "error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn_update({...item, active: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: Connection) {
|
||||||
|
e.preventDefault()
|
||||||
|
console.log('[DEBUG] right click connection =', item, 'event =', e)
|
||||||
|
console.log(`[DEBUG] click position: [${e.pageX}, ${e.pageY}]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.list}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.filter}>
|
||||||
|
<Input
|
||||||
|
value={conn_filter}
|
||||||
|
className={styles.filter_input}
|
||||||
|
contentAfter={
|
||||||
|
<Button appearance={'transparent'} onClick={async () => {
|
||||||
|
set_conn_filter('')
|
||||||
|
}} size="small" icon={<DismissRegular/>}/>
|
||||||
|
}
|
||||||
|
placeholder="搜索连接"
|
||||||
|
onChange={(e) => set_conn_filter(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.items}>
|
||||||
|
<MenuList>
|
||||||
|
{conn_list.filter(item => item.name.includes(conn_filter)).map(item => {
|
||||||
|
return <MenuItem
|
||||||
|
className={item.active ? mergeClasses(styles.items_one, "active") : styles.items_one}
|
||||||
|
onClick={async () => {
|
||||||
|
await handleSelect(item)
|
||||||
|
}}
|
||||||
|
onDoubleClick={async () => {
|
||||||
|
await handleConnect(item)
|
||||||
|
}}
|
||||||
|
onContextMenu={async (e) => {
|
||||||
|
await handleRightClick(e, item)
|
||||||
|
}}
|
||||||
|
icon={<DatabaseLinkRegular/>}
|
||||||
|
key={item.id}>
|
||||||
|
{item.name}
|
||||||
|
<Tooltip
|
||||||
|
content="断开连接"
|
||||||
|
relationship="label">
|
||||||
|
<Button
|
||||||
|
appearance={'transparent'}
|
||||||
|
size="small"
|
||||||
|
icon={<DismissRegular/>}
|
||||||
|
className={styles.items_disconn}
|
||||||
|
onClick={async () => {
|
||||||
|
await handleDisconnect(item)
|
||||||
|
}}/>
|
||||||
|
</Tooltip>
|
||||||
|
</MenuItem>
|
||||||
|
})}
|
||||||
|
</MenuList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.slider}>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
147
frontend/src/component/connection/new.tsx
Normal file
147
frontend/src/component/connection/new.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
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 interface ConnectionCreateProps {
|
||||||
|
openFn: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConnectionCreate(props: ConnectionCreateProps) {
|
||||||
|
const actionStyle = useActionStyle();
|
||||||
|
const {dispatchMessage} = useToast();
|
||||||
|
const [testLoading, setTestLoading] = useState<"initial" | "loading" | "success" | "error">("initial");
|
||||||
|
const {conn_get} = 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() {
|
||||||
|
// self
|
||||||
|
// qUvfW8xpOTc23O96
|
||||||
|
// eTcuc8BebHPVpZZwIaNmzfwxRxPYGfTj
|
||||||
|
|
||||||
|
// 48-dev
|
||||||
|
// OSIsqPrl0TkAUj3R
|
||||||
|
// FYF4BBzL2j2ObbVYH0FrvOZqJf1EACRy
|
||||||
|
let res = await Dial("/api/connection/create", value)
|
||||||
|
dispatchMessage(res.msg, res.status === 200 ? "success" : "error");
|
||||||
|
if (res.status === 200) {
|
||||||
|
dispatchMessage("新建连接成功", "success");
|
||||||
|
conn_get()
|
||||||
|
props.openFn(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
</>
|
||||||
|
}
|
32
frontend/src/component/file/content.tsx
Normal file
32
frontend/src/component/file/content.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {Path} from "./path";
|
||||||
|
import {ListBucketComponent} from "./list_bucket";
|
||||||
|
import {makeStyles} from "@fluentui/react-components";
|
||||||
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
|
import {useStoreFile} from "../../store/file";
|
||||||
|
import {ListFileComponent} from "./list_file";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
content: {
|
||||||
|
flex: '1',
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Content() {
|
||||||
|
|
||||||
|
const styles = useStyles()
|
||||||
|
const {bucket_active, bucket_list} = useStoreBucket()
|
||||||
|
const {file_list} = useStoreFile()
|
||||||
|
|
||||||
|
return <div className={styles.content}>
|
||||||
|
<Path/>
|
||||||
|
{
|
||||||
|
bucket_active ?
|
||||||
|
<ListFileComponent/> :
|
||||||
|
<ListBucketComponent/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
76
frontend/src/component/file/list_bucket.tsx
Normal file
76
frontend/src/component/file/list_bucket.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import {makeStyles, MenuItem, MenuList, Text, tokens} from "@fluentui/react-components";
|
||||||
|
import {ArchiveRegular, DocumentBulletListRegular} from "@fluentui/react-icons";
|
||||||
|
import {VirtualizerScrollView} from "@fluentui/react-components/unstable";
|
||||||
|
import React from "react";
|
||||||
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
|
import {Bucket} from "../../interfaces/connection";
|
||||||
|
import {useStoreFile} from "../../store/file";
|
||||||
|
import {useStoreConnection} from "../../store/connection";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
container: {
|
||||||
|
marginTop: '0.5rem',
|
||||||
|
maxWidth: 'calc(100vw - 25rem - 1px)',
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
height: '32px',
|
||||||
|
display: 'flex',
|
||||||
|
marginLeft: '0.5rem',
|
||||||
|
marginRight: '0.5rem',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
"&:hover": {
|
||||||
|
color: tokens.colorNeutralForeground2BrandPressed,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
width: 'calc(100vw - 32rem)',
|
||||||
|
display: "block",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function ListBucketComponent() {
|
||||||
|
|
||||||
|
const styles = useStyles();
|
||||||
|
const {conn_active} = useStoreConnection()
|
||||||
|
const {bucket_set, bucket_list} = useStoreBucket()
|
||||||
|
const {files_get} = useStoreFile()
|
||||||
|
|
||||||
|
async function handleClick(item: Bucket) {
|
||||||
|
bucket_set(item)
|
||||||
|
files_get(conn_active!, item, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: Bucket) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <MenuList className={styles.container}>
|
||||||
|
<VirtualizerScrollView
|
||||||
|
numItems={bucket_list.length}
|
||||||
|
itemSize={32}
|
||||||
|
container={{role: 'list', style: {maxHeight: 'calc(100vh - 9rem)'}}}
|
||||||
|
>
|
||||||
|
{(idx) => {
|
||||||
|
return <div
|
||||||
|
className={styles.row} key={idx}
|
||||||
|
onClick={async () => {
|
||||||
|
await handleClick(bucket_list[idx])
|
||||||
|
}}
|
||||||
|
onContextMenu={async (e) => {
|
||||||
|
handleRightClick(e, bucket_list[idx])
|
||||||
|
}}>
|
||||||
|
<MenuItem className={styles.item}
|
||||||
|
icon={<ArchiveRegular/>}>
|
||||||
|
<Text truncate wrap={false} className={styles.text}>
|
||||||
|
{bucket_list[idx].name}
|
||||||
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
}}
|
||||||
|
</VirtualizerScrollView>
|
||||||
|
</MenuList>
|
||||||
|
}
|
105
frontend/src/component/file/list_file.tsx
Normal file
105
frontend/src/component/file/list_file.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import {makeStyles, MenuItem, MenuList, Text, tokens} from "@fluentui/react-components";
|
||||||
|
import {ArchiveRegular, DocumentBulletListRegular, DocumentDismissRegular, FolderRegular} from "@fluentui/react-icons";
|
||||||
|
import {VirtualizerScrollView} from "@fluentui/react-components/unstable";
|
||||||
|
import React, {useEffect} from "react";
|
||||||
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
|
import {Bucket, S3File} from "../../interfaces/connection";
|
||||||
|
import {useStoreFile} from "../../store/file";
|
||||||
|
import {useStoreConnection} from "../../store/connection";
|
||||||
|
import {TrimSuffix} from "../../hook/strings";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
container: {
|
||||||
|
marginTop: '0.5rem',
|
||||||
|
maxWidth: 'calc(100vw - 25rem - 1px)',
|
||||||
|
width: 'calc(100vw - 25rem - 1px)',
|
||||||
|
height: 'calc(100vh - 9rem)',
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
height: '32px',
|
||||||
|
display: 'flex',
|
||||||
|
marginLeft: '0.5rem',
|
||||||
|
marginRight: '0.5rem',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
"&:hover": {
|
||||||
|
color: tokens.colorNeutralForeground2BrandPressed,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
width: 'calc(100vw - 32rem)',
|
||||||
|
display: "block",
|
||||||
|
},
|
||||||
|
no_data: {
|
||||||
|
flex: "1",
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: '8rem',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function ListFileComponent() {
|
||||||
|
|
||||||
|
const styles = useStyles();
|
||||||
|
const {conn_active} = useStoreConnection();
|
||||||
|
const {bucket_active} = useStoreBucket()
|
||||||
|
const {files_get, files_list} = useStoreFile()
|
||||||
|
|
||||||
|
const filename = (key: string) => {
|
||||||
|
let strs = TrimSuffix(key, "/").split("/")
|
||||||
|
return strs[strs.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleClick(item: S3File) {
|
||||||
|
if (item.type === 1) {
|
||||||
|
files_get(conn_active!, bucket_active!, item.key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: S3File) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <MenuList className={styles.container}>
|
||||||
|
{files_list.length ?
|
||||||
|
<VirtualizerScrollView
|
||||||
|
numItems={files_list.length}
|
||||||
|
itemSize={32}
|
||||||
|
container={{role: 'list', style: {maxHeight: 'calc(100vh - 9rem)'}}}
|
||||||
|
>
|
||||||
|
{(idx) => {
|
||||||
|
return <div
|
||||||
|
className={styles.row} key={idx}
|
||||||
|
onClick={async () => {
|
||||||
|
await handleClick(files_list[idx])
|
||||||
|
}}
|
||||||
|
onContextMenu={async (e) => {
|
||||||
|
handleRightClick(e, files_list[idx])
|
||||||
|
}}>
|
||||||
|
<MenuItem className={styles.item}
|
||||||
|
icon={files_list[idx].type ? <FolderRegular/> : <DocumentBulletListRegular/>}>
|
||||||
|
<Text truncate wrap={false} className={styles.text}>
|
||||||
|
{filename(files_list[idx].key)}
|
||||||
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
}}
|
||||||
|
</VirtualizerScrollView> : <div className={styles.no_data}>
|
||||||
|
<div>
|
||||||
|
<DocumentDismissRegular />
|
||||||
|
</div>
|
||||||
|
<Text size={900}>
|
||||||
|
没有文件
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</MenuList>
|
||||||
|
}
|
114
frontend/src/component/file/path.tsx
Normal file
114
frontend/src/component/file/path.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import {Button, Input, makeStyles, Text, tokens, Tooltip} from "@fluentui/react-components";
|
||||||
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
|
import {ArchiveRegular, ArrowCurveUpLeftFilled} from "@fluentui/react-icons";
|
||||||
|
import {useStoreFile} from "../../store/file";
|
||||||
|
import React from "react";
|
||||||
|
import {debounce} from 'lodash'
|
||||||
|
import {useStoreConnection} from "../../store/connection";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
container: {
|
||||||
|
height: '4rem',
|
||||||
|
width: '100%',
|
||||||
|
borderBottom: '1px solid lightgray',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
marginLeft: '0.5rem',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
show_line: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
show_text: {
|
||||||
|
backgroundColor: tokens.colorNeutralBackground1Hover,
|
||||||
|
padding: '0.5rem 0.5rem',
|
||||||
|
borderRadius: '0.5rem',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'block',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: '0.5rem',
|
||||||
|
overflow: 'hidden',
|
||||||
|
maxWidth: '8rem',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
'&:hover': {
|
||||||
|
textDecoration: 'none',
|
||||||
|
backgroundColor: tokens.colorNeutralBackground1Pressed,
|
||||||
|
},
|
||||||
|
'& > div': {
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
op_up: {},
|
||||||
|
filter_prefix: {
|
||||||
|
margin: '0.5rem',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Path() {
|
||||||
|
const styles = useStyles()
|
||||||
|
const {conn_active} = useStoreConnection()
|
||||||
|
const {bucket_active} = useStoreBucket()
|
||||||
|
const {prefix, files_get} = useStoreFile()
|
||||||
|
|
||||||
|
async function handleClickUp() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const handleFilterChange = debounce((e) => {
|
||||||
|
files_get(conn_active!, bucket_active!, prefix, e.target.value)
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
return <div className={styles.container}>
|
||||||
|
{bucket_active && (
|
||||||
|
<>
|
||||||
|
<div className={styles.show}>
|
||||||
|
<Tooltip content="返回上一级" relationship="label">
|
||||||
|
<Button className={styles.op_up}
|
||||||
|
onClick={async () => {
|
||||||
|
await handleClickUp()
|
||||||
|
}}
|
||||||
|
size="small" icon={<ArrowCurveUpLeftFilled/>}/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content={bucket_active.name} relationship={'description'}>
|
||||||
|
<Text className={styles.show_text}
|
||||||
|
truncate
|
||||||
|
wrap={false}
|
||||||
|
align={'justify'}
|
||||||
|
style={{maxWidth: '16rem'}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<ArchiveRegular style={{margin: '0rem 0.5rem 0 0'}}/>
|
||||||
|
{bucket_active.name}
|
||||||
|
</div>
|
||||||
|
</Text>
|
||||||
|
</Tooltip>
|
||||||
|
{prefix && (
|
||||||
|
prefix.split("/").filter(item => item).map((item, idx) => {
|
||||||
|
return <div className={styles.show_line} key={idx}>
|
||||||
|
<Text style={{marginLeft: '0.5rem'}}>/</Text>
|
||||||
|
<Text className={styles.show_text} truncate wrap={false}>{item}</Text>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={styles.filter_prefix}>
|
||||||
|
<Input
|
||||||
|
onChange={(e) => {
|
||||||
|
handleFilterChange(e)
|
||||||
|
}}
|
||||||
|
placeholder={"输入前缀过滤"}
|
||||||
|
// contentBefore={<Text>/</Text>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
34
frontend/src/component/home/body.tsx
Normal file
34
frontend/src/component/home/body.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {Button, Input, makeStyles, MenuItem, MenuList, mergeClasses, tokens, Tooltip} from "@fluentui/react-components";
|
||||||
|
import {DismissRegular} from "@fluentui/react-icons";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {Connection} from "../../interfaces/connection";
|
||||||
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
|
import {useStoreConnection} from "../../store/connection";
|
||||||
|
import {Dial} from "../../api";
|
||||||
|
import {useToast} from "../../message";
|
||||||
|
import {ConnectionList} from "../connection/list";
|
||||||
|
import {Content} from "../file/content";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
body: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: "100%",
|
||||||
|
flex: '1',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Body() {
|
||||||
|
const styles = useStyles();
|
||||||
|
const {conn_get} = useStoreConnection();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
conn_get()
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return <div className={styles.body}>
|
||||||
|
<ConnectionList/>
|
||||||
|
<Content />
|
||||||
|
</div>
|
||||||
|
}
|
3
frontend/src/component/home/footer.tsx
Normal file
3
frontend/src/component/home/footer.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function Footer() {
|
||||||
|
return <div></div>
|
||||||
|
}
|
37
frontend/src/component/home/header.tsx
Normal file
37
frontend/src/component/home/header.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import {Button, Dialog, DialogTrigger, makeStyles} from "@fluentui/react-components";
|
||||||
|
import {ConnectionCreate} from "../connection/new";
|
||||||
|
import {CloudAddFilled} from "@fluentui/react-icons";
|
||||||
|
import {useState} from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
header: {
|
||||||
|
height: "5rem",
|
||||||
|
width: "100%",
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: "center",
|
||||||
|
borderBottom: "1px solid lightgray",
|
||||||
|
},
|
||||||
|
button_new_connection: {
|
||||||
|
margin: '0.5rem',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Header() {
|
||||||
|
const styles = useStyles();
|
||||||
|
const [openCreate, setOpenCreate] = useState(false);
|
||||||
|
|
||||||
|
return <div className={styles.header}>
|
||||||
|
<div className={styles.button_new_connection}>
|
||||||
|
<Dialog
|
||||||
|
open={openCreate}
|
||||||
|
onOpenChange={(event, data) => setOpenCreate(data.open)}>
|
||||||
|
<DialogTrigger disableButtonEnhancement>
|
||||||
|
<Button appearance="primary" icon={<CloudAddFilled/>}>
|
||||||
|
新建连接
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<ConnectionCreate openFn={setOpenCreate}/>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
26
frontend/src/component/home/home.tsx
Normal file
26
frontend/src/component/home/home.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {Header} from "./header";
|
||||||
|
import {Body} from "./body";
|
||||||
|
import {makeStyles} from "@fluentui/react-components";
|
||||||
|
import {Footer} from "./footer";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
container: {
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Home() {
|
||||||
|
|
||||||
|
const styles = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Header />
|
||||||
|
<Body />
|
||||||
|
<Footer/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
6
frontend/src/hook/strings.ts
Normal file
6
frontend/src/hook/strings.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function TrimSuffix(str: string, suffix: string) {
|
||||||
|
if (str.lastIndexOf(suffix) === str.length - suffix.length) {
|
||||||
|
return str.substring(0, str.length - suffix.length);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
22
frontend/src/interfaces/connection.ts
Normal file
22
frontend/src/interfaces/connection.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export interface Connection {
|
||||||
|
id: number;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
deleted_at: number;
|
||||||
|
name: string;
|
||||||
|
endpoint: string;
|
||||||
|
active: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Bucket {
|
||||||
|
name: string;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface S3File {
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
last_modified: number;
|
||||||
|
size: number;
|
||||||
|
type: 0 | 1;
|
||||||
|
}
|
23
frontend/src/main.tsx
Normal file
23
frontend/src/main.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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 {Home} from "./component/home/home";
|
||||||
|
import {ToastProvider} from "./message";
|
||||||
|
|
||||||
|
const container = document.getElementById('root')
|
||||||
|
|
||||||
|
const root = createRoot(container!)
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{path: '/', element: <Home/>},
|
||||||
|
])
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<FluentProvider theme={webLightTheme} style={{height: '100%'}}>
|
||||||
|
<ToastProvider>
|
||||||
|
<RouterProvider router={router}/>
|
||||||
|
</ToastProvider>
|
||||||
|
</FluentProvider>,
|
||||||
|
);
|
37
frontend/src/message.tsx
Normal file
37
frontend/src/message.tsx
Normal file
@ -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<ToastContextType | undefined>(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(
|
||||||
|
<Toast>
|
||||||
|
<ToastTitle>{content}</ToastTitle>
|
||||||
|
</Toast>,
|
||||||
|
{position: "top-end", intent: type}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ToastContext.Provider value={{dispatchMessage}}>
|
||||||
|
{children}
|
||||||
|
<Toaster toasterId={toasterId}/>
|
||||||
|
</ToastContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useToast = () => {
|
||||||
|
const context = useContext(ToastContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useToast must be used within a ToastProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
38
frontend/src/store/bucket.tsx
Normal file
38
frontend/src/store/bucket.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {create} from 'zustand'
|
||||||
|
import {Bucket, Connection} from "../interfaces/connection";
|
||||||
|
import {Dial, Resp} from "../api";
|
||||||
|
|
||||||
|
interface StoreBucket {
|
||||||
|
bucket_active: Bucket | null;
|
||||||
|
bucket_set: (Bucket: Bucket | null) => void;
|
||||||
|
bucket_list: Bucket[];
|
||||||
|
bucket_get: (conn: Connection, refresh: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bucket_map: { [id: number]: Bucket[] };
|
||||||
|
|
||||||
|
export const useStoreBucket = create<StoreBucket>()((set) => ({
|
||||||
|
bucket_active: null,
|
||||||
|
bucket_set: async (bucket: Bucket | null) => {
|
||||||
|
set({bucket_active: bucket});
|
||||||
|
},
|
||||||
|
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]};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
38
frontend/src/store/connection.tsx
Normal file
38
frontend/src/store/connection.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {create} from 'zustand'
|
||||||
|
import {Connection} from "../interfaces/connection";
|
||||||
|
import {Dial} from "../api";
|
||||||
|
|
||||||
|
interface StoreConnection {
|
||||||
|
conn_active: Connection | null;
|
||||||
|
conn_list: Connection[];
|
||||||
|
conn_get: () => void;
|
||||||
|
conn_update: (connection: Connection) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStoreConnection = create<StoreConnection>()((set) => ({
|
||||||
|
conn_active: null,
|
||||||
|
conn_list: [],
|
||||||
|
conn_get: async () => {
|
||||||
|
const res = await Dial<{ list: Connection[] }>('/api/connection/list');
|
||||||
|
if (res.status !== 200) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
29
frontend/src/store/file.tsx
Normal file
29
frontend/src/store/file.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {create} from 'zustand'
|
||||||
|
import {Bucket, Connection, S3File} from "../interfaces/connection";
|
||||||
|
import {Dial} from "../api";
|
||||||
|
|
||||||
|
interface StoreFile {
|
||||||
|
prefix: string;
|
||||||
|
files_list: S3File[];
|
||||||
|
files_get: (conn: Connection, bucket: Bucket, prefix?: string, filter?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStoreFile = create<StoreFile>()((set) => ({
|
||||||
|
prefix: "",
|
||||||
|
files_list: [],
|
||||||
|
files_get: async (conn: Connection, bucket: Bucket, prefix = '', filter = '') => {
|
||||||
|
const res = await Dial<{ list: S3File[] }>('/api/bucket/files', {
|
||||||
|
conn_id: conn.id,
|
||||||
|
bucket: bucket.name,
|
||||||
|
prefix: prefix + filter,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
set((state) => {
|
||||||
|
return {files_list: res.data.list, prefix: prefix}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
21
frontend/src/style.css
Normal file
21
frontend/src/style.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
:root {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
color: white;
|
||||||
|
font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||||
|
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
|
sans-serif;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Nunito";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(""),
|
||||||
|
url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
1
frontend/src/vite-env.d.ts
vendored
Normal file
1
frontend/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
31
frontend/tsconfig.json
Normal file
31
frontend/tsconfig.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": [
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable",
|
||||||
|
"ESNext"
|
||||||
|
],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
11
frontend/tsconfig.node.json
Normal file
11
frontend/tsconfig.node.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"vite.config.ts"
|
||||||
|
]
|
||||||
|
}
|
7
frontend/vite.config.ts
Normal file
7
frontend/vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import {defineConfig} from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()]
|
||||||
|
})
|
7
frontend/wailsjs/go/controller/App.d.ts
vendored
Executable file
7
frontend/wailsjs/go/controller/App.d.ts
vendored
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
import {context} from '../models';
|
||||||
|
|
||||||
|
export function Init(arg1:context.Context):Promise<void>;
|
||||||
|
|
||||||
|
export function Invoke(arg1:string,arg2:string):Promise<string>;
|
11
frontend/wailsjs/go/controller/App.js
Executable file
11
frontend/wailsjs/go/controller/App.js
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export function Init(arg1) {
|
||||||
|
return window['go']['controller']['App']['Init'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Invoke(arg1, arg2) {
|
||||||
|
return window['go']['controller']['App']['Invoke'](arg1, arg2);
|
||||||
|
}
|
24
frontend/wailsjs/runtime/package.json
Normal file
24
frontend/wailsjs/runtime/package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "@wailsapp/runtime",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "Wails Javascript runtime library",
|
||||||
|
"main": "runtime.js",
|
||||||
|
"types": "runtime.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/wailsapp/wails.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Wails",
|
||||||
|
"Javascript",
|
||||||
|
"Go"
|
||||||
|
],
|
||||||
|
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/wailsapp/wails/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||||
|
}
|
249
frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
249
frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Position {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Size {
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Screen {
|
||||||
|
isCurrent: boolean;
|
||||||
|
isPrimary: boolean;
|
||||||
|
width : number
|
||||||
|
height : number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment information such as platform, buildtype, ...
|
||||||
|
export interface EnvironmentInfo {
|
||||||
|
buildType: string;
|
||||||
|
platform: string;
|
||||||
|
arch: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
|
||||||
|
// emits the given event. Optional data may be passed with the event.
|
||||||
|
// This will trigger any event listeners.
|
||||||
|
export function EventsEmit(eventName: string, ...data: any): void;
|
||||||
|
|
||||||
|
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
|
||||||
|
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
|
||||||
|
|
||||||
|
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
|
||||||
|
// sets up a listener for the given event name, but will only trigger a given number times.
|
||||||
|
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
|
||||||
|
|
||||||
|
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
|
||||||
|
// sets up a listener for the given event name, but will only trigger once.
|
||||||
|
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
|
||||||
|
|
||||||
|
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
|
||||||
|
// unregisters the listener for the given event name.
|
||||||
|
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
|
||||||
|
|
||||||
|
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
|
||||||
|
// unregisters all listeners.
|
||||||
|
export function EventsOffAll(): void;
|
||||||
|
|
||||||
|
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
|
||||||
|
// logs the given message as a raw message
|
||||||
|
export function LogPrint(message: string): void;
|
||||||
|
|
||||||
|
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
|
||||||
|
// logs the given message at the `trace` log level.
|
||||||
|
export function LogTrace(message: string): void;
|
||||||
|
|
||||||
|
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
|
||||||
|
// logs the given message at the `debug` log level.
|
||||||
|
export function LogDebug(message: string): void;
|
||||||
|
|
||||||
|
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
|
||||||
|
// logs the given message at the `error` log level.
|
||||||
|
export function LogError(message: string): void;
|
||||||
|
|
||||||
|
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
|
||||||
|
// logs the given message at the `fatal` log level.
|
||||||
|
// The application will quit after calling this method.
|
||||||
|
export function LogFatal(message: string): void;
|
||||||
|
|
||||||
|
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
|
||||||
|
// logs the given message at the `info` log level.
|
||||||
|
export function LogInfo(message: string): void;
|
||||||
|
|
||||||
|
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
|
||||||
|
// logs the given message at the `warning` log level.
|
||||||
|
export function LogWarning(message: string): void;
|
||||||
|
|
||||||
|
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
|
||||||
|
// Forces a reload by the main application as well as connected browsers.
|
||||||
|
export function WindowReload(): void;
|
||||||
|
|
||||||
|
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
|
||||||
|
// Reloads the application frontend.
|
||||||
|
export function WindowReloadApp(): void;
|
||||||
|
|
||||||
|
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
|
||||||
|
// Sets the window AlwaysOnTop or not on top.
|
||||||
|
export function WindowSetAlwaysOnTop(b: boolean): void;
|
||||||
|
|
||||||
|
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
|
||||||
|
// *Windows only*
|
||||||
|
// Sets window theme to system default (dark/light).
|
||||||
|
export function WindowSetSystemDefaultTheme(): void;
|
||||||
|
|
||||||
|
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
|
||||||
|
// *Windows only*
|
||||||
|
// Sets window to light theme.
|
||||||
|
export function WindowSetLightTheme(): void;
|
||||||
|
|
||||||
|
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
|
||||||
|
// *Windows only*
|
||||||
|
// Sets window to dark theme.
|
||||||
|
export function WindowSetDarkTheme(): void;
|
||||||
|
|
||||||
|
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
|
||||||
|
// Centers the window on the monitor the window is currently on.
|
||||||
|
export function WindowCenter(): void;
|
||||||
|
|
||||||
|
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
|
||||||
|
// Sets the text in the window title bar.
|
||||||
|
export function WindowSetTitle(title: string): void;
|
||||||
|
|
||||||
|
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
|
||||||
|
// Makes the window full screen.
|
||||||
|
export function WindowFullscreen(): void;
|
||||||
|
|
||||||
|
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
|
||||||
|
// Restores the previous window dimensions and position prior to full screen.
|
||||||
|
export function WindowUnfullscreen(): void;
|
||||||
|
|
||||||
|
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
|
||||||
|
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||||
|
export function WindowIsFullscreen(): Promise<boolean>;
|
||||||
|
|
||||||
|
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||||
|
// Sets the width and height of the window.
|
||||||
|
export function WindowSetSize(width: number, height: number): Promise<Size>;
|
||||||
|
|
||||||
|
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||||
|
// Gets the width and height of the window.
|
||||||
|
export function WindowGetSize(): Promise<Size>;
|
||||||
|
|
||||||
|
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
|
||||||
|
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
|
||||||
|
// Setting a size of 0,0 will disable this constraint.
|
||||||
|
export function WindowSetMaxSize(width: number, height: number): void;
|
||||||
|
|
||||||
|
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
|
||||||
|
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
|
||||||
|
// Setting a size of 0,0 will disable this constraint.
|
||||||
|
export function WindowSetMinSize(width: number, height: number): void;
|
||||||
|
|
||||||
|
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
|
||||||
|
// Sets the window position relative to the monitor the window is currently on.
|
||||||
|
export function WindowSetPosition(x: number, y: number): void;
|
||||||
|
|
||||||
|
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
|
||||||
|
// Gets the window position relative to the monitor the window is currently on.
|
||||||
|
export function WindowGetPosition(): Promise<Position>;
|
||||||
|
|
||||||
|
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
|
||||||
|
// Hides the window.
|
||||||
|
export function WindowHide(): void;
|
||||||
|
|
||||||
|
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
|
||||||
|
// Shows the window, if it is currently hidden.
|
||||||
|
export function WindowShow(): void;
|
||||||
|
|
||||||
|
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
|
||||||
|
// Maximises the window to fill the screen.
|
||||||
|
export function WindowMaximise(): void;
|
||||||
|
|
||||||
|
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
|
||||||
|
// Toggles between Maximised and UnMaximised.
|
||||||
|
export function WindowToggleMaximise(): void;
|
||||||
|
|
||||||
|
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
|
||||||
|
// Restores the window to the dimensions and position prior to maximising.
|
||||||
|
export function WindowUnmaximise(): void;
|
||||||
|
|
||||||
|
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
|
||||||
|
// Returns the state of the window, i.e. whether the window is maximised or not.
|
||||||
|
export function WindowIsMaximised(): Promise<boolean>;
|
||||||
|
|
||||||
|
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
|
||||||
|
// Minimises the window.
|
||||||
|
export function WindowMinimise(): void;
|
||||||
|
|
||||||
|
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
|
||||||
|
// Restores the window to the dimensions and position prior to minimising.
|
||||||
|
export function WindowUnminimise(): void;
|
||||||
|
|
||||||
|
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
|
||||||
|
// Returns the state of the window, i.e. whether the window is minimised or not.
|
||||||
|
export function WindowIsMinimised(): Promise<boolean>;
|
||||||
|
|
||||||
|
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
|
||||||
|
// Returns the state of the window, i.e. whether the window is normal or not.
|
||||||
|
export function WindowIsNormal(): Promise<boolean>;
|
||||||
|
|
||||||
|
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
|
||||||
|
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
|
||||||
|
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
|
||||||
|
|
||||||
|
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
|
||||||
|
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||||
|
export function ScreenGetAll(): Promise<Screen[]>;
|
||||||
|
|
||||||
|
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
|
||||||
|
// Opens the given URL in the system browser.
|
||||||
|
export function BrowserOpenURL(url: string): void;
|
||||||
|
|
||||||
|
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
|
||||||
|
// Returns information about the environment
|
||||||
|
export function Environment(): Promise<EnvironmentInfo>;
|
||||||
|
|
||||||
|
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
|
||||||
|
// Quits the application.
|
||||||
|
export function Quit(): void;
|
||||||
|
|
||||||
|
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
|
||||||
|
// Hides the application.
|
||||||
|
export function Hide(): void;
|
||||||
|
|
||||||
|
// [Show](https://wails.io/docs/reference/runtime/intro#show)
|
||||||
|
// Shows the application.
|
||||||
|
export function Show(): void;
|
||||||
|
|
||||||
|
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
|
||||||
|
// Returns the current text stored on clipboard
|
||||||
|
export function ClipboardGetText(): Promise<string>;
|
||||||
|
|
||||||
|
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||||
|
// Sets a text on the clipboard
|
||||||
|
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||||
|
|
||||||
|
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
|
||||||
|
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||||
|
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
|
||||||
|
|
||||||
|
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
|
||||||
|
// OnFileDropOff removes the drag and drop listeners and handlers.
|
||||||
|
export function OnFileDropOff() :void
|
||||||
|
|
||||||
|
// Check if the file path resolver is available
|
||||||
|
export function CanResolveFilePaths(): boolean;
|
||||||
|
|
||||||
|
// Resolves file paths for an array of files
|
||||||
|
export function ResolveFilePaths(files: File[]): void
|
238
frontend/wailsjs/runtime/runtime.js
Normal file
238
frontend/wailsjs/runtime/runtime.js
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
The electron alternative for Go
|
||||||
|
(c) Lea Anthony 2019-present
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function LogPrint(message) {
|
||||||
|
window.runtime.LogPrint(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogTrace(message) {
|
||||||
|
window.runtime.LogTrace(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogDebug(message) {
|
||||||
|
window.runtime.LogDebug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogInfo(message) {
|
||||||
|
window.runtime.LogInfo(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogWarning(message) {
|
||||||
|
window.runtime.LogWarning(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogError(message) {
|
||||||
|
window.runtime.LogError(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogFatal(message) {
|
||||||
|
window.runtime.LogFatal(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
|
||||||
|
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventsOn(eventName, callback) {
|
||||||
|
return EventsOnMultiple(eventName, callback, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventsOff(eventName, ...additionalEventNames) {
|
||||||
|
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventsOnce(eventName, callback) {
|
||||||
|
return EventsOnMultiple(eventName, callback, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventsEmit(eventName) {
|
||||||
|
let args = [eventName].slice.call(arguments);
|
||||||
|
return window.runtime.EventsEmit.apply(null, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowReload() {
|
||||||
|
window.runtime.WindowReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowReloadApp() {
|
||||||
|
window.runtime.WindowReloadApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetAlwaysOnTop(b) {
|
||||||
|
window.runtime.WindowSetAlwaysOnTop(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetSystemDefaultTheme() {
|
||||||
|
window.runtime.WindowSetSystemDefaultTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetLightTheme() {
|
||||||
|
window.runtime.WindowSetLightTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetDarkTheme() {
|
||||||
|
window.runtime.WindowSetDarkTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowCenter() {
|
||||||
|
window.runtime.WindowCenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetTitle(title) {
|
||||||
|
window.runtime.WindowSetTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowFullscreen() {
|
||||||
|
window.runtime.WindowFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowUnfullscreen() {
|
||||||
|
window.runtime.WindowUnfullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowIsFullscreen() {
|
||||||
|
return window.runtime.WindowIsFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowGetSize() {
|
||||||
|
return window.runtime.WindowGetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetSize(width, height) {
|
||||||
|
window.runtime.WindowSetSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetMaxSize(width, height) {
|
||||||
|
window.runtime.WindowSetMaxSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetMinSize(width, height) {
|
||||||
|
window.runtime.WindowSetMinSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetPosition(x, y) {
|
||||||
|
window.runtime.WindowSetPosition(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowGetPosition() {
|
||||||
|
return window.runtime.WindowGetPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowHide() {
|
||||||
|
window.runtime.WindowHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowShow() {
|
||||||
|
window.runtime.WindowShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowMaximise() {
|
||||||
|
window.runtime.WindowMaximise();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowToggleMaximise() {
|
||||||
|
window.runtime.WindowToggleMaximise();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowUnmaximise() {
|
||||||
|
window.runtime.WindowUnmaximise();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowIsMaximised() {
|
||||||
|
return window.runtime.WindowIsMaximised();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowMinimise() {
|
||||||
|
window.runtime.WindowMinimise();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowUnminimise() {
|
||||||
|
window.runtime.WindowUnminimise();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowSetBackgroundColour(R, G, B, A) {
|
||||||
|
window.runtime.WindowSetBackgroundColour(R, G, B, A);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScreenGetAll() {
|
||||||
|
return window.runtime.ScreenGetAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowIsMinimised() {
|
||||||
|
return window.runtime.WindowIsMinimised();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WindowIsNormal() {
|
||||||
|
return window.runtime.WindowIsNormal();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BrowserOpenURL(url) {
|
||||||
|
window.runtime.BrowserOpenURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Environment() {
|
||||||
|
return window.runtime.Environment();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Quit() {
|
||||||
|
window.runtime.Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Hide() {
|
||||||
|
window.runtime.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Show() {
|
||||||
|
window.runtime.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClipboardGetText() {
|
||||||
|
return window.runtime.ClipboardGetText();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClipboardSetText(text) {
|
||||||
|
return window.runtime.ClipboardSetText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @callback OnFileDropCallback
|
||||||
|
* @param {number} x - x coordinate of the drop
|
||||||
|
* @param {number} y - y coordinate of the drop
|
||||||
|
* @param {string[]} paths - A list of file paths.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||||
|
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
|
||||||
|
*/
|
||||||
|
export function OnFileDrop(callback, useDropTarget) {
|
||||||
|
return window.runtime.OnFileDrop(callback, useDropTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OnFileDropOff removes the drag and drop listeners and handlers.
|
||||||
|
*/
|
||||||
|
export function OnFileDropOff() {
|
||||||
|
return window.runtime.OnFileDropOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CanResolveFilePaths() {
|
||||||
|
return window.runtime.CanResolveFilePaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ResolveFilePaths(files) {
|
||||||
|
return window.runtime.ResolveFilePaths(files);
|
||||||
|
}
|
78
go.mod
Normal file
78
go.mod
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
module github.com/loveuer/nf-disk
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.23.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.31.0
|
||||||
|
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/samber/lo v1.38.1
|
||||||
|
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 (
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 // indirect
|
||||||
|
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/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
|
||||||
|
github.com/leaanthony/gosod v1.0.3 // indirect
|
||||||
|
github.com/leaanthony/slicer v1.6.0 // indirect
|
||||||
|
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/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.27.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||||
|
golang.org/x/net v0.25.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
|
170
go.sum
Normal file
170
go.sum
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 h1:xDAuZTn4IMm8o1LnBZvmrL8JA1io4o3YWNXgohbf20g=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5/go.mod h1:wYSv6iDS621sEFLfKvpPE2ugjTuGlAG7iROg0hLOkfc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.27.38 h1:mMVyJJuSUdbD4zKXoxDgWrgM60QwlFEg+JhihCq6wCw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.27.38/go.mod h1:6xOiNEn58bj/64MPKx89r6G/el9JZn8pvVbquSqTKK4=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.36 h1:zwI5WrT+oWWfzSKoTNmSyeBKQhsFRJRv+PGW/UZW+Yk=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.36/go.mod h1:3AG/sY1rc9NJrNWcN/3KPU4SIDPGTrd/qegKB0TnFdE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 h1:OWYvKL53l1rbsUmW7bQyJVsYU/Ii3bbAAQIIFNbM0Tk=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18/go.mod h1:CUx0G1v3wG6l01tUB+j7Y8kclA8NSqK4ef0YG79a4cg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 h1:rTWjG6AvWekO2B1LHeM3ktU7MqyX9rzWQ7hgzneZW7E=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20/go.mod h1:RGW2DDpVc8hu6Y6yG8G5CHVmVOAn1oV8rNKOHRJyswg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 h1:eb+tFOIl9ZsUe2259/BKPeniKuz4/02zZFH/i4Nf8Rg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18/go.mod h1:GVCC2IJNJTmdlyEsSmofEy7EfJncP7DNnXDzRjJ5Keg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.63.2 h1:1iXmXy8SJzQVMGvo40TSzBYS9ig6BSyXfRIMzLfmBfE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.63.2/go.mod h1:NLTqRLe3pUNu3nTEHI6XlHLKYmc8fbHUdMxAB6+s41Q=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.23.2 h1:yzi/y/vKlLyzOfG7pSu5ONNGRxHIgLeDrV4w2AMRCo0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.23.2/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.2 h1:3gb6pYhYLjo8rB1h2Tqs61wpjRd3rQymYcVq/pp0yxI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.2/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.31.2 h1:O6tyji8mXmBGsHvTCB0VIhrDw19lGTUSbKIyjnw79s8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.31.2/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI=
|
||||||
|
github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA=
|
||||||
|
github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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=
|
||||||
|
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||||
|
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
|
||||||
|
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||||
|
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
|
||||||
|
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
|
||||||
|
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||||
|
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
|
||||||
|
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=
|
||||||
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
|
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
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=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.16 h1:wffnvnkkLvhRex/aOrA3R7FP7rkvOqL/bir1br7BekU=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.16/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
|
||||||
|
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||||
|
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.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=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
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.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.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=
|
36
internal/api/api.go
Normal file
36
internal/api/api.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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)
|
||||||
|
register("/api/connection/connect", handler.ConnectionConnect)
|
||||||
|
register("/api/connection/disconnect", handler.ConnectionDisconnect)
|
||||||
|
register("/api/connection/buckets", handler.ConnectionBuckets)
|
||||||
|
register("/api/bucket/files", handler.BucketFiles)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
38
internal/controller/app.go
Normal file
38
internal/controller/app.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/loveuer/nf-disk/internal/api"
|
||||||
|
"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/tool"
|
||||||
|
"github.com/loveuer/nf-disk/ndh"
|
||||||
|
"github.com/loveuer/nf/nft/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
ctx context.Context
|
||||||
|
handlers map[string]ndh.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApp() *App {
|
||||||
|
return &App{
|
||||||
|
handlers: make(map[string]ndh.Handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Init(ctx context.Context) {
|
||||||
|
log.Info("app init!!!")
|
||||||
|
|
||||||
|
a.ctx = ctx
|
||||||
|
|
||||||
|
tool.Must(db.Init(ctx, "sqlite::memory", db.OptSqliteByMem(nil)))
|
||||||
|
tool.Must(model.Init(db.Default.Session()))
|
||||||
|
tool.Must(manager.Init(ctx))
|
||||||
|
tool.Must(api.Init(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Startup(ctx context.Context) {
|
||||||
|
log.Info("app startup!!!")
|
||||||
|
}
|
51
internal/controller/invoke.go
Normal file
51
internal/controller/invoke.go
Normal file
@ -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()
|
||||||
|
}
|
61
internal/db/client.go
Normal file
61
internal/db/client.go
Normal file
@ -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
|
||||||
|
}
|
44
internal/db/db_test.go
Normal file
44
internal/db/db_test.go
Normal file
@ -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()
|
||||||
|
}
|
54
internal/db/init.go
Normal file
54
internal/db/init.go
Normal file
@ -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
|
||||||
|
}
|
27
internal/db/option.go
Normal file
27
internal/db/option.go
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
63
internal/db/sqlite.go
Normal file
63
internal/db/sqlite.go
Normal file
@ -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
|
||||||
|
}
|
40
internal/handler/bucket.go
Normal file
40
internal/handler/bucket.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/loveuer/nf-disk/internal/manager"
|
||||||
|
"github.com/loveuer/nf-disk/internal/s3"
|
||||||
|
"github.com/loveuer/nf-disk/ndh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BucketFiles(c *ndh.Ctx) error {
|
||||||
|
type Req struct {
|
||||||
|
ConnId uint64 `json:"conn_id"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
Prefix string `json:"prefix"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
req = new(Req)
|
||||||
|
client *s3.Client
|
||||||
|
list []*s3.ListFileRes
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = c.ReqParse(req); err != nil {
|
||||||
|
return c.Send400(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ConnId == 0 || req.Bucket == "" {
|
||||||
|
return c.Send400(req, "缺少参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, client, err = manager.Manager.Use(req.ConnId); err != nil {
|
||||||
|
return c.Send500(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if list, err = client.ListFile(c.Context(), req.Bucket, req.Prefix); err != nil {
|
||||||
|
return c.Send500(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send200(map[string]any{"list": list})
|
||||||
|
}
|
228
internal/handler/connection.go
Normal file
228
internal/handler/connection.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConnectionTest(c *ndh.Ctx) error {
|
||||||
|
type Req struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Access string `json:"access"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
req = new(Req)
|
||||||
|
)
|
||||||
|
|
||||||
|
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 _, err = s3.New(c.Context(), req.Endpoint, req.Access, req.Key); err != nil {
|
||||||
|
return c.Send500(err.Error(), "连接失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Manager.Register(connection, client); err != nil {
|
||||||
|
return c.Send500(err.Error(), "创建连接失败(2)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send200(connection, "创建连接成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
listMap := lo.SliceToMap(list, func(item *model.Connection) (uint64, *model.Connection) {
|
||||||
|
return item.Id, item
|
||||||
|
})
|
||||||
|
|
||||||
|
manager.Manager.Map(func(c *model.Connection, s *s3.Client) error {
|
||||||
|
if item, ok := listMap[c.Id]; ok {
|
||||||
|
item.Active = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.Send200(map[string]any{"list": list})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectionConnect(c *ndh.Ctx) error {
|
||||||
|
type Req struct {
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
req = new(Req)
|
||||||
|
client *s3.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
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 client, err = s3.New(c.Context(), conn.Endpoint, conn.Access, conn.Key); err != nil {
|
||||||
|
return c.Send500(err.Error(), "连接失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = manager.Manager.Register(conn, client); err != nil {
|
||||||
|
return c.Send500(err.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())
|
||||||
|
}
|
||||||
|
|
||||||
|
buckets = append(buckets, &s3.ListBucketRes{
|
||||||
|
Name: "这是一个非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长的名字",
|
||||||
|
CreatedAt: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// todo: for frontend test
|
||||||
|
for i := 1; i <= 500; i++ {
|
||||||
|
buckets = append(buckets, &s3.ListBucketRes{
|
||||||
|
CreatedAt: time.Now().UnixMilli(),
|
||||||
|
Name: fmt.Sprintf("test-bucket-%03d", i),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send200(map[string]any{"list": buckets})
|
||||||
|
}
|
21
internal/handler/item.go
Normal file
21
internal/handler/item.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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!!!")
|
||||||
|
}
|
7
internal/manager/error.go
Normal file
7
internal/manager/error.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package manager
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
)
|
75
internal/manager/manager.go
Normal file
75
internal/manager/manager.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/loveuer/nf-disk/internal/model"
|
||||||
|
"github.com/loveuer/nf-disk/internal/s3"
|
||||||
|
"github.com/loveuer/nf/nft/log"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
conn *model.Connection
|
||||||
|
client *s3.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type manager struct {
|
||||||
|
sync.Mutex
|
||||||
|
clients map[uint64]*client
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
Manager *manager
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init(ctx context.Context) error {
|
||||||
|
Manager = &manager{
|
||||||
|
clients: make(map[uint64]*client),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Register(c *model.Connection, s *s3.Client) error {
|
||||||
|
log.Debug("manager: register connection-client: id = %d, name = %s", c.Id, c.Name)
|
||||||
|
|
||||||
|
Manager.Lock()
|
||||||
|
defer Manager.Unlock()
|
||||||
|
Manager.clients[c.Id] = &client{conn: c, client: s}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) UnRegister(id uint64) error {
|
||||||
|
Manager.Lock()
|
||||||
|
defer Manager.Unlock()
|
||||||
|
c, ok := m.clients[id]
|
||||||
|
if !ok {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("manager: register connection-client: id = %d, name = %s", c.conn, c.conn.Name)
|
||||||
|
|
||||||
|
delete(m.clients, id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Map(fn func(*model.Connection, *s3.Client) error) error {
|
||||||
|
for _, item := range m.clients {
|
||||||
|
if err := fn(item.conn, item.client); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) Use(id uint64) (*model.Connection, *s3.Client, error) {
|
||||||
|
c, ok := m.clients[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.conn, c.client, nil
|
||||||
|
}
|
34
internal/model/init.go
Normal file
34
internal/model/init.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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(
|
||||||
|
&Connection{},
|
||||||
|
)
|
||||||
|
|
||||||
|
if opt.Debug {
|
||||||
|
err = tx.Create([]*Connection{
|
||||||
|
{
|
||||||
|
Name: "dev-minio",
|
||||||
|
Endpoint: "http://10.220.10.15:9000",
|
||||||
|
Access: "8ALV3DUZI31YG4BDRJ0Z",
|
||||||
|
Key: "CRqwS1MsiUj27TbRK+3T2n+LpKWd07VvaDKuzU0H",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "test",
|
||||||
|
Endpoint: "http://10.220.10.14:19000",
|
||||||
|
Access: "5VCR05L4BSGNCTCD8DXP",
|
||||||
|
Key: "FPTMYBEiHhWLJ05C3aGXW8bjFXXNmghc8Za3Fo2u",
|
||||||
|
},
|
||||||
|
}).Clauses(clause.OnConflict{
|
||||||
|
DoNothing: true,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
36
internal/model/s3.go
Normal file
36
internal/model/s3.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/loveuer/nf-disk/ndh"
|
||||||
|
"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"`
|
||||||
|
|
||||||
|
Active bool `json:"active" gorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
11
internal/opt/var.go
Normal file
11
internal/opt/var.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package opt
|
||||||
|
|
||||||
|
const (
|
||||||
|
Msg200 = "操作成功"
|
||||||
|
Msg400 = "输入不正确"
|
||||||
|
Msg500 = "发生错误"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Debug bool = false
|
||||||
|
)
|
101
internal/s3/list.go
Normal file
101
internal/s3/list.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
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"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListBucketRes struct {
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListFileType int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
ListFileTypeFile ListFileType = iota
|
||||||
|
ListFileTypeDir
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListFileRes struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
LastModified time.Time `json:"last_modified"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Type ListFileType `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListFile(ctx context.Context, bucket string, prefix string) ([]*ListFileRes, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
input = &s3.ListObjectsV2Input{
|
||||||
|
Delimiter: aws.String("/"),
|
||||||
|
MaxKeys: aws.Int32(1000),
|
||||||
|
Bucket: aws.String(bucket),
|
||||||
|
}
|
||||||
|
output *s3.ListObjectsV2Output
|
||||||
|
)
|
||||||
|
|
||||||
|
if prefix != "" {
|
||||||
|
input.Prefix = aws.String(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if output, err = c.client.ListObjectsV2(ctx, input); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
folder := lo.FilterMap(
|
||||||
|
output.CommonPrefixes,
|
||||||
|
func(item types.CommonPrefix, index int) (*ListFileRes, bool) {
|
||||||
|
name := strings.TrimPrefix(*item.Prefix, prefix)
|
||||||
|
return &ListFileRes{
|
||||||
|
Name: name,
|
||||||
|
Key: *item.Prefix,
|
||||||
|
Type: ListFileTypeDir,
|
||||||
|
}, name != ""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
list := lo.Map(
|
||||||
|
output.Contents,
|
||||||
|
func(item types.Object, index int) *ListFileRes {
|
||||||
|
return &ListFileRes{
|
||||||
|
Key: strings.Clone(*item.Key),
|
||||||
|
Name: strings.TrimPrefix(*item.Key, prefix),
|
||||||
|
LastModified: *item.LastModified,
|
||||||
|
Size: *item.Size,
|
||||||
|
Type: ListFileTypeFile,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return append(folder, list...), nil
|
||||||
|
}
|
72
internal/s3/s3.go
Normal file
72
internal/s3/s3.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolverV2 struct{}
|
||||||
|
|
||||||
|
func (*resolverV2) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) (smithyendpoints.Endpoint, error) {
|
||||||
|
u, err := url.Parse(*params.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("resolver v2: parse url = %s, err = %s", params.Endpoint, err.Error())
|
||||||
|
return smithyendpoints.Endpoint{}, err
|
||||||
|
}
|
||||||
|
return smithyendpoints.Endpoint{
|
||||||
|
URI: *u,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
client *s3.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, endpoint string, access string, key string) (*Client, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
sdkConfig aws.Config
|
||||||
|
output *s3.ListBucketsOutput
|
||||||
|
)
|
||||||
|
|
||||||
|
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
||||||
|
return aws.Endpoint{
|
||||||
|
URL: endpoint,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if sdkConfig, err = config.LoadDefaultConfig(
|
||||||
|
ctx,
|
||||||
|
config.WithEndpointResolverWithOptions(customResolver),
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s3Client := s3.NewFromConfig(sdkConfig, func(o *s3.Options) {
|
||||||
|
//o.BaseEndpoint = aws.String(endpoint)
|
||||||
|
//o.EndpointResolverV2 = &resolverV2{}
|
||||||
|
o.Credentials = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(access, key, ""))
|
||||||
|
o.UsePathStyle = true
|
||||||
|
o.Region = "auto"
|
||||||
|
})
|
||||||
|
|
||||||
|
if output, err = s3Client.ListBuckets(tool.Timeout(5), &s3.ListBucketsInput{
|
||||||
|
MaxBuckets: aws.Int32(2),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range output.Buckets {
|
||||||
|
log.Debug("s3.New: list bucket name = %s", *item.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{client: s3Client}, nil
|
||||||
|
}
|
37
internal/s3/s3_test.go
Normal file
37
internal/s3/s3_test.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/loveuer/nf-disk/internal/tool"
|
||||||
|
"github.com/loveuer/nf/nft/log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewClient(t *testing.T) {
|
||||||
|
log.SetLogLevel(log.LogLevelDebug)
|
||||||
|
_, err := New(context.TODO(), "http://10.220.10.15:9000/", "8ALV3DUZI31YG4BDRJ0Z", "CRqwS1MsiUj27TbRK+3T2n+LpKWd07VvaDKuzU0H")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("call s3.New err = %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListFile(t *testing.T) {
|
||||||
|
//log.SetLogLevel(log.LogLevelDebug)
|
||||||
|
|
||||||
|
//cli, err := New(context.TODO(), "http://10.220.10.14:19000", "5VCR05L4BSGNCTCD8DXP", "FPTMYBEiHhWLJ05C3aGXW8bjFXXNmghc8Za3Fo2u")
|
||||||
|
cli, err := New(context.TODO(), "http://10.220.10.15:9000/", "8ALV3DUZI31YG4BDRJ0Z", "CRqwS1MsiUj27TbRK+3T2n+LpKWd07VvaDKuzU0H")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("call s3.New err = %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := cli.ListFile(tool.Timeout(30), "topic-audit", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("call s3.ListFile err = %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("[x] file length = %d", len(files))
|
||||||
|
|
||||||
|
for _, item := range files {
|
||||||
|
t.Logf("[x: %d] file = %s, size = %d", item.Type, item.Name, item.Size)
|
||||||
|
}
|
||||||
|
}
|
38
internal/tool/ctx.go
Normal file
38
internal/tool/ctx.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package tool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Timeout(seconds ...int) (ctx context.Context) {
|
||||||
|
var (
|
||||||
|
duration time.Duration
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(seconds) > 0 && seconds[0] > 0 {
|
||||||
|
duration = time.Duration(seconds[0]) * time.Second
|
||||||
|
} else {
|
||||||
|
duration = time.Duration(30) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, _ = context.WithTimeout(context.Background(), duration)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeoutCtx(ctx context.Context, seconds ...int) context.Context {
|
||||||
|
var (
|
||||||
|
duration time.Duration
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(seconds) > 0 && seconds[0] > 0 {
|
||||||
|
duration = time.Duration(seconds[0]) * time.Second
|
||||||
|
} else {
|
||||||
|
duration = time.Duration(30) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
nctx, _ := context.WithTimeout(ctx, duration)
|
||||||
|
|
||||||
|
return nctx
|
||||||
|
}
|
11
internal/tool/must.go
Normal file
11
internal/tool/must.go
Normal file
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
main.go
Normal file
54
main.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"embed"
|
||||||
|
"flag"
|
||||||
|
"github.com/loveuer/nf-disk/internal/controller"
|
||||||
|
"github.com/loveuer/nf-disk/internal/opt"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/loveuer/nf/nft/log"
|
||||||
|
"github.com/wailsapp/wails/v2"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed all:frontend/dist
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
flag.BoolVar(&opt.Debug, "debug", true, "debug mode")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if opt.Debug {
|
||||||
|
log.SetLogLevel(log.LogLevelDebug)
|
||||||
|
}
|
||||||
|
|
||||||
|
app := controller.NewApp()
|
||||||
|
|
||||||
|
app.Init(ctx)
|
||||||
|
|
||||||
|
if err := wails.Run(&options.App{
|
||||||
|
Title: "nf-disk",
|
||||||
|
Width: 1024,
|
||||||
|
Height: 768,
|
||||||
|
AssetServer: &assetserver.Options{
|
||||||
|
Assets: assets,
|
||||||
|
},
|
||||||
|
BackgroundColour: &options.RGBA{R: 223, G: 223, B: 223, A: 1},
|
||||||
|
OnStartup: app.Startup,
|
||||||
|
Bind: []interface{}{
|
||||||
|
app,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
log.Fatal("wails run err: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
72
ndh/ctx.go
Normal file
72
ndh/ctx.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package ndh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ctx struct {
|
||||||
|
ctx context.Context
|
||||||
|
req io.Reader
|
||||||
|
res io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCtx(ctx context.Context, req io.Reader, res io.Writer) *Ctx {
|
||||||
|
return &Ctx{
|
||||||
|
ctx: ctx,
|
||||||
|
req: req,
|
||||||
|
res: res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) Context() context.Context {
|
||||||
|
return c.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) Write(bs []byte) (int, error) {
|
||||||
|
return c.res.Write(bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) ReqParse(req any) error {
|
||||||
|
return json.NewDecoder(c.req).Decode(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) Send200(data any, msg ...string) error {
|
||||||
|
m := "操作成功"
|
||||||
|
if len(msg) > 0 && msg[0] != "" {
|
||||||
|
m = msg[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send(200, m, "", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) Send400(data any, msg ...string) error {
|
||||||
|
m := "参数错误"
|
||||||
|
if len(msg) > 0 && msg[0] != "" {
|
||||||
|
m = msg[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send(400, m, "", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) Send500(data any, msg ...string) error {
|
||||||
|
m := "系统错误"
|
||||||
|
if len(msg) > 0 && msg[0] != "" {
|
||||||
|
m = msg[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Send(500, m, "", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) Send(status uint32, msg, error string, data any) error {
|
||||||
|
value := map[string]any{"status": status, "msg": msg, "err": error, "data": data}
|
||||||
|
bs, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Write(bs)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
3
ndh/handler.go
Normal file
3
ndh/handler.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package ndh
|
||||||
|
|
||||||
|
type Handler func(c *Ctx) error
|
41
package-lock.json
generated
Normal file
41
package-lock.json
generated
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
package.json
Normal file
5
package.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"zustand": "^5.0.0-rc.2"
|
||||||
|
}
|
||||||
|
}
|
13
wails.json
Normal file
13
wails.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://wails.io/schemas/config.v2.json",
|
||||||
|
"name": "nf-disk",
|
||||||
|
"outputfilename": "nf-disk",
|
||||||
|
"frontend:install": "npm install",
|
||||||
|
"frontend:build": "npm run build",
|
||||||
|
"frontend:dev:watcher": "npm run dev",
|
||||||
|
"frontend:dev:serverUrl": "auto",
|
||||||
|
"author": {
|
||||||
|
"name": "zhaoyupeng",
|
||||||
|
"email": "zhaoyupeng@umisen.com"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user