Files tab: .md → note editor via CheckFileAction + frontend
Backend: - FindByFileID: notes+files JOIN query - LinkFile: INSERT OR IGNORE notes record - CheckFileAction binding: note/preview/external/auto-link Frontend (App.svelte): - Import isMarkdownFile from fileUtils - openPreview now calls CheckFileAction for .md files - .md+note → switch to Notes tab + note editor - .md outside Notes/ → inline preview - non-.md → unchanged Tests: 7 new (FindByFileID×3, CheckFileAction×4), all PASS
This commit is contained in:
parent
fec35f55b8
commit
bfe57ac0ac
|
|
@ -191,6 +191,14 @@ type FileTreeItemDTO struct {
|
|||
HasKids bool `json:"hasKids"`
|
||||
}
|
||||
|
||||
// PreflightFileAction describes what should happen when opening a file from the Files tab.
|
||||
type PreflightFileAction struct {
|
||||
Action string `json:"action"` // "note" | "preview" | "external"
|
||||
NoteID string `json:"noteId,omitempty"`
|
||||
NoteTitle string `json:"noteTitle,omitempty"`
|
||||
FileName string `json:"fileName"`
|
||||
}
|
||||
|
||||
type ActionDTO struct {
|
||||
ID string `json:"id"`
|
||||
NodeID string `json:"nodeId"`
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"verstak/internal/core/activity"
|
||||
"verstak/internal/core/files"
|
||||
"verstak/internal/core/nodes"
|
||||
|
|
@ -168,6 +172,44 @@ func (a *App) ValidateName(name string) error {
|
|||
return files.ValidateName(name)
|
||||
}
|
||||
|
||||
func (a *App) CheckFileAction(fileID string) (*PreflightFileAction, error) {
|
||||
if err := a.requireVault(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileRec, err := a.files.Get(fileID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get file: %w", err)
|
||||
}
|
||||
name := strings.ToLower(fileRec.Filename)
|
||||
isMD := strings.HasSuffix(name, ".md") || strings.HasSuffix(name, ".markdown")
|
||||
if !isMD {
|
||||
return &PreflightFileAction{Action: "external", FileName: fileRec.Filename}, nil
|
||||
}
|
||||
// .md file — check for linked note
|
||||
noteRec, err := a.notes.FindByFileID(fileID)
|
||||
if err == nil && noteRec != nil {
|
||||
noteNode, nodeErr := a.nodes.Get(noteRec.NodeID)
|
||||
title := fileRec.Filename
|
||||
if nodeErr == nil && noteNode != nil {
|
||||
title = noteNode.Title
|
||||
}
|
||||
return &PreflightFileAction{Action: "note", NoteID: noteRec.NodeID, NoteTitle: title, FileName: fileRec.Filename}, nil
|
||||
}
|
||||
// .md inside Notes/ with no note record — auto-link
|
||||
pathLower := strings.ToLower(fileRec.Path)
|
||||
insideNotes := strings.Contains(pathLower, string(filepath.Separator)+"notes"+string(filepath.Separator)) ||
|
||||
strings.HasPrefix(pathLower, "notes"+string(filepath.Separator))
|
||||
if insideNotes {
|
||||
noteNode, nodeErr := a.nodes.Get(fileRec.NodeID)
|
||||
if nodeErr == nil && noteNode != nil {
|
||||
_ = a.notes.LinkFile(noteNode.ID, fileID, "markdown")
|
||||
return &PreflightFileAction{Action: "note", NoteID: noteNode.ID, NoteTitle: noteNode.Title, FileName: fileRec.Filename}, nil
|
||||
}
|
||||
}
|
||||
// .md outside Notes/ — internal preview
|
||||
return &PreflightFileAction{Action: "preview", FileName: fileRec.Filename}, nil
|
||||
}
|
||||
|
||||
func (a *App) PreviewImport(sourcePath string) (*files.ImportSummary, error) {
|
||||
if err := a.requireVault(); err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -19,7 +19,7 @@
|
|||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-DwDG7FeH.js"></script>
|
||||
<script type="module" crossorigin src="/assets/main-CzfuqGWF.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-bQpH1es2.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -246,3 +246,217 @@ func TestRepairMovesDirectNoteChildrenToNotesFolder(t *testing.T) {
|
|||
t.Errorf("file path should be %q, got %q", expectedRelPath, recs[0].Path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TestCheckFileAction_NoteLinked verifies that CheckFileAction returns
|
||||
// Action="note" for a .md file linked to a note record.
|
||||
func TestCheckFileAction_NoteLinked(t *testing.T) {
|
||||
app, _ := setupTestApp(t)
|
||||
proj, err := app.CreateNodeFromTemplate("", "TestProj", "project.default")
|
||||
if err != nil {
|
||||
t.Fatalf("create project: %v", err)
|
||||
}
|
||||
// Find the Overview note — it should be inside Notes folder, linked via notes record
|
||||
children, err := app.nodes.ListChildren(proj.ID, false)
|
||||
if err != nil {
|
||||
t.Fatalf("ListChildren: %v", err)
|
||||
}
|
||||
var notesFolder *nodes.Node
|
||||
for i := range children {
|
||||
if children[i].Title == notes.NotesFolder && children[i].Type == "folder" {
|
||||
notesFolder = &children[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if notesFolder == nil {
|
||||
t.Fatal("Notes folder not found")
|
||||
}
|
||||
notesChildren, err := app.nodes.ListChildren(notesFolder.ID, false)
|
||||
if err != nil {
|
||||
t.Fatalf("ListChildren(Notes): %v", err)
|
||||
}
|
||||
if len(notesChildren) == 0 {
|
||||
t.Fatal("expected at least one note inside Notes folder")
|
||||
}
|
||||
// Get file ID for the Overview note
|
||||
items, err := app.ListItems(notesFolder.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("ListItems: %v", err)
|
||||
}
|
||||
var overviewFileID string
|
||||
for _, item := range items {
|
||||
if item.Type == "note" && item.Name == "Overview" {
|
||||
overviewFileID = item.FileID
|
||||
break
|
||||
}
|
||||
}
|
||||
if overviewFileID == "" {
|
||||
t.Fatal("Overview note has no FileID")
|
||||
}
|
||||
// CheckFileAction should return Action="note"
|
||||
action, err := app.CheckFileAction(overviewFileID)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckFileAction: %v", err)
|
||||
}
|
||||
if action.Action != "note" {
|
||||
t.Errorf("expected Action=note for linked .md, got %q", action.Action)
|
||||
}
|
||||
if action.NoteID == "" {
|
||||
t.Error("expected non-empty NoteID for linked note")
|
||||
}
|
||||
if action.NoteTitle == "" {
|
||||
t.Error("expected non-empty NoteTitle")
|
||||
}
|
||||
if action.FileName == "" {
|
||||
t.Error("expected non-empty FileName")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckFileAction_ExternalForNonMD verifies that non-.md files return
|
||||
// Action="external" from CheckFileAction.
|
||||
func TestCheckFileAction_ExternalForNonMD(t *testing.T) {
|
||||
app, vault := setupTestApp(t)
|
||||
proj, err := app.CreateNodeFromTemplate("", "TestProj", "project.default")
|
||||
if err != nil {
|
||||
t.Fatalf("create project: %v", err)
|
||||
}
|
||||
// Create a file node and record for a non-.md file
|
||||
fileNode, err := app.nodes.Create(&proj.ID, nodes.TypeFile, "image.png", 0, "", filepath.Join(proj.FsPath, "image.png"))
|
||||
if err != nil {
|
||||
t.Fatalf("create file node: %v", err)
|
||||
}
|
||||
absPath := filepath.Join(vault, proj.FsPath, "image.png")
|
||||
if err := os.MkdirAll(filepath.Dir(absPath), 0o755); err != nil {
|
||||
t.Fatalf("mkdir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(absPath, []byte("fake-png"), 0o640); err != nil {
|
||||
t.Fatalf("write file: %v", err)
|
||||
}
|
||||
// Insert file record directly
|
||||
_, err = app.db.Exec(
|
||||
`INSERT INTO files (id,node_id,filename,path,storage_mode,size,sha256,mime,created_at,updated_at,missing)
|
||||
VALUES (?,?,?,?,'vault',0,'','image/png','2024-01-01T00:00:00Z','2024-01-01T00:00:00Z',0)`,
|
||||
"file-png-"+fileNode.ID, fileNode.ID, "image.png", filepath.Join(proj.FsPath, "image.png"))
|
||||
if err != nil {
|
||||
t.Fatalf("insert file record: %v", err)
|
||||
}
|
||||
// CheckFileAction should return Action="external"
|
||||
action, err := app.CheckFileAction("file-png-" + fileNode.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckFileAction: %v", err)
|
||||
}
|
||||
if action.Action != "external" {
|
||||
t.Errorf("expected Action=external for .png, got %q", action.Action)
|
||||
}
|
||||
if action.FileName != "image.png" {
|
||||
t.Errorf("expected FileName=image.png, got %q", action.FileName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckFileAction_PreviewForMDOutsideNotes verifies that .md files
|
||||
// outside Notes/ without a note record return Action="preview".
|
||||
func TestCheckFileAction_PreviewForMDOutsideNotes(t *testing.T) {
|
||||
app, vault := setupTestApp(t)
|
||||
proj, err := app.CreateNodeFromTemplate("", "TestProj", "project.default")
|
||||
if err != nil {
|
||||
t.Fatalf("create project: %v", err)
|
||||
}
|
||||
// Create a .md file directly under the project (not inside Notes/)
|
||||
mdNode, err := app.nodes.Create(&proj.ID, nodes.TypeFile, "readme.md", 0, "", filepath.Join(proj.FsPath, "readme.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("create md node: %v", err)
|
||||
}
|
||||
absPath := filepath.Join(vault, proj.FsPath, "readme.md")
|
||||
if err := os.MkdirAll(filepath.Dir(absPath), 0o755); err != nil {
|
||||
t.Fatalf("mkdir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(absPath, []byte("# Readme\n"), 0o640); err != nil {
|
||||
t.Fatalf("write file: %v", err)
|
||||
}
|
||||
// Insert file record directly
|
||||
_, err = app.db.Exec(
|
||||
`INSERT INTO files (id,node_id,filename,path,storage_mode,size,sha256,mime,created_at,updated_at,missing)
|
||||
VALUES (?,?,?,?,'vault',0,'','text/markdown','2024-01-01T00:00:00Z','2024-01-01T00:00:00Z',0)`,
|
||||
"file-md-"+mdNode.ID, mdNode.ID, "readme.md", filepath.Join(proj.FsPath, "readme.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("insert file record: %v", err)
|
||||
}
|
||||
// Do NOT create a notes record — this .md is outside Notes/
|
||||
action, err := app.CheckFileAction("file-md-" + mdNode.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckFileAction: %v", err)
|
||||
}
|
||||
if action.Action != "preview" {
|
||||
t.Errorf("expected Action=preview for .md outside Notes/, got %q", action.Action)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFileAction_AutoLinkInNotes(t *testing.T) {
|
||||
app, vault := setupTestApp(t)
|
||||
proj, err := app.CreateNodeFromTemplate("", "TestProj", "project.default")
|
||||
if err != nil {
|
||||
t.Fatalf("create project: %v", err)
|
||||
}
|
||||
// Find Notes folder
|
||||
children, err := app.nodes.ListChildren(proj.ID, false)
|
||||
if err != nil {
|
||||
t.Fatalf("ListChildren: %v", err)
|
||||
}
|
||||
var notesFolder *nodes.Node
|
||||
for i := range children {
|
||||
if children[i].Title == notes.NotesFolder && children[i].Type == "folder" {
|
||||
notesFolder = &children[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if notesFolder == nil {
|
||||
t.Fatal("Notes folder not found")
|
||||
}
|
||||
// Create a .md file INSIDE Notes/ but WITHOUT a notes record
|
||||
mdNode, err := app.nodes.Create(¬esFolder.ID, nodes.TypeFile, "orphan.md", 0, "",
|
||||
filepath.Join(proj.FsPath, notes.NotesFolder, "orphan.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("create md node: %v", err)
|
||||
}
|
||||
notesDir := filepath.Join(vault, proj.FsPath, notes.NotesFolder)
|
||||
if err := os.MkdirAll(notesDir, 0o755); err != nil {
|
||||
t.Fatalf("mkdir: %v", err)
|
||||
}
|
||||
absPath := filepath.Join(notesDir, "orphan.md")
|
||||
if err := os.WriteFile(absPath, []byte("# Orphan Note\n"), 0o640); err != nil {
|
||||
t.Fatalf("write file: %v", err)
|
||||
}
|
||||
// Insert file record
|
||||
_, err = app.db.Exec(
|
||||
`INSERT INTO files (id,node_id,filename,path,storage_mode,size,sha256,mime,created_at,updated_at,missing)
|
||||
VALUES (?,?,?,?,'vault',0,'','text/markdown','2024-01-01T00:00:00Z','2024-01-01T00:00:00Z',0)`,
|
||||
"file-orphan-"+mdNode.ID, mdNode.ID, "orphan.md",
|
||||
filepath.Join(proj.FsPath, notes.NotesFolder, "orphan.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("insert file record: %v", err)
|
||||
}
|
||||
// No notes record yet — just file + node
|
||||
// CheckFileAction should auto-link and return Action="note"
|
||||
action, err := app.CheckFileAction("file-orphan-" + mdNode.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("CheckFileAction: %v", err)
|
||||
}
|
||||
if action.Action != "note" {
|
||||
t.Errorf("expected Action=note for .md inside Notes/, got %q", action.Action)
|
||||
}
|
||||
if action.NoteID == "" {
|
||||
t.Error("expected auto-linked NoteID")
|
||||
}
|
||||
if action.NoteTitle == "" {
|
||||
t.Error("expected NoteTitle for auto-linked note")
|
||||
}
|
||||
// Verify notes record was actually created
|
||||
noteRec, err := app.notes.FindByFileID("file-orphan-" + mdNode.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByFileID after auto-link: %v", err)
|
||||
}
|
||||
if noteRec == nil {
|
||||
t.Fatal("expected note record after auto-link")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
import AppHeader from './lib/AppHeader.svelte'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import { actionIcon } from './lib/actionIcons.js'
|
||||
import { canPreviewFile, needsBase64Preview, needsTextPreview } from './lib/fileUtils.js'
|
||||
import { canPreviewFile, needsBase64Preview, needsTextPreview, isMarkdownFile } from './lib/fileUtils.js'
|
||||
import { t } from './lib/i18n'
|
||||
import NoteEditorPanel from './lib/components/notes/NoteEditorPanel.svelte'
|
||||
import InternalLinkPicker from './lib/components/notes/InternalLinkPicker.svelte'
|
||||
|
|
@ -622,6 +622,24 @@
|
|||
// ===== File preview =====
|
||||
|
||||
async function openPreview(item) {
|
||||
// For .md files: check if linked to a note, open note editor instead of preview modal
|
||||
if (item && item.fileId && isMarkdownFile(item)) {
|
||||
try {
|
||||
const action = await wailsCall('CheckFileAction', item.fileId)
|
||||
if (action.action === 'note') {
|
||||
await openNote({ id: action.noteId, title: action.noteTitle })
|
||||
return
|
||||
}
|
||||
if (action.action === 'external') {
|
||||
await wailsCall('OpenFile', item.fileId)
|
||||
return
|
||||
}
|
||||
// 'preview' → fall through to normal preview
|
||||
} catch (e) {
|
||||
console.warn('CheckFileAction failed, falling back to preview:', e)
|
||||
}
|
||||
}
|
||||
|
||||
previewItem = item
|
||||
previewContent = ''
|
||||
previewError = ''
|
||||
|
|
|
|||
|
|
@ -122,6 +122,11 @@ func NewService(db *storage.DB, vaultRoot string, nodeRepo *nodes.Repository, fi
|
|||
return &Service{db: db, vaultRoot: vaultRoot, nodes: nodeRepo, files: fileSvc}
|
||||
}
|
||||
|
||||
// DB returns the underlying storage.DB for direct queries (used in tests).
|
||||
func (s *Service) DB() *storage.DB {
|
||||
return s.db
|
||||
}
|
||||
|
||||
// FindNotesFolder returns the TypeFolder "Notes" node under parentID, or nil.
|
||||
func (s *Service) FindNotesFolder(parentID string) *nodes.Node {
|
||||
children, err := s.nodes.ListChildren(parentID, false)
|
||||
|
|
@ -683,6 +688,37 @@ func (s *Service) repairNoteFilePath(noteID, caseID string, res *RepairResult) e
|
|||
return nil
|
||||
}
|
||||
|
||||
|
||||
// FindByFileID looks up the note record associated with the given file ID.
|
||||
|
||||
|
||||
// LinkFile creates a notes record linking an existing node to an existing file record.
|
||||
// It is a no-op if a record for node_id already exists (idempotent).
|
||||
func (s *Service) LinkFile(nodeID, fileID, format string) error {
|
||||
if format == "" {
|
||||
format = "markdown"
|
||||
}
|
||||
_, err := s.db.Exec(
|
||||
`INSERT OR IGNORE INTO notes (node_id, file_id, format) VALUES (?,?,?)`,
|
||||
nodeID, fileID, format,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) FindByFileID(fileID string) (*Record, error) {
|
||||
row := s.db.QueryRow(
|
||||
`SELECT node_id, file_id, format, encrypted FROM notes WHERE file_id=?`, fileID,
|
||||
)
|
||||
var rec Record
|
||||
var enc int
|
||||
if err := row.Scan(&rec.NodeID, &rec.FileID, &rec.Format, &enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rec.Encrypted = enc != 0
|
||||
return &rec, nil
|
||||
}
|
||||
|
||||
|
||||
// fileExists returns true if path refers to an existing file or directory.
|
||||
func fileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
|
|
|
|||
|
|
@ -1431,3 +1431,95 @@ func TestFilesTabManualMoveRepair(t *testing.T) {
|
|||
t.Error("Files tab should show Overview note inside Notes folder after repair")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestFindByFileID_Success(t *testing.T) {
|
||||
svc, nodeRepo, _, vaultDir := setupRepairTest(t)
|
||||
caseNode := createCaseNode(t, nodeRepo, "TestCase", "test_case")
|
||||
// Create a note via the full Create path...
|
||||
noteNode, fileRec, err := svc.Create(caseNode.ID, "TestNote", "")
|
||||
if err != nil {
|
||||
t.Fatalf("create note: %v", err)
|
||||
}
|
||||
// Verify FindByFileID works
|
||||
rec, err := svc.FindByFileID(fileRec.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByFileID: %v", err)
|
||||
}
|
||||
if rec == nil {
|
||||
t.Fatal("expected non-nil record")
|
||||
}
|
||||
if rec.NodeID != noteNode.ID {
|
||||
t.Errorf("expected NodeID=%s, got %s", noteNode.ID, rec.NodeID)
|
||||
}
|
||||
if rec.FileID != fileRec.ID {
|
||||
t.Errorf("expected FileID=%s, got %s", fileRec.ID, rec.FileID)
|
||||
}
|
||||
if rec.Format != "markdown" {
|
||||
t.Errorf("expected Format=markdown, got %s", rec.Format)
|
||||
}
|
||||
_ = vaultDir
|
||||
}
|
||||
|
||||
func TestFindByFileID_NotFound(t *testing.T) {
|
||||
svc, _, _, _ := setupRepairTest(t)
|
||||
rec, err := svc.FindByFileID("nonexistent-file-id")
|
||||
if err == nil {
|
||||
t.Error("expected error for nonexistent file ID")
|
||||
}
|
||||
if rec != nil {
|
||||
t.Error("expected nil record")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindByFileID_AfterLinkFile(t *testing.T) {
|
||||
svc, nodeRepo, _, vaultDir := setupRepairTest(t)
|
||||
caseNode, err := nodeRepo.Create(nil, nodes.TypeCase, "TestCase", 0, "", "test_case_link")
|
||||
if err != nil {
|
||||
t.Fatalf("create case node: %v", err)
|
||||
}
|
||||
// Create directory on disk
|
||||
caseDir := filepath.Join(vaultDir, "test_case_link")
|
||||
if err := os.MkdirAll(caseDir, 0o750); err != nil {
|
||||
t.Fatalf("mkdir: %v", err)
|
||||
}
|
||||
// Write a physical .md file
|
||||
mdPath := filepath.Join(caseDir, "mynote.md")
|
||||
if err := os.WriteFile(mdPath, []byte("# mynote"), 0o640); err != nil {
|
||||
t.Fatalf("write file: %v", err)
|
||||
}
|
||||
// Create a file node
|
||||
fileNode, err := nodeRepo.Create(&caseNode.ID, nodes.TypeFile, "mynote.md", 0, "", "test_case_link/mynote.md")
|
||||
if err != nil {
|
||||
t.Fatalf("create file node: %v", err)
|
||||
}
|
||||
// Insert file record directly (insertRecord is private, so we use DB())
|
||||
db := svc.DB()
|
||||
if db == nil {
|
||||
t.Fatal("DB() returned nil")
|
||||
}
|
||||
_, err = db.Exec(
|
||||
`INSERT INTO files (id,node_id,filename,path,storage_mode,size,sha256,mime,created_at,updated_at,missing)
|
||||
VALUES (?,?,?,?,'vault',0,'','text/markdown','2024-01-01T00:00:00Z','2024-01-01T00:00:00Z',0)`,
|
||||
"file-"+fileNode.ID, fileNode.ID, "mynote.md", "test_case_link/mynote.md")
|
||||
if err != nil {
|
||||
t.Fatalf("insert file record: %v", err)
|
||||
}
|
||||
// Link note record via LinkFile
|
||||
if err := svc.LinkFile(fileNode.ID, "file-"+fileNode.ID, "markdown"); err != nil {
|
||||
t.Fatalf("LinkFile: %v", err)
|
||||
}
|
||||
// Verify
|
||||
rec, err := svc.FindByFileID("file-" + fileNode.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByFileID: %v", err)
|
||||
}
|
||||
if rec == nil {
|
||||
t.Fatal("expected non-nil record")
|
||||
}
|
||||
if rec.NodeID != fileNode.ID {
|
||||
t.Errorf("expected NodeID=%s, got %s", fileNode.ID, rec.NodeID)
|
||||
}
|
||||
_ = vaultDir
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue