Compare commits
2 Commits
292a769250
...
fab2d199d1
Author | SHA1 | Date | |
---|---|---|---|
fab2d199d1 | |||
ae6966edb2 |
274
examples/example_old.v
Normal file
274
examples/example_old.v
Normal file
@@ -0,0 +1,274 @@
|
||||
module main
|
||||
|
||||
import zinit
|
||||
|
||||
|
||||
// Example 1: Get the OpenRPC API specification
|
||||
fn example_rpc_discover(mut client zinit.Client) {
|
||||
println('1. Getting API specification...')
|
||||
|
||||
spec := client.rpc_discover() or {
|
||||
eprintln('Error getting API spec: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('API Title: ${spec['info'] or { map[string]interface{}{} }['title'] or { 'Unknown' }}')
|
||||
println('API Version: ${spec['info'] or { map[string]interface{}{} }['version'] or { 'Unknown' }}')
|
||||
println()
|
||||
}
|
||||
|
||||
// Example 2: List all services
|
||||
fn example_service_list(mut client zinit.Client) {
|
||||
println('2. Listing all services...')
|
||||
|
||||
services := client.service_list() or {
|
||||
eprintln('Error listing services: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
if services.len == 0 {
|
||||
println('No services found.')
|
||||
} else {
|
||||
for name, state in services {
|
||||
println(' ${name}: ${state}')
|
||||
}
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
// Example 3: Get detailed status for a specific service
|
||||
fn example_service_status(mut client zinit.Client) {
|
||||
println('3. Getting service status...')
|
||||
|
||||
// Try to get status for a service (replace 'redis' with an actual service name)
|
||||
service_name := 'redis'
|
||||
|
||||
status := client.service_status(service_name) or {
|
||||
eprintln('Error getting service status: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service: ${status.name}')
|
||||
println(' State: ${status.state}')
|
||||
println(' Target: ${status.target}')
|
||||
println(' PID: ${status.pid}')
|
||||
|
||||
if status.after.len > 0 {
|
||||
println(' Dependencies:')
|
||||
for dep_name, dep_state in status.after {
|
||||
println(' ${dep_name}: ${dep_state}')
|
||||
}
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
// Example 4: Create a new service configuration
|
||||
fn example_service_create(mut client zinit.Client) {
|
||||
println('4. Creating a new service...')
|
||||
|
||||
// Create a simple service configuration
|
||||
config := zinit.ServiceConfig{
|
||||
exec: '/usr/bin/echo "Hello from my service"'
|
||||
oneshot: true
|
||||
log: 'stdout'
|
||||
env: {
|
||||
'MY_VAR': 'my_value'
|
||||
'PATH': '/usr/bin:/bin'
|
||||
}
|
||||
shutdown_timeout: 30
|
||||
}
|
||||
|
||||
service_name := 'example_service'
|
||||
|
||||
result := client.service_create(service_name, config) or {
|
||||
eprintln('Error creating service: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service created: ${result}')
|
||||
println()
|
||||
}
|
||||
|
||||
// Example 5: Service management operations
|
||||
fn example_service_management(mut client zinit.Client) {
|
||||
println('5. Service management operations...')
|
||||
|
||||
service_name := 'example_service'
|
||||
|
||||
// Monitor the service
|
||||
client.service_monitor(service_name) or {
|
||||
eprintln('Error monitoring service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service ${service_name} is now being monitored')
|
||||
|
||||
// Start the service
|
||||
client.service_start(service_name) or {
|
||||
eprintln('Error starting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service ${service_name} started')
|
||||
|
||||
// Wait a moment for the service to run (since it's oneshot)
|
||||
// In a real application, you might want to check the status instead
|
||||
|
||||
// Stop the service (if it's still running)
|
||||
client.service_stop(service_name) or {
|
||||
// This might fail if the service already finished (oneshot)
|
||||
println('Note: Could not stop service (might have already finished): ${err}')
|
||||
}
|
||||
|
||||
// Forget the service (stop monitoring)
|
||||
client.service_forget(service_name) or {
|
||||
eprintln('Error forgetting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service ${service_name} is no longer being monitored')
|
||||
|
||||
// Delete the service configuration
|
||||
client.service_delete(service_name) or {
|
||||
eprintln('Error deleting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service ${service_name} configuration deleted')
|
||||
println()
|
||||
}
|
||||
|
||||
// Example 6: Get service statistics
|
||||
fn example_service_stats(mut client zinit.Client) {
|
||||
println('6. Getting service statistics...')
|
||||
|
||||
// Try to get stats for a running service
|
||||
service_name := 'redis' // Replace with an actual running service
|
||||
|
||||
stats := client.service_stats(service_name) or {
|
||||
eprintln('Error getting service stats: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service: ${stats.name}')
|
||||
println(' PID: ${stats.pid}')
|
||||
println(' Memory Usage: ${stats.memory_usage} bytes (${stats.memory_usage / 1024 / 1024} MB)')
|
||||
println(' CPU Usage: ${stats.cpu_usage}%')
|
||||
|
||||
if stats.children.len > 0 {
|
||||
println(' Child Processes:')
|
||||
for child in stats.children {
|
||||
println(' PID ${child.pid}: ${child.memory_usage} bytes, ${child.cpu_usage}% CPU')
|
||||
}
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
// Example 7: Log streaming
|
||||
fn example_log_streaming(mut client zinit.Client) {
|
||||
println('7. Getting current logs...')
|
||||
|
||||
// Get all current logs
|
||||
logs := client.stream_current_logs(none) or {
|
||||
eprintln('Error getting logs: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
if logs.len == 0 {
|
||||
println('No logs available')
|
||||
} else {
|
||||
println('Recent logs:')
|
||||
for i, log_entry in logs {
|
||||
println(' ${i + 1}: ${log_entry}')
|
||||
if i >= 4 { // Show only first 5 logs
|
||||
println(' ... (${logs.len - 5} more logs)')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get logs for a specific service
|
||||
service_logs := client.stream_current_logs('redis') or {
|
||||
eprintln('Error getting service logs: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
if service_logs.len > 0 {
|
||||
println('Redis service logs:')
|
||||
for log_entry in service_logs {
|
||||
println(' ${log_entry}')
|
||||
}
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
// Example 8: System operations
|
||||
fn example_system_operations(mut client zinit.Client) {
|
||||
println('8. System operations...')
|
||||
|
||||
// Start HTTP server (be careful with this in production)
|
||||
server_result := client.system_start_http_server('127.0.0.1:8080') or {
|
||||
eprintln('Error starting HTTP server: ${err}')
|
||||
return
|
||||
}
|
||||
println('HTTP server: ${server_result}')
|
||||
|
||||
// Stop HTTP server
|
||||
client.system_stop_http_server() or {
|
||||
eprintln('Error stopping HTTP server: ${err}')
|
||||
return
|
||||
}
|
||||
println('HTTP server stopped')
|
||||
|
||||
// Note: system_shutdown() and system_reboot() are commented out
|
||||
// as they would actually shut down or reboot the system!
|
||||
|
||||
// client.system_shutdown() or {
|
||||
// eprintln('Error shutting down system: ${err}')
|
||||
// return
|
||||
// }
|
||||
// println('System shutdown initiated')
|
||||
|
||||
// client.system_reboot() or {
|
||||
// eprintln('Error rebooting system: ${err}')
|
||||
// return
|
||||
// }
|
||||
// println('System reboot initiated')
|
||||
|
||||
println('System operations completed (shutdown/reboot skipped for safety)')
|
||||
println()
|
||||
}
|
||||
|
||||
|
||||
// Create a new zinit client with default socket path (/tmp/zinit.sock)
|
||||
mut client := zinit.new_default_client()
|
||||
|
||||
// Alternatively, you can specify a custom socket path:
|
||||
// mut client := zinit.new_client('/custom/path/to/zinit.sock')
|
||||
|
||||
// Ensure we disconnect when done
|
||||
defer {
|
||||
client.disconnect()
|
||||
}
|
||||
|
||||
println('=== Zinit Client Example ===\n')
|
||||
|
||||
// Example 1: Get API specification
|
||||
example_rpc_discover(mut client)
|
||||
|
||||
// Example 2: List all services
|
||||
example_service_list(mut client)
|
||||
|
||||
// Example 3: Get service status
|
||||
example_service_status(mut client)
|
||||
|
||||
// Example 4: Create a new service
|
||||
example_service_create(mut client)
|
||||
|
||||
// Example 5: Service management operations
|
||||
example_service_management(mut client)
|
||||
|
||||
// Example 6: Get service statistics
|
||||
example_service_stats(mut client)
|
||||
|
||||
// Example 7: Log streaming
|
||||
example_log_streaming(mut client)
|
||||
|
||||
// Example 8: System operations
|
||||
example_system_operations(mut client)
|
124
examples/zinit_client_example.vsh
Executable file
124
examples/zinit_client_example.vsh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.heroweb.clients.zinit
|
||||
import x.json2
|
||||
|
||||
// Create a new Zinit client with the default socket path
|
||||
mut client := zinit.new_default_client()
|
||||
|
||||
println('Connected to Zinit via OpenRPC')
|
||||
|
||||
// Example 1: Get the OpenRPC API specification
|
||||
println('\n=== Getting API Specification ===')
|
||||
api_spec_response := client.rpc_discover() or {
|
||||
println('Error getting API spec: ${err}')
|
||||
return
|
||||
}
|
||||
println('API Specification (first 100 chars): ${json2.encode(api_spec_response.spec)[..100]}...')
|
||||
|
||||
// Example 2: List all services
|
||||
println('\n=== Listing Services ===')
|
||||
service_list_response := client.service_list() or {
|
||||
println('Error listing services: ${err}')
|
||||
return
|
||||
}
|
||||
println('Services:')
|
||||
for name, state in service_list_response.services {
|
||||
println('- ${name}: ${state}')
|
||||
}
|
||||
|
||||
// Example 3: Get detailed status of a service (if any exist)
|
||||
if service_list_response.services.len > 0 {
|
||||
service_name := service_list_response.services.keys()[0]
|
||||
println('\n=== Getting Status for Service: ${service_name} ===')
|
||||
|
||||
status := client.service_status(service_name) or {
|
||||
println('Error getting status: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service Status:')
|
||||
println('- Name: ${status.name}')
|
||||
println('- PID: ${status.pid}')
|
||||
println('- State: ${status.state}')
|
||||
println('- Target: ${status.target}')
|
||||
println('- Dependencies:')
|
||||
for dep_name, dep_state in status.after {
|
||||
println(' - ${dep_name}: ${dep_state}')
|
||||
}
|
||||
|
||||
// Example 4: Get service stats
|
||||
println('\n=== Getting Stats for Service: ${service_name} ===')
|
||||
stats := client.service_stats(service_name) or {
|
||||
println('Error getting stats: ${err}')
|
||||
println('Note: Stats are only available for running services')
|
||||
return
|
||||
}
|
||||
|
||||
println('Service Stats:')
|
||||
println('- Memory Usage: ${stats.memory_usage} bytes (${zinit.format_memory_usage(stats.memory_usage)})')
|
||||
println('- CPU Usage: ${stats.cpu_usage}% (${zinit.format_cpu_usage(stats.cpu_usage)})')
|
||||
if stats.children.len > 0 {
|
||||
println('- Child Processes:')
|
||||
for child in stats.children {
|
||||
println(' - PID: ${child.pid}, Memory: ${zinit.format_memory_usage(child.memory_usage)}, CPU: ${zinit.format_cpu_usage(child.cpu_usage)}')
|
||||
}
|
||||
}
|
||||
|
||||
// Example 5: Get logs for a service
|
||||
println('\n=== Getting Logs for Service: ${service_name} ===')
|
||||
logs_response := client.stream_current_logs(service_name) or {
|
||||
println('Error getting logs: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service logs:')
|
||||
for log in logs_response.logs {
|
||||
println('- ${log}')
|
||||
}
|
||||
} else {
|
||||
println('\nNo services found to query')
|
||||
}
|
||||
|
||||
// Example 6: Create a new service (commented out for safety)
|
||||
/*
|
||||
println('\n=== Creating a New Service ===')
|
||||
new_service_config := zinit.ServiceConfig{
|
||||
exec: '/bin/echo "Hello from Zinit"'
|
||||
oneshot: true
|
||||
after: []string{}
|
||||
log: zinit.log_stdout
|
||||
env: {
|
||||
'ENV_VAR': 'value'
|
||||
}
|
||||
}
|
||||
|
||||
create_response := client.service_create('example_service', new_service_config) or {
|
||||
println('Error creating service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service created: ${create_response.path}')
|
||||
|
||||
// Start the service
|
||||
client.service_start('example_service') or {
|
||||
println('Error starting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service started')
|
||||
|
||||
// Delete the service when done
|
||||
client.service_stop('example_service') or {
|
||||
println('Error stopping service: ${err}')
|
||||
return
|
||||
}
|
||||
client.service_forget('example_service') or {
|
||||
println('Error forgetting service: ${err}')
|
||||
return
|
||||
}
|
||||
delete_response := client.service_delete('example_service') or {
|
||||
println('Error deleting service: ${err}')
|
||||
return
|
||||
}
|
||||
println('Service deleted: ${delete_response.result}')
|
||||
*/
|
||||
|
||||
println('\nZinit OpenRPC client example completed')
|
66
examples/zinit_rpc_example.vsh
Executable file
66
examples/zinit_rpc_example.vsh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.clients.zinit
|
||||
import json
|
||||
|
||||
// We'll use the ServiceStatusResponse from the zinit module
|
||||
// No need to define our own struct
|
||||
|
||||
// Create a client using the Unix socket transport
|
||||
mut cl := jsonrpc.new_unix_socket_client("/tmp/zinit.sock")
|
||||
|
||||
// Example 1: Discover the API using rpc_discover
|
||||
// Create a request for rpc_discover method with empty parameters
|
||||
discover_request := jsonrpc.new_request_generic('rpc.discover', []string{})
|
||||
|
||||
// Send the request and receive the OpenRPC specification as a JSON string
|
||||
println('Sending rpc_discover request...')
|
||||
println('This will return the OpenRPC specification for the API')
|
||||
|
||||
// Use map[string]string for the result to avoid json2.Any issues
|
||||
api_spec_raw := cl.send[[]string, string](discover_request)!
|
||||
|
||||
// Parse the JSON string manually
|
||||
println('API Specification (raw):')
|
||||
println(api_spec_raw)
|
||||
|
||||
// Example 2: List all services
|
||||
// Create a request for service_list method with empty parameters
|
||||
list_request := jsonrpc.new_request_generic('service_list', []string{})
|
||||
|
||||
// Send the request and receive a map of service names to states
|
||||
println('\nSending service_list request...')
|
||||
service_list := cl.send[[]string, map[string]string](list_request)!
|
||||
|
||||
// Display the service list
|
||||
println('Service List:')
|
||||
println(service_list)
|
||||
|
||||
// Example 3: Get status of a specific service
|
||||
// First, check if we have any services to query
|
||||
if service_list.len > 0 {
|
||||
// Get the first service name from the list
|
||||
service_name := service_list.keys()[0]
|
||||
|
||||
// Create a request for service_status method with the service name as parameter
|
||||
// The parameter for service_status is a single string (service name)
|
||||
status_request := jsonrpc.new_request_generic('service_status', service_name)
|
||||
|
||||
// Send the request and receive a ServiceStatusResponse object
|
||||
println('\nSending service_status request for service: $service_name')
|
||||
service_status := cl.send[string, zinit.ServiceStatusResponse](status_request)!
|
||||
|
||||
// Display the service status details
|
||||
println('Service Status:')
|
||||
println('- Name: ${service_status.name}')
|
||||
println('- PID: ${service_status.pid}')
|
||||
println('- State: ${service_status.state}')
|
||||
println('- Target: ${service_status.target}')
|
||||
println('- Dependencies:')
|
||||
for dep_name, dep_state in service_status.after {
|
||||
println(' - $dep_name: $dep_state')
|
||||
}
|
||||
} else {
|
||||
println('\nNo services found to query status')
|
||||
}
|
205
src/agentui/agentui.v
Normal file
205
src/agentui/agentui.v
Normal file
@@ -0,0 +1,205 @@
|
||||
module main
|
||||
|
||||
import veb
|
||||
import json
|
||||
|
||||
// Context struct must embed veb.Context
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
pub mut:
|
||||
user_id string
|
||||
}
|
||||
|
||||
// App struct for shared data
|
||||
pub struct App {
|
||||
pub mut:
|
||||
process_controller ProcessController
|
||||
job_controller JobController
|
||||
system_controller SystemController
|
||||
openrpc_controller OpenRPCController
|
||||
}
|
||||
|
||||
// Main function
|
||||
fn main() {
|
||||
mut app := &App{
|
||||
process_controller: ProcessController{}
|
||||
job_controller: JobController{}
|
||||
system_controller: SystemController{}
|
||||
openrpc_controller: OpenRPCController{}
|
||||
}
|
||||
|
||||
// Use veb.run with App and Context types
|
||||
veb.run[App, Context](mut app, 8082)
|
||||
}
|
||||
|
||||
// Middleware to run before each request
|
||||
pub fn (mut ctx Context) before_request() bool {
|
||||
ctx.user_id = ctx.get_cookie('id') or { '0' }
|
||||
return true
|
||||
}
|
||||
|
||||
// Index endpoint - Dashboard overview
|
||||
@['/']
|
||||
pub fn (app &App) index(mut ctx Context) veb.Result {
|
||||
stats := app.system_controller.get_system_stats()
|
||||
processes := app.process_controller.get_all_processes()
|
||||
jobs := app.job_controller.get_all_jobs()
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// Processes endpoint
|
||||
@['/processes']
|
||||
pub fn (app &App) processes(mut ctx Context) veb.Result {
|
||||
processes := app.process_controller.get_all_processes()
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// Process details endpoint
|
||||
@['/processes/:pid']
|
||||
pub fn (app &App) process_details(mut ctx Context, pid string) veb.Result {
|
||||
pid_int := pid.int()
|
||||
process := app.process_controller.get_process_by_pid(pid_int) or {
|
||||
return ctx.text('Process not found')
|
||||
}
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// Jobs endpoint
|
||||
@['/jobs']
|
||||
pub fn (app &App) jobs(mut ctx Context) veb.Result {
|
||||
jobs := app.job_controller.get_all_jobs()
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// Job details endpoint
|
||||
@['/jobs/:id']
|
||||
pub fn (app &App) job_details(mut ctx Context, id string) veb.Result {
|
||||
id_int := id.u32()
|
||||
job := app.job_controller.get_job_by_id(id_int) or {
|
||||
return ctx.text('Job not found')
|
||||
}
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// OpenRPC endpoint
|
||||
@['/openrpc']
|
||||
pub fn (app &App) openrpc(mut ctx Context) veb.Result {
|
||||
specs := app.openrpc_controller.get_all_specs()
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// OpenRPC spec details endpoint
|
||||
@['/openrpc/:name']
|
||||
pub fn (app &App) openrpc_spec(mut ctx Context, name string) veb.Result {
|
||||
spec := app.openrpc_controller.get_spec_by_name(name) or {
|
||||
return ctx.text('OpenRPC specification not found')
|
||||
}
|
||||
methods := app.openrpc_controller.get_methods_for_spec(name)
|
||||
return $veb.html()
|
||||
}
|
||||
|
||||
// API endpoints
|
||||
|
||||
// API endpoint to get all processes
|
||||
@['/api/processes'; get]
|
||||
pub fn (app &App) api_processes(mut ctx Context) veb.Result {
|
||||
processes := app.process_controller.get_all_processes()
|
||||
json_result := json.encode(processes)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to get process by PID
|
||||
@['/api/processes/:pid'; get]
|
||||
pub fn (app &App) api_process_by_pid(mut ctx Context, pid string) veb.Result {
|
||||
pid_int := pid.int()
|
||||
process := app.process_controller.get_process_by_pid(pid_int) or {
|
||||
return ctx.text('Process not found')
|
||||
}
|
||||
json_result := json.encode(process)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to kill a process
|
||||
@['/api/processes/:pid/kill'; post]
|
||||
pub fn (app &App) api_kill_process(mut ctx Context, pid string) veb.Result {
|
||||
pid_int := pid.int()
|
||||
success := app.process_controller.kill_process(pid_int)
|
||||
return ctx.json('{"success": $success}')
|
||||
}
|
||||
|
||||
// API endpoint to get all jobs
|
||||
@['/api/jobs'; get]
|
||||
pub fn (app &App) api_jobs(mut ctx Context) veb.Result {
|
||||
jobs := app.job_controller.get_all_jobs()
|
||||
json_result := json.encode(jobs)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to get job by ID
|
||||
@['/api/jobs/:id'; get]
|
||||
pub fn (app &App) api_job_by_id(mut ctx Context, id string) veb.Result {
|
||||
id_int := id.u32()
|
||||
job := app.job_controller.get_job_by_id(id_int) or {
|
||||
return ctx.text('Job not found')
|
||||
}
|
||||
json_result := json.encode(job)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to get jobs by status
|
||||
@['/api/jobs/status/:status'; get]
|
||||
pub fn (app &App) api_jobs_by_status(mut ctx Context, status string) veb.Result {
|
||||
status_enum := match status {
|
||||
'new' { JobStatus.new }
|
||||
'active' { JobStatus.active }
|
||||
'done' { JobStatus.done }
|
||||
'error' { JobStatus.error }
|
||||
else { JobStatus.new }
|
||||
}
|
||||
|
||||
jobs := app.job_controller.get_jobs_by_status(status_enum)
|
||||
json_result := json.encode(jobs)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to get jobs by circle
|
||||
@['/api/jobs/circle/:id'; get]
|
||||
pub fn (app &App) api_jobs_by_circle(mut ctx Context, id string) veb.Result {
|
||||
jobs := app.job_controller.get_jobs_by_circle(id)
|
||||
json_result := json.encode(jobs)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to get system stats
|
||||
@['/api/stats'; get]
|
||||
pub fn (app &App) api_stats(mut ctx Context) veb.Result {
|
||||
stats := app.system_controller.get_system_stats()
|
||||
json_result := json.encode(stats)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to get all OpenRPC specs
|
||||
@['/api/openrpc'; get]
|
||||
pub fn (app &App) api_openrpc_specs(mut ctx Context) veb.Result {
|
||||
specs := app.openrpc_controller.get_all_specs()
|
||||
json_result := json.encode(specs)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to get OpenRPC spec by name
|
||||
@['/api/openrpc/:name'; get]
|
||||
pub fn (app &App) api_openrpc_spec_by_name(mut ctx Context, name string) veb.Result {
|
||||
spec := app.openrpc_controller.get_spec_by_name(name) or {
|
||||
return ctx.text('OpenRPC specification not found')
|
||||
}
|
||||
json_result := json.encode(spec)
|
||||
return ctx.json(json_result)
|
||||
}
|
||||
|
||||
// API endpoint to execute an RPC call
|
||||
@['/api/openrpc/:name/:method'; post]
|
||||
pub fn (app &App) api_execute_rpc(mut ctx Context, name string, method string) veb.Result {
|
||||
params := ctx.req.body
|
||||
result := app.openrpc_controller.execute_rpc(name, method, params)
|
||||
return ctx.json(result)
|
||||
}
|
0
src/agentui/controllers/fake_data.v
Normal file
0
src/agentui/controllers/fake_data.v
Normal file
51
src/agentui/controllers/job_ctrl.v
Normal file
51
src/agentui/controllers/job_ctrl.v
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
// JobController handles job-related operations
|
||||
pub struct JobController {
|
||||
pub:
|
||||
// Will add dependencies here when connecting to openrpc
|
||||
}
|
||||
|
||||
// get_all_jobs returns all jobs in the system
|
||||
pub fn (jc &JobController) get_all_jobs() []JobInfo {
|
||||
// For now using fake data, will be replaced with openrpc calls
|
||||
return get_all_jobs()
|
||||
}
|
||||
|
||||
// get_job_by_id returns a specific job by ID
|
||||
pub fn (jc &JobController) get_job_by_id(id u32) ?JobInfo {
|
||||
jobs := get_all_jobs()
|
||||
for job in jobs {
|
||||
if job.job_id == id {
|
||||
return job
|
||||
}
|
||||
}
|
||||
return error('Job not found')
|
||||
}
|
||||
|
||||
// get_jobs_by_status returns jobs with a specific status
|
||||
pub fn (jc &JobController) get_jobs_by_status(status JobStatus) []JobInfo {
|
||||
jobs := get_all_jobs()
|
||||
mut filtered_jobs := []JobInfo{}
|
||||
|
||||
for job in jobs {
|
||||
if job.status == status {
|
||||
filtered_jobs << job
|
||||
}
|
||||
}
|
||||
|
||||
return filtered_jobs
|
||||
}
|
||||
|
||||
// get_jobs_by_circle returns jobs for a specific circle
|
||||
pub fn (jc &JobController) get_jobs_by_circle(circle_id string) []JobInfo {
|
||||
jobs := get_all_jobs()
|
||||
mut filtered_jobs := []JobInfo{}
|
||||
|
||||
for job in jobs {
|
||||
if job.circle_id == circle_id {
|
||||
filtered_jobs << job
|
||||
}
|
||||
}
|
||||
|
||||
return filtered_jobs
|
||||
}
|
31
src/agentui/controllers/process_ctrl.v
Normal file
31
src/agentui/controllers/process_ctrl.v
Normal file
@@ -0,0 +1,31 @@
|
||||
module controllers
|
||||
|
||||
// ProcessController handles process-related operations
|
||||
pub struct ProcessController {
|
||||
pub:
|
||||
// Will add dependencies here when connecting to openrpc
|
||||
}
|
||||
|
||||
// get_all_processes returns all processes in the system
|
||||
pub fn (pc &ProcessController) get_all_processes() ![]Process {
|
||||
// For now using fake data, will be replaced with openrpc calls
|
||||
return get_all_processes()
|
||||
}
|
||||
|
||||
// get_process_by_pid returns a specific process by PID
|
||||
pub fn (pc &ProcessController) get_process_by_pid(pid int) !Process {
|
||||
processes := get_all_processes()
|
||||
for process in processes {
|
||||
if process.pid == pid {
|
||||
return process
|
||||
}
|
||||
}
|
||||
return error('Process not found')
|
||||
}
|
||||
|
||||
// kill_process attempts to kill a process
|
||||
pub fn (pc &ProcessController) kill_process(pid int) bool {
|
||||
// Fake implementation for now
|
||||
// Will be replaced with actual implementation using openrpc
|
||||
return true
|
||||
}
|
38
src/agentui/controllers/rpc_ctrl.v
Normal file
38
src/agentui/controllers/rpc_ctrl.v
Normal file
@@ -0,0 +1,38 @@
|
||||
module controllers
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
|
||||
// OpenRPCController handles OpenRPC-related operations
|
||||
pub struct OpenRPCController {
|
||||
pub:
|
||||
// Will add dependencies here when connecting to openrpc
|
||||
}
|
||||
|
||||
// get_all_specs returns all OpenRPC specifications
|
||||
pub fn (oc &OpenRPCController) get_all_specs() []OpenRPCSpec {
|
||||
// For now using fake data, will be replaced with openrpc calls
|
||||
return get_all_openrpc_specs()
|
||||
}
|
||||
|
||||
// get_spec_by_name returns a specific OpenRPC specification by name
|
||||
pub fn (oc &OpenRPCController) get_spec_by_name(name string) !openrpc.OpenRPC {
|
||||
specs := get_all_openrpc_specs()
|
||||
for spec in specs {
|
||||
if spec.title.to_lower() == name.to_lower() {
|
||||
return spec
|
||||
}
|
||||
}
|
||||
return error('OpenRPC specification not found')
|
||||
}
|
||||
|
||||
// get_methods_for_spec returns all methods for a specific OpenRPC specification
|
||||
pub fn (oc &OpenRPCController) get_methods_for_spec(spec_name string) []OpenRPCMethod {
|
||||
spec := oc.get_spec_by_name(spec_name) or { return [] }
|
||||
return spec.methods
|
||||
}
|
||||
|
||||
// execute_rpc executes an RPC call
|
||||
pub fn (oc &OpenRPCController) execute_rpc(spec_name string, method_name string, params string) string {
|
||||
// Fake implementation for now
|
||||
// Will be replaced with actual implementation using openrpc
|
||||
return '{"result": "Success", "data": {"id": 123, "name": "Test Result"}}'
|
||||
}
|
13
src/agentui/controllers/system_controller.v
Normal file
13
src/agentui/controllers/system_controller.v
Normal file
@@ -0,0 +1,13 @@
|
||||
module controllers
|
||||
|
||||
// SystemController handles system-related operations
|
||||
pub struct SystemController {
|
||||
pub:
|
||||
// Will add dependencies here when connecting to openrpc
|
||||
}
|
||||
|
||||
// get_system_stats returns the current system statistics
|
||||
pub fn (sc &SystemController) get_system_stats() SystemStats {
|
||||
// For now using fake data, will be replaced with openrpc calls
|
||||
return get_system_stats()
|
||||
}
|
97
src/agentui/models/jobs.v
Normal file
97
src/agentui/models/jobs.v
Normal file
@@ -0,0 +1,97 @@
|
||||
module main
|
||||
|
||||
import time
|
||||
|
||||
// JobStatus represents the status of a job
|
||||
pub enum JobStatus {
|
||||
new
|
||||
active
|
||||
done
|
||||
error
|
||||
}
|
||||
|
||||
// ParamsType represents the type of parameters for a job
|
||||
pub enum ParamsType {
|
||||
hero_script
|
||||
open_rpc
|
||||
rhai_script
|
||||
ai
|
||||
}
|
||||
|
||||
// JobInfo represents job information for the UI
|
||||
pub struct JobInfo {
|
||||
pub:
|
||||
job_id u32
|
||||
session_key string
|
||||
circle_id string
|
||||
topic string
|
||||
params_type ParamsType
|
||||
status JobStatus
|
||||
time_scheduled time.Time
|
||||
time_start time.Time
|
||||
time_end time.Time
|
||||
duration string
|
||||
error string
|
||||
has_error bool
|
||||
}
|
||||
|
||||
|
||||
// get_all_jobs returns a list of mock jobs
|
||||
pub fn get_all_jobs() []JobInfo {
|
||||
now := time.now()
|
||||
|
||||
return [
|
||||
JobInfo{
|
||||
job_id: 1
|
||||
circle_id: 'circle1'
|
||||
topic: 'email'
|
||||
params_type: .hero_script
|
||||
status: .done
|
||||
time_scheduled: now.add(-30 * time.minute)
|
||||
time_start: now.add(-29 * time.minute)
|
||||
time_end: now.add(-28 * time.minute)
|
||||
duration: '1m0s'
|
||||
error: ''
|
||||
has_error: false
|
||||
},
|
||||
JobInfo{
|
||||
job_id: 2
|
||||
circle_id: 'circle1'
|
||||
topic: 'backup'
|
||||
params_type: .open_rpc
|
||||
status: .active
|
||||
time_scheduled: now.add(-15 * time.minute)
|
||||
time_start: now.add(-14 * time.minute)
|
||||
time_end: time.Time{}
|
||||
duration: '14m0s'
|
||||
error: ''
|
||||
has_error: false
|
||||
},
|
||||
JobInfo{
|
||||
job_id: 3
|
||||
circle_id: 'circle2'
|
||||
topic: 'sync'
|
||||
params_type: .rhai_script
|
||||
status: .error
|
||||
time_scheduled: now.add(-45 * time.minute)
|
||||
time_start: now.add(-44 * time.minute)
|
||||
time_end: now.add(-43 * time.minute)
|
||||
duration: '1m0s'
|
||||
error: 'Failed to connect to remote server'
|
||||
has_error: true
|
||||
},
|
||||
JobInfo{
|
||||
job_id: 4
|
||||
circle_id: 'circle2'
|
||||
topic: 'email'
|
||||
params_type: .hero_script
|
||||
status: .new
|
||||
time_scheduled: now.add(-5 * time.minute)
|
||||
time_start: time.Time{}
|
||||
time_end: time.Time{}
|
||||
duration: 'Not started'
|
||||
error: ''
|
||||
has_error: false
|
||||
}
|
||||
]
|
||||
}
|
68
src/agentui/models/openrpc.v
Normal file
68
src/agentui/models/openrpc.v
Normal file
@@ -0,0 +1,68 @@
|
||||
module main
|
||||
|
||||
import time
|
||||
|
||||
// OpenRPCMethod represents a method in an OpenRPC specification
|
||||
pub struct OpenRPCMethod {
|
||||
pub:
|
||||
name string
|
||||
description string
|
||||
summary string
|
||||
}
|
||||
|
||||
// OpenRPCSpec represents an OpenRPC specification
|
||||
pub struct OpenRPCSpec {
|
||||
pub:
|
||||
title string
|
||||
description string
|
||||
version string
|
||||
methods []OpenRPCMethod
|
||||
}
|
||||
|
||||
|
||||
// Mock data functions
|
||||
|
||||
// get_all_openrpc_specs returns a list of mock OpenRPC specifications
|
||||
pub fn get_all_openrpc_specs() []OpenRPCSpec {
|
||||
return [
|
||||
OpenRPCSpec{
|
||||
title: 'Process Manager'
|
||||
description: 'API for managing system processes'
|
||||
version: '1.0.0'
|
||||
methods: [
|
||||
OpenRPCMethod{
|
||||
name: 'get_processes'
|
||||
description: 'Get a list of all processes'
|
||||
summary: 'List processes'
|
||||
},
|
||||
OpenRPCMethod{
|
||||
name: 'kill_process'
|
||||
description: 'Kill a process by PID'
|
||||
summary: 'Kill process'
|
||||
}
|
||||
]
|
||||
},
|
||||
OpenRPCSpec{
|
||||
title: 'Job Manager'
|
||||
description: 'API for managing jobs'
|
||||
version: '1.0.0'
|
||||
methods: [
|
||||
OpenRPCMethod{
|
||||
name: 'get_jobs'
|
||||
description: 'Get a list of all jobs'
|
||||
summary: 'List jobs'
|
||||
},
|
||||
OpenRPCMethod{
|
||||
name: 'get_job'
|
||||
description: 'Get a job by ID'
|
||||
summary: 'Get job'
|
||||
},
|
||||
OpenRPCMethod{
|
||||
name: 'create_job'
|
||||
description: 'Create a new job'
|
||||
summary: 'Create job'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
50
src/agentui/models/process.v
Normal file
50
src/agentui/models/process.v
Normal file
@@ -0,0 +1,50 @@
|
||||
module main
|
||||
|
||||
|
||||
// Process represents a single running process with its relevant details
|
||||
pub struct Process {
|
||||
pub:
|
||||
pid int
|
||||
name string // CPU usage percentage
|
||||
memory f64 // Memory usage in MB
|
||||
cpu f64
|
||||
}
|
||||
|
||||
|
||||
|
||||
// get_all_processes returns a list of mock processes
|
||||
pub fn get_all_processes() []Process {
|
||||
// Fake data for now, will be replaced with openrpc calls later
|
||||
return [
|
||||
Process{
|
||||
pid: 1001
|
||||
name: 'SystemIdleProcess'
|
||||
cpu: 95.5
|
||||
memory: 0.1
|
||||
},
|
||||
Process{
|
||||
pid: 1002
|
||||
name: 'explorer.exe'
|
||||
cpu: 1.2
|
||||
memory: 150.7
|
||||
},
|
||||
Process{
|
||||
pid: 1003
|
||||
name: 'chrome.exe'
|
||||
cpu: 25.8
|
||||
memory: 512.3
|
||||
},
|
||||
Process{
|
||||
pid: 1004
|
||||
name: 'code.exe'
|
||||
cpu: 5.1
|
||||
memory: 350.0
|
||||
},
|
||||
Process{
|
||||
pid: 1005
|
||||
name: 'v.exe'
|
||||
cpu: 0.5
|
||||
memory: 80.2
|
||||
}
|
||||
]
|
||||
}
|
28
src/agentui/models/stats.v
Normal file
28
src/agentui/models/stats.v
Normal file
@@ -0,0 +1,28 @@
|
||||
module main
|
||||
|
||||
import time
|
||||
|
||||
|
||||
// SystemStats represents system statistics
|
||||
pub struct SystemStats {
|
||||
pub:
|
||||
cpu_usage f32 [json: 'cpu_usage']
|
||||
memory_usage f32 [json: 'memory_usage']
|
||||
disk_usage f32 [json: 'disk_usage']
|
||||
uptime string [json: 'uptime']
|
||||
process_count int [json: 'process_count']
|
||||
job_count int [json: 'job_count']
|
||||
}
|
||||
|
||||
// get_system_stats returns mock system statistics
|
||||
pub fn get_system_stats() SystemStats {
|
||||
// Fake data for now, will be replaced with openrpc calls later
|
||||
return SystemStats{
|
||||
cpu_usage: 45.2
|
||||
memory_usage: 62.7
|
||||
disk_usage: 38.5
|
||||
uptime: '3 days, 7 hours, 22 minutes'
|
||||
process_count: 87
|
||||
job_count: 4
|
||||
}
|
||||
}
|
728
src/agentui/static/css/style.css
Normal file
728
src/agentui/static/css/style.css
Normal file
@@ -0,0 +1,728 @@
|
||||
/* Base styles */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f4f7f9;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
nav a:hover, nav a.active {
|
||||
background-color: #34495e;
|
||||
}
|
||||
|
||||
nav a.active {
|
||||
font-weight: bold;
|
||||
background-color: #1a252f;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1200px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Common components */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: #f39c12;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background-color: #e67e22;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #e74c3c;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.data-table th, .data-table td {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.data-table tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
background-color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.filter-controls input[type="text"] {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.sort-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sort-controls select {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sort-controls button {
|
||||
padding: 0.5rem;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.sort-controls button:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin: 1.5rem 0;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-item:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
width: 150px;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.error-item {
|
||||
background-color: #fdedec;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.error-value {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* Dashboard styles */
|
||||
.dashboard-overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.stats-section, .processes-section, .jobs-section {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.stat-bar {
|
||||
height: 10px;
|
||||
background-color: #ecf0f1;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.stat-fill {
|
||||
height: 100%;
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.view-all {
|
||||
font-size: 0.9rem;
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.view-all:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Process styles */
|
||||
.processes-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.process-details-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.process-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.process-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-bar {
|
||||
height: 10px;
|
||||
background-color: #ecf0f1;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-fill {
|
||||
height: 100%;
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.process-info {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
height: 200px;
|
||||
background-color: #ecf0f1;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
color: #7f8c8d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Job styles */
|
||||
.jobs-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.job-details-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.job-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
border-left: 5px solid #3498db;
|
||||
}
|
||||
|
||||
.job-header.status-new {
|
||||
border-left-color: #3498db;
|
||||
}
|
||||
|
||||
.job-header.status-active {
|
||||
border-left-color: #2ecc71;
|
||||
}
|
||||
|
||||
.job-header.status-done {
|
||||
border-left-color: #27ae60;
|
||||
}
|
||||
|
||||
.job-header.status-error {
|
||||
border-left-color: #e74c3c;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-badge.status-new {
|
||||
background-color: #ebf5fb;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.status-badge.status-active {
|
||||
background-color: #e9f7ef;
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.status-badge.status-done {
|
||||
background-color: #eafaf1;
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.status-badge.status-error {
|
||||
background-color: #fdedec;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.job-timeline {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 20px;
|
||||
width: 2px;
|
||||
background-color: #ecf0f1;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
padding-left: 50px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.timeline-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.timeline-icon {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.timeline-icon.scheduled {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.timeline-icon.started {
|
||||
background-color: #2ecc71;
|
||||
}
|
||||
|
||||
.timeline-icon.completed {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
|
||||
.timeline-icon.error {
|
||||
background-color: #e74c3c;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.timeline-content h4 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #e74c3c;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background-color: #fdedec;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* OpenRPC styles */
|
||||
.openrpc-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.specs-list {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.specs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.spec-card {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.spec-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.spec-version {
|
||||
font-size: 0.9rem;
|
||||
color: #7f8c8d;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.spec-description {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.spec-methods {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.openrpc-info {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.openrpc-info ul {
|
||||
margin-left: 1.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.openrpc-spec-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.spec-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.spec-meta {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.spec-description {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.methods-section {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.methods-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.method-card {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.method-name {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.method-summary {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.method-description {
|
||||
margin-bottom: 1rem;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.method-executor {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.method-executor.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.executor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.executor-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.params-section, .result-section {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
#params-editor {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
font-family: monospace;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
#result-display {
|
||||
width: 100%;
|
||||
min-height: 150px;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.executor-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Status colors for table rows */
|
||||
tr.status-new {
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
tr.status-active {
|
||||
border-left: 4px solid #2ecc71;
|
||||
}
|
||||
|
||||
tr.status-done {
|
||||
border-left: 4px solid #27ae60;
|
||||
}
|
||||
|
||||
tr.status-error {
|
||||
border-left: 4px solid #e74c3c;
|
||||
}
|
174
src/agentui/static/js/main.js
Normal file
174
src/agentui/static/js/main.js
Normal file
@@ -0,0 +1,174 @@
|
||||
// Main JavaScript file for Hero Agent UI
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Hero Agent UI loaded');
|
||||
|
||||
// Fetch latest data for the page if needed
|
||||
refreshPageData();
|
||||
|
||||
// Set up auto-refresh for dashboard if on that page
|
||||
if (window.location.pathname === '/') {
|
||||
setInterval(refreshDashboard, 10000); // Refresh every 10 seconds
|
||||
}
|
||||
});
|
||||
|
||||
// Refresh data based on current page
|
||||
function refreshPageData() {
|
||||
const path = window.location.pathname;
|
||||
|
||||
if (path === '/') {
|
||||
refreshDashboard();
|
||||
} else if (path === '/processes') {
|
||||
refreshProcesses();
|
||||
} else if (path.startsWith('/processes/')) {
|
||||
const pid = path.split('/').pop();
|
||||
refreshProcessDetails(pid);
|
||||
} else if (path === '/jobs') {
|
||||
refreshJobs();
|
||||
} else if (path.startsWith('/jobs/')) {
|
||||
const jobId = path.split('/').pop();
|
||||
refreshJobDetails(jobId);
|
||||
} else if (path === '/openrpc') {
|
||||
refreshOpenRPCSpecs();
|
||||
} else if (path.startsWith('/openrpc/')) {
|
||||
const specName = path.split('/').pop();
|
||||
refreshOpenRPCSpecDetails(specName);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch and update dashboard data
|
||||
function refreshDashboard() {
|
||||
// Fetch system stats
|
||||
fetch('/api/stats')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Dashboard stats refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
// For now, this is just a placeholder
|
||||
updateStatBars(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing dashboard stats:', error);
|
||||
});
|
||||
|
||||
// Fetch processes
|
||||
fetch('/api/processes')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Dashboard processes refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing dashboard processes:', error);
|
||||
});
|
||||
|
||||
// Fetch jobs
|
||||
fetch('/api/jobs')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Dashboard jobs refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing dashboard jobs:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Update stat bars with new data
|
||||
function updateStatBars(stats) {
|
||||
// This is a placeholder function that would update the stat bars
|
||||
// with the new data from the API
|
||||
if (!stats) return;
|
||||
|
||||
const cpuBar = document.querySelector('.stat-fill[data-stat="cpu"]');
|
||||
const memoryBar = document.querySelector('.stat-fill[data-stat="memory"]');
|
||||
const diskBar = document.querySelector('.stat-fill[data-stat="disk"]');
|
||||
|
||||
if (cpuBar) cpuBar.style.width = `${stats.cpu_usage}%`;
|
||||
if (memoryBar) memoryBar.style.width = `${stats.memory_usage}%`;
|
||||
if (diskBar) diskBar.style.width = `${stats.disk_usage}%`;
|
||||
}
|
||||
|
||||
// Fetch and update processes list
|
||||
function refreshProcesses() {
|
||||
fetch('/api/processes')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Processes refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing processes:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch and update process details
|
||||
function refreshProcessDetails(pid) {
|
||||
fetch(`/api/processes/${pid}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Process details refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
|
||||
// Update CPU usage bar
|
||||
const cpuBar = document.querySelector('.detail-fill');
|
||||
if (cpuBar && data.cpu) {
|
||||
cpuBar.style.width = `${data.cpu}%`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing process details:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch and update jobs list
|
||||
function refreshJobs() {
|
||||
fetch('/api/jobs')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Jobs refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing jobs:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch and update job details
|
||||
function refreshJobDetails(jobId) {
|
||||
fetch(`/api/jobs/${jobId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Job details refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing job details:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch and update OpenRPC specs
|
||||
function refreshOpenRPCSpecs() {
|
||||
fetch('/api/openrpc')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('OpenRPC specs refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing OpenRPC specs:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch and update OpenRPC spec details
|
||||
function refreshOpenRPCSpecDetails(specName) {
|
||||
fetch(`/api/openrpc/${specName}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('OpenRPC spec details refreshed');
|
||||
// In a real implementation, we would update the DOM here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error refreshing OpenRPC spec details:', error);
|
||||
});
|
||||
}
|
100
src/agentui/templates/agent_details.html
Normal file
100
src/agentui/templates/agent_details.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Agent Details - Hero Agent UI</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Agent Details</h1>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="agent-details">
|
||||
<h2>@agent.name</h2>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-item">
|
||||
<span class="label">ID:</span>
|
||||
<span class="value">@agent.id</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Type:</span>
|
||||
<span class="value">@agent.type_</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Status:</span>
|
||||
<span class="value status-@agent.status">@agent.status</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Created:</span>
|
||||
<span class="value">@agent.created_at</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Last Active:</span>
|
||||
<span class="value">@agent.last_active</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-actions">
|
||||
<button class="btn btn-primary" onclick="startAgent('@agent.id')">Start</button>
|
||||
<button class="btn btn-warning" onclick="restartAgent('@agent.id')">Restart</button>
|
||||
<button class="btn btn-danger" onclick="stopAgent('@agent.id')">Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script>
|
||||
function startAgent(id) {
|
||||
fetch(`/api/agents/${id}/start`, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Agent started successfully');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to start agent');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function restartAgent(id) {
|
||||
fetch(`/api/agents/${id}/restart`, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Agent restarted successfully');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to restart agent');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function stopAgent(id) {
|
||||
fetch(`/api/agents/${id}/stop`, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Agent stopped successfully');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to stop agent');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
59
src/agentui/templates/dashboard.html
Normal file
59
src/agentui/templates/dashboard.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Dashboard - Hero Agent UI</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>System Dashboard</h1>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="dashboard-stats">
|
||||
<div class="stat-card">
|
||||
<h3>CPU Usage</h3>
|
||||
<div class="stat-value">@stats.cpu_usage%</div>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-fill" style="width: @stats.cpu_usage%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Memory Usage</h3>
|
||||
<div class="stat-value">@stats.memory_usage%</div>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-fill" style="width: @stats.memory_usage%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Disk Usage</h3>
|
||||
<div class="stat-value">@stats.disk_usage%</div>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-fill" style="width: @stats.disk_usage%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Uptime</h3>
|
||||
<div class="stat-value">@stats.uptime</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Agent Count</h3>
|
||||
<div class="stat-value">@stats.agent_count</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
134
src/agentui/templates/index.html
Normal file
134
src/agentui/templates/index.html
Normal file
@@ -0,0 +1,134 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Agent UI - Dashboard</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Hero Agent Dashboard</h1>
|
||||
<nav>
|
||||
<a href="/" class="active">Dashboard</a>
|
||||
<a href="/processes">Processes</a>
|
||||
<a href="/jobs">Jobs</a>
|
||||
<a href="/openrpc">OpenRPC</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="dashboard-overview">
|
||||
<div class="stats-section">
|
||||
<h2>System Statistics</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<h3>CPU Usage</h3>
|
||||
<div class="stat-value">@stats.cpu_usage%</div>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-fill" style="width: @stats.cpu_usage%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Memory Usage</h3>
|
||||
<div class="stat-value">@stats.memory_usage%</div>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-fill" style="width: @stats.memory_usage%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Disk Usage</h3>
|
||||
<div class="stat-value">@stats.disk_usage%</div>
|
||||
<div class="stat-bar">
|
||||
<div class="stat-fill" style="width: @stats.disk_usage%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>Uptime</h3>
|
||||
<div class="stat-value">@stats.uptime</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="processes-section">
|
||||
<h2>Top Processes <a href="/processes" class="view-all">View All</a></h2>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PID</th>
|
||||
<th>Name</th>
|
||||
<th>CPU %</th>
|
||||
<th>Memory (MB)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for process in processes
|
||||
<tr>
|
||||
<td>@process.pid</td>
|
||||
<td>@process.name</td>
|
||||
<td>@process.cpu%</td>
|
||||
<td>@process.memory</td>
|
||||
<td>
|
||||
<a href="/processes/@process.pid" class="btn btn-small">Details</a>
|
||||
<button onclick="killProcess(@process.pid)" class="btn btn-small btn-danger">Kill</button>
|
||||
</td>
|
||||
</tr>
|
||||
@end
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="jobs-section">
|
||||
<h2>Recent Jobs <a href="/jobs" class="view-all">View All</a></h2>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Topic</th>
|
||||
<th>Status</th>
|
||||
<th>Duration</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for job in jobs
|
||||
<tr class="status-@job.status">
|
||||
<td>@job.job_id</td>
|
||||
<td>@job.topic</td>
|
||||
<td>@job.status</td>
|
||||
<td>@job.duration</td>
|
||||
<td>
|
||||
<a href="/jobs/@job.job_id" class="btn btn-small">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
@end
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script>
|
||||
function killProcess(pid) {
|
||||
if (confirm('Are you sure you want to kill process ' + pid + '?')) {
|
||||
fetch(`/api/processes/${pid}/kill`, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Process killed successfully');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to kill process');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
114
src/agentui/templates/job_details.html
Normal file
114
src/agentui/templates/job_details.html
Normal file
@@ -0,0 +1,114 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Agent UI - Job Details</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Job Details</h1>
|
||||
<nav>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/processes">Processes</a>
|
||||
<a href="/jobs" class="active">Jobs</a>
|
||||
<a href="/openrpc">OpenRPC</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="job-details-container">
|
||||
<div class="job-header status-@job.status">
|
||||
<h2>Job #@job.job_id: @job.topic</h2>
|
||||
<div class="job-status">
|
||||
<span class="status-badge status-@job.status">@job.status</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-item">
|
||||
<span class="label">Circle ID:</span>
|
||||
<span class="value">@job.circle_id</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Session Key:</span>
|
||||
<span class="value">@job.session_key</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Parameters Type:</span>
|
||||
<span class="value">@job.params_type</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Scheduled Time:</span>
|
||||
<span class="value">@job.time_scheduled</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Start Time:</span>
|
||||
<span class="value">@job.time_start</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">End Time:</span>
|
||||
<span class="value">@job.time_end</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Duration:</span>
|
||||
<span class="value">@job.duration</span>
|
||||
</div>
|
||||
|
||||
@if job.has_error
|
||||
<div class="detail-item error-item">
|
||||
<span class="label">Error:</span>
|
||||
<span class="value error-value">@job.error</span>
|
||||
</div>
|
||||
@end
|
||||
</div>
|
||||
|
||||
<div class="job-timeline">
|
||||
<h3>Job Timeline</h3>
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon scheduled"></div>
|
||||
<div class="timeline-content">
|
||||
<h4>Scheduled</h4>
|
||||
<p>@job.time_scheduled</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if !job.time_start.str().is_blank()
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon started"></div>
|
||||
<div class="timeline-content">
|
||||
<h4>Started</h4>
|
||||
<p>@job.time_start</p>
|
||||
</div>
|
||||
</div>
|
||||
@end
|
||||
|
||||
@if !job.time_end.str().is_blank()
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon @if job.has_error { 'error' } else { 'completed' }"></div>
|
||||
<div class="timeline-content">
|
||||
<h4>@if job.has_error { 'Failed' } else { 'Completed' }</h4>
|
||||
<p>@job.time_end</p>
|
||||
@if job.has_error
|
||||
<p class="error-message">@job.error</p>
|
||||
@end
|
||||
</div>
|
||||
</div>
|
||||
@end
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
117
src/agentui/templates/jobs.html
Normal file
117
src/agentui/templates/jobs.html
Normal file
@@ -0,0 +1,117 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Agent UI - Jobs</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Job Management</h1>
|
||||
<nav>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/processes">Processes</a>
|
||||
<a href="/jobs" class="active">Jobs</a>
|
||||
<a href="/openrpc">OpenRPC</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="jobs-container">
|
||||
<div class="filter-controls">
|
||||
<input type="text" id="job-search" placeholder="Search jobs..." onkeyup="filterJobs()">
|
||||
<div class="filter-buttons">
|
||||
<button class="filter-btn active" data-status="all" onclick="filterByStatus('all')">All</button>
|
||||
<button class="filter-btn" data-status="new" onclick="filterByStatus('new')">New</button>
|
||||
<button class="filter-btn" data-status="active" onclick="filterByStatus('active')">Active</button>
|
||||
<button class="filter-btn" data-status="done" onclick="filterByStatus('done')">Done</button>
|
||||
<button class="filter-btn" data-status="error" onclick="filterByStatus('error')">Error</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="data-table" id="jobs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Circle</th>
|
||||
<th>Topic</th>
|
||||
<th>Status</th>
|
||||
<th>Scheduled</th>
|
||||
<th>Duration</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for job in jobs
|
||||
<tr class="status-@job.status" data-status="@job.status">
|
||||
<td>@job.job_id</td>
|
||||
<td>@job.circle_id</td>
|
||||
<td>@job.topic</td>
|
||||
<td>@job.status</td>
|
||||
<td>@job.time_scheduled</td>
|
||||
<td>@job.duration</td>
|
||||
<td>
|
||||
<a href="/jobs/@job.job_id" class="btn btn-small">Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
@end
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script>
|
||||
function filterJobs() {
|
||||
const input = document.getElementById('job-search');
|
||||
const filter = input.value.toUpperCase();
|
||||
const table = document.getElementById('jobs-table');
|
||||
const rows = table.getElementsByTagName('tr');
|
||||
const activeStatus = document.querySelector('.filter-btn.active').dataset.status;
|
||||
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
const topicCell = rows[i].getElementsByTagName('td')[2];
|
||||
const circleCell = rows[i].getElementsByTagName('td')[1];
|
||||
const statusMatch = activeStatus === 'all' || rows[i].dataset.status === activeStatus;
|
||||
|
||||
if (topicCell && circleCell) {
|
||||
const topicValue = topicCell.textContent || topicCell.innerText;
|
||||
const circleValue = circleCell.textContent || circleCell.innerText;
|
||||
const textMatch = topicValue.toUpperCase().indexOf(filter) > -1 ||
|
||||
circleValue.toUpperCase().indexOf(filter) > -1;
|
||||
|
||||
if (textMatch && statusMatch) {
|
||||
rows[i].style.display = '';
|
||||
} else {
|
||||
rows[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function filterByStatus(status) {
|
||||
// Update active button
|
||||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`.filter-btn[data-status="${status}"]`).classList.add('active');
|
||||
|
||||
// Filter table
|
||||
const table = document.getElementById('jobs-table');
|
||||
const rows = table.getElementsByTagName('tr');
|
||||
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
if (status === 'all') {
|
||||
rows[i].style.display = '';
|
||||
} else if (rows[i].dataset.status === status) {
|
||||
rows[i].style.display = '';
|
||||
} else {
|
||||
rows[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
63
src/agentui/templates/openrpc.html
Normal file
63
src/agentui/templates/openrpc.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Agent UI - OpenRPC</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>OpenRPC Specifications</h1>
|
||||
<nav>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/processes">Processes</a>
|
||||
<a href="/jobs">Jobs</a>
|
||||
<a href="/openrpc" class="active">OpenRPC</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="openrpc-container">
|
||||
<div class="specs-list">
|
||||
<h2>Available Specifications</h2>
|
||||
<div class="specs-grid">
|
||||
@for spec in specs
|
||||
<div class="spec-card">
|
||||
<h3>@spec.title</h3>
|
||||
<p class="spec-version">Version: @spec.version</p>
|
||||
<p class="spec-description">@spec.description</p>
|
||||
<p class="spec-methods"><strong>@spec.methods.len</strong> methods available</p>
|
||||
<a href="/openrpc/@spec.title" class="btn">View Details</a>
|
||||
</div>
|
||||
@end
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="openrpc-info">
|
||||
<h2>About OpenRPC</h2>
|
||||
<p>
|
||||
OpenRPC is a standard for defining JSON-RPC 2.0 APIs. It provides a machine-readable
|
||||
specification format that allows for automatic documentation generation, client SDK
|
||||
generation, and server implementation validation.
|
||||
</p>
|
||||
<p>
|
||||
The Hero Agent system uses OpenRPC to define and document its APIs, making it easier
|
||||
to interact with the system programmatically.
|
||||
</p>
|
||||
<h3>Key Benefits</h3>
|
||||
<ul>
|
||||
<li>Standardized API definitions</li>
|
||||
<li>Automatic documentation generation</li>
|
||||
<li>Client SDK generation</li>
|
||||
<li>Server implementation validation</li>
|
||||
<li>Improved developer experience</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
121
src/agentui/templates/openrpc_spec.html
Normal file
121
src/agentui/templates/openrpc_spec.html
Normal file
@@ -0,0 +1,121 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Agent UI - OpenRPC Spec</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>OpenRPC Specification</h1>
|
||||
<nav>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/processes">Processes</a>
|
||||
<a href="/jobs">Jobs</a>
|
||||
<a href="/openrpc" class="active">OpenRPC</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="openrpc-spec-container">
|
||||
<div class="spec-header">
|
||||
<h2>@spec.title</h2>
|
||||
<div class="spec-meta">
|
||||
<span class="spec-version">Version: @spec.version</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-description">
|
||||
<p>@spec.description</p>
|
||||
</div>
|
||||
|
||||
<div class="methods-section">
|
||||
<h3>Available Methods</h3>
|
||||
<div class="methods-list">
|
||||
@for method in methods
|
||||
<div class="method-card">
|
||||
<h4 class="method-name">@method.name</h4>
|
||||
<p class="method-summary">@method.summary</p>
|
||||
<p class="method-description">@method.description</p>
|
||||
<button class="btn" onclick="showMethodExecutor('@method.name')">Execute Method</button>
|
||||
</div>
|
||||
@end
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="method-executor" class="method-executor hidden">
|
||||
<h3>Method Executor</h3>
|
||||
<div class="executor-content">
|
||||
<div class="executor-header">
|
||||
<h4 id="executor-method-name">Method Name</h4>
|
||||
<button class="close-btn" onclick="hideMethodExecutor()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="executor-body">
|
||||
<div class="params-section">
|
||||
<h5>Parameters</h5>
|
||||
<textarea id="params-editor" placeholder="Enter JSON parameters here..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="executor-actions">
|
||||
<button class="btn btn-primary" onclick="executeMethod()">Execute</button>
|
||||
</div>
|
||||
|
||||
<div class="result-section">
|
||||
<h5>Result</h5>
|
||||
<pre id="result-display">Results will appear here...</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script>
|
||||
let currentMethod = '';
|
||||
|
||||
function showMethodExecutor(methodName) {
|
||||
currentMethod = methodName;
|
||||
document.getElementById('executor-method-name').textContent = methodName;
|
||||
document.getElementById('method-executor').classList.remove('hidden');
|
||||
document.getElementById('params-editor').value = '{\n \n}';
|
||||
document.getElementById('result-display').textContent = 'Results will appear here...';
|
||||
}
|
||||
|
||||
function hideMethodExecutor() {
|
||||
document.getElementById('method-executor').classList.add('hidden');
|
||||
}
|
||||
|
||||
function executeMethod() {
|
||||
const params = document.getElementById('params-editor').value;
|
||||
const specName = '@spec.title';
|
||||
|
||||
try {
|
||||
// Validate JSON
|
||||
JSON.parse(params);
|
||||
|
||||
// Execute the RPC call
|
||||
fetch(`/api/openrpc/${specName}/${currentMethod}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: params
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('result-display').textContent = JSON.stringify(data, null, 2);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('result-display').textContent = `Error: ${error.message}`;
|
||||
});
|
||||
} catch (e) {
|
||||
document.getElementById('result-display').textContent = `Invalid JSON: ${e.message}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
91
src/agentui/templates/process_details.html
Normal file
91
src/agentui/templates/process_details.html
Normal file
@@ -0,0 +1,91 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Agent UI - Process Details</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Process Details</h1>
|
||||
<nav>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/processes" class="active">Processes</a>
|
||||
<a href="/jobs">Jobs</a>
|
||||
<a href="/openrpc">OpenRPC</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="process-details-container">
|
||||
<div class="process-header">
|
||||
<h2>@process.name</h2>
|
||||
<div class="process-actions">
|
||||
<button onclick="killProcess('@process.pid')" class="btn btn-danger">Kill Process</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-item">
|
||||
<span class="label">PID:</span>
|
||||
<span class="value">@process.pid</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">CPU Usage:</span>
|
||||
<span class="value">@process.cpu%</span>
|
||||
<div class="detail-bar">
|
||||
<div class="detail-fill" style="width: @process.cpu%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-item">
|
||||
<span class="label">Memory Usage:</span>
|
||||
<span class="value">@process.memory MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="process-info">
|
||||
<h3>Process Information</h3>
|
||||
<div class="info-grid">
|
||||
<div class="info-card">
|
||||
<h4>CPU Usage Over Time</h4>
|
||||
<div class="chart-placeholder">
|
||||
<!-- In a real implementation, this would be a chart -->
|
||||
<div class="placeholder-text">CPU usage chart would be displayed here</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card">
|
||||
<h4>Memory Usage Over Time</h4>
|
||||
<div class="chart-placeholder">
|
||||
<!-- In a real implementation, this would be a chart -->
|
||||
<div class="placeholder-text">Memory usage chart would be displayed here</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script>
|
||||
function killProcess(pid) {
|
||||
if (confirm('Are you sure you want to kill process ' + pid + '?')) {
|
||||
fetch(`/api/processes/${pid}/kill`, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Process killed successfully');
|
||||
window.location.href = '/processes';
|
||||
} else {
|
||||
alert('Failed to kill process');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
159
src/agentui/templates/processes.html
Normal file
159
src/agentui/templates/processes.html
Normal file
@@ -0,0 +1,159 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hero Agent UI - Processes</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>System Processes</h1>
|
||||
<nav>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/processes" class="active">Processes</a>
|
||||
<a href="/jobs">Jobs</a>
|
||||
<a href="/openrpc">OpenRPC</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="processes-container">
|
||||
<div class="filter-controls">
|
||||
<input type="text" id="process-search" placeholder="Search processes..." onkeyup="filterProcesses()">
|
||||
<div class="sort-controls">
|
||||
<label>Sort by:</label>
|
||||
<select id="sort-field" onchange="sortProcesses()">
|
||||
<option value="pid">PID</option>
|
||||
<option value="name">Name</option>
|
||||
<option value="cpu">CPU Usage</option>
|
||||
<option value="memory">Memory Usage</option>
|
||||
</select>
|
||||
<button id="sort-direction" onclick="toggleSortDirection()">↑</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="data-table" id="processes-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PID</th>
|
||||
<th>Name</th>
|
||||
<th>CPU %</th>
|
||||
<th>Memory (MB)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for process in processes
|
||||
<tr>
|
||||
<td>@process.pid</td>
|
||||
<td>@process.name</td>
|
||||
<td>@process.cpu</td>
|
||||
<td>@process.memory</td>
|
||||
<td>
|
||||
<a href="/processes/@process.pid" class="btn btn-small">Details</a>
|
||||
<button onclick="killProcess('@process.pid')" class="btn btn-small btn-danger">Kill</button>
|
||||
</td>
|
||||
</tr>
|
||||
@end
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Hero Agent System</p>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script>
|
||||
function killProcess(pid) {
|
||||
if (confirm('Are you sure you want to kill process ' + pid + '?')) {
|
||||
fetch(`/api/processes/${pid}/kill`, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Process killed successfully');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to kill process');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function filterProcesses() {
|
||||
const input = document.getElementById('process-search');
|
||||
const filter = input.value.toUpperCase();
|
||||
const table = document.getElementById('processes-table');
|
||||
const rows = table.getElementsByTagName('tr');
|
||||
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
const nameCell = rows[i].getElementsByTagName('td')[1];
|
||||
if (nameCell) {
|
||||
const nameValue = nameCell.textContent || nameCell.innerText;
|
||||
if (nameValue.toUpperCase().indexOf(filter) > -1) {
|
||||
rows[i].style.display = '';
|
||||
} else {
|
||||
rows[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sortAscending = true;
|
||||
|
||||
function toggleSortDirection() {
|
||||
sortAscending = !sortAscending;
|
||||
const button = document.getElementById('sort-direction');
|
||||
button.textContent = sortAscending ? '↑' : '↓';
|
||||
sortProcesses();
|
||||
}
|
||||
|
||||
function sortProcesses() {
|
||||
const table = document.getElementById('processes-table');
|
||||
const rows = Array.from(table.getElementsByTagName('tr')).slice(1);
|
||||
const sortField = document.getElementById('sort-field').value;
|
||||
|
||||
let columnIndex;
|
||||
let isNumeric = false;
|
||||
|
||||
switch (sortField) {
|
||||
case 'pid':
|
||||
columnIndex = 0;
|
||||
isNumeric = true;
|
||||
break;
|
||||
case 'name':
|
||||
columnIndex = 1;
|
||||
break;
|
||||
case 'cpu':
|
||||
columnIndex = 2;
|
||||
isNumeric = true;
|
||||
break;
|
||||
case 'memory':
|
||||
columnIndex = 3;
|
||||
isNumeric = true;
|
||||
break;
|
||||
default:
|
||||
columnIndex = 0;
|
||||
isNumeric = true;
|
||||
}
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const aValue = a.getElementsByTagName('td')[columnIndex].textContent;
|
||||
const bValue = b.getElementsByTagName('td')[columnIndex].textContent;
|
||||
|
||||
if (isNumeric) {
|
||||
return sortAscending
|
||||
? parseFloat(aValue) - parseFloat(bValue)
|
||||
: parseFloat(bValue) - parseFloat(aValue);
|
||||
} else {
|
||||
return sortAscending
|
||||
? aValue.localeCompare(bValue)
|
||||
: bValue.localeCompare(aValue);
|
||||
}
|
||||
});
|
||||
|
||||
const tbody = table.getElementsByTagName('tbody')[0];
|
||||
rows.forEach(row => tbody.appendChild(row));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user