Module: Tina4::DevAdmin

Defined in:
lib/tina4/dev_admin.rb

Overview

Developer dashboard module - only active in debug mode

Class Method Summary collapse

Class Method Details

.enabled?Boolean

Returns:

  • (Boolean)


313
314
315
# File 'lib/tina4/dev_admin.rb', line 313

def enabled?
  Tina4::Env.is_truthy(ENV["TINA4_DEBUG"])
end

.error_trackerObject



309
310
311
# File 'lib/tina4/dev_admin.rb', line 309

def error_tracker
  @error_tracker ||= ErrorTracker.new
end

.handle_request(env) ⇒ Object

Handle a /__dev request; returns [status, headers, body] or nil if not a dev path



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/tina4/dev_admin.rb', line 318

def handle_request(env)
  return nil unless enabled?

  path = env["PATH_INFO"] || "/"
  method = env["REQUEST_METHOD"]

  case [method, path]
  when ["GET", "/__dev"], ["GET", "/__dev/"]
    serve_dashboard
  when ["GET", "/__dev/js/tina4-dev-admin.min.js"]
    serve_dev_js
  when ["GET", "/__dev/api/mtime"]
    json_response({ mtime: @reload_mtime || 0, file: @reload_file || "" })
  when ["POST", "/__dev/api/reload"]
    body = read_json_body(env) || {}
    @reload_mtime = Time.now.to_i
    @reload_file = body["file"] || ""
    reload_type = body["type"] || "reload"
    Tina4::Log.info("External reload trigger: #{reload_type}#{@reload_file.empty? ? '' : " (#{@reload_file})"}")
    json_response({ ok: true, type: reload_type })
  when ["GET", "/__dev/api/status"]
    json_response(status_payload)
  when ["GET", "/__dev/api/routes"]
    json_response(routes_payload)
  when ["GET", "/__dev/api/messages"]
    category = query_param(env, "category")
    messages = message_log.get(category: category)
    counts = message_log.count
    json_response({ messages: messages, counts: counts })
  when ["POST", "/__dev/api/messages/clear"]
    body = read_json_body(env)
    category = body["category"] if body
    message_log.clear(category: category)
    json_response({ cleared: true })
  when ["GET", "/__dev/api/requests"]
    limit = (query_param(env, "limit") || 50).to_i
    json_response({ requests: request_inspector.get(limit: limit), stats: request_inspector.stats })
  when ["POST", "/__dev/api/requests/clear"]
    request_inspector.clear
    json_response({ cleared: true })
  when ["GET", "/__dev/api/system"]
    json_response(system_payload)
  when ["GET", "/__dev/api/queue"]
    json_response({ jobs: [], stats: { pending: 0, completed: 0, failed: 0, reserved: 0 } })
  when ["GET", "/__dev/api/mailbox"]
    messages = mailbox.inbox
    json_response({ messages: messages, count: messages.size, unread: mailbox.unread_count })
  when ["GET", "/__dev/api/broken"]
    errors   = error_tracker.get(include_resolved: true)
    h        = error_tracker.health
    json_response({ errors: errors, count: errors.size, health: h })
  when ["POST", "/__dev/api/broken/resolve"]
    body = read_json_body(env)
    id   = body && body["id"]
    resolved = id ? error_tracker.resolve(id) : false
    json_response({ resolved: resolved, id: id })
  when ["POST", "/__dev/api/broken/clear"]
    error_tracker.clear_resolved
    json_response({ cleared: true })
  when ["GET", "/__dev/api/websockets"]
    json_response({ connections: [], count: 0 })
  when ["POST", "/__dev/api/websockets/disconnect"]
    body = read_json_body(env)
    # TODO: disconnect WS connection by id from body["id"]
    json_response({ disconnected: true })
  when ["GET", "/__dev/api/mailbox/read"]
    message_id = query_param(env, "id")
    message = mailbox.read(message_id)
    if message
      json_response(message)
    else
      body = JSON.generate({ error: "Message not found", id: message_id })
      [404, { "content-type" => "application/json; charset=utf-8" }, [body]]
    end
  when ["POST", "/__dev/api/mailbox/seed"]
    body = read_json_body(env)
    count = ((body && body["count"]) || 5).to_i
    mailbox.seed(count: count)
    json_response({ seeded: count })
  when ["POST", "/__dev/api/mailbox/clear"]
    mailbox.clear
    json_response({ cleared: true })
  when ["GET", "/__dev/api/messages/search"]
    keyword = query_param(env, "q") || query_param(env, "keyword") || ""
    all_messages = message_log.get
    filtered = keyword.empty? ? all_messages : all_messages.select { |m| m[:message].to_s.downcase.include?(keyword.downcase) }
    json_response({ messages: filtered, count: filtered.size, keyword: keyword })
  when ["POST", "/__dev/api/queue/retry"]
    body = read_json_body(env)
    # TODO: retry failed jobs by id from body["id"]
    json_response({ retried: true })
  when ["POST", "/__dev/api/queue/purge"]
    # TODO: purge completed jobs
    json_response({ purged: true })
  when ["POST", "/__dev/api/queue/replay"]
    body = read_json_body(env)
    # TODO: replay a specific job by id from body["id"]
    json_response({ replayed: true })
  when ["GET", "/__dev/api/table"]
    table_name = query_param(env, "name")
    json_response(table_detail_payload(table_name))
  when ["POST", "/__dev/api/seed"]
    body = read_json_body(env)
    table_name = (body && body["table"]) || ""
    count = (body && body["count"]) || 10
    json_response(seed_table_data(table_name, count.to_i))
  when ["POST", "/__dev/api/tool"]
    body = read_json_body(env)
    tool = (body && body["tool"]) || ""
    json_response(run_tool(tool))
  when ["POST", "/__dev/api/chat"]
    body = read_json_body(env)
    message = (body && body["message"]) || ""
    json_response({
      reply: "Chat is not yet connected to an AI backend. You said: \"#{message}\"",
      timestamp: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
    })
  when ["GET", "/__dev/api/connections"]
    handle_connections_get
  when ["POST", "/__dev/api/connections/test"]
    body = read_json_body(env)
    handle_connections_test(body)
  when ["POST", "/__dev/api/connections/save"]
    body = read_json_body(env)
    handle_connections_save(body)
  when ["POST", "/__dev/api/query"]
    body = read_json_body(env)
    sql = (body && (body["query"] || body["sql"])) || ""
    json_response(run_query(sql))
  when ["GET", "/__dev/api/tables"]
    json_response(tables_payload)
  when ["GET", "/__dev/api/gallery"]
    json_response(gallery_list)
  when ["POST", "/__dev/api/gallery/deploy"]
    body = read_json_body(env)
    name = (body && body["name"]) || ""
    json_response(gallery_deploy(name))
  when ["GET", "/__dev/api/version-check"]
    json_response(version_check_payload)
  when ["GET", "/__dev/api/metrics"]
    json_response(Tina4::Metrics.quick_metrics)
  when ["GET", "/__dev/api/metrics/full"]
    json_response(Tina4::Metrics.full_analysis)
  when ["GET", "/__dev/api/metrics/file"]
    file_path = (query_param(env, "path") || "").to_s
    json_response(Tina4::Metrics.file_detail(file_path))
  when ["GET", "/__dev/api/graphql/schema"]
    begin
      gql = Tina4::GraphQL.new
      # Auto-discover and register all ORM subclasses
      ObjectSpace.each_object(Class).select { |c| c < Tina4::ORM }.each do |model_class|
        gql.from_orm(model_class.new)
      end
      json_response({ schema: gql.introspect, sdl: gql.schema_sdl })
    rescue => e
      json_response({ error: e.message }, 400)
    end
  else
    nil
  end
end

.mailboxObject



305
306
307
# File 'lib/tina4/dev_admin.rb', line 305

def mailbox
  @mailbox ||= DevMailbox.new
end

.message_logObject



297
298
299
# File 'lib/tina4/dev_admin.rb', line 297

def message_log
  @message_log ||= MessageLog.new
end

.request_inspectorObject



301
302
303
# File 'lib/tina4/dev_admin.rb', line 301

def request_inspector
  @request_inspector ||= RequestInspector.new
end