diff --git a/docs/PLAN.md b/docs/PLAN.md
index 32515b9..7dcaf24 100644
--- a/docs/PLAN.md
+++ b/docs/PLAN.md
@@ -20,7 +20,7 @@
| 7 | Actions: Run URL/File/Command + GUI Tab | ✅ выполнен |
| 8 | Worklog: Entries + Report + GUI Tab | ✅ выполнен |
| 9 | FTS5 Search: Rebuild Index + GUI Search Bar | ✅ выполнен |
-| 10 | Plugins System (Lua + Templates) | ⬜ не начат |
+| 10 | Plugins System (Lua + Templates) | ✅ выполнен |
| 11 | Sync Server Skeleton | ⬜ не начат |
| 12 | Sync Client MVP | ⬜ не начат |
| 13 | Activity + File Scanner/Watcher | ⬜ не начат |
diff --git a/internal/gui/index.html.go b/internal/gui/index.html.go
index 3ca0fd2..34965ea 100644
--- a/internal/gui/index.html.go
+++ b/internal/gui/index.html.go
@@ -217,9 +217,11 @@ input[type=checkbox]{width:auto!important;margin-right:6px;display:inline}
Действие
@@ -792,6 +794,17 @@ async function submitWorklog(){
}catch(e){alert('Ошибка: '+e.message)}
}
+async function submitFile(){
+ const path=G('mf-path').value.trim();
+ if(!path)return;
+ if(!sel.nodeId){E('Выберите дело слева');return}
+ try{
+ const rec=await api('/api/files/upload',{method:'PUT',body:JSON.stringify({node_id:sel.nodeId,file_path:path,node_slug:''})});
+ closeM('m-file');
+ selectNode({dataset:{id:sel.nodeId}});
+ }catch(e){alert('Ошибка: '+e.message)}
+}
+
/* ════════════════════════════════════════════
SEARCH
════════════════════════════════════════════ */
diff --git a/internal/gui/server.go b/internal/gui/server.go
index cebe7bd..53865d0 100644
--- a/internal/gui/server.go
+++ b/internal/gui/server.go
@@ -59,6 +59,7 @@ func (s *Server) Start() (string, error) {
mux.HandleFunc("/api/nodes/from-template", s.handleNodeFromTemplate)
mux.HandleFunc("/api/nodes/", s.handleNodeDetail)
mux.HandleFunc("/api/notes/", s.handleNotes)
+ mux.HandleFunc("/api/files/upload", s.handleFileUpload)
mux.HandleFunc("/api/files/", s.handleFiles)
mux.HandleFunc("/api/actions/", s.handleActions)
mux.HandleFunc("/api/worklog/", s.handleWorklog)
@@ -291,6 +292,30 @@ func (s *Server) handleNotes(w http.ResponseWriter, r *http.Request) {
}
}
+// PUT /api/files/upload — register an external file into the vault.
+func (s *Server) handleFileUpload(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "PUT" {
+ jsonErr(w, 405, "method not allowed")
+ return
+ }
+ var req struct {
+ NodeID string `json:"node_id"`
+ FilePath string `json:"file_path"`
+ NodeSlug string `json:"node_slug"`
+ }
+ json.NewDecoder(r.Body).Decode(&req)
+ if req.NodeID == "" || req.FilePath == "" {
+ jsonErr(w, 400, "node_id and file_path required")
+ return
+ }
+ rec, err := s.files.CopyIntoVault(req.NodeID, req.FilePath, req.NodeSlug)
+ if err != nil {
+ jsonErr(w, 500, err.Error())
+ return
+ }
+ jsonOK(w, rec)
+}
+
// GET/DELETE /api/files/{id}
func (s *Server) handleFiles(w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/api/files/")
diff --git a/verstak-gui b/verstak-gui
new file mode 100755
index 0000000..a4562d8
Binary files /dev/null and b/verstak-gui differ