summaryrefslogtreecommitdiff
path: root/Omni/Ava/Web.hs
blob: 4d4ece687444cf5ffe6dc8c19359ef938bea0003 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}

-- | Web server for Ava trace viewer.
--
-- Serves the trace viewer UI for debugging tool executions.
--
-- : out omni-ava-web
-- : dep warp
-- : dep wai
-- : dep http-types
-- : dep aeson
-- : dep text
-- : dep bytestring
-- : dep sqlite-simple
module Omni.Ava.Web
  ( startWebServer,
    app,
    defaultPort,
    main,
  )
where

import Alpha
import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Encoding
import qualified Data.Text.IO as TextIO
import qualified Database.SQLite.Simple as SQL
import qualified Network.HTTP.Types as HTTP
import qualified Network.Wai as Wai
import qualified Network.Wai.Handler.Warp as Warp
import qualified Omni.Ava.Trace as Trace
import qualified System.Environment as Environment

main :: IO ()
main = putText "Use Omni.Ava for the main entry point"

defaultPort :: Int
defaultPort = 8079

startWebServer :: Int -> FilePath -> IO ()
startWebServer port dbPath = do
  putText <| "Starting Ava web server on port " <> tshow port
  SQL.withConnection dbPath Trace.initTraceDb
  Warp.run port (app dbPath)

app :: FilePath -> Wai.Application
app dbPath request respond = do
  case Wai.pathInfo request of
    ["trace", tid] -> serveTracePage dbPath tid request respond
    ["api", "trace", tid] -> serveTraceJson dbPath tid request respond
    ["health"] -> respond <| Wai.responseLBS HTTP.status200 [(HTTP.hContentType, "text/plain")] "ok"
    _ -> respond <| Wai.responseLBS HTTP.status404 [(HTTP.hContentType, "text/plain")] "Not found"

serveTracePage :: FilePath -> Text -> Wai.Request -> (Wai.Response -> IO Wai.ResponseReceived) -> IO Wai.ResponseReceived
serveTracePage dbPath tid _req respond = do
  SQL.withConnection dbPath <| \conn -> do
    maybeRec <- Trace.getTrace conn tid
    case maybeRec of
      Nothing -> respond <| Wai.responseLBS HTTP.status404 [(HTTP.hContentType, "text/plain")] "Trace not found"
      Just rec -> do
        html <- renderTraceHtml rec
        respond <| Wai.responseLBS HTTP.status200 [(HTTP.hContentType, "text/html; charset=utf-8")] (LBS.fromStrict <| Encoding.encodeUtf8 html)

serveTraceJson :: FilePath -> Text -> Wai.Request -> (Wai.Response -> IO Wai.ResponseReceived) -> IO Wai.ResponseReceived
serveTraceJson dbPath tid _req respond = do
  SQL.withConnection dbPath <| \conn -> do
    maybeRec <- Trace.getTrace conn tid
    case maybeRec of
      Nothing -> respond <| Wai.responseLBS HTTP.status404 [(HTTP.hContentType, "application/json")] "{\"error\":\"not found\"}"
      Just rec -> do
        let json = Aeson.encode rec
        respond <| Wai.responseLBS HTTP.status200 [(HTTP.hContentType, "application/json")] json

renderTraceHtml :: Trace.TraceRecord -> IO Text
renderTraceHtml rec = do
  coderoot <- Environment.getEnv "CODEROOT"
  let templatePath = coderoot <> "/Omni/Ava/Web/trace.html"
  template <- TextIO.readFile templatePath
  pure
    <| Text.replace "{{trace_id}}" (Trace.trcId rec)
    <| Text.replace "{{tool_name}}" (Trace.trcToolName rec)
    <| Text.replace "{{created_at}}" (Trace.trcCreatedAt rec)
    <| Text.replace "{{duration_ms}}" (tshow <| Trace.trcDurationMs rec)
    <| Text.replace "{{input_json}}" (Trace.trcInput rec)
    <| Text.replace "{{output_json}}" (Trace.trcOutput rec)
    <| template