step 7: actions — table, service, CLI, GUI tab + confirm dialog
- Migration 005: actions table (node_id, title, kind, command, args_json, working_dir, url, confirm_required, capture_output) - ActionService: Create, Get, ListByNode, Delete, Run Run dispatches: open_url/file/folder (xdg-open), run_command/script (exec.Command), open_terminal, launch_app - CLI: verstak action add/list/run/delete 'run' shows confirm prompt for confirm_required actions - GUI 'Действия' tab: button list with kind label, confirm_required opens editor overlay with action info + confirm, delete button - 7 unit tests for ActionService Acceptance: go build ./... pass, go test ./... pass.
This commit is contained in:
@@ -5,7 +5,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"verstak/internal/core/actions"
|
||||
"verstak/internal/core/storage"
|
||||
"verstak/internal/core/vault"
|
||||
)
|
||||
|
||||
@@ -26,6 +29,8 @@ func main() {
|
||||
runInit(os.Args[2:])
|
||||
case "node":
|
||||
runNode(os.Args[2:])
|
||||
case "action":
|
||||
runAction(os.Args[2:])
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", os.Args[1])
|
||||
os.Exit(1)
|
||||
@@ -40,6 +45,7 @@ func usage() {
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" init Initialize a new vault")
|
||||
fmt.Println(" node Manage nodes")
|
||||
fmt.Println(" action Manage actions")
|
||||
fmt.Println(" --version Show version")
|
||||
fmt.Println(" --help Show this help")
|
||||
}
|
||||
@@ -191,3 +197,201 @@ func vaultPathFromFlags(args []string) string {
|
||||
}
|
||||
return "."
|
||||
}
|
||||
|
||||
// --- action ---
|
||||
|
||||
func runAction(args []string) {
|
||||
if len(args) == 0 {
|
||||
actionUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
sub := args[0]
|
||||
rest := args[1:]
|
||||
switch sub {
|
||||
case "add":
|
||||
runActionAdd(rest)
|
||||
case "list":
|
||||
runActionList(rest)
|
||||
case "run":
|
||||
runActionRun(rest)
|
||||
case "delete":
|
||||
runActionDelete(rest)
|
||||
case "--help", "-h":
|
||||
actionUsage()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown action command: %s\n", sub)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func actionUsage() {
|
||||
fmt.Println("verstak action — manage actions")
|
||||
fmt.Println()
|
||||
fmt.Println("Usage: verstak action <command> [options]")
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" add --node ID --kind KIND --title TITLE Add action")
|
||||
fmt.Println(" list --node ID List actions for node")
|
||||
fmt.Println(" run --id ID Run action")
|
||||
fmt.Println(" delete --id ID Delete action")
|
||||
fmt.Println()
|
||||
fmt.Println("Kinds: open_url, open_file, open_folder, run_command, run_script, open_terminal, launch_app")
|
||||
}
|
||||
|
||||
func openActionDB(args []string) (*actions.Service, string, func()) {
|
||||
vaultPath, _ := stringFlag(args, "--vault")
|
||||
abs, _ := filepath.Abs(vaultPath)
|
||||
dbPath := filepath.Join(abs, ".verstak", "index.db")
|
||||
db, err := storage.Open(dbPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Open vault: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
svc := actions.NewService(db)
|
||||
return svc, abs, func() { db.Close() }
|
||||
}
|
||||
|
||||
func runActionAdd(args []string) {
|
||||
nodeID, _ := stringFlag(args, "--node")
|
||||
kind, _ := stringFlag(args, "--kind")
|
||||
title, _ := stringFlag(args, "--title")
|
||||
cmd, _ := stringFlag(args, "--command")
|
||||
url, _ := stringFlag(args, "--url")
|
||||
workingDir, _ := stringFlag(args, "--working-dir")
|
||||
vaultPath, _ := stringFlag(args, "--vault")
|
||||
|
||||
if nodeID == "" || kind == "" || title == "" {
|
||||
fmt.Fprintln(os.Stderr, "Error: --node, --kind and --title required")
|
||||
actionUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
abs, _ := filepath.Abs(vaultPath)
|
||||
dbPath := filepath.Join(abs, ".verstak", "index.db")
|
||||
db, err := storage.Open(dbPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Open vault: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
svc := actions.NewService(db)
|
||||
rec, err := svc.Create(nodeID, kind, title, cmd, workingDir, url, nil, false, false)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Add failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("created\t%s\t%s\t%s\t%s\n", rec.ID, rec.Kind, rec.NodeID, rec.Title)
|
||||
}
|
||||
|
||||
func runActionList(args []string) {
|
||||
nodeID, _ := stringFlag(args, "--node")
|
||||
vaultPath, _ := stringFlag(args, "--vault")
|
||||
|
||||
if nodeID == "" {
|
||||
fmt.Fprintln(os.Stderr, "Error: --node required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
abs, _ := filepath.Abs(vaultPath)
|
||||
dbPath := filepath.Join(abs, ".verstak", "index.db")
|
||||
db, err := storage.Open(dbPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Open vault: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
svc := actions.NewService(db)
|
||||
list, err := svc.ListByNode(nodeID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "List failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, a := range list {
|
||||
fmt.Printf("%s\t%s\t%v\t%s\n", a.ID, a.Kind, a.ConfirmRequired, a.Title)
|
||||
}
|
||||
}
|
||||
|
||||
func runActionRun(args []string) {
|
||||
id, _ := stringFlag(args, "--id")
|
||||
vaultPath, _ := stringFlag(args, "--vault")
|
||||
|
||||
if id == "" {
|
||||
fmt.Fprintln(os.Stderr, "Error: --id required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
abs, _ := filepath.Abs(vaultPath)
|
||||
dbPath := filepath.Join(abs, ".verstak", "index.db")
|
||||
db, err := storage.Open(dbPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Open vault: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
svc := actions.NewService(db)
|
||||
rec, err := svc.Get(id)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Get failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if rec.ConfirmRequired {
|
||||
fmt.Printf("Action: %s\n", rec.Title)
|
||||
fmt.Printf("Kind: %s\n", actions.KindLabel(rec.Kind))
|
||||
if rec.Command != "" {
|
||||
fmt.Printf("Cmd: %s\n", rec.Command)
|
||||
}
|
||||
if rec.URL != "" {
|
||||
fmt.Printf("URL: %s\n", rec.URL)
|
||||
}
|
||||
fmt.Print("Run? [y/N] ")
|
||||
var input string
|
||||
fmt.Scanln(&input)
|
||||
if strings.ToLower(input) != "y" {
|
||||
fmt.Println("Cancelled")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result, err := svc.Run(id)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Run failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if result.ExitCode != 0 {
|
||||
fmt.Printf("exit=%d\n", result.ExitCode)
|
||||
}
|
||||
if result.Output != "" {
|
||||
fmt.Print(result.Output)
|
||||
}
|
||||
fmt.Println("done")
|
||||
}
|
||||
|
||||
func runActionDelete(args []string) {
|
||||
id, _ := stringFlag(args, "--id")
|
||||
vaultPath, _ := stringFlag(args, "--vault")
|
||||
|
||||
if id == "" {
|
||||
fmt.Fprintln(os.Stderr, "Error: --id required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
abs, _ := filepath.Abs(vaultPath)
|
||||
dbPath := filepath.Join(abs, ".verstak", "index.db")
|
||||
db, err := storage.Open(dbPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Open vault: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
svc := actions.NewService(db)
|
||||
if err := svc.Delete(id); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Delete failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("deleted")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user