5759c1862e
M0-M7 已完成:核心网关(身份/RBAC/TOTP/OIDC/SAML/Provider/配额/路由/内容策略/审计/定价)+ 资源市场(MCP/Skills/数字员工)。 含 22 个 PostgreSQL 迁移、管理端/门户端前端源码、OpenAPI 契约、部署 compose。 Co-Authored-By: Claude <noreply@anthropic.com>
784 lines
24 KiB
Go
784 lines
24 KiB
Go
package workbench
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"aigateway.local/core/internal/identity"
|
|
"aigateway.local/core/internal/platform/apiresponse"
|
|
)
|
|
|
|
type AdminHTTPHandler struct {
|
|
service *Service
|
|
tools *ToolService
|
|
notifications *NotificationService
|
|
identity *identity.Service
|
|
mux *http.ServeMux
|
|
}
|
|
|
|
func NewAdminHTTPHandler(service *Service, tools *ToolService, notifications *NotificationService, identityService *identity.Service) *AdminHTTPHandler {
|
|
h := &AdminHTTPHandler{service: service, tools: tools, notifications: notifications, identity: identityService, mux: http.NewServeMux()}
|
|
h.mux.HandleFunc("GET /api/v1/admin/prompt-categories", h.listPromptCategories)
|
|
h.mux.HandleFunc("POST /api/v1/admin/prompt-categories", h.createPromptCategory)
|
|
h.mux.HandleFunc("DELETE /api/v1/admin/prompt-categories/{id}", h.deletePromptCategory)
|
|
h.mux.HandleFunc("GET /api/v1/admin/prompts", h.listPrompts)
|
|
h.mux.HandleFunc("POST /api/v1/admin/prompts", h.createPrompt)
|
|
h.mux.HandleFunc("GET /api/v1/admin/prompts/{id}", h.getPrompt)
|
|
h.mux.HandleFunc("PUT /api/v1/admin/prompts/{id}", h.updatePrompt)
|
|
h.mux.HandleFunc("DELETE /api/v1/admin/prompts/{id}", h.deletePrompt)
|
|
h.mux.HandleFunc("GET /api/v1/admin/prompts/{id}/versions", h.listPromptVersions)
|
|
h.mux.HandleFunc("POST /api/v1/admin/prompts/{id}/versions", h.addPromptVersion)
|
|
h.mux.HandleFunc("POST /api/v1/admin/prompts/{id}/versions/{version}/activate", h.activatePromptVersion)
|
|
h.mux.HandleFunc("POST /api/v1/admin/prompts/{id}/render", h.previewPrompt)
|
|
h.mux.HandleFunc("GET /api/v1/admin/knowledge-bases", h.listKnowledgeBases)
|
|
h.mux.HandleFunc("POST /api/v1/admin/knowledge-bases", h.createKnowledgeBase)
|
|
h.mux.HandleFunc("PUT /api/v1/admin/knowledge-bases/{id}", h.updateKnowledgeBase)
|
|
h.mux.HandleFunc("DELETE /api/v1/admin/knowledge-bases/{id}", h.deleteKnowledgeBase)
|
|
h.mux.HandleFunc("GET /api/v1/admin/knowledge-bases/{id}/documents", h.listDocuments)
|
|
h.mux.HandleFunc("POST /api/v1/admin/knowledge-bases/{id}/documents", h.addDocument)
|
|
h.mux.HandleFunc("DELETE /api/v1/admin/knowledge-bases/{id}/documents/{document_id}", h.deleteDocument)
|
|
h.mux.HandleFunc("POST /api/v1/admin/knowledge-bases/{id}/documents/{document_id}/reprocess", h.reprocessDocument)
|
|
h.mux.HandleFunc("POST /api/v1/admin/knowledge-bases/{id}/search", h.searchKnowledge)
|
|
h.mux.HandleFunc("GET /api/v1/admin/tools", h.listTools)
|
|
h.mux.HandleFunc("POST /api/v1/admin/tools", h.createTool)
|
|
h.mux.HandleFunc("PUT /api/v1/admin/tools/{id}", h.updateTool)
|
|
h.mux.HandleFunc("DELETE /api/v1/admin/tools/{id}", h.deleteTool)
|
|
h.mux.HandleFunc("POST /api/v1/admin/tools/{id}/test", h.testTool)
|
|
h.mux.HandleFunc("GET /api/v1/admin/applications", h.listApplications)
|
|
h.mux.HandleFunc("POST /api/v1/admin/applications", h.createApplication)
|
|
h.mux.HandleFunc("GET /api/v1/admin/applications/catalog", h.applicationCatalog)
|
|
h.mux.HandleFunc("GET /api/v1/admin/applications/asset-dependencies", h.applicationDependencies)
|
|
h.mux.HandleFunc("GET /api/v1/admin/applications/{id}", h.getApplication)
|
|
h.mux.HandleFunc("PUT /api/v1/admin/applications/{id}", h.updateApplication)
|
|
h.mux.HandleFunc("DELETE /api/v1/admin/applications/{id}", h.deleteApplication)
|
|
h.mux.HandleFunc("POST /api/v1/admin/applications/{id}/publish", h.publishApplication)
|
|
h.mux.HandleFunc("POST /api/v1/admin/applications/{id}/rollback/{version}", h.rollbackApplication)
|
|
h.mux.HandleFunc("GET /api/v1/admin/applications/{id}/versions", h.listApplicationVersions)
|
|
h.mux.HandleFunc("GET /api/v1/admin/applications/{id}/runs", h.listApplicationRuns)
|
|
h.mux.HandleFunc("GET /api/v1/admin/notification-channels", h.listChannels)
|
|
h.mux.HandleFunc("POST /api/v1/admin/notification-channels", h.createChannel)
|
|
h.mux.HandleFunc("PUT /api/v1/admin/notification-channels/{id}", h.updateChannel)
|
|
h.mux.HandleFunc("DELETE /api/v1/admin/notification-channels/{id}", h.deleteChannel)
|
|
h.mux.HandleFunc("GET /api/v1/admin/notification-deliveries", h.listDeliveries)
|
|
h.mux.HandleFunc("POST /api/v1/admin/notification-deliveries/{id}/retry", h.retryDelivery)
|
|
return h
|
|
}
|
|
func (h *AdminHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mux.ServeHTTP(w, r) }
|
|
func (h *AdminHTTPHandler) require(w http.ResponseWriter, r *http.Request, permission string) (identity.Account, bool) {
|
|
account, err := h.identity.Authenticate(r.Context(), identity.KindAdmin, r.Header.Get("Authorization"))
|
|
if err != nil {
|
|
apiresponse.Error(w, 401, "登录状态无效")
|
|
return account, false
|
|
}
|
|
if !identity.HasPermission(account, permission) {
|
|
apiresponse.Error(w, 403, "缺少 AI 资产管理权限")
|
|
return account, false
|
|
}
|
|
return account, true
|
|
}
|
|
func decodeAsset(w http.ResponseWriter, r *http.Request, target any) bool {
|
|
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 3<<20))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(target); err != nil {
|
|
apiresponse.Error(w, 400, "请求格式无效")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
func assetError(w http.ResponseWriter, err error) {
|
|
switch {
|
|
case errors.Is(err, ErrNotFound):
|
|
apiresponse.Error(w, 404, "资源不存在")
|
|
case errors.Is(err, ErrConflict):
|
|
apiresponse.Error(w, 409, "资源仍被已发布或草稿应用引用")
|
|
default:
|
|
apiresponse.Error(w, 400, err.Error())
|
|
}
|
|
}
|
|
|
|
func (h *AdminHTTPHandler) listPromptCategories(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.service.ListPromptCategories(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) createPromptCategory(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionPromptManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var input struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
}
|
|
if !decodeAsset(w, r, &input) {
|
|
return
|
|
}
|
|
item, err := h.service.CreatePromptCategory(r.Context(), input.Name, input.Description, a.ID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) deletePromptCategory(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionPromptManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.service.DeletePromptCategory(r.Context(), r.PathValue("id"), a.ID); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"deleted": true})
|
|
}
|
|
|
|
type promptPayload struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
CategoryID *string `json:"category_id"`
|
|
Tags []string `json:"tags"`
|
|
DepartmentIDs []string `json:"department_ids"`
|
|
Enabled bool `json:"enabled"`
|
|
Content string `json:"content"`
|
|
Variables []Variable `json:"variables"`
|
|
ChangeNote string `json:"change_note"`
|
|
}
|
|
|
|
func promptInput(p promptPayload) PromptInput {
|
|
return PromptInput{Name: p.Name, Description: p.Description, CategoryID: p.CategoryID, Tags: p.Tags, DepartmentIDs: p.DepartmentIDs, Enabled: p.Enabled, Content: p.Content, Variables: p.Variables, ChangeNote: p.ChangeNote}
|
|
}
|
|
func (h *AdminHTTPHandler) listPrompts(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.service.ListPrompts(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) getPrompt(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok {
|
|
return
|
|
}
|
|
item, err := h.service.GetPrompt(r.Context(), r.PathValue("id"))
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) createPrompt(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionPromptManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p promptPayload
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
item, err := h.service.CreatePrompt(r.Context(), promptInput(p), a.ID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) updatePrompt(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionPromptManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p promptPayload
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
item, err := h.service.UpdatePrompt(r.Context(), r.PathValue("id"), promptInput(p), a.ID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) deletePrompt(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionPromptManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.service.DeletePrompt(r.Context(), r.PathValue("id"), a.ID); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"deleted": true})
|
|
}
|
|
func (h *AdminHTTPHandler) listPromptVersions(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.service.ListPromptVersions(r.Context(), r.PathValue("id"))
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) addPromptVersion(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionPromptManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
Content string `json:"content"`
|
|
Variables []Variable `json:"variables"`
|
|
ChangeNote string `json:"change_note"`
|
|
Activate bool `json:"activate"`
|
|
}
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
item, err := h.service.AddPromptVersion(r.Context(), r.PathValue("id"), p.Content, p.Variables, p.ChangeNote, a.ID, p.Activate)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) activatePromptVersion(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionPromptManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
version, err := strconv.Atoi(r.PathValue("version"))
|
|
if err != nil {
|
|
apiresponse.Error(w, 400, "版本号无效")
|
|
return
|
|
}
|
|
if err = h.service.ActivatePromptVersion(r.Context(), r.PathValue("id"), version, a.ID); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"activated": true})
|
|
}
|
|
func (h *AdminHTTPHandler) previewPrompt(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
Variables map[string]any `json:"variables"`
|
|
}
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
prompt, err := h.service.GetPrompt(r.Context(), r.PathValue("id"))
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
if prompt.Current == nil {
|
|
apiresponse.Error(w, 409, "Prompt 没有生效版本")
|
|
return
|
|
}
|
|
rendered, err := RenderPrompt(prompt.Current.Content, prompt.Current.Variables, p.Variables)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]any{"rendered": rendered, "version": prompt.Current.Version})
|
|
}
|
|
|
|
func (h *AdminHTTPHandler) listKnowledgeBases(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionKnowledgeRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.service.ListKnowledgeBases(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) createKnowledgeBase(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionKnowledgeManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var k KnowledgeBase
|
|
if !decodeAsset(w, r, &k) {
|
|
return
|
|
}
|
|
item, err := h.service.SaveKnowledgeBase(r.Context(), k, a.ID, true)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) updateKnowledgeBase(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionKnowledgeManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var k KnowledgeBase
|
|
if !decodeAsset(w, r, &k) {
|
|
return
|
|
}
|
|
k.ID = r.PathValue("id")
|
|
item, err := h.service.SaveKnowledgeBase(r.Context(), k, a.ID, false)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) deleteKnowledgeBase(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionKnowledgeManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.service.DeleteKnowledgeBase(r.Context(), r.PathValue("id"), a.ID); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"deleted": true})
|
|
}
|
|
func (h *AdminHTTPHandler) listDocuments(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionKnowledgeRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.service.ListKnowledgeDocuments(r.Context(), r.PathValue("id"))
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) addDocument(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionKnowledgeManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
Title string `json:"title"`
|
|
SourceType string `json:"source_type"`
|
|
SourceURI string `json:"source_uri"`
|
|
Content string `json:"content"`
|
|
}
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
item, err := h.service.AddKnowledgeDocument(r.Context(), r.PathValue("id"), p.Title, p.SourceType, p.SourceURI, p.Content, a.ID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) deleteDocument(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionKnowledgeManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.service.DeleteKnowledgeDocument(r.Context(), r.PathValue("id"), r.PathValue("document_id"), a.ID); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"deleted": true})
|
|
}
|
|
func (h *AdminHTTPHandler) reprocessDocument(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionKnowledgeManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
item, err := h.service.ReprocessKnowledgeDocument(r.Context(), r.PathValue("id"), r.PathValue("document_id"), a.ID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) searchKnowledge(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionKnowledgeRead); !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
Query string `json:"query"`
|
|
TopK int `json:"top_k"`
|
|
}
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
items, err := NewPostgreSQLRetriever(h.service).Search(r.Context(), r.PathValue("id"), p.Query, p.TopK)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
|
|
type toolPayload struct {
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
EndpointURL string `json:"endpoint_url"`
|
|
HTTPMethod string `json:"http_method"`
|
|
Headers *map[string]string `json:"headers"`
|
|
InputSchema json.RawMessage `json:"input_schema"`
|
|
TimeoutSeconds int `json:"timeout_seconds"`
|
|
DepartmentIDs []string `json:"department_ids"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
func toolInput(p toolPayload, create bool) ToolInput {
|
|
var headers map[string]string
|
|
if p.Headers != nil {
|
|
headers = *p.Headers
|
|
} else if create {
|
|
headers = map[string]string{}
|
|
}
|
|
return ToolInput{Code: p.Code, Name: p.Name, Description: p.Description, EndpointURL: p.EndpointURL, HTTPMethod: p.HTTPMethod, Headers: headers, InputSchema: p.InputSchema, TimeoutSeconds: p.TimeoutSeconds, DepartmentIDs: p.DepartmentIDs, Enabled: p.Enabled}
|
|
}
|
|
func (h *AdminHTTPHandler) listTools(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionToolRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.tools.List(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) createTool(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionToolManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p toolPayload
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
item, err := h.tools.Save(r.Context(), "", toolInput(p, true), a.ID, true)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) updateTool(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionToolManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p toolPayload
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
item, err := h.tools.Save(r.Context(), r.PathValue("id"), toolInput(p, false), a.ID, false)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) deleteTool(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionToolManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.tools.Delete(r.Context(), r.PathValue("id"), a.ID); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"deleted": true})
|
|
}
|
|
func (h *AdminHTTPHandler) testTool(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionToolManage); !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
Input map[string]any `json:"input"`
|
|
}
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
tool, err := h.tools.Get(r.Context(), r.PathValue("id"))
|
|
if err == nil {
|
|
result, executeErr := h.tools.Execute(r.Context(), tool, p.Input, "", "admin-test")
|
|
err = executeErr
|
|
if err == nil {
|
|
apiresponse.OK(w, result)
|
|
return
|
|
}
|
|
}
|
|
assetError(w, err)
|
|
}
|
|
|
|
func (h *AdminHTTPHandler) listApplications(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.service.ListApplications(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) getApplication(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok {
|
|
return
|
|
}
|
|
item, err := h.service.GetApplication(r.Context(), r.PathValue("id"))
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
versions, err := h.service.ListApplicationVersions(r.Context(), item.ID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]any{"application": item, "versions": versions})
|
|
}
|
|
func (h *AdminHTTPHandler) applicationCatalog(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok {
|
|
return
|
|
}
|
|
prompts, err := h.service.ListPrompts(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
knowledge, err := h.service.ListKnowledgeBases(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
tools, err := h.tools.List(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]any{"prompts": prompts, "knowledge_bases": knowledge, "tools": tools})
|
|
}
|
|
func (h *AdminHTTPHandler) applicationDependencies(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok {
|
|
return
|
|
}
|
|
assetType := strings.TrimSpace(r.URL.Query().Get("asset_type"))
|
|
assetID := strings.TrimSpace(r.URL.Query().Get("asset_id"))
|
|
if assetID == "" || (assetType != "prompt" && assetType != "knowledge" && assetType != "tool") {
|
|
apiresponse.Error(w, 400, "asset_type 或 asset_id 无效")
|
|
return
|
|
}
|
|
items, err := h.service.ApplicationDependencies(r.Context(), assetType, assetID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) createApplication(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionApplicationManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var app Application
|
|
if !decodeAsset(w, r, &app) {
|
|
return
|
|
}
|
|
item, err := h.service.SaveApplication(r.Context(), app, a.ID, true)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) updateApplication(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionApplicationManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var app Application
|
|
if !decodeAsset(w, r, &app) {
|
|
return
|
|
}
|
|
app.ID = r.PathValue("id")
|
|
item, err := h.service.SaveApplication(r.Context(), app, a.ID, false)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) deleteApplication(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionApplicationManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.service.DeleteApplication(r.Context(), r.PathValue("id"), a.ID); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"deleted": true})
|
|
}
|
|
func (h *AdminHTTPHandler) publishApplication(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionApplicationManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p struct {
|
|
ChangeNote string `json:"change_note"`
|
|
}
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
app, err := h.service.PublishApplication(r.Context(), r.PathValue("id"), p.ChangeNote, a.ID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, app)
|
|
}
|
|
func (h *AdminHTTPHandler) rollbackApplication(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionApplicationManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
version, err := strconv.Atoi(r.PathValue("version"))
|
|
if err != nil || version < 1 {
|
|
apiresponse.Error(w, 400, "版本号无效")
|
|
return
|
|
}
|
|
var input struct {
|
|
ChangeNote string `json:"change_note"`
|
|
}
|
|
if !decodeAsset(w, r, &input) {
|
|
return
|
|
}
|
|
item, err := h.service.RollbackApplication(r.Context(), r.PathValue("id"), version, input.ChangeNote, a.ID)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) listApplicationVersions(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.service.ListApplicationVersions(r.Context(), r.PathValue("id"))
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) listApplicationRuns(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.service.ListApplicationRuns(r.Context(), r.PathValue("id"), 100)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
|
|
type channelPayload struct {
|
|
Name string `json:"name"`
|
|
WebhookURL string `json:"webhook_url"`
|
|
SigningSecret *string `json:"signing_secret"`
|
|
EventPatterns []string `json:"event_patterns"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
func (h *AdminHTTPHandler) listChannels(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionNotificationRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.notifications.ListChannels(r.Context())
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) createChannel(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionNotificationManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p channelPayload
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
item, err := h.notifications.SaveChannel(r.Context(), "", NotificationInput{Name: p.Name, WebhookURL: p.WebhookURL, SigningSecret: p.SigningSecret, EventPatterns: p.EventPatterns, Enabled: p.Enabled}, a.ID, true)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) updateChannel(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionNotificationManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p channelPayload
|
|
if !decodeAsset(w, r, &p) {
|
|
return
|
|
}
|
|
item, err := h.notifications.SaveChannel(r.Context(), r.PathValue("id"), NotificationInput{Name: p.Name, WebhookURL: p.WebhookURL, SigningSecret: p.SigningSecret, EventPatterns: p.EventPatterns, Enabled: p.Enabled}, a.ID, false)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, item)
|
|
}
|
|
func (h *AdminHTTPHandler) deleteChannel(w http.ResponseWriter, r *http.Request) {
|
|
a, ok := h.require(w, r, identity.PermissionNotificationManage)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.notifications.DeleteChannel(r.Context(), r.PathValue("id"), a.ID); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"deleted": true})
|
|
}
|
|
func (h *AdminHTTPHandler) listDeliveries(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionNotificationRead); !ok {
|
|
return
|
|
}
|
|
items, err := h.notifications.ListDeliveries(r.Context(), 100)
|
|
if err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, items)
|
|
}
|
|
func (h *AdminHTTPHandler) retryDelivery(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := h.require(w, r, identity.PermissionNotificationManage); !ok {
|
|
return
|
|
}
|
|
if err := h.notifications.RetryDelivery(r.Context(), r.PathValue("id")); err != nil {
|
|
assetError(w, err)
|
|
return
|
|
}
|
|
apiresponse.OK(w, map[string]bool{"retried": true})
|
|
}
|
|
|
|
var _ = strings.TrimSpace
|