mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-12 09:07:28 +08:00
chore: update tui and api docs
This commit is contained in:
@@ -157,7 +157,7 @@ make dev
|
||||
WebUI:
|
||||
|
||||
```text
|
||||
http://<host>:<port>/webui?token=<gateway.token>
|
||||
http://<host>:<port>/?token=<gateway.token>
|
||||
```
|
||||
|
||||
## 架构概览
|
||||
@@ -279,7 +279,7 @@ user -> main -> worker -> main -> user
|
||||
说明:
|
||||
|
||||
- `webrtc` 建连失败时,调度层仍会回退到现有 relay / tunnel 路径
|
||||
- Dashboard、`status`、`/webui/api/nodes` 会显示当前 Node P2P 状态和会话摘要
|
||||
- Dashboard、`status`、`/api/nodes` 会显示当前 Node P2P 状态和会话摘要
|
||||
- 两台公网机器的实网验证流程见 [docs/node-p2p-e2e.md](/Users/lpf/Desktop/project/clawgo/docs/node-p2p-e2e.md)
|
||||
|
||||
## MCP 服务支持
|
||||
|
||||
@@ -148,7 +148,7 @@ make dev
|
||||
WebUI:
|
||||
|
||||
```text
|
||||
http://<host>:<port>/webui?token=<gateway.token>
|
||||
http://<host>:<port>/?token=<gateway.token>
|
||||
```
|
||||
|
||||
## Architecture
|
||||
@@ -269,7 +269,7 @@ Example:
|
||||
Notes:
|
||||
|
||||
- when `webrtc` session setup fails, dispatch still falls back to the existing relay / tunnel path
|
||||
- Dashboard, `status`, and `/webui/api/nodes` expose the current Node P2P runtime summary
|
||||
- Dashboard, `status`, and `/api/nodes` expose the current Node P2P runtime summary
|
||||
- a reusable public-network validation flow is documented in [docs/node-p2p-e2e.md](/Users/lpf/Desktop/project/clawgo/docs/node-p2p-e2e.md)
|
||||
|
||||
## MCP Server Support
|
||||
|
||||
205
cmd/cmd_tui.go
205
cmd/cmd_tui.go
@@ -112,6 +112,9 @@ type tuiModel struct {
|
||||
sessionCursor int
|
||||
sessionFilter string
|
||||
sessionActive map[string]time.Time
|
||||
inputHistory []string
|
||||
historyCursor int
|
||||
historyDraft string
|
||||
status string
|
||||
globalErr string
|
||||
}
|
||||
@@ -153,6 +156,10 @@ func newTUIModel(client *tuiClient, runtime *tuiRuntime, opts tuiOptions) tuiMod
|
||||
input.Focus()
|
||||
input.CharLimit = 0
|
||||
input.Prompt = "› "
|
||||
input.PromptStyle = lipgloss.NewStyle().Bold(true).Foreground(tuiColorShell)
|
||||
input.TextStyle = lipgloss.NewStyle().Foreground(tuiColorText)
|
||||
input.PlaceholderStyle = lipgloss.NewStyle().Foreground(tuiColorTextSoft)
|
||||
input.Cursor.Style = lipgloss.NewStyle().Foreground(tuiColorShell)
|
||||
|
||||
pane := newTUIPane(1, opts.session)
|
||||
if opts.noHistory {
|
||||
@@ -160,16 +167,17 @@ func newTUIModel(client *tuiClient, runtime *tuiRuntime, opts tuiOptions) tuiMod
|
||||
}
|
||||
|
||||
return tuiModel{
|
||||
client: client,
|
||||
runtime: runtime,
|
||||
input: input,
|
||||
panes: []tuiPane{pane},
|
||||
focus: 0,
|
||||
nextPaneID: 2,
|
||||
client: client,
|
||||
runtime: runtime,
|
||||
input: input,
|
||||
panes: []tuiPane{pane},
|
||||
focus: 0,
|
||||
nextPaneID: 2,
|
||||
historyCursor: -1,
|
||||
sessionActive: map[string]time.Time{
|
||||
strings.TrimSpace(opts.session): time.Now(),
|
||||
},
|
||||
status: "Tab/Shift+Tab: focus • Ctrl+O: open selected session • Ctrl+R: replace pane • Enter: send • Ctrl+C: quit",
|
||||
status: "Tab/Shift+Tab: focus • Ctrl+↑/↓: select session • Ctrl+O: open selected • Enter: send • Ctrl+C: quit",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,13 +275,23 @@ func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
return m, nil
|
||||
case "up":
|
||||
if m.recallHistory(-1) {
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
case "down":
|
||||
if m.recallHistory(1) {
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
case "ctrl+up":
|
||||
filtered := m.filteredSessions()
|
||||
if len(filtered) > 0 && m.sessionCursor > 0 {
|
||||
m.sessionCursor--
|
||||
m.status = "Selected session: " + filtered[m.sessionCursor].Key
|
||||
}
|
||||
return m, nil
|
||||
case "down":
|
||||
case "ctrl+down":
|
||||
filtered := m.filteredSessions()
|
||||
if len(filtered) > 0 && m.sessionCursor < len(filtered)-1 {
|
||||
m.sessionCursor++
|
||||
@@ -323,6 +341,7 @@ func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if len(m.panes) == 0 || m.focusedPane().Sending || m.focusedPane().Loading {
|
||||
return m, nil
|
||||
}
|
||||
m.pushInputHistory(value)
|
||||
m.input.SetValue("")
|
||||
if strings.HasPrefix(value, "/") {
|
||||
return m.handleCommand(value)
|
||||
@@ -486,7 +505,8 @@ func (m *tuiModel) resize() {
|
||||
}
|
||||
m.bodyHeight = bodyHeight
|
||||
m.sidebarWidth = min(28, max(20, m.width/5))
|
||||
availablePaneWidth := m.width - m.sidebarWidth - 1
|
||||
gapSize := lipgloss.Width(m.paneGap())
|
||||
availablePaneWidth := m.width - m.sidebarWidth - gapSize
|
||||
if availablePaneWidth < 24 {
|
||||
availablePaneWidth = 24
|
||||
}
|
||||
@@ -495,23 +515,23 @@ func (m *tuiModel) resize() {
|
||||
case 1:
|
||||
m.setPaneSize(0, availablePaneWidth, bodyHeight)
|
||||
case 2:
|
||||
leftWidth := (availablePaneWidth - 1) / 2
|
||||
rightWidth := availablePaneWidth - 1 - leftWidth
|
||||
leftWidth := (availablePaneWidth - gapSize) / 2
|
||||
rightWidth := availablePaneWidth - gapSize - leftWidth
|
||||
m.setPaneSize(0, leftWidth, bodyHeight)
|
||||
m.setPaneSize(1, rightWidth, bodyHeight)
|
||||
case 3:
|
||||
leftWidth := (availablePaneWidth - 1) / 2
|
||||
rightWidth := availablePaneWidth - 1 - leftWidth
|
||||
topHeight := (bodyHeight - 1) / 2
|
||||
bottomHeight := bodyHeight - 1 - topHeight
|
||||
leftWidth := (availablePaneWidth - gapSize) / 2
|
||||
rightWidth := availablePaneWidth - gapSize - leftWidth
|
||||
topHeight := (bodyHeight - gapSize) / 2
|
||||
bottomHeight := bodyHeight - gapSize - topHeight
|
||||
m.setPaneSize(0, leftWidth, bodyHeight)
|
||||
m.setPaneSize(1, rightWidth, topHeight)
|
||||
m.setPaneSize(2, rightWidth, bottomHeight)
|
||||
default:
|
||||
leftWidth := (availablePaneWidth - 1) / 2
|
||||
rightWidth := availablePaneWidth - 1 - leftWidth
|
||||
topHeight := (bodyHeight - 1) / 2
|
||||
bottomHeight := bodyHeight - 1 - topHeight
|
||||
leftWidth := (availablePaneWidth - gapSize) / 2
|
||||
rightWidth := availablePaneWidth - gapSize - leftWidth
|
||||
topHeight := (bodyHeight - gapSize) / 2
|
||||
bottomHeight := bodyHeight - gapSize - topHeight
|
||||
m.setPaneSize(0, leftWidth, topHeight)
|
||||
m.setPaneSize(1, rightWidth, topHeight)
|
||||
m.setPaneSize(2, leftWidth, bottomHeight)
|
||||
@@ -523,7 +543,7 @@ func (m *tuiModel) resize() {
|
||||
func (m *tuiModel) renderPanes() string {
|
||||
sidebar := m.renderSessionSidebar()
|
||||
panes := m.renderPaneGrid()
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top, sidebar, tuiPaneGapStyle.Render(" "), panes)
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top, sidebar, tuiPaneGapStyle.Render(m.paneGap()), panes)
|
||||
}
|
||||
|
||||
func (m *tuiModel) renderPane(index int, pane tuiPane) string {
|
||||
@@ -610,7 +630,8 @@ func (m *tuiModel) renderSessionSidebar() string {
|
||||
if strings.TrimSpace(m.sessionFilter) != "" {
|
||||
lines = append(lines, tuiMutedStyle.Render("Filter: "+m.sessionFilter))
|
||||
}
|
||||
lines = append(lines, tuiMutedStyle.Render("Up/Down: select"))
|
||||
lines = append(lines, tuiMutedStyle.Render("Ctrl+Up/Down: select"))
|
||||
lines = append(lines, tuiMutedStyle.Render("Up/Down: input history"))
|
||||
lines = append(lines, tuiMutedStyle.Render("Enter: open selected"))
|
||||
lines = append(lines, tuiMutedStyle.Render("Ctrl+O: open pane"))
|
||||
lines = append(lines, tuiMutedStyle.Render("Ctrl+N: new pane"))
|
||||
@@ -628,45 +649,99 @@ func (m *tuiModel) renderPaneGrid() string {
|
||||
if len(m.panes) == 0 {
|
||||
return tuiViewportStyle.Width(max(20, m.width-m.sidebarWidth-1)).Height(m.bodyHeight).Render("No panes.")
|
||||
}
|
||||
gap := tuiPaneGapStyle.Render(m.paneGap())
|
||||
switch len(m.panes) {
|
||||
case 1:
|
||||
return m.renderPane(0, m.panes[0])
|
||||
case 2:
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
m.renderPane(0, m.panes[0]),
|
||||
tuiPaneGapStyle.Render(" "),
|
||||
gap,
|
||||
m.renderPane(1, m.panes[1]),
|
||||
)
|
||||
case 3:
|
||||
right := lipgloss.JoinVertical(lipgloss.Left,
|
||||
m.renderPane(1, m.panes[1]),
|
||||
tuiPaneGapStyle.Render(" "),
|
||||
gap,
|
||||
m.renderPane(2, m.panes[2]),
|
||||
)
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
m.renderPane(0, m.panes[0]),
|
||||
tuiPaneGapStyle.Render(" "),
|
||||
gap,
|
||||
right,
|
||||
)
|
||||
default:
|
||||
top := lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
m.renderPane(0, m.panes[0]),
|
||||
tuiPaneGapStyle.Render(" "),
|
||||
gap,
|
||||
m.renderPane(1, m.panes[1]),
|
||||
)
|
||||
bottom := lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
m.renderPane(2, m.panes[2]),
|
||||
tuiPaneGapStyle.Render(" "),
|
||||
gap,
|
||||
m.renderPane(3, m.panes[3]),
|
||||
)
|
||||
return lipgloss.JoinVertical(lipgloss.Left,
|
||||
top,
|
||||
tuiPaneGapStyle.Render(" "),
|
||||
gap,
|
||||
bottom,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *tuiModel) paneGap() string {
|
||||
if len(m.panes) >= 4 {
|
||||
return ""
|
||||
}
|
||||
return " "
|
||||
}
|
||||
|
||||
func (m *tuiModel) pushInputHistory(value string) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
if n := len(m.inputHistory); n > 0 && m.inputHistory[n-1] == value {
|
||||
m.historyCursor = len(m.inputHistory)
|
||||
m.historyDraft = ""
|
||||
return
|
||||
}
|
||||
m.inputHistory = append(m.inputHistory, value)
|
||||
m.historyCursor = len(m.inputHistory)
|
||||
m.historyDraft = ""
|
||||
}
|
||||
|
||||
func (m *tuiModel) recallHistory(delta int) bool {
|
||||
if len(m.inputHistory) == 0 {
|
||||
return false
|
||||
}
|
||||
if m.historyCursor < 0 || m.historyCursor > len(m.inputHistory) {
|
||||
m.historyCursor = len(m.inputHistory)
|
||||
}
|
||||
if m.historyCursor == len(m.inputHistory) {
|
||||
m.historyDraft = m.input.Value()
|
||||
}
|
||||
next := m.historyCursor + delta
|
||||
if next < 0 {
|
||||
next = 0
|
||||
}
|
||||
if next > len(m.inputHistory) {
|
||||
next = len(m.inputHistory)
|
||||
}
|
||||
if next == m.historyCursor {
|
||||
return true
|
||||
}
|
||||
m.historyCursor = next
|
||||
if m.historyCursor == len(m.inputHistory) {
|
||||
m.input.SetValue(m.historyDraft)
|
||||
} else {
|
||||
m.input.SetValue(m.inputHistory[m.historyCursor])
|
||||
}
|
||||
m.input.CursorEnd()
|
||||
m.input.Focus()
|
||||
return true
|
||||
}
|
||||
|
||||
func (m tuiModel) loadHistoryCmd(paneID int, session string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
messages, err := m.client.history(context.Background(), session)
|
||||
@@ -1312,108 +1387,124 @@ func (c *tuiClient) newRequest(ctx context.Context, method, path string, body io
|
||||
}
|
||||
|
||||
var (
|
||||
tuiColorBorder = lipgloss.Color("#F7B08A")
|
||||
tuiColorClaw = lipgloss.Color("#FFB36B")
|
||||
tuiColorShell = lipgloss.Color("#F05A28")
|
||||
tuiColorShellDeep = lipgloss.Color("#D9481C")
|
||||
tuiColorAccent = lipgloss.Color("#C44B21")
|
||||
tuiColorAccentDeep = lipgloss.Color("#A43A18")
|
||||
tuiColorText = lipgloss.Color("#472018")
|
||||
tuiColorTextSoft = lipgloss.Color("#6E3B2E")
|
||||
tuiColorError = lipgloss.Color("#B93815")
|
||||
tuiColorSurfaceText = lipgloss.Color("#FFF4EE")
|
||||
|
||||
tuiHeaderStyle = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
BorderBottom(true).
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240"))
|
||||
Foreground(tuiColorText).
|
||||
BorderForeground(tuiColorBorder)
|
||||
|
||||
tuiTitleStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("212"))
|
||||
Foreground(tuiColorShellDeep)
|
||||
|
||||
tuiBannerClawStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("69"))
|
||||
Foreground(tuiColorClaw)
|
||||
|
||||
tuiBannerGoStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("204"))
|
||||
Foreground(tuiColorShell)
|
||||
|
||||
tuiMutedStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("245"))
|
||||
Foreground(tuiColorTextSoft)
|
||||
|
||||
tuiInputBoxStyle = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
BorderTop(true).
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240"))
|
||||
Foreground(tuiColorText).
|
||||
BorderForeground(tuiColorBorder)
|
||||
|
||||
tuiViewportStyle = lipgloss.NewStyle().Padding(0, 1)
|
||||
|
||||
tuiFooterStyle = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
Foreground(lipgloss.Color("245"))
|
||||
Foreground(tuiColorText)
|
||||
|
||||
tuiErrorStyle = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
Foreground(lipgloss.Color("203"))
|
||||
Foreground(tuiColorError)
|
||||
|
||||
tuiPaneStyle = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("240"))
|
||||
Foreground(tuiColorText).
|
||||
BorderForeground(tuiColorBorder)
|
||||
|
||||
tuiPaneActiveStyle = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("69"))
|
||||
Foreground(tuiColorText).
|
||||
BorderForeground(tuiColorShell)
|
||||
|
||||
tuiPaneTitleStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("252"))
|
||||
Foreground(tuiColorAccentDeep)
|
||||
|
||||
tuiPaneTitleActiveStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("69"))
|
||||
Foreground(tuiColorShellDeep)
|
||||
|
||||
tuiPaneTitleActiveBadgeStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("16")).
|
||||
Background(lipgloss.Color("69"))
|
||||
Foreground(tuiColorSurfaceText).
|
||||
Background(tuiColorShell)
|
||||
|
||||
tuiPaneGapStyle = lipgloss.NewStyle()
|
||||
|
||||
tuiSidebarStyle = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("240"))
|
||||
Foreground(tuiColorText).
|
||||
BorderForeground(tuiColorBorder)
|
||||
|
||||
tuiSidebarItemStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("252"))
|
||||
Foreground(tuiColorText)
|
||||
|
||||
tuiSidebarItemActiveStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("69"))
|
||||
Foreground(tuiColorShellDeep)
|
||||
tuiFooterHintStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241"))
|
||||
Foreground(tuiColorTextSoft)
|
||||
|
||||
tuiPaneReplyingStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("230")).
|
||||
Background(lipgloss.Color("62"))
|
||||
Foreground(tuiColorSurfaceText).
|
||||
Background(tuiColorShellDeep)
|
||||
|
||||
tuiPaneLoadingStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("16")).
|
||||
Background(lipgloss.Color("221"))
|
||||
Foreground(tuiColorText).
|
||||
Background(tuiColorClaw)
|
||||
|
||||
tuiPaneErrorBadgeStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("255")).
|
||||
Background(lipgloss.Color("160"))
|
||||
Foreground(tuiColorSurfaceText).
|
||||
Background(tuiColorError)
|
||||
|
||||
tuiLabelStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("249"))
|
||||
Foreground(tuiColorAccent)
|
||||
|
||||
tuiContentStyle = lipgloss.NewStyle().PaddingLeft(1)
|
||||
tuiUserStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("87"))
|
||||
tuiContentStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(tuiColorText)
|
||||
tuiUserStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(tuiColorShellDeep)
|
||||
tuiAssistantStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(1).
|
||||
Foreground(lipgloss.Color("230"))
|
||||
tuiSystemStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("221"))
|
||||
tuiToolStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("141"))
|
||||
Foreground(tuiColorText)
|
||||
tuiSystemStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(tuiColorAccent)
|
||||
tuiToolStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(tuiColorAccentDeep)
|
||||
)
|
||||
|
||||
func renderTUIBanner(width int) string {
|
||||
|
||||
@@ -445,51 +445,50 @@ func (s *Server) Start(ctx context.Context) error {
|
||||
mux.HandleFunc("/nodes/register", s.handleRegister)
|
||||
mux.HandleFunc("/nodes/heartbeat", s.handleHeartbeat)
|
||||
mux.HandleFunc("/nodes/connect", s.handleNodeConnect)
|
||||
mux.HandleFunc("/webui", s.handleWebUI)
|
||||
mux.HandleFunc("/webui/", s.handleWebUIAsset)
|
||||
mux.HandleFunc("/webui/api/config", s.handleWebUIConfig)
|
||||
mux.HandleFunc("/webui/api/chat", s.handleWebUIChat)
|
||||
mux.HandleFunc("/webui/api/chat/history", s.handleWebUIChatHistory)
|
||||
mux.HandleFunc("/webui/api/chat/stream", s.handleWebUIChatStream)
|
||||
mux.HandleFunc("/webui/api/chat/live", s.handleWebUIChatLive)
|
||||
mux.HandleFunc("/webui/api/runtime", s.handleWebUIRuntime)
|
||||
mux.HandleFunc("/webui/api/version", s.handleWebUIVersion)
|
||||
mux.HandleFunc("/webui/api/provider/oauth/start", s.handleWebUIProviderOAuthStart)
|
||||
mux.HandleFunc("/webui/api/provider/oauth/complete", s.handleWebUIProviderOAuthComplete)
|
||||
mux.HandleFunc("/webui/api/provider/oauth/import", s.handleWebUIProviderOAuthImport)
|
||||
mux.HandleFunc("/webui/api/provider/oauth/accounts", s.handleWebUIProviderOAuthAccounts)
|
||||
mux.HandleFunc("/webui/api/provider/models", s.handleWebUIProviderModels)
|
||||
mux.HandleFunc("/webui/api/provider/runtime", s.handleWebUIProviderRuntime)
|
||||
mux.HandleFunc("/webui/api/provider/runtime/summary", s.handleWebUIProviderRuntimeSummary)
|
||||
mux.HandleFunc("/webui/api/whatsapp/status", s.handleWebUIWhatsAppStatus)
|
||||
mux.HandleFunc("/webui/api/whatsapp/logout", s.handleWebUIWhatsAppLogout)
|
||||
mux.HandleFunc("/webui/api/whatsapp/qr.svg", s.handleWebUIWhatsAppQR)
|
||||
mux.HandleFunc("/webui/api/upload", s.handleWebUIUpload)
|
||||
mux.HandleFunc("/webui/api/nodes", s.handleWebUINodes)
|
||||
mux.HandleFunc("/webui/api/node_dispatches", s.handleWebUINodeDispatches)
|
||||
mux.HandleFunc("/webui/api/node_dispatches/replay", s.handleWebUINodeDispatchReplay)
|
||||
mux.HandleFunc("/webui/api/node_artifacts", s.handleWebUINodeArtifacts)
|
||||
mux.HandleFunc("/webui/api/node_artifacts/export", s.handleWebUINodeArtifactsExport)
|
||||
mux.HandleFunc("/webui/api/node_artifacts/download", s.handleWebUINodeArtifactDownload)
|
||||
mux.HandleFunc("/webui/api/node_artifacts/delete", s.handleWebUINodeArtifactDelete)
|
||||
mux.HandleFunc("/webui/api/node_artifacts/prune", s.handleWebUINodeArtifactPrune)
|
||||
mux.HandleFunc("/webui/api/cron", s.handleWebUICron)
|
||||
mux.HandleFunc("/webui/api/skills", s.handleWebUISkills)
|
||||
mux.HandleFunc("/webui/api/sessions", s.handleWebUISessions)
|
||||
mux.HandleFunc("/webui/api/memory", s.handleWebUIMemory)
|
||||
mux.HandleFunc("/webui/api/subagent_profiles", s.handleWebUISubagentProfiles)
|
||||
mux.HandleFunc("/webui/api/subagents_runtime", s.handleWebUISubagentsRuntime)
|
||||
mux.HandleFunc("/webui/api/subagents_runtime/live", s.handleWebUISubagentsRuntimeLive)
|
||||
mux.HandleFunc("/webui/api/tool_allowlist_groups", s.handleWebUIToolAllowlistGroups)
|
||||
mux.HandleFunc("/webui/api/tools", s.handleWebUITools)
|
||||
mux.HandleFunc("/webui/api/mcp/install", s.handleWebUIMCPInstall)
|
||||
mux.HandleFunc("/webui/api/task_audit", s.handleWebUITaskAudit)
|
||||
mux.HandleFunc("/webui/api/task_queue", s.handleWebUITaskQueue)
|
||||
mux.HandleFunc("/webui/api/ekg_stats", s.handleWebUIEKGStats)
|
||||
mux.HandleFunc("/webui/api/exec_approvals", s.handleWebUIExecApprovals)
|
||||
mux.HandleFunc("/webui/api/logs/stream", s.handleWebUILogsStream)
|
||||
mux.HandleFunc("/webui/api/logs/live", s.handleWebUILogsLive)
|
||||
mux.HandleFunc("/webui/api/logs/recent", s.handleWebUILogsRecent)
|
||||
mux.HandleFunc("/", s.handleWebUIAsset)
|
||||
mux.HandleFunc("/api/config", s.handleWebUIConfig)
|
||||
mux.HandleFunc("/api/chat", s.handleWebUIChat)
|
||||
mux.HandleFunc("/api/chat/history", s.handleWebUIChatHistory)
|
||||
mux.HandleFunc("/api/chat/stream", s.handleWebUIChatStream)
|
||||
mux.HandleFunc("/api/chat/live", s.handleWebUIChatLive)
|
||||
mux.HandleFunc("/api/runtime", s.handleWebUIRuntime)
|
||||
mux.HandleFunc("/api/version", s.handleWebUIVersion)
|
||||
mux.HandleFunc("/api/provider/oauth/start", s.handleWebUIProviderOAuthStart)
|
||||
mux.HandleFunc("/api/provider/oauth/complete", s.handleWebUIProviderOAuthComplete)
|
||||
mux.HandleFunc("/api/provider/oauth/import", s.handleWebUIProviderOAuthImport)
|
||||
mux.HandleFunc("/api/provider/oauth/accounts", s.handleWebUIProviderOAuthAccounts)
|
||||
mux.HandleFunc("/api/provider/models", s.handleWebUIProviderModels)
|
||||
mux.HandleFunc("/api/provider/runtime", s.handleWebUIProviderRuntime)
|
||||
mux.HandleFunc("/api/provider/runtime/summary", s.handleWebUIProviderRuntimeSummary)
|
||||
mux.HandleFunc("/api/whatsapp/status", s.handleWebUIWhatsAppStatus)
|
||||
mux.HandleFunc("/api/whatsapp/logout", s.handleWebUIWhatsAppLogout)
|
||||
mux.HandleFunc("/api/whatsapp/qr.svg", s.handleWebUIWhatsAppQR)
|
||||
mux.HandleFunc("/api/upload", s.handleWebUIUpload)
|
||||
mux.HandleFunc("/api/nodes", s.handleWebUINodes)
|
||||
mux.HandleFunc("/api/node_dispatches", s.handleWebUINodeDispatches)
|
||||
mux.HandleFunc("/api/node_dispatches/replay", s.handleWebUINodeDispatchReplay)
|
||||
mux.HandleFunc("/api/node_artifacts", s.handleWebUINodeArtifacts)
|
||||
mux.HandleFunc("/api/node_artifacts/export", s.handleWebUINodeArtifactsExport)
|
||||
mux.HandleFunc("/api/node_artifacts/download", s.handleWebUINodeArtifactDownload)
|
||||
mux.HandleFunc("/api/node_artifacts/delete", s.handleWebUINodeArtifactDelete)
|
||||
mux.HandleFunc("/api/node_artifacts/prune", s.handleWebUINodeArtifactPrune)
|
||||
mux.HandleFunc("/api/cron", s.handleWebUICron)
|
||||
mux.HandleFunc("/api/skills", s.handleWebUISkills)
|
||||
mux.HandleFunc("/api/sessions", s.handleWebUISessions)
|
||||
mux.HandleFunc("/api/memory", s.handleWebUIMemory)
|
||||
mux.HandleFunc("/api/subagent_profiles", s.handleWebUISubagentProfiles)
|
||||
mux.HandleFunc("/api/subagents_runtime", s.handleWebUISubagentsRuntime)
|
||||
mux.HandleFunc("/api/subagents_runtime/live", s.handleWebUISubagentsRuntimeLive)
|
||||
mux.HandleFunc("/api/tool_allowlist_groups", s.handleWebUIToolAllowlistGroups)
|
||||
mux.HandleFunc("/api/tools", s.handleWebUITools)
|
||||
mux.HandleFunc("/api/mcp/install", s.handleWebUIMCPInstall)
|
||||
mux.HandleFunc("/api/task_audit", s.handleWebUITaskAudit)
|
||||
mux.HandleFunc("/api/task_queue", s.handleWebUITaskQueue)
|
||||
mux.HandleFunc("/api/ekg_stats", s.handleWebUIEKGStats)
|
||||
mux.HandleFunc("/api/exec_approvals", s.handleWebUIExecApprovals)
|
||||
mux.HandleFunc("/api/logs/stream", s.handleWebUILogsStream)
|
||||
mux.HandleFunc("/api/logs/live", s.handleWebUILogsLive)
|
||||
mux.HandleFunc("/api/logs/recent", s.handleWebUILogsRecent)
|
||||
base := strings.TrimRight(strings.TrimSpace(s.whatsAppBase), "/")
|
||||
if base == "" {
|
||||
base = "/whatsapp"
|
||||
@@ -702,7 +701,7 @@ func (s *Server) handleWebUI(w http.ResponseWriter, r *http.Request) {
|
||||
MaxAge: 86400,
|
||||
})
|
||||
}
|
||||
if s.tryServeWebUIDist(w, r, "/webui/index.html") {
|
||||
if s.tryServeWebUIDist(w, r, "/index.html") {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
@@ -718,11 +717,19 @@ func (s *Server) handleWebUIAsset(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/" {
|
||||
s.handleWebUI(w, r)
|
||||
return
|
||||
}
|
||||
if s.tryServeWebUIDist(w, r, r.URL.Path) {
|
||||
return
|
||||
}
|
||||
// SPA fallback
|
||||
if s.tryServeWebUIDist(w, r, "/webui/index.html") {
|
||||
if s.tryServeWebUIDist(w, r, "/index.html") {
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
@@ -733,8 +740,8 @@ func (s *Server) tryServeWebUIDist(w http.ResponseWriter, r *http.Request, reqPa
|
||||
if dir == "" {
|
||||
return false
|
||||
}
|
||||
p := strings.TrimPrefix(reqPath, "/webui/")
|
||||
if reqPath == "/webui" || reqPath == "/webui/" || reqPath == "/webui/index.html" {
|
||||
p := strings.TrimPrefix(reqPath, "/")
|
||||
if reqPath == "/" || reqPath == "/index.html" {
|
||||
p = "index.html"
|
||||
}
|
||||
p = filepath.Clean(strings.TrimPrefix(p, "/"))
|
||||
@@ -1600,7 +1607,7 @@ func (s *Server) handleWebUIChatHistory(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
func (s *Server) handleWebUIChatStream(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Deprecation", "true")
|
||||
w.Header().Set("X-Clawgo-Replaced-By", "/webui/api/chat/live")
|
||||
w.Header().Set("X-Clawgo-Replaced-By", "/api/chat/live")
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
@@ -3559,7 +3566,7 @@ func (s *Server) fetchRemoteNodeRegistry(ctx context.Context, node nodes.NodeInf
|
||||
if baseURL == "" {
|
||||
return nil, fmt.Errorf("node %s endpoint missing", strings.TrimSpace(node.ID))
|
||||
}
|
||||
reqURL := baseURL + "/webui/api/subagents_runtime?action=registry"
|
||||
reqURL := baseURL + "/api/subagents_runtime?action=registry"
|
||||
if tok := strings.TrimSpace(node.Token); tok != "" {
|
||||
reqURL += "&token=" + url.QueryEscape(tok)
|
||||
}
|
||||
@@ -6023,7 +6030,7 @@ func (s *Server) handleWebUILogsLive(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) handleWebUILogsStream(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Deprecation", "true")
|
||||
w.Header().Set("X-Clawgo-Replaced-By", "/webui/api/logs/live")
|
||||
w.Header().Set("X-Clawgo-Replaced-By", "/api/logs/live")
|
||||
if !s.checkAuth(r) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
@@ -6132,13 +6139,13 @@ const webUIHTML = `<!doctype html>
|
||||
<div id="chatlog"></div>
|
||||
<script>
|
||||
function auth(){const t=document.getElementById('token').value.trim();return t?('?token='+encodeURIComponent(t)):''}
|
||||
async function loadCfg(){let r=await fetch('/webui/api/config'+auth());document.getElementById('cfg').value=await r.text()}
|
||||
async function saveCfg(){let j=JSON.parse(document.getElementById('cfg').value);let r=await fetch('/webui/api/config'+auth(),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(j)});alert(await r.text())}
|
||||
async function loadCfg(){let r=await fetch('/api/config'+auth());document.getElementById('cfg').value=await r.text()}
|
||||
async function saveCfg(){let j=JSON.parse(document.getElementById('cfg').value);let r=await fetch('/api/config'+auth(),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(j)});alert(await r.text())}
|
||||
async function sendChat(){
|
||||
let media='';const f=document.getElementById('file').files[0];
|
||||
if(f){let fd=new FormData();fd.append('file',f);let ur=await fetch('/webui/api/upload'+auth(),{method:'POST',body:fd});let uj=await ur.json();media=uj.path||''}
|
||||
if(f){let fd=new FormData();fd.append('file',f);let ur=await fetch('/api/upload'+auth(),{method:'POST',body:fd});let uj=await ur.json();media=uj.path||''}
|
||||
const payload={session:document.getElementById('session').value,message:document.getElementById('msg').value,media};
|
||||
let r=await fetch('/webui/api/chat'+auth(),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});let t=await r.text();
|
||||
let r=await fetch('/api/chat'+auth(),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});let t=await r.text();
|
||||
document.getElementById('chatlog').textContent += '\nUSER> '+payload.message+(media?(' [file:'+media+']'):'')+'\nBOT> '+t+'\n';
|
||||
}
|
||||
loadCfg();
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestHandleWebUIWhatsAppStatus(t *testing.T) {
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/whatsapp/status", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/whatsapp/status", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUIWhatsAppStatus(rec, req)
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestHandleWebUIWhatsAppQR(t *testing.T) {
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/whatsapp/qr.svg", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/whatsapp/qr.svg", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUIWhatsAppQR(rec, req)
|
||||
|
||||
@@ -158,7 +158,7 @@ func TestHandleWebUIWhatsAppStatusWithNestedBridgePath(t *testing.T) {
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/whatsapp/status", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/whatsapp/status", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUIWhatsAppStatus(rec, req)
|
||||
|
||||
@@ -220,7 +220,7 @@ func TestHandleWebUIWhatsAppStatusMapsLegacyBridgeURLToEmbeddedPath(t *testing.T
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/whatsapp/status", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/whatsapp/status", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUIWhatsAppStatus(rec, req)
|
||||
|
||||
@@ -270,7 +270,7 @@ func TestHandleWebUIConfigRequiresConfirmForProviderAPIBaseChange(t *testing.T)
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/webui/api/config", bytes.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/config", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestHandleWebUIConfigRequiresConfirmForCustomProviderSecretChange(t *testin
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/webui/api/config", bytes.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/config", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -364,7 +364,7 @@ func TestHandleWebUIConfigRunsReloadHookSynchronously(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/webui/api/config", bytes.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/config", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -400,7 +400,7 @@ func TestHandleWebUIConfigReturnsReloadHookError(t *testing.T) {
|
||||
return fmt.Errorf("reload boom")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/webui/api/config", bytes.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/config", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -600,7 +600,7 @@ func TestHandleWebUISessionsHidesInternalSessionsByDefault(t *testing.T) {
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetWorkspacePath(filepath.Join(tmp, "workspace"))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/sessions", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/sessions", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUISessions(rec, req)
|
||||
|
||||
@@ -659,11 +659,11 @@ func TestHandleWebUISubagentsRuntimeLive(t *testing.T) {
|
||||
})
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/webui/api/subagents_runtime/live", srv.handleWebUISubagentsRuntimeLive)
|
||||
mux.HandleFunc("/api/subagents_runtime/live", srv.handleWebUISubagentsRuntimeLive)
|
||||
httpSrv := httptest.NewServer(mux)
|
||||
defer httpSrv.Close()
|
||||
|
||||
wsURL := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/webui/api/subagents_runtime/live?task_id=subagent-1&preview_task_id=subagent-1"
|
||||
wsURL := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/api/subagents_runtime/live?task_id=subagent-1&preview_task_id=subagent-1"
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial websocket: %v", err)
|
||||
@@ -698,11 +698,11 @@ func TestHandleWebUIChatLive(t *testing.T) {
|
||||
})
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/webui/api/chat/live", srv.handleWebUIChatLive)
|
||||
mux.HandleFunc("/api/chat/live", srv.handleWebUIChatLive)
|
||||
httpSrv := httptest.NewServer(mux)
|
||||
defer httpSrv.Close()
|
||||
|
||||
wsURL := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/webui/api/chat/live"
|
||||
wsURL := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/api/chat/live"
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial websocket: %v", err)
|
||||
@@ -744,11 +744,11 @@ func TestHandleWebUILogsLive(t *testing.T) {
|
||||
srv.SetLogFilePath(logPath)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/webui/api/logs/live", srv.handleWebUILogsLive)
|
||||
mux.HandleFunc("/api/logs/live", srv.handleWebUILogsLive)
|
||||
httpSrv := httptest.NewServer(mux)
|
||||
defer httpSrv.Close()
|
||||
|
||||
wsURL := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/webui/api/logs/live"
|
||||
wsURL := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/api/logs/live"
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial websocket: %v", err)
|
||||
@@ -805,7 +805,7 @@ func TestHandleWebUINodesIncludesP2PSummary(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/nodes", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/nodes", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUINodes(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
@@ -855,7 +855,7 @@ func TestHandleWebUINodesEnrichesLocalNodeMetadata(t *testing.T) {
|
||||
}, nil
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/nodes", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/nodes", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUINodes(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
@@ -909,7 +909,7 @@ func TestHandleWebUINodeDispatchReplay(t *testing.T) {
|
||||
})
|
||||
|
||||
body := `{"node":"edge-a","action":"screen_snapshot","mode":"auto","args":{"quality":"high"}}`
|
||||
req := httptest.NewRequest(http.MethodPost, "/webui/api/node_dispatches/replay", strings.NewReader(body))
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/node_dispatches/replay", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -940,7 +940,7 @@ func TestHandleWebUINodeArtifactsListAndDelete(t *testing.T) {
|
||||
t.Fatalf("write audit: %v", err)
|
||||
}
|
||||
|
||||
listReq := httptest.NewRequest(http.MethodGet, "/webui/api/node_artifacts", nil)
|
||||
listReq := httptest.NewRequest(http.MethodGet, "/api/node_artifacts", nil)
|
||||
listRec := httptest.NewRecorder()
|
||||
srv.handleWebUINodeArtifacts(listRec, listReq)
|
||||
if listRec.Code != http.StatusOK {
|
||||
@@ -960,7 +960,7 @@ func TestHandleWebUINodeArtifactsListAndDelete(t *testing.T) {
|
||||
t.Fatalf("expected artifact id, got %+v", item)
|
||||
}
|
||||
|
||||
deleteReq := httptest.NewRequest(http.MethodPost, "/webui/api/node_artifacts/delete", strings.NewReader(fmt.Sprintf(`{"id":"%s"}`, artifactID)))
|
||||
deleteReq := httptest.NewRequest(http.MethodPost, "/api/node_artifacts/delete", strings.NewReader(fmt.Sprintf(`{"id":"%s"}`, artifactID)))
|
||||
deleteReq.Header.Set("Content-Type", "application/json")
|
||||
deleteRec := httptest.NewRecorder()
|
||||
srv.handleWebUINodeArtifactDelete(deleteRec, deleteReq)
|
||||
@@ -987,7 +987,7 @@ func TestHandleWebUINodeArtifactsExport(t *testing.T) {
|
||||
}
|
||||
srv.mgr.Upsert(nodes.NodeInfo{ID: "edge-a", Name: "Edge A", Online: true})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/node_artifacts/export?node=edge-a&action=screen_snapshot&kind=text", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/node_artifacts/export?node=edge-a&action=screen_snapshot&kind=text", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUINodeArtifactsExport(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
@@ -1048,7 +1048,7 @@ func TestHandleWebUINodeArtifactsPrune(t *testing.T) {
|
||||
t.Fatalf("write audit: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/webui/api/node_artifacts/prune", strings.NewReader(`{"node":"edge-a","action":"screen_snapshot","kind":"text","keep_latest":1}`))
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/node_artifacts/prune", strings.NewReader(`{"node":"edge-a","action":"screen_snapshot","kind":"text","keep_latest":1}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUINodeArtifactPrune(rec, req)
|
||||
@@ -1091,7 +1091,7 @@ func TestHandleWebUINodeArtifactsAppliesRetentionConfig(t *testing.T) {
|
||||
t.Fatalf("write audit: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/node_artifacts", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/node_artifacts", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUINodeArtifacts(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
@@ -1142,7 +1142,7 @@ func TestHandleWebUINodeArtifactsAppliesRetentionDays(t *testing.T) {
|
||||
t.Fatalf("write audit: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/node_artifacts", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/node_artifacts", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUINodeArtifacts(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
|
||||
Reference in New Issue
Block a user