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