gui: actions CRUD + FromTemplate bindings + UI

- CreateAction / DeleteAction Wails bindings

- FromTemplate / ListTemplates bindings with recursive tree creation

- Plugin manager stored in App struct for template access

- Action creation modal (title, kind, data) in Overview and Actions tabs

- Delete action button on action cards

- Template selector in new-node dialog
This commit is contained in:
2026-06-01 22:17:18 +08:00
parent a098cf721c
commit 996322f3a9
8 changed files with 233 additions and 34 deletions
+80
View File
@@ -17,6 +17,7 @@ import (
"verstak/internal/core/files"
"verstak/internal/core/notes"
"verstak/internal/core/nodes"
"verstak/internal/core/plugins"
"verstak/internal/core/search"
"verstak/internal/core/storage"
"verstak/internal/core/worklog"
@@ -35,6 +36,7 @@ type App struct {
actions *actions.Service
worklog *worklog.Service
search *search.Service
plugins *plugins.Manager
vault string
}
@@ -392,6 +394,66 @@ func (a *App) DeleteNode(id string) error {
return a.nodes.SoftDelete(id)
}
// ============================================================
// Templates
// ============================================================
type TemplateDTO struct {
Name string `json:"name"`
Description string `json:"description"`
Icon string `json:"icon"`
}
func (a *App) ListTemplates() []TemplateDTO {
templates := a.plugins.Templates()
out := make([]TemplateDTO, 0, len(templates))
for _, t := range templates {
out = append(out, TemplateDTO{
Name: t.Name,
Description: t.Description,
Icon: t.Icon,
})
}
return out
}
func (a *App) FromTemplate(parentID, nodeType, title, section, template string) (*NodeDTO, error) {
var tmpl *plugins.TemplateDefinition
for _, t := range a.plugins.Templates() {
if t.Name == template {
tmpl = &t
break
}
}
if tmpl == nil {
return nil, fmt.Errorf("template %q not found", template)
}
root, err := a.nodes.Create(parentID, tmpl.RootType, title, section)
if err != nil {
return nil, err
}
var createTree func(parentID string, nodes []plugins.TreeNode) error
createTree = func(parentID string, nodes []plugins.TreeNode) error {
for _, tn := range nodes {
child, err := a.nodes.Create(parentID, tn.Type, tn.Title, "")
if err != nil {
return err
}
if len(tn.Children) > 0 {
if err := createTree(child.ID, tn.Children); err != nil {
return err
}
}
}
return nil
}
if err := createTree(root.ID, tmpl.Tree); err != nil {
return nil, err
}
dto := toNodeDTO(root)
return &dto, nil
}
// ============================================================
// Notes
// ============================================================
@@ -658,6 +720,24 @@ func (a *App) ListActions(nodeID string) ([]ActionDTO, error) {
return result, nil
}
func (a *App) CreateAction(nodeID, kind, title, data string) (*ActionDTO, error) {
rec, err := a.actions.Create(nodeID, kind, title, data, "", data, nil, kind == "run_command" || kind == "run_script", false)
if err != nil {
return nil, err
}
return &ActionDTO{
ID: rec.ID,
NodeID: rec.NodeID,
Title: rec.Title,
Type: rec.Kind,
Data: data,
}, nil
}
func (a *App) DeleteAction(id string) error {
return a.actions.Delete(id)
}
func (a *App) RunAction(id string) error {
_, err := a.actions.Run(id)
return err
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -16,8 +16,8 @@
background: #13131f;
}
</style>
<script type="module" crossorigin src="/assets/main-DtITCkHU.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-5x3eoU2l.css">
<script type="module" crossorigin src="/assets/main-DZkGJWBF.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-BnVt-oqm.css">
</head>
<body>
<div id="app"></div>
+3 -1
View File
@@ -50,7 +50,8 @@ func main() {
activitySvc := activity.NewService(db)
worklogSvc := worklog.NewService(db)
searchSvc := search.NewService(db)
plugins.NewManager(abs).Discover()
pm := plugins.NewManager(abs)
pm.Discover()
app := &App{
db: db,
@@ -61,6 +62,7 @@ func main() {
actions: actionSvc,
worklog: worklogSvc,
search: searchSvc,
plugins: pm,
vault: abs,
}