...
This commit is contained in:
parent
292a769250
commit
ae6966edb2
274
examples/example.v
Normal file
274
examples/example.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)
|
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)
|
||||||
|
}
|
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
|
||||||
|
}
|
36
src/agentui/controllers/rpc_ctrl.v
Normal file
36
src/agentui/controllers/rpc_ctrl.v
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
module controllers
|
||||||
|
// 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) ?OpenRPCSpec {
|
||||||
|
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>
|
406
src/clients/zinit/README.md
Normal file
406
src/clients/zinit/README.md
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
# Zinit Client Module
|
||||||
|
|
||||||
|
A well-documented V (Vlang) client library for interacting with the Zinit service manager via JSON-RPC over Unix socket.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This module provides a complete client implementation for the Zinit JSON-RPC API, allowing you to manage services, monitor system resources, and control the Zinit service manager programmatically.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Service Management**: Start, stop, monitor, and forget services
|
||||||
|
- **Service Configuration**: Create, delete, and retrieve service configurations
|
||||||
|
- **System Control**: Shutdown, reboot, and HTTP server management
|
||||||
|
- **Monitoring**: Get service status, statistics, and resource usage
|
||||||
|
- **Logging**: Stream current logs and subscribe to log updates
|
||||||
|
- **Error Handling**: Comprehensive error handling with detailed error messages
|
||||||
|
- **Type Safety**: Strongly typed structs for all API responses
|
||||||
|
- **Connection Management**: Automatic connection handling with proper cleanup
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Simply import the module in your V project:
|
||||||
|
|
||||||
|
```v
|
||||||
|
import zinit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```v
|
||||||
|
import zinit
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Create a client with default socket path (/tmp/zinit.sock)
|
||||||
|
mut client := zinit.new_default_client()
|
||||||
|
|
||||||
|
// Or specify a custom socket path
|
||||||
|
// mut client := zinit.new_client('/custom/path/to/zinit.sock')
|
||||||
|
|
||||||
|
defer {
|
||||||
|
client.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all services
|
||||||
|
services := client.service_list() or {
|
||||||
|
eprintln('Error: ${err}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, state in services {
|
||||||
|
println('${name}: ${state}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Client Creation
|
||||||
|
|
||||||
|
#### `new_client(socket_path string) &Client`
|
||||||
|
Creates a new zinit client with a custom socket path.
|
||||||
|
|
||||||
|
#### `new_default_client() &Client`
|
||||||
|
Creates a new zinit client with the default socket path (`/tmp/zinit.sock`).
|
||||||
|
|
||||||
|
### Connection Management
|
||||||
|
|
||||||
|
#### `connect() !`
|
||||||
|
Establishes a connection to the zinit Unix socket. Called automatically by API methods.
|
||||||
|
|
||||||
|
#### `disconnect()`
|
||||||
|
Closes the connection to the zinit Unix socket. Should be called when done with the client.
|
||||||
|
|
||||||
|
### Service Management
|
||||||
|
|
||||||
|
#### `service_list() !map[string]string`
|
||||||
|
Lists all services managed by Zinit.
|
||||||
|
|
||||||
|
**Returns**: A map of service names to their current states.
|
||||||
|
|
||||||
|
```v
|
||||||
|
services := client.service_list()!
|
||||||
|
for name, state in services {
|
||||||
|
println('${name}: ${state}')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `service_status(name string) !ServiceStatus`
|
||||||
|
Shows detailed status information for a specific service.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service
|
||||||
|
|
||||||
|
**Returns**: `ServiceStatus` struct with detailed information.
|
||||||
|
|
||||||
|
```v
|
||||||
|
status := client.service_status('redis')!
|
||||||
|
println('Service: ${status.name}')
|
||||||
|
println('State: ${status.state}')
|
||||||
|
println('PID: ${status.pid}')
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `service_start(name string) !`
|
||||||
|
Starts a service.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to start
|
||||||
|
|
||||||
|
#### `service_stop(name string) !`
|
||||||
|
Stops a service.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to stop
|
||||||
|
|
||||||
|
#### `service_monitor(name string) !`
|
||||||
|
Starts monitoring a service. The service configuration is loaded from the config directory.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to monitor
|
||||||
|
|
||||||
|
#### `service_forget(name string) !`
|
||||||
|
Stops monitoring a service. You can only forget a stopped service.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to forget
|
||||||
|
|
||||||
|
#### `service_kill(name string, signal string) !`
|
||||||
|
Sends a signal to a running service.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to send the signal to
|
||||||
|
- `signal`: The signal to send (e.g., "SIGTERM", "SIGKILL")
|
||||||
|
|
||||||
|
### Service Configuration
|
||||||
|
|
||||||
|
#### `service_create(name string, config ServiceConfig) !string`
|
||||||
|
Creates a new service configuration file.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to create
|
||||||
|
- `config`: The service configuration
|
||||||
|
|
||||||
|
**Returns**: Result message.
|
||||||
|
|
||||||
|
```v
|
||||||
|
config := zinit.ServiceConfig{
|
||||||
|
exec: '/usr/bin/redis-server'
|
||||||
|
oneshot: false
|
||||||
|
log: 'stdout'
|
||||||
|
env: {
|
||||||
|
'REDIS_PORT': '6379'
|
||||||
|
}
|
||||||
|
shutdown_timeout: 30
|
||||||
|
}
|
||||||
|
|
||||||
|
result := client.service_create('redis', config)!
|
||||||
|
println('Service created: ${result}')
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `service_delete(name string) !string`
|
||||||
|
Deletes a service configuration file.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to delete
|
||||||
|
|
||||||
|
**Returns**: Result message.
|
||||||
|
|
||||||
|
#### `service_get(name string) !ServiceConfig`
|
||||||
|
Gets a service configuration file.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to get
|
||||||
|
|
||||||
|
**Returns**: `ServiceConfig` struct with the service configuration.
|
||||||
|
|
||||||
|
### Service Statistics
|
||||||
|
|
||||||
|
#### `service_stats(name string) !ServiceStats`
|
||||||
|
Gets memory and CPU usage statistics for a service.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: The name of the service to get stats for
|
||||||
|
|
||||||
|
**Returns**: `ServiceStats` struct with usage information.
|
||||||
|
|
||||||
|
```v
|
||||||
|
stats := client.service_stats('redis')!
|
||||||
|
println('Memory Usage: ${stats.memory_usage / 1024 / 1024} MB')
|
||||||
|
println('CPU Usage: ${stats.cpu_usage}%')
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Operations
|
||||||
|
|
||||||
|
#### `system_shutdown() !`
|
||||||
|
Stops all services and powers off the system.
|
||||||
|
|
||||||
|
⚠️ **Warning**: This will actually shut down the system!
|
||||||
|
|
||||||
|
#### `system_reboot() !`
|
||||||
|
Stops all services and reboots the system.
|
||||||
|
|
||||||
|
⚠️ **Warning**: This will actually reboot the system!
|
||||||
|
|
||||||
|
#### `system_start_http_server(address string) !string`
|
||||||
|
Starts an HTTP/RPC server at the specified address.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `address`: The network address to bind the server to (e.g., "127.0.0.1:8080")
|
||||||
|
|
||||||
|
**Returns**: Result message.
|
||||||
|
|
||||||
|
#### `system_stop_http_server() !`
|
||||||
|
Stops the HTTP/RPC server if running.
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
#### `stream_current_logs(name ?string) ![]string`
|
||||||
|
Gets current logs from zinit and monitored services.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: Optional service name filter. If provided, only logs from this service will be returned.
|
||||||
|
|
||||||
|
**Returns**: Array of log strings.
|
||||||
|
|
||||||
|
```v
|
||||||
|
// Get all logs
|
||||||
|
all_logs := client.stream_current_logs(none)!
|
||||||
|
|
||||||
|
// Get logs for a specific service
|
||||||
|
redis_logs := client.stream_current_logs('redis')!
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `stream_subscribe_logs(name ?string) !string`
|
||||||
|
Subscribes to log messages generated by zinit and monitored services.
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
- `name`: Optional service name filter.
|
||||||
|
|
||||||
|
**Returns**: A single log message.
|
||||||
|
|
||||||
|
**Note**: For continuous streaming, call this method repeatedly.
|
||||||
|
|
||||||
|
### API Discovery
|
||||||
|
|
||||||
|
#### `rpc_discover() !map[string]interface{}`
|
||||||
|
Returns the OpenRPC specification for the API.
|
||||||
|
|
||||||
|
**Returns**: The complete OpenRPC specification as a map.
|
||||||
|
|
||||||
|
## Data Types
|
||||||
|
|
||||||
|
### ServiceConfig
|
||||||
|
Represents the configuration for a zinit service.
|
||||||
|
|
||||||
|
```v
|
||||||
|
struct ServiceConfig {
|
||||||
|
pub mut:
|
||||||
|
exec string // Command to run
|
||||||
|
oneshot bool // Whether the service should be restarted
|
||||||
|
after []string // Services that must be running before this one starts
|
||||||
|
log string // How to handle service output (null, ring, stdout)
|
||||||
|
env map[string]string // Environment variables for the service
|
||||||
|
shutdown_timeout int // Maximum time to wait for service to stop during shutdown
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ServiceStatus
|
||||||
|
Represents the detailed status information for a service.
|
||||||
|
|
||||||
|
```v
|
||||||
|
struct ServiceStatus {
|
||||||
|
pub mut:
|
||||||
|
name string // Service name
|
||||||
|
pid int // Process ID of the running service (if running)
|
||||||
|
state string // Current state of the service (Running, Success, Error, etc.)
|
||||||
|
target string // Target state of the service (Up, Down)
|
||||||
|
after map[string]string // Dependencies of the service and their states
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ServiceStats
|
||||||
|
Represents memory and CPU usage statistics for a service.
|
||||||
|
|
||||||
|
```v
|
||||||
|
struct ServiceStats {
|
||||||
|
pub mut:
|
||||||
|
name string // Service name
|
||||||
|
pid int // Process ID of the service
|
||||||
|
memory_usage i64 // Memory usage in bytes
|
||||||
|
cpu_usage f64 // CPU usage as a percentage (0-100)
|
||||||
|
children []ChildStats // Stats for child processes
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ChildStats
|
||||||
|
Represents statistics for a child process.
|
||||||
|
|
||||||
|
```v
|
||||||
|
struct ChildStats {
|
||||||
|
pub mut:
|
||||||
|
pid int // Process ID of the child process
|
||||||
|
memory_usage i64 // Memory usage in bytes
|
||||||
|
cpu_usage f64 // CPU usage as a percentage (0-100)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The client provides comprehensive error handling through V's error system. All API methods that can fail return a result type (`!`).
|
||||||
|
|
||||||
|
### ZinitError
|
||||||
|
Custom error type for zinit-specific errors.
|
||||||
|
|
||||||
|
```v
|
||||||
|
struct ZinitError {
|
||||||
|
pub mut:
|
||||||
|
code int // Error code
|
||||||
|
message string // Error message
|
||||||
|
data string // Additional error data
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Common error codes:
|
||||||
|
- `-32000`: Service not found
|
||||||
|
- `-32001`: Service already monitored
|
||||||
|
- `-32002`: Service is up
|
||||||
|
- `-32003`: Service is down
|
||||||
|
- `-32004`: Invalid signal
|
||||||
|
- `-32005`: Config error
|
||||||
|
- `-32006`: Shutting down
|
||||||
|
- `-32007`: Service already exists
|
||||||
|
- `-32008`: Service file error
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
See `example.v` for comprehensive usage examples covering all API methods.
|
||||||
|
|
||||||
|
### Basic Service Management
|
||||||
|
|
||||||
|
```v
|
||||||
|
import zinit
|
||||||
|
|
||||||
|
fn manage_service() ! {
|
||||||
|
mut client := zinit.new_default_client()
|
||||||
|
defer { client.disconnect() }
|
||||||
|
|
||||||
|
// Create a service
|
||||||
|
config := zinit.ServiceConfig{
|
||||||
|
exec: '/usr/bin/nginx'
|
||||||
|
oneshot: false
|
||||||
|
log: 'stdout'
|
||||||
|
after: ['network']
|
||||||
|
}
|
||||||
|
|
||||||
|
client.service_create('nginx', config)!
|
||||||
|
client.service_monitor('nginx')!
|
||||||
|
client.service_start('nginx')!
|
||||||
|
|
||||||
|
// Check status
|
||||||
|
status := client.service_status('nginx')!
|
||||||
|
println('Nginx is ${status.state}')
|
||||||
|
|
||||||
|
// Get statistics
|
||||||
|
stats := client.service_stats('nginx')!
|
||||||
|
println('Memory: ${stats.memory_usage / 1024 / 1024} MB')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Monitoring
|
||||||
|
|
||||||
|
```v
|
||||||
|
import zinit
|
||||||
|
import time
|
||||||
|
|
||||||
|
fn monitor_logs() ! {
|
||||||
|
mut client := zinit.new_default_client()
|
||||||
|
defer { client.disconnect() }
|
||||||
|
|
||||||
|
// Get current logs
|
||||||
|
logs := client.stream_current_logs(none)!
|
||||||
|
for log in logs {
|
||||||
|
println(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to new logs (simplified example)
|
||||||
|
for i in 0..10 {
|
||||||
|
log_entry := client.stream_subscribe_logs(none) or { continue }
|
||||||
|
println('New log: ${log_entry}')
|
||||||
|
time.sleep(1 * time.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Thread Safety
|
||||||
|
|
||||||
|
The client is not thread-safe. If you need to use it from multiple threads, you should create separate client instances or implement your own synchronization.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- V compiler
|
||||||
|
- Unix-like operating system (for Unix socket support)
|
||||||
|
- Running Zinit service manager
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This module follows the same license as the parent project.
|
211
src/clients/zinit/client.v
Normal file
211
src/clients/zinit/client.v
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
module zinit
|
||||||
|
|
||||||
|
import net.unix
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
// service_status shows detailed status information for a specific service
|
||||||
|
// name: the name of the service
|
||||||
|
pub fn (mut c Client) service_status(name string) !ServiceStatus {
|
||||||
|
params := [name]
|
||||||
|
response := c.send_request('service_status', params)!
|
||||||
|
|
||||||
|
result_map := response.result as map[string]interface{}
|
||||||
|
|
||||||
|
mut after_map := map[string]string{}
|
||||||
|
if after_raw := result_map['after'] {
|
||||||
|
if after_obj := after_raw as map[string]interface{} {
|
||||||
|
for key, value in after_obj {
|
||||||
|
after_map[key] = value.str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServiceStatus{
|
||||||
|
name: result_map['name'] or { '' }.str()
|
||||||
|
pid: result_map['pid'] or { 0 }.int()
|
||||||
|
state: result_map['state'] or { '' }.str()
|
||||||
|
target: result_map['target'] or { '' }.str()
|
||||||
|
after: after_map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_start starts a service
|
||||||
|
// name: the name of the service to start
|
||||||
|
pub fn (mut c Client) service_start(name string) ! {
|
||||||
|
params := [name]
|
||||||
|
c.send_request('service_start', params)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_stop stops a service
|
||||||
|
// name: the name of the service to stop
|
||||||
|
pub fn (mut c Client) service_stop(name string) ! {
|
||||||
|
params := [name]
|
||||||
|
c.send_request('service_stop', params)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_monitor starts monitoring a service
|
||||||
|
// The service configuration is loaded from the config directory
|
||||||
|
// name: the name of the service to monitor
|
||||||
|
pub fn (mut c Client) service_monitor(name string) ! {
|
||||||
|
params := [name]
|
||||||
|
c.send_request('service_monitor', params)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_forget stops monitoring a service
|
||||||
|
// You can only forget a stopped service
|
||||||
|
// name: the name of the service to forget
|
||||||
|
pub fn (mut c Client) service_forget(name string) ! {
|
||||||
|
params := [name]
|
||||||
|
c.send_request('service_forget', params)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_kill sends a signal to a running service
|
||||||
|
// name: the name of the service to send the signal to
|
||||||
|
// signal: the signal to send (e.g., SIGTERM, SIGKILL)
|
||||||
|
pub fn (mut c Client) service_kill(name string, signal string) ! {
|
||||||
|
params := [name, signal]
|
||||||
|
c.send_request('service_kill', params)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_create creates a new service configuration file
|
||||||
|
// name: the name of the service to create
|
||||||
|
// config: the service configuration
|
||||||
|
pub fn (mut c Client) service_create(name string, config ServiceConfig) !string {
|
||||||
|
params := [name, config]
|
||||||
|
response := c.send_request('service_create', params)!
|
||||||
|
return response.result.str()
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_delete deletes a service configuration file
|
||||||
|
// name: the name of the service to delete
|
||||||
|
pub fn (mut c Client) service_delete(name string) !string {
|
||||||
|
params := [name]
|
||||||
|
response := c.send_request('service_delete', params)!
|
||||||
|
return response.result.str()
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_get gets a service configuration file
|
||||||
|
// name: the name of the service to get
|
||||||
|
pub fn (mut c Client) service_get(name string) !ServiceConfig {
|
||||||
|
params := [name]
|
||||||
|
response := c.send_request('service_get', params)!
|
||||||
|
|
||||||
|
result_map := response.result as map[string]interface{}
|
||||||
|
|
||||||
|
mut after_list := []string{}
|
||||||
|
if after_raw := result_map['after'] {
|
||||||
|
if after_array := after_raw as []interface{} {
|
||||||
|
for item in after_array {
|
||||||
|
after_list << item.str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mut env_map := map[string]string{}
|
||||||
|
if env_raw := result_map['env'] {
|
||||||
|
if env_obj := env_raw as map[string]interface{} {
|
||||||
|
for key, value in env_obj {
|
||||||
|
env_map[key] = value.str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServiceConfig{
|
||||||
|
exec: result_map['exec'] or { '' }.str()
|
||||||
|
oneshot: result_map['oneshot'] or { false }.bool()
|
||||||
|
after: after_list
|
||||||
|
log: result_map['log'] or { '' }.str()
|
||||||
|
env: env_map
|
||||||
|
shutdown_timeout: result_map['shutdown_timeout'] or { 0 }.int()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_stats gets memory and CPU usage statistics for a service
|
||||||
|
// name: the name of the service to get stats for
|
||||||
|
pub fn (mut c Client) service_stats(name string) !ServiceStats {
|
||||||
|
params := [name]
|
||||||
|
response := c.send_request('service_stats', params)!
|
||||||
|
|
||||||
|
result_map := response.result as map[string]interface{}
|
||||||
|
|
||||||
|
mut children_list := []ChildStats{}
|
||||||
|
if children_raw := result_map['children'] {
|
||||||
|
if children_array := children_raw as []interface{} {
|
||||||
|
for child_raw in children_array {
|
||||||
|
if child_map := child_raw as map[string]interface{} {
|
||||||
|
children_list << ChildStats{
|
||||||
|
pid: child_map['pid'] or { 0 }.int()
|
||||||
|
memory_usage: child_map['memory_usage'] or { i64(0) }.i64()
|
||||||
|
cpu_usage: child_map['cpu_usage'] or { 0.0 }.f64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServiceStats{
|
||||||
|
name: result_map['name'] or { '' }.str()
|
||||||
|
pid: result_map['pid'] or { 0 }.int()
|
||||||
|
memory_usage: result_map['memory_usage'] or { i64(0) }.i64()
|
||||||
|
cpu_usage: result_map['cpu_usage'] or { 0.0 }.f64()
|
||||||
|
children: children_list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// system_shutdown stops all services and powers off the system
|
||||||
|
pub fn (mut c Client) system_shutdown() ! {
|
||||||
|
c.send_request('system_shutdown', []interface{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// system_reboot stops all services and reboots the system
|
||||||
|
pub fn (mut c Client) system_reboot() ! {
|
||||||
|
c.send_request('system_reboot', []interface{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// system_start_http_server starts an HTTP/RPC server at the specified address
|
||||||
|
// address: the network address to bind the server to (e.g., '127.0.0.1:8080')
|
||||||
|
pub fn (mut c Client) system_start_http_server(address string) !string {
|
||||||
|
params := [address]
|
||||||
|
response := c.send_request('system_start_http_server', params)!
|
||||||
|
return response.result.str()
|
||||||
|
}
|
||||||
|
|
||||||
|
// system_stop_http_server stops the HTTP/RPC server if running
|
||||||
|
pub fn (mut c Client) system_stop_http_server() ! {
|
||||||
|
c.send_request('system_stop_http_server', []interface{})!
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream_current_logs gets current logs from zinit and monitored services
|
||||||
|
// name: optional service name filter. If provided, only logs from this service will be returned
|
||||||
|
pub fn (mut c Client) stream_current_logs(name ?string) ![]string {
|
||||||
|
mut params := []interface{}
|
||||||
|
if service_name := name {
|
||||||
|
params << service_name
|
||||||
|
}
|
||||||
|
|
||||||
|
response := c.send_request('stream_currentLogs', params)!
|
||||||
|
|
||||||
|
if logs_array := response.result as []interface{} {
|
||||||
|
mut logs := []string{}
|
||||||
|
for log_entry in logs_array {
|
||||||
|
logs << log_entry.str()
|
||||||
|
}
|
||||||
|
return logs
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream_subscribe_logs subscribes to log messages generated by zinit and monitored services
|
||||||
|
// name: optional service name filter. If provided, only logs from this service will be returned
|
||||||
|
// Note: This method returns a single log message. For continuous streaming, call this method repeatedly
|
||||||
|
pub fn (mut c Client) stream_subscribe_logs(name ?string) !string {
|
||||||
|
mut params := []interface{}
|
||||||
|
if service_name := name {
|
||||||
|
params << service_name
|
||||||
|
}
|
||||||
|
|
||||||
|
response := c.send_request('stream_subscribeLogs', params)!
|
||||||
|
return response.result.str()
|
||||||
|
}
|
873
src/clients/zinit/openrpc.json
Normal file
873
src/clients/zinit/openrpc.json
Normal file
@ -0,0 +1,873 @@
|
|||||||
|
{
|
||||||
|
"openrpc": "1.2.6",
|
||||||
|
"info": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"title": "Zinit JSON-RPC API",
|
||||||
|
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
|
||||||
|
"license": {
|
||||||
|
"name": "MIT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"name": "Unix Socket",
|
||||||
|
"url": "unix:///tmp/zinit.sock"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "rpc_discover",
|
||||||
|
"description": "Returns the OpenRPC specification for the API",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "OpenRPCSpec",
|
||||||
|
"description": "The OpenRPC specification",
|
||||||
|
"schema": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Get API specification",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "OpenRPCSpecResult",
|
||||||
|
"value": {
|
||||||
|
"openrpc": "1.2.6",
|
||||||
|
"info": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"title": "Zinit JSON-RPC API"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_list",
|
||||||
|
"description": "Lists all services managed by Zinit",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "ServiceList",
|
||||||
|
"description": "A map of service names to their current states",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Service state (Running, Success, Error, etc.)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "List all services",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "ServiceListResult",
|
||||||
|
"value": {
|
||||||
|
"service1": "Running",
|
||||||
|
"service2": "Success",
|
||||||
|
"service3": "Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_status",
|
||||||
|
"description": "Shows detailed status information for a specific service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "ServiceStatus",
|
||||||
|
"description": "Detailed status information for the service",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Service name"
|
||||||
|
},
|
||||||
|
"pid": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Process ID of the running service (if running)"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Current state of the service (Running, Success, Error, etc.)"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Target state of the service (Up, Down)"
|
||||||
|
},
|
||||||
|
"after": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Dependencies of the service and their states",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "State of the dependency"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Get status of redis service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "ServiceStatusResult",
|
||||||
|
"value": {
|
||||||
|
"name": "redis",
|
||||||
|
"pid": 1234,
|
||||||
|
"state": "Running",
|
||||||
|
"target": "Up",
|
||||||
|
"after": {
|
||||||
|
"dependency1": "Success",
|
||||||
|
"dependency2": "Running"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Service not found",
|
||||||
|
"data": "service name \"unknown\" unknown"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_start",
|
||||||
|
"description": "Starts a service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to start",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "StartResult",
|
||||||
|
"description": "Result of the start operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Start redis service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "StartResult",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Service not found",
|
||||||
|
"data": "service name \"unknown\" unknown"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_stop",
|
||||||
|
"description": "Stops a service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to stop",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "StopResult",
|
||||||
|
"description": "Result of the stop operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Stop redis service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "StopResult",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Service not found",
|
||||||
|
"data": "service name \"unknown\" unknown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32003,
|
||||||
|
"message": "Service is down",
|
||||||
|
"data": "service \"redis\" is down"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_monitor",
|
||||||
|
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to monitor",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "MonitorResult",
|
||||||
|
"description": "Result of the monitor operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Monitor redis service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "MonitorResult",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32001,
|
||||||
|
"message": "Service already monitored",
|
||||||
|
"data": "service \"redis\" already monitored"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32005,
|
||||||
|
"message": "Config error",
|
||||||
|
"data": "failed to load service configuration"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_forget",
|
||||||
|
"description": "Stops monitoring a service. You can only forget a stopped service.",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to forget",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "ForgetResult",
|
||||||
|
"description": "Result of the forget operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Forget redis service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "ForgetResult",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Service not found",
|
||||||
|
"data": "service name \"unknown\" unknown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32002,
|
||||||
|
"message": "Service is up",
|
||||||
|
"data": "service \"redis\" is up"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_kill",
|
||||||
|
"description": "Sends a signal to a running service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to send the signal to",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "signal",
|
||||||
|
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "KillResult",
|
||||||
|
"description": "Result of the kill operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Send SIGTERM to redis service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "signal",
|
||||||
|
"value": "SIGTERM"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "KillResult",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Service not found",
|
||||||
|
"data": "service name \"unknown\" unknown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32003,
|
||||||
|
"message": "Service is down",
|
||||||
|
"data": "service \"redis\" is down"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32004,
|
||||||
|
"message": "Invalid signal",
|
||||||
|
"data": "invalid signal: INVALID"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_shutdown",
|
||||||
|
"description": "Stops all services and powers off the system",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "ShutdownResult",
|
||||||
|
"description": "Result of the shutdown operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Shutdown the system",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "ShutdownResult",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32006,
|
||||||
|
"message": "Shutting down",
|
||||||
|
"data": "system is already shutting down"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_reboot",
|
||||||
|
"description": "Stops all services and reboots the system",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "RebootResult",
|
||||||
|
"description": "Result of the reboot operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Reboot the system",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "RebootResult",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32006,
|
||||||
|
"message": "Shutting down",
|
||||||
|
"data": "system is already shutting down"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_create",
|
||||||
|
"description": "Creates a new service configuration file",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to create",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content",
|
||||||
|
"description": "The service configuration content",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"exec": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Command to run"
|
||||||
|
},
|
||||||
|
"oneshot": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the service should be restarted"
|
||||||
|
},
|
||||||
|
"after": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Services that must be running before this one starts"
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["null", "ring", "stdout"],
|
||||||
|
"description": "How to handle service output"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Environment variables for the service"
|
||||||
|
},
|
||||||
|
"shutdown_timeout": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Maximum time to wait for service to stop during shutdown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "CreateServiceResult",
|
||||||
|
"description": "Result of the create operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32007,
|
||||||
|
"message": "Service already exists",
|
||||||
|
"data": "Service 'name' already exists"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32008,
|
||||||
|
"message": "Service file error",
|
||||||
|
"data": "Failed to create service file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_delete",
|
||||||
|
"description": "Deletes a service configuration file",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to delete",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "DeleteServiceResult",
|
||||||
|
"description": "Result of the delete operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Service not found",
|
||||||
|
"data": "Service 'name' not found"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32008,
|
||||||
|
"message": "Service file error",
|
||||||
|
"data": "Failed to delete service file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_get",
|
||||||
|
"description": "Gets a service configuration file",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to get",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "GetServiceResult",
|
||||||
|
"description": "The service configuration",
|
||||||
|
"schema": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Service not found",
|
||||||
|
"data": "Service 'name' not found"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32008,
|
||||||
|
"message": "Service file error",
|
||||||
|
"data": "Failed to read service file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "service_stats",
|
||||||
|
"description": "Get memory and CPU usage statistics for a service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "The name of the service to get stats for",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "ServiceStats",
|
||||||
|
"description": "Memory and CPU usage statistics for the service",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Service name"
|
||||||
|
},
|
||||||
|
"pid": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Process ID of the service"
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Memory usage in bytes"
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "CPU usage as a percentage (0-100)"
|
||||||
|
},
|
||||||
|
"children": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Stats for child processes",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"pid": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Process ID of the child process"
|
||||||
|
},
|
||||||
|
"memory_usage": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Memory usage in bytes"
|
||||||
|
},
|
||||||
|
"cpu_usage": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "CPU usage as a percentage (0-100)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Get stats for redis service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "ServiceStatsResult",
|
||||||
|
"value": {
|
||||||
|
"name": "redis",
|
||||||
|
"pid": 1234,
|
||||||
|
"memory_usage": 10485760,
|
||||||
|
"cpu_usage": 2.5,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"pid": 1235,
|
||||||
|
"memory_usage": 5242880,
|
||||||
|
"cpu_usage": 1.2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Service not found",
|
||||||
|
"data": "service name \"unknown\" unknown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": -32003,
|
||||||
|
"message": "Service is down",
|
||||||
|
"data": "service \"redis\" is down"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_start_http_server",
|
||||||
|
"description": "Start an HTTP/RPC server at the specified address",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "address",
|
||||||
|
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "StartHttpServerResult",
|
||||||
|
"description": "Result of the start HTTP server operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Start HTTP server on localhost:8080",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "address",
|
||||||
|
"value": "127.0.0.1:8080"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "StartHttpServerResult",
|
||||||
|
"value": "HTTP server started at 127.0.0.1:8080"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32602,
|
||||||
|
"message": "Invalid address",
|
||||||
|
"data": "Invalid network address format"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_stop_http_server",
|
||||||
|
"description": "Stop the HTTP/RPC server if running",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "StopHttpServerResult",
|
||||||
|
"description": "Result of the stop HTTP server operation",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Stop the HTTP server",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "StopHttpServerResult",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": -32602,
|
||||||
|
"message": "Server not running",
|
||||||
|
"data": "No HTTP server is currently running"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stream_currentLogs",
|
||||||
|
"description": "Get current logs from zinit and monitored services",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "LogsResult",
|
||||||
|
"description": "Array of log strings",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Get all logs",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "LogsResult",
|
||||||
|
"value": [
|
||||||
|
"2023-01-01T12:00:00 redis: Starting service",
|
||||||
|
"2023-01-01T12:00:01 nginx: Starting service"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get logs for a specific service",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "LogsResult",
|
||||||
|
"value": [
|
||||||
|
"2023-01-01T12:00:00 redis: Starting service",
|
||||||
|
"2023-01-01T12:00:02 redis: Service started"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stream_subscribeLogs",
|
||||||
|
"description": "Subscribe to log messages generated by zinit and monitored services",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"description": "Optional service name filter. If provided, only logs from this service will be returned",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "LogSubscription",
|
||||||
|
"description": "A subscription to log messages",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "Subscribe to all logs",
|
||||||
|
"params": [],
|
||||||
|
"result": {
|
||||||
|
"name": "LogSubscription",
|
||||||
|
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Subscribe to filtered logs",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"value": "redis"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"name": "LogSubscription",
|
||||||
|
"value": "2023-01-01T12:00:00 redis: Service started"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
161
src/clients/zinit/openrpc.v
Normal file
161
src/clients/zinit/openrpc.v
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
module zinit
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||||
|
import net.unix
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
// UnixSocketTransport implements the jsonrpc.IRPCTransportClient interface for Unix domain sockets
|
||||||
|
struct UnixSocketTransport {
|
||||||
|
mut:
|
||||||
|
socket_path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// new_unix_socket_transport creates a new Unix socket transport
|
||||||
|
fn new_unix_socket_transport(socket_path string) &UnixSocketTransport {
|
||||||
|
return &UnixSocketTransport{
|
||||||
|
socket_path: socket_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send implements the jsonrpc.IRPCTransportClient interface
|
||||||
|
fn (mut t UnixSocketTransport) send(request string, params jsonrpc.SendParams) !string {
|
||||||
|
// Create a Unix domain socket client
|
||||||
|
mut socket := unix.connect_stream(t.socket_path)!
|
||||||
|
defer { socket.close() or {} }
|
||||||
|
|
||||||
|
// Set timeout if specified
|
||||||
|
if params.timeout > 0 {
|
||||||
|
socket.set_read_timeout(params.timeout * time.second)
|
||||||
|
socket.set_write_timeout(params.timeout * time.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
socket.write_string(request + '\n')!
|
||||||
|
|
||||||
|
// Read the response
|
||||||
|
mut response := ''
|
||||||
|
mut buf := []u8{len: 4096}
|
||||||
|
|
||||||
|
for {
|
||||||
|
bytes_read := socket.read(mut buf)!
|
||||||
|
if bytes_read <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
response += buf[..bytes_read].bytestr()
|
||||||
|
|
||||||
|
// Check if we've received a complete JSON response
|
||||||
|
if response.ends_with('}') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client provides a client interface to the zinit JSON-RPC API over Unix socket
|
||||||
|
@[heap]
|
||||||
|
pub struct Client {
|
||||||
|
mut:
|
||||||
|
socket_path string
|
||||||
|
rpc_client &jsonrpc.Client
|
||||||
|
request_id int
|
||||||
|
}
|
||||||
|
|
||||||
|
// new_client creates a new zinit client instance
|
||||||
|
// socket_path: path to the Unix socket (default: /tmp/zinit.sock)
|
||||||
|
pub fn new_client(socket_path string) &Client {
|
||||||
|
mut transport := new_unix_socket_transport(socket_path)
|
||||||
|
mut rpc_client := jsonrpc.new_client(transport)
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
socket_path: socket_path
|
||||||
|
rpc_client: rpc_client
|
||||||
|
request_id: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// new_default_client creates a new zinit client with default socket path
|
||||||
|
pub fn new_default_client() &Client {
|
||||||
|
return new_client('/tmp/zinit.sock')
|
||||||
|
}
|
||||||
|
|
||||||
|
// rpc_discover returns the OpenRPC specification for the API
|
||||||
|
pub fn (mut c Client) rpc_discover() !map[string]json.Any {
|
||||||
|
send_params := jsonrpc.SendParams{
|
||||||
|
timeout: 30
|
||||||
|
retry: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
request := jsonrpc.new_request_generic('rpc_discover', []string{})
|
||||||
|
result := c.rpc_client.send[[]string, map[string]json.Any](request, send_params)!
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_list lists all services managed by Zinit
|
||||||
|
// Returns a map of service names to their current states
|
||||||
|
pub fn (mut c Client) service_list() !map[string]string {
|
||||||
|
send_params := jsonrpc.SendParams{
|
||||||
|
timeout: 30
|
||||||
|
retry: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
request := jsonrpc.new_request_generic('service_list', []string{})
|
||||||
|
result := c.rpc_client.send[[]string, map[string]string](request, send_params)!
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_status shows detailed status information for a specific service
|
||||||
|
// name: the name of the service
|
||||||
|
pub fn (mut c Client) service_status(name string) !ServiceStatus {
|
||||||
|
send_params := jsonrpc.SendParams{
|
||||||
|
timeout: 30
|
||||||
|
retry: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
request := jsonrpc.new_request_generic('service_status', [name])
|
||||||
|
result_map := c.rpc_client.send[[string], map[string]json.Any](request, send_params)!
|
||||||
|
|
||||||
|
mut after_map := map[string]string{}
|
||||||
|
if after_raw := result_map['after'] {
|
||||||
|
if after_raw is map[string]json.Any {
|
||||||
|
for key, value in after_raw {
|
||||||
|
after_map[key] = value.str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServiceStatus{
|
||||||
|
name: result_map['name'].str()
|
||||||
|
pid: result_map['pid'].int()
|
||||||
|
state: result_map['state'].str()
|
||||||
|
target: result_map['target'].str()
|
||||||
|
after: after_map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_start starts a service
|
||||||
|
// name: the name of the service to start
|
||||||
|
pub fn (mut c Client) service_start(name string) ! {
|
||||||
|
send_params := jsonrpc.SendParams{
|
||||||
|
timeout: 30
|
||||||
|
retry: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
request := jsonrpc.new_request_generic('service_start', [name])
|
||||||
|
c.rpc_client.send[[string], json.Any](request, send_params)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// service_stop stops a service
|
||||||
|
// name: the name of the service to stop
|
||||||
|
pub fn (mut c Client) service_stop(name string) ! {
|
||||||
|
send_params := jsonrpc.SendParams{
|
||||||
|
timeout: 30
|
||||||
|
retry: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
request := jsonrpc.new_request_generic('service_stop', [name])
|
||||||
|
c.rpc_client.send[[string], json.Any](request, send_params)!
|
||||||
|
}
|
204
src/clients/zinit/test.v
Normal file
204
src/clients/zinit/test.v
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
module zinit
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
// Basic tests for the zinit client module
|
||||||
|
// Note: These tests require a running zinit instance at /tmp/zinit.sock
|
||||||
|
|
||||||
|
// test_client_creation tests basic client creation
|
||||||
|
fn test_client_creation() {
|
||||||
|
// Test default client creation
|
||||||
|
client1 := new_default_client()
|
||||||
|
assert client1.socket_path == '/tmp/zinit.sock'
|
||||||
|
|
||||||
|
// Test custom client creation
|
||||||
|
client2 := new_client('/custom/path/zinit.sock')
|
||||||
|
assert client2.socket_path == '/custom/path/zinit.sock'
|
||||||
|
|
||||||
|
println('✓ Client creation tests passed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// test_service_config tests ServiceConfig struct
|
||||||
|
fn test_service_config() {
|
||||||
|
config := ServiceConfig{
|
||||||
|
exec: '/usr/bin/test'
|
||||||
|
oneshot: true
|
||||||
|
after: ['dependency1', 'dependency2']
|
||||||
|
log: 'stdout'
|
||||||
|
env: {
|
||||||
|
'TEST_VAR': 'test_value'
|
||||||
|
'PATH': '/usr/bin:/bin'
|
||||||
|
}
|
||||||
|
shutdown_timeout: 30
|
||||||
|
}
|
||||||
|
|
||||||
|
assert config.exec == '/usr/bin/test'
|
||||||
|
assert config.oneshot == true
|
||||||
|
assert config.after.len == 2
|
||||||
|
assert config.after[0] == 'dependency1'
|
||||||
|
assert config.env['TEST_VAR'] == 'test_value'
|
||||||
|
assert config.shutdown_timeout == 30
|
||||||
|
|
||||||
|
println('✓ ServiceConfig tests passed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// test_service_status tests ServiceStatus struct
|
||||||
|
fn test_service_status() {
|
||||||
|
mut after_map := map[string]string{}
|
||||||
|
after_map['dep1'] = 'Running'
|
||||||
|
after_map['dep2'] = 'Success'
|
||||||
|
|
||||||
|
status := ServiceStatus{
|
||||||
|
name: 'test_service'
|
||||||
|
pid: 1234
|
||||||
|
state: 'Running'
|
||||||
|
target: 'Up'
|
||||||
|
after: after_map
|
||||||
|
}
|
||||||
|
|
||||||
|
assert status.name == 'test_service'
|
||||||
|
assert status.pid == 1234
|
||||||
|
assert status.state == 'Running'
|
||||||
|
assert status.target == 'Up'
|
||||||
|
assert status.after['dep1'] == 'Running'
|
||||||
|
|
||||||
|
println('✓ ServiceStatus tests passed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// test_service_stats tests ServiceStats and ChildStats structs
|
||||||
|
fn test_service_stats() {
|
||||||
|
child1 := ChildStats{
|
||||||
|
pid: 1235
|
||||||
|
memory_usage: 1024 * 1024 // 1MB
|
||||||
|
cpu_usage: 2.5
|
||||||
|
}
|
||||||
|
|
||||||
|
child2 := ChildStats{
|
||||||
|
pid: 1236
|
||||||
|
memory_usage: 2 * 1024 * 1024 // 2MB
|
||||||
|
cpu_usage: 1.2
|
||||||
|
}
|
||||||
|
|
||||||
|
stats := ServiceStats{
|
||||||
|
name: 'test_service'
|
||||||
|
pid: 1234
|
||||||
|
memory_usage: 10 * 1024 * 1024 // 10MB
|
||||||
|
cpu_usage: 5.0
|
||||||
|
children: [child1, child2]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert stats.name == 'test_service'
|
||||||
|
assert stats.pid == 1234
|
||||||
|
assert stats.memory_usage == 10 * 1024 * 1024
|
||||||
|
assert stats.cpu_usage == 5.0
|
||||||
|
assert stats.children.len == 2
|
||||||
|
assert stats.children[0].pid == 1235
|
||||||
|
assert stats.children[1].memory_usage == 2 * 1024 * 1024
|
||||||
|
|
||||||
|
println('✓ ServiceStats tests passed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// test_zinit_error tests ZinitError struct and error message
|
||||||
|
fn test_zinit_error() {
|
||||||
|
error := ZinitError{
|
||||||
|
code: -32000
|
||||||
|
message: 'Service not found'
|
||||||
|
data: 'service name "unknown" unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
assert error.code == -32000
|
||||||
|
assert error.message == 'Service not found'
|
||||||
|
assert error.data == 'service name "unknown" unknown'
|
||||||
|
|
||||||
|
error_msg := error.msg()
|
||||||
|
assert error_msg.contains('Zinit Error -32000')
|
||||||
|
assert error_msg.contains('Service not found')
|
||||||
|
assert error_msg.contains('service name "unknown" unknown')
|
||||||
|
|
||||||
|
println('✓ ZinitError tests passed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// test_connection_handling tests connection management (without actual connection)
|
||||||
|
fn test_connection_handling() {
|
||||||
|
mut client := new_default_client()
|
||||||
|
|
||||||
|
// Initially no connection
|
||||||
|
assert client.conn == none
|
||||||
|
|
||||||
|
// Test disconnect on non-connected client (should not panic)
|
||||||
|
client.disconnect()
|
||||||
|
assert client.conn == none
|
||||||
|
|
||||||
|
println('✓ Connection handling tests passed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// integration_test_basic performs basic integration tests if zinit is available
|
||||||
|
fn integration_test_basic() {
|
||||||
|
mut client := new_default_client()
|
||||||
|
defer {
|
||||||
|
client.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
println('Running integration tests (requires running zinit)...')
|
||||||
|
|
||||||
|
// Test RPC discovery
|
||||||
|
spec := client.rpc_discover() or {
|
||||||
|
println('⚠ Integration test skipped: zinit not available (${err})')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
println('✓ RPC discovery successful')
|
||||||
|
|
||||||
|
// Test service list
|
||||||
|
services := client.service_list() or {
|
||||||
|
println('✗ Service list failed: ${err}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
println('✓ Service list successful (${services.len} services)')
|
||||||
|
|
||||||
|
// If there are services, test getting status of the first one
|
||||||
|
if services.len > 0 {
|
||||||
|
service_name := services.keys()[0]
|
||||||
|
status := client.service_status(service_name) or {
|
||||||
|
println('⚠ Could not get status for ${service_name}: ${err}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println('✓ Service status for ${service_name}: ${status.state}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting current logs
|
||||||
|
logs := client.stream_current_logs(none) or {
|
||||||
|
println('⚠ Could not get logs: ${err}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
println('✓ Log streaming successful (${logs.len} log entries)')
|
||||||
|
|
||||||
|
println('✓ All integration tests passed')
|
||||||
|
}
|
||||||
|
|
||||||
|
// run_all_tests runs all test functions
|
||||||
|
pub fn run_all_tests() {
|
||||||
|
println('Running Zinit Client Tests...\n')
|
||||||
|
|
||||||
|
// Unit tests
|
||||||
|
test_client_creation()
|
||||||
|
test_service_config()
|
||||||
|
test_service_status()
|
||||||
|
test_service_stats()
|
||||||
|
test_zinit_error()
|
||||||
|
test_connection_handling()
|
||||||
|
|
||||||
|
println('\n--- Unit Tests Complete ---\n')
|
||||||
|
|
||||||
|
// Integration tests (optional, requires running zinit)
|
||||||
|
integration_test_basic()
|
||||||
|
|
||||||
|
println('\n--- All Tests Complete ---')
|
||||||
|
}
|
||||||
|
|
||||||
|
// main function for running tests directly
|
||||||
|
fn main() {
|
||||||
|
run_all_tests()
|
||||||
|
}
|
53
src/clients/zinit/types.v
Normal file
53
src/clients/zinit/types.v
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
module zinit
|
||||||
|
|
||||||
|
// ServiceConfig represents the configuration for a zinit service
|
||||||
|
pub struct ServiceConfig {
|
||||||
|
pub mut:
|
||||||
|
exec string // Command to run
|
||||||
|
oneshot bool // Whether the service should be restarted
|
||||||
|
after []string // Services that must be running before this one starts
|
||||||
|
log string // How to handle service output (null, ring, stdout)
|
||||||
|
env map[string]string // Environment variables for the service
|
||||||
|
shutdown_timeout int // Maximum time to wait for service to stop during shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceStatus represents the detailed status information for a service
|
||||||
|
pub struct ServiceStatus {
|
||||||
|
pub mut:
|
||||||
|
name string // Service name
|
||||||
|
pid int // Process ID of the running service (if running)
|
||||||
|
state string // Current state of the service (Running, Success, Error, etc.)
|
||||||
|
target string // Target state of the service (Up, Down)
|
||||||
|
after map[string]string // Dependencies of the service and their states
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceStats represents memory and CPU usage statistics for a service
|
||||||
|
pub struct ServiceStats {
|
||||||
|
pub mut:
|
||||||
|
name string // Service name
|
||||||
|
pid int // Process ID of the service
|
||||||
|
memory_usage i64 // Memory usage in bytes
|
||||||
|
cpu_usage f64 // CPU usage as a percentage (0-100)
|
||||||
|
children []ChildStats // Stats for child processes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildStats represents statistics for a child process
|
||||||
|
pub struct ChildStats {
|
||||||
|
pub mut:
|
||||||
|
pid int // Process ID of the child process
|
||||||
|
memory_usage i64 // Memory usage in bytes
|
||||||
|
cpu_usage f64 // CPU usage as a percentage (0-100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZinitError represents an error returned by the zinit API
|
||||||
|
pub struct ZinitError {
|
||||||
|
pub mut:
|
||||||
|
code int // Error code
|
||||||
|
message string // Error message
|
||||||
|
data string // Additional error data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface for ZinitError
|
||||||
|
pub fn (e ZinitError) msg() string {
|
||||||
|
return 'Zinit Error ${e.code}: ${e.message} - ${e.data}'
|
||||||
|
}
|
145
src/clients/zinit/zinit.v
Normal file
145
src/clients/zinit/zinit.v
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
module zinit
|
||||||
|
|
||||||
|
// Zinit Client Module
|
||||||
|
//
|
||||||
|
// This module provides a comprehensive V (Vlang) client library for interacting
|
||||||
|
// with the Zinit service manager via JSON-RPC over Unix socket.
|
||||||
|
//
|
||||||
|
// The module includes:
|
||||||
|
// - Complete type definitions for all API structures
|
||||||
|
// - Full client implementation with all OpenRPC methods
|
||||||
|
// - Comprehensive error handling
|
||||||
|
// - Connection management
|
||||||
|
// - Well-documented API with examples
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// import zinit
|
||||||
|
//
|
||||||
|
// mut client := zinit.new_default_client()
|
||||||
|
// defer { client.disconnect() }
|
||||||
|
//
|
||||||
|
// services := client.service_list()!
|
||||||
|
// for name, state in services {
|
||||||
|
// println('${name}: ${state}')
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// For detailed documentation, see README.md
|
||||||
|
// For usage examples, see example.v
|
||||||
|
// For tests, see test.v
|
||||||
|
|
||||||
|
// Module version information
|
||||||
|
pub const (
|
||||||
|
version = '1.0.0'
|
||||||
|
author = 'Hero Code'
|
||||||
|
license = 'MIT'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default socket path for zinit
|
||||||
|
pub const default_socket_path = '/tmp/zinit.sock'
|
||||||
|
|
||||||
|
// Common service states
|
||||||
|
pub const (
|
||||||
|
state_running = 'Running'
|
||||||
|
state_success = 'Success'
|
||||||
|
state_error = 'Error'
|
||||||
|
state_stopped = 'Stopped'
|
||||||
|
state_failed = 'Failed'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common service targets
|
||||||
|
pub const (
|
||||||
|
target_up = 'Up'
|
||||||
|
target_down = 'Down'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common log types
|
||||||
|
pub const (
|
||||||
|
log_null = 'null'
|
||||||
|
log_ring = 'ring'
|
||||||
|
log_stdout = 'stdout'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common signals
|
||||||
|
pub const (
|
||||||
|
signal_term = 'SIGTERM'
|
||||||
|
signal_kill = 'SIGKILL'
|
||||||
|
signal_hup = 'SIGHUP'
|
||||||
|
signal_usr1 = 'SIGUSR1'
|
||||||
|
signal_usr2 = 'SIGUSR2'
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSON-RPC error codes as defined in the OpenRPC specification
|
||||||
|
pub const (
|
||||||
|
error_service_not_found = -32000
|
||||||
|
error_service_already_monitored = -32001
|
||||||
|
error_service_is_up = -32002
|
||||||
|
error_service_is_down = -32003
|
||||||
|
error_invalid_signal = -32004
|
||||||
|
error_config_error = -32005
|
||||||
|
error_shutting_down = -32006
|
||||||
|
error_service_already_exists = -32007
|
||||||
|
error_service_file_error = -32008
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper function to create a basic service configuration
|
||||||
|
pub fn new_service_config(exec string) ServiceConfig {
|
||||||
|
return ServiceConfig{
|
||||||
|
exec: exec
|
||||||
|
oneshot: false
|
||||||
|
log: log_stdout
|
||||||
|
env: map[string]string{}
|
||||||
|
shutdown_timeout: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create a oneshot service configuration
|
||||||
|
pub fn new_oneshot_service_config(exec string) ServiceConfig {
|
||||||
|
return ServiceConfig{
|
||||||
|
exec: exec
|
||||||
|
oneshot: true
|
||||||
|
log: log_stdout
|
||||||
|
env: map[string]string{}
|
||||||
|
shutdown_timeout: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if an error is a specific zinit error code
|
||||||
|
pub fn is_zinit_error_code(err IError, code int) bool {
|
||||||
|
if zinit_err := err as ZinitError {
|
||||||
|
return zinit_err.code == code
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if service is not found error
|
||||||
|
pub fn is_service_not_found_error(err IError) bool {
|
||||||
|
return is_zinit_error_code(err, error_service_not_found)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if service is already monitored error
|
||||||
|
pub fn is_service_already_monitored_error(err IError) bool {
|
||||||
|
return is_zinit_error_code(err, error_service_already_monitored)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if service is down error
|
||||||
|
pub fn is_service_down_error(err IError) bool {
|
||||||
|
return is_zinit_error_code(err, error_service_is_down)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to format memory usage in human-readable format
|
||||||
|
pub fn format_memory_usage(bytes i64) string {
|
||||||
|
if bytes < 1024 {
|
||||||
|
return '${bytes} B'
|
||||||
|
} else if bytes < 1024 * 1024 {
|
||||||
|
return '${bytes / 1024} KB'
|
||||||
|
} else if bytes < 1024 * 1024 * 1024 {
|
||||||
|
return '${bytes / 1024 / 1024} MB'
|
||||||
|
} else {
|
||||||
|
return '${bytes / 1024 / 1024 / 1024} GB'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to format CPU usage
|
||||||
|
pub fn format_cpu_usage(cpu_percent f64) string {
|
||||||
|
return '${cpu_percent:.1f}%'
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user