Compare commits
No commits in common. "0b62ac9ecdcad391f28b302bd05a0bce9f05af24" and "2ee8a95a907eea858155053922914d2f77fb5930" have entirely different histories.
0b62ac9ecd
...
2ee8a95a90
@ -1,173 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
orpcmodels "git.ourworld.tf/herocode/heroagent/pkg/openrpc/models"
|
|
||||||
uimodels "git.ourworld.tf/herocode/heroagent/pkg/servers/ui/models"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenRPCController handles requests related to OpenRPC specifications
|
|
||||||
type OpenRPCController struct {
|
|
||||||
openrpcManager uimodels.OpenRPCUIManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOpenRPCController creates a new instance of OpenRPCController
|
|
||||||
func NewOpenRPCController(openrpcManager uimodels.OpenRPCUIManager) *OpenRPCController {
|
|
||||||
return &OpenRPCController{
|
|
||||||
openrpcManager: openrpcManager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenRPCPageData represents the data needed for the OpenRPC UI pages
|
|
||||||
type OpenRPCPageData struct {
|
|
||||||
Title string
|
|
||||||
Specs []string
|
|
||||||
SelectedSpec string
|
|
||||||
Methods []string
|
|
||||||
SelectedMethod string
|
|
||||||
Method *orpcmodels.Method
|
|
||||||
SocketPath string
|
|
||||||
ExampleParams string
|
|
||||||
Result string
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowOpenRPCUI renders the OpenRPC UI page
|
|
||||||
func (c *OpenRPCController) ShowOpenRPCUI(ctx *fiber.Ctx) error {
|
|
||||||
// Get query parameters
|
|
||||||
selectedSpec := ctx.Query("spec", "")
|
|
||||||
selectedMethod := ctx.Query("method", "")
|
|
||||||
socketPath := ctx.Query("socketPath", "")
|
|
||||||
|
|
||||||
// Get all specs
|
|
||||||
specs := c.openrpcManager.ListSpecs()
|
|
||||||
|
|
||||||
// Initialize page data using fiber.Map instead of struct
|
|
||||||
pageData := fiber.Map{
|
|
||||||
"Title": "OpenRPC UI",
|
|
||||||
"SpecList": specs,
|
|
||||||
"SelectedSpec": selectedSpec,
|
|
||||||
"SocketPath": socketPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a spec is selected, get its methods
|
|
||||||
if selectedSpec != "" {
|
|
||||||
methods := c.openrpcManager.ListMethods(selectedSpec)
|
|
||||||
pageData["Methods"] = methods
|
|
||||||
pageData["SelectedMethod"] = selectedMethod
|
|
||||||
|
|
||||||
// If a method is selected, get its details
|
|
||||||
if selectedMethod != "" {
|
|
||||||
method := c.openrpcManager.GetMethod(selectedSpec, selectedMethod)
|
|
||||||
if method != nil {
|
|
||||||
pageData["Method"] = method
|
|
||||||
|
|
||||||
// Generate example parameters if available
|
|
||||||
if len(method.Examples) > 0 {
|
|
||||||
exampleParams, err := json.MarshalIndent(method.Examples[0].Params, "", " ")
|
|
||||||
if err == nil {
|
|
||||||
pageData["ExampleParams"] = string(exampleParams)
|
|
||||||
}
|
|
||||||
} else if len(method.Params) > 0 {
|
|
||||||
// Generate example from parameter schema
|
|
||||||
exampleParams := generateExampleParams(method.Params)
|
|
||||||
jsonParams, err := json.MarshalIndent(exampleParams, "", " ")
|
|
||||||
if err == nil {
|
|
||||||
pageData["ExampleParams"] = string(jsonParams)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx.Render("pages/rpcui", pageData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecuteRPC handles RPC execution requests
|
|
||||||
func (c *OpenRPCController) ExecuteRPC(ctx *fiber.Ctx) error {
|
|
||||||
// Parse request
|
|
||||||
var request struct {
|
|
||||||
Spec string `json:"spec"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
SocketPath string `json:"socketPath"`
|
|
||||||
Params json.RawMessage `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ctx.BodyParser(&request); err != nil {
|
|
||||||
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"error": "Invalid request: " + err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate request
|
|
||||||
if request.Spec == "" || request.Method == "" || request.SocketPath == "" {
|
|
||||||
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"error": "Missing required fields: spec, method, or socketPath",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse params
|
|
||||||
var params interface{}
|
|
||||||
if len(request.Params) > 0 {
|
|
||||||
if err := json.Unmarshal(request.Params, ¶ms); err != nil {
|
|
||||||
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"error": "Invalid parameters: " + err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute RPC
|
|
||||||
result, err := c.openrpcManager.ExecuteRPC(request.Spec, request.Method, request.SocketPath, params)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error executing RPC: %v", err)
|
|
||||||
return ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return result
|
|
||||||
return ctx.JSON(fiber.Map{
|
|
||||||
"result": result,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateExampleParams generates example parameters from parameter schemas
|
|
||||||
func generateExampleParams(params []orpcmodels.Parameter) map[string]interface{} {
|
|
||||||
example := make(map[string]interface{})
|
|
||||||
|
|
||||||
for _, param := range params {
|
|
||||||
example[param.Name] = generateExampleValue(param.Schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
return example
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateExampleValue generates an example value from a schema
|
|
||||||
func generateExampleValue(schema orpcmodels.SchemaObject) interface{} {
|
|
||||||
switch schema.Type {
|
|
||||||
case "string":
|
|
||||||
return "example"
|
|
||||||
case "number":
|
|
||||||
return 0
|
|
||||||
case "integer":
|
|
||||||
return 0
|
|
||||||
case "boolean":
|
|
||||||
return false
|
|
||||||
case "array":
|
|
||||||
if schema.Items != nil {
|
|
||||||
return []interface{}{generateExampleValue(*schema.Items)}
|
|
||||||
}
|
|
||||||
return []interface{}{}
|
|
||||||
case "object":
|
|
||||||
obj := make(map[string]interface{})
|
|
||||||
for name, propSchema := range schema.Properties {
|
|
||||||
obj[name] = generateExampleValue(propSchema)
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.ourworld.tf/herocode/heroagent/pkg/openrpc"
|
|
||||||
"git.ourworld.tf/herocode/heroagent/pkg/openrpc/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenRPCUIManager is the interface for managing OpenRPC specifications in the UI
|
|
||||||
type OpenRPCUIManager interface {
|
|
||||||
// ListSpecs returns a list of all loaded specification names
|
|
||||||
ListSpecs() []string
|
|
||||||
|
|
||||||
// GetSpec returns an OpenRPC specification by name
|
|
||||||
GetSpec(name string) *models.OpenRPCSpec
|
|
||||||
|
|
||||||
// ListMethods returns a list of all method names in a specification
|
|
||||||
ListMethods(specName string) []string
|
|
||||||
|
|
||||||
// GetMethod returns a method from a specification
|
|
||||||
GetMethod(specName, methodName string) *models.Method
|
|
||||||
|
|
||||||
// ExecuteRPC executes an RPC call
|
|
||||||
ExecuteRPC(specName, methodName, socketPath string, params interface{}) (interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenRPCManager implements the OpenRPCUIManager interface
|
|
||||||
type OpenRPCManager struct {
|
|
||||||
manager *openrpc.ORPCManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOpenRPCManager creates a new OpenRPCUIManager
|
|
||||||
func NewOpenRPCManager() OpenRPCUIManager {
|
|
||||||
manager := openrpc.NewORPCManager()
|
|
||||||
|
|
||||||
// Try to load specs from the default directory
|
|
||||||
specDirs := []string{
|
|
||||||
"./pkg/openrpc/services",
|
|
||||||
"./pkg/openrpc/specs",
|
|
||||||
"./specs/openrpc",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dir := range specDirs {
|
|
||||||
err := manager.LoadSpecs(dir)
|
|
||||||
if err == nil {
|
|
||||||
// Successfully loaded specs from this directory
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &OpenRPCManager{
|
|
||||||
manager: manager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListSpecs returns a list of all loaded specification names
|
|
||||||
func (m *OpenRPCManager) ListSpecs() []string {
|
|
||||||
return m.manager.ListSpecs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSpec returns an OpenRPC specification by name
|
|
||||||
func (m *OpenRPCManager) GetSpec(name string) *models.OpenRPCSpec {
|
|
||||||
return m.manager.GetSpec(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMethods returns a list of all method names in a specification
|
|
||||||
func (m *OpenRPCManager) ListMethods(specName string) []string {
|
|
||||||
return m.manager.ListMethods(specName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMethod returns a method from a specification
|
|
||||||
func (m *OpenRPCManager) GetMethod(specName, methodName string) *models.Method {
|
|
||||||
return m.manager.GetMethod(specName, methodName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecuteRPC executes an RPC call
|
|
||||||
func (m *OpenRPCManager) ExecuteRPC(specName, methodName, socketPath string, params interface{}) (interface{}, error) {
|
|
||||||
// Create JSON-RPC request
|
|
||||||
request := map[string]interface{}{
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"method": methodName,
|
|
||||||
"params": params,
|
|
||||||
"id": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal request to JSON
|
|
||||||
requestJSON, err := json.Marshal(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if socket path is a Unix socket or HTTP endpoint
|
|
||||||
if socketPath[:1] == "/" {
|
|
||||||
// Unix socket
|
|
||||||
return executeUnixSocketRPC(socketPath, requestJSON)
|
|
||||||
} else {
|
|
||||||
// HTTP endpoint
|
|
||||||
return executeHTTPRPC(socketPath, requestJSON)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeUnixSocketRPC executes an RPC call over a Unix socket
|
|
||||||
func executeUnixSocketRPC(socketPath string, requestJSON []byte) (interface{}, error) {
|
|
||||||
// Connect to Unix socket
|
|
||||||
conn, err := net.Dial("unix", socketPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to connect to socket %s: %w", socketPath, err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
// Set timeout
|
|
||||||
deadline := time.Now().Add(10 * time.Second)
|
|
||||||
if err := conn.SetDeadline(deadline); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to set deadline: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
if _, err := conn.Write(requestJSON); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read response
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := buf.ReadFrom(conn); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response map[string]interface{}
|
|
||||||
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for error
|
|
||||||
if errObj, ok := response["error"]; ok {
|
|
||||||
return nil, fmt.Errorf("RPC error: %v", errObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return result
|
|
||||||
return response["result"], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeHTTPRPC executes an RPC call over HTTP
|
|
||||||
func executeHTTPRPC(endpoint string, requestJSON []byte) (interface{}, error) {
|
|
||||||
// Create HTTP request
|
|
||||||
req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(requestJSON))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set headers
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Create HTTP client with timeout
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to send HTTP request: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Check status code
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("HTTP error: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response map[string]interface{}
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for error
|
|
||||||
if errObj, ok := response["error"]; ok {
|
|
||||||
return nil, fmt.Errorf("RPC error: %v", errObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return result
|
|
||||||
return response["result"], nil
|
|
||||||
}
|
|
@ -12,13 +12,11 @@ func SetupRoutes(app *fiber.App) {
|
|||||||
// For now, using the mock process manager
|
// For now, using the mock process manager
|
||||||
processManagerService := models.NewMockProcessManager()
|
processManagerService := models.NewMockProcessManager()
|
||||||
jobManagerService := models.NewMockJobManager()
|
jobManagerService := models.NewMockJobManager()
|
||||||
openrpcManagerService := models.NewOpenRPCManager()
|
|
||||||
|
|
||||||
dashboardController := controllers.NewDashboardController()
|
dashboardController := controllers.NewDashboardController()
|
||||||
processController := controllers.NewProcessController(processManagerService)
|
processController := controllers.NewProcessController(processManagerService)
|
||||||
jobController := controllers.NewJobController(jobManagerService)
|
jobController := controllers.NewJobController(jobManagerService)
|
||||||
authController := controllers.NewAuthController()
|
authController := controllers.NewAuthController()
|
||||||
openrpcController := controllers.NewOpenRPCController(openrpcManagerService)
|
|
||||||
|
|
||||||
// --- Public Routes ---
|
// --- Public Routes ---
|
||||||
// Login and Logout
|
// Login and Logout
|
||||||
@ -45,10 +43,6 @@ func SetupRoutes(app *fiber.App) {
|
|||||||
app.Get("/jobs", jobController.ShowJobsPage)
|
app.Get("/jobs", jobController.ShowJobsPage)
|
||||||
app.Get("/jobs/:id", jobController.ShowJobDetails)
|
app.Get("/jobs/:id", jobController.ShowJobDetails)
|
||||||
|
|
||||||
// OpenRPC UI routes
|
|
||||||
app.Get("/rpcui", openrpcController.ShowOpenRPCUI)
|
|
||||||
app.Post("/api/rpcui/execute", openrpcController.ExecuteRPC)
|
|
||||||
|
|
||||||
// Debug routes
|
// Debug routes
|
||||||
app.Get("/debug", func(c *fiber.Ctx) error {
|
app.Get("/debug", func(c *fiber.Ctx) error {
|
||||||
// Get all data from the jobs page to debug
|
// Get all data from the jobs page to debug
|
||||||
|
@ -1,144 +0,0 @@
|
|||||||
/* OpenRPC UI Styles */
|
|
||||||
|
|
||||||
.method-tree {
|
|
||||||
max-height: 600px;
|
|
||||||
overflow-y: auto;
|
|
||||||
border-right: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-item {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 8px 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-item:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-item.active {
|
|
||||||
background-color: #e9ecef;
|
|
||||||
font-weight: bold;
|
|
||||||
border-left: 3px solid #0d6efd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.param-card {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-container {
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-editor {
|
|
||||||
font-family: 'Courier New', Courier, monospace;
|
|
||||||
min-height: 200px;
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
white-space: pre;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ced4da;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema-table {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema-table th {
|
|
||||||
font-weight: 600;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema-required {
|
|
||||||
color: #dc3545;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schema-optional {
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-description {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #6c757d;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
border-bottom: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-container {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-header {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-content {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Socket path input styling */
|
|
||||||
.socket-path-container {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socket-path-label {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Execute button styling */
|
|
||||||
.execute-button {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Response styling */
|
|
||||||
.response-success {
|
|
||||||
border-left: 4px solid #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
.response-error {
|
|
||||||
border-left: 4px solid #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading indicator */
|
|
||||||
.loading-spinner {
|
|
||||||
display: inline-block;
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
border: 0.2em solid currentColor;
|
|
||||||
border-right-color: transparent;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spinner-border .75s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spinner-border {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
@ -1,141 +0,0 @@
|
|||||||
/**
|
|
||||||
* OpenRPC UI JavaScript
|
|
||||||
* Handles the interactive functionality of the OpenRPC UI
|
|
||||||
*/
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Initialize form elements
|
|
||||||
const specForm = document.getElementById('specForm');
|
|
||||||
const rpcForm = document.getElementById('rpcForm');
|
|
||||||
const paramsEditor = document.getElementById('paramsEditor');
|
|
||||||
const resultContainer = document.getElementById('resultContainer');
|
|
||||||
const resultOutput = document.getElementById('resultOutput');
|
|
||||||
const errorContainer = document.getElementById('errorContainer');
|
|
||||||
const errorOutput = document.getElementById('errorOutput');
|
|
||||||
|
|
||||||
// Format JSON in the parameters editor
|
|
||||||
if (paramsEditor && paramsEditor.value) {
|
|
||||||
try {
|
|
||||||
const params = JSON.parse(paramsEditor.value);
|
|
||||||
paramsEditor.value = JSON.stringify(params, null, 2);
|
|
||||||
} catch (e) {
|
|
||||||
// If not valid JSON, leave as is
|
|
||||||
console.warn('Could not format parameters as JSON:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle RPC execution
|
|
||||||
if (rpcForm) {
|
|
||||||
rpcForm.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// Hide previous results
|
|
||||||
if (resultContainer) resultContainer.classList.add('d-none');
|
|
||||||
if (errorContainer) errorContainer.classList.add('d-none');
|
|
||||||
|
|
||||||
// Get form data
|
|
||||||
const spec = document.getElementById('spec').value;
|
|
||||||
const method = document.querySelector('input[name="selectedMethod"]').value;
|
|
||||||
const socketPath = document.getElementById('socketPath').value;
|
|
||||||
const paramsText = paramsEditor.value;
|
|
||||||
|
|
||||||
// Show loading indicator
|
|
||||||
const submitButton = rpcForm.querySelector('button[type="submit"]');
|
|
||||||
const originalButtonText = submitButton.innerHTML;
|
|
||||||
submitButton.disabled = true;
|
|
||||||
submitButton.innerHTML = '<span class="loading-spinner me-2"></span>Executing...';
|
|
||||||
|
|
||||||
// Validate
|
|
||||||
if (!spec || !method || !socketPath) {
|
|
||||||
showError('Missing required fields: spec, method, or socketPath');
|
|
||||||
resetButton();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse params
|
|
||||||
let params;
|
|
||||||
try {
|
|
||||||
params = JSON.parse(paramsText);
|
|
||||||
} catch (e) {
|
|
||||||
showError('Invalid JSON parameters: ' + e.message);
|
|
||||||
resetButton();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute RPC
|
|
||||||
fetch('/api/rpcui/execute', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
spec: spec,
|
|
||||||
method: method,
|
|
||||||
socketPath: socketPath,
|
|
||||||
params: params
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.error) {
|
|
||||||
showError(data.error);
|
|
||||||
} else {
|
|
||||||
showResult(data.result);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
showError('Request failed: ' + error.message);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
resetButton();
|
|
||||||
});
|
|
||||||
|
|
||||||
function resetButton() {
|
|
||||||
submitButton.disabled = false;
|
|
||||||
submitButton.innerHTML = originalButtonText;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(message) {
|
|
||||||
if (errorContainer && errorOutput) {
|
|
||||||
errorContainer.classList.remove('d-none');
|
|
||||||
errorOutput.textContent = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showResult(result) {
|
|
||||||
if (resultContainer && resultOutput) {
|
|
||||||
resultContainer.classList.remove('d-none');
|
|
||||||
resultOutput.textContent = JSON.stringify(result, null, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method tree navigation
|
|
||||||
const methodItems = document.querySelectorAll('.method-item');
|
|
||||||
methodItems.forEach(item => {
|
|
||||||
item.addEventListener('click', function(e) {
|
|
||||||
// Already handled by href, but could add additional functionality here
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Format JSON examples
|
|
||||||
const jsonExamples = document.querySelectorAll('pre code');
|
|
||||||
jsonExamples.forEach(example => {
|
|
||||||
try {
|
|
||||||
const content = example.textContent;
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
example.textContent = JSON.stringify(json, null, 2);
|
|
||||||
} catch (e) {
|
|
||||||
// If not valid JSON, leave as is
|
|
||||||
console.warn('Could not format example as JSON:', e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add syntax highlighting if a library like highlight.js is available
|
|
||||||
if (typeof hljs !== 'undefined') {
|
|
||||||
document.querySelectorAll('pre code').forEach((block) => {
|
|
||||||
hljs.highlightBlock(block);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -18,12 +18,6 @@
|
|||||||
Job Manager
|
Job Manager
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/rpcui">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-code"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>
|
|
||||||
OpenRPC UI
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<!-- Add more menu items here as needed -->
|
<!-- Add more menu items here as needed -->
|
||||||
</ul>
|
</ul>
|
||||||
{{ end }}
|
{{ end }}
|
@ -1,157 +0,0 @@
|
|||||||
{{extends "../layouts/base"}}
|
|
||||||
|
|
||||||
{{block title()}}
|
|
||||||
OpenRPC UI
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{block css()}}
|
|
||||||
<link rel="stylesheet" href="/static/css/rpcui.css">
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{block body()}}
|
|
||||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
||||||
<h1 class="h2">OpenRPC UI</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5>Select OpenRPC Specification</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<form id="specForm" action="/rpcui" method="get" class="row g-3">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<label for="spec" class="form-label">Specification</label>
|
|
||||||
<select class="form-select" id="spec" name="spec" onchange="this.form.submit()">
|
|
||||||
<option value="">Select a specification</option>
|
|
||||||
{{ if SpecList }}
|
|
||||||
{{ range spec := SpecList }}
|
|
||||||
<option value="{{ spec }}" >
|
|
||||||
{{ end }}
|
|
||||||
{{ else }}
|
|
||||||
<option value="" disabled>No specifications available</option>
|
|
||||||
{{ end }}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<label for="socketPath" class="form-label">Socket Path</label>
|
|
||||||
<input type="text" class="form-control" id="socketPath" name="socketPath" value="{{ SocketPath }}" placeholder="e.g., /tmp/rpc.sock">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 d-flex align-items-end">
|
|
||||||
<button type="submit" class="btn btn-primary">Apply</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<p>This is the OpenRPC UI page. It allows you to interact with OpenRPC specifications.</p>
|
|
||||||
<p>Currently available specs: {{ if SpecList }}{{ len(SpecList) }}{{ else }}0{{ end }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ if SelectedSpec }}
|
|
||||||
<div class="row">
|
|
||||||
<!-- Method Tree -->
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header"><h5>Methods</h5></div>
|
|
||||||
<div class="card-body p-0">
|
|
||||||
<div class="method-tree list-group list-group-flush">
|
|
||||||
{{ if Methods }}
|
|
||||||
{{ range m := Methods }}
|
|
||||||
<a href="/rpcui?spec={{ SelectedSpec }}&method={{ m }}&socketPath={{ SocketPath }}"
|
|
||||||
class="list-group-item list-group-item-action method-item {{ if eq(m, SelectedMethod) }}active{{ end }}">
|
|
||||||
{{ m }}
|
|
||||||
</a>
|
|
||||||
{{ end }}
|
|
||||||
{{ else }}
|
|
||||||
<div class="list-group-item">No methods available</div>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Method Details -->
|
|
||||||
<div class="col-md-9">
|
|
||||||
{{ if Method }}
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5>{{ Method.Name }}</h5>
|
|
||||||
{{ if Method.Description }}<p class="text-muted mb-0">{{ Method.Description }}</p>{{ end }}
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<!-- Parameters -->
|
|
||||||
<h6>Parameters</h6>
|
|
||||||
<table class="table table-sm schema-table">
|
|
||||||
<thead><tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{{ if Method.Params }}
|
|
||||||
{{ range p := Method.Params }}
|
|
||||||
<tr>
|
|
||||||
<td>{{ p.Name }}</td>
|
|
||||||
<td><code>{{ p.Schema.Type }}</code></td>
|
|
||||||
<td>{{ if p.Required }}<span class="schema-required">Yes</span>{{ else }}<span class="schema-optional">No</span>{{ end }}</td>
|
|
||||||
<td>{{ p.Description }}</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
{{ else }}
|
|
||||||
<tr><td colspan="4">No parameters</td></tr>
|
|
||||||
{{ end }}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- Result -->
|
|
||||||
<h6 class="mt-4">Result</h6>
|
|
||||||
<table class="table table-sm schema-table">
|
|
||||||
<thead><tr><th>Name</th><th>Type</th><th>Description</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>{{ Method.Result.Name }}</td>
|
|
||||||
<td><code>{{ Method.Result.Schema.Type }}</code></td>
|
|
||||||
<td>{{ Method.Result.Description }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- Try It -->
|
|
||||||
<h6 class="mt-4">Try It</h6>
|
|
||||||
<form id="rpcForm" class="mb-3">
|
|
||||||
<input type="hidden" name="selectedMethod" value="{{ SelectedMethod }}">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="paramsEditor" class="form-label">Parameters:</label>
|
|
||||||
<textarea class="form-control code-editor" id="paramsEditor" rows="10">{{ ExampleParams }}</textarea>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Execute</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div id="resultContainer" class="result-container d-none">
|
|
||||||
<h6>Result:</h6>
|
|
||||||
<pre id="resultOutput" class="bg-light p-2 rounded"></pre>
|
|
||||||
</div>
|
|
||||||
<div id="errorContainer" class="result-container d-none">
|
|
||||||
<h6>Error:</h6>
|
|
||||||
<pre id="errorOutput" class="bg-light p-2 rounded text-danger"></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ else if SelectedMethod }}
|
|
||||||
<div class="alert alert-warning">Method not found: {{ SelectedMethod }}</div>
|
|
||||||
{{ else }}
|
|
||||||
<div class="alert alert-info">Select a method from the list to view details.</div>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{block scripts()}}
|
|
||||||
<script src="/static/js/rpcui.js"></script>
|
|
||||||
{{end}}
|
|
Loading…
Reference in New Issue
Block a user