Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter,dev,dev.*}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:ecto, :ecto_sql, :plug, :phoenix]
import_deps: [:ecto, :ecto_sql, :plug, :phoenix],
inputs: ["{mix,.formatter,dev,dev.*}.exs", "{config,lib,test}/**/*.{heex,ex,exs}"],
plugins: [Phoenix.LiveView.HTMLFormatter]
]
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ error_tracker-*.tar
# Temporary files, for example, from tests.
/tmp/

/assets/node_modules

/priv/static/app.js
/priv/static/app.css

dev.local.exs
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,42 @@ defmodule MyApp.Endpoint do
use ErrorTracker.Integrations.Plug
end
```

## Development

### Development server

We have a `dev.exs` script that starts a development server.

To run it together with an `IEx` console you can do:

```
iex -S mix dev
```

### Assets

In ortder to participate in the development of this library, you may need to
know how to compile the assets needed to use the Web UI.

To do so, you need to first make a clean build:

```
mix do assets.install, assets.build
```

That task will build the JS and CSS of the project.

The JS is not expected to change too much because we rely in LiveView, but if
you make any change just execute that command again and you are good to go.

In the case of CSS, as it is automatically generated by Tailwind, you need to
start the watcher when your intention is to modify the classes used.

To do so you can execute this task in a separate terminal:

```
mix assets.watch
```


Binary file added assets/bun.lockb
Binary file not shown.
4 changes: 4 additions & 0 deletions assets/css/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

20 changes: 20 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Establish Phoenix Socket and LiveView configuration.
import { Socket, LongPoll } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import topbar from "topbar";

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let socketPath = document.querySelector("meta[name='socket-path']").getAttribute("content");
let socketTransport = document.querySelector("meta[name='socket-transport']").getAttribute("content");
let normalizedTransport = (socketTransport == "longpoll") ? LongPoll : WebSocket;

let liveSocket = new LiveSocket(socketPath, Socket, { transport: normalizedTransport, params: { _csrf_token: csrfToken }});

// Show progress bar on live navigation and form submits
topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" });
window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300));
window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide());

// connect if there are any LiveViews on the page
liveSocket.connect();
window.liveSocket = liveSocket;
10 changes: 10 additions & 0 deletions assets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"workspaces": [
"../deps/*"
],
"dependencies": {
"phoenix": "workspace:*",
"phoenix_live_view": "workspace:*",
"topbar": "^3.0.0"
}
}
22 changes: 22 additions & 0 deletions assets/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// See the Tailwind configuration guide for advanced usage
// https://tailwindcss.com/docs/configuration

let plugin = require('tailwindcss/plugin')

module.exports = {
content: [
'./js/**/*.js',
'../lib/error_tracker/web.ex',
'../lib/error_tracker/web/**/*.*ex'
],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/forms'),
plugin(({addVariant}) => addVariant('phx-no-feedback', ['&.phx-no-feedback', '.phx-no-feedback &'])),
plugin(({addVariant}) => addVariant('phx-click-loading', ['&.phx-click-loading', '.phx-click-loading &'])),
plugin(({addVariant}) => addVariant('phx-submit-loading', ['&.phx-submit-loading', '.phx-submit-loading &'])),
plugin(({addVariant}) => addVariant('phx-change-loading', ['&.phx-change-loading', '.phx-change-loading &']))
]
}
10 changes: 9 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ if config_env() == :dev do
args: ~w(
--config=tailwind.config.js
--input=css/app.css
--output=../priv/static/assets/app.css
--output=../priv/static/app.css
),
cd: Path.expand("../assets", __DIR__)
]

config :bun,
version: "1.1.18",
default: [
args: ~w(build app.js --outdir=../../priv/static),
cd: Path.expand("../assets/js", __DIR__),
env: %{}
]
end
3 changes: 3 additions & 0 deletions dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ end

defmodule ErrorTrackerDevWeb.Router do
use Phoenix.Router
use ErrorTracker.Web, :router

pipeline :browser do
plug :fetch_session
Expand All @@ -106,6 +107,8 @@ defmodule ErrorTrackerDevWeb.Router do
get "/noroute", ErrorTrackerDevWeb.PageController, :noroute
get "/exception", ErrorTrackerDevWeb.PageController, :exception
get "/exit", ErrorTrackerDevWeb.PageController, :exit

error_tracker_dashboard("/errors")
end
end

Expand Down
83 changes: 83 additions & 0 deletions lib/error_tracker/web.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
defmodule ErrorTracker.Web do
@moduledoc """
ErrorTracker includes a Web UI to view and inspect errors occurred on your
application and already stored on the database.

In order to use it, you need to add the following to your Phoenix's `
router.ex` file:

```elixir
defmodule YourAppWeb.Router do
use Phoenix.Router
use ErrorTracker.Web, :router

...

error_tracker_dashboard "/errors"
end
```

This will add the routes needed for the ErrorTracker LiveView UI to work.

## LiveView socket options

By default the library expects you to have your LiveView socket at `/live` and
using `websocket` transport.

If that's not the case, you can configure it adding the following
configuration to your app's config files:

```elixir
config :error_tracker,
socket: [
path: "/my-custom-socket-path"
transport: :longpoll # (accepted values are :longpoll or :websocket)
]
```
"""

def html do
quote do
import Phoenix.Controller, only: [get_csrf_token: 0]

unquote(html_helpers())
end
end

def live_view do
quote do
use Phoenix.LiveView, layout: {ErrorTracker.Web.Layouts, :live}

unquote(html_helpers())
end
end

def live_component do
quote do
use Phoenix.LiveComponent

unquote(html_helpers())
end
end

def router do
quote do
import ErrorTracker.Web.Router
end
end

defp html_helpers do
quote do
use Phoenix.Component

import Phoenix.HTML
import Phoenix.LiveView.Helpers

alias Phoenix.LiveView.JS
end
end

defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
end
19 changes: 19 additions & 0 deletions lib/error_tracker/web/components/layouts.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule ErrorTracker.Web.Layouts do
use ErrorTracker.Web, :html

@css_path :code.priv_dir(:error_tracker) |> Path.join("static/app.css")
@js_path :code.priv_dir(:error_tracker) |> Path.join("static/app.js")

@default_docket_config %{path: "/live", transport: :websocket}

embed_templates "layouts/*"

def get_content(:css), do: File.read!(@css_path)
def get_content(:js), do: File.read!(@js_path)

def get_socket_config(key) do
default = Map.get(@default_docket_config, key)
config = Application.get_env(:error_tracker, :live_view_socket, [])
Keyword.get(config, key, default)
end
end
3 changes: 3 additions & 0 deletions lib/error_tracker/web/components/layouts/live.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<main>
<%= @inner_content %>
</main>
24 changes: 24 additions & 0 deletions lib/error_tracker/web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="csrf-token" content={get_csrf_token()} />
<meta name="socket-path" content={get_socket_config(:path)} />
<meta name="socket-transport" content={get_socket_config(:transport)} />

<title><%= assigns[:page_title] || "ErrorTracker" %></title>

<style>
<%= raw get_content(:css) %>
</style>
<script>
<%= raw get_content(:js) %>
</script>
</head>

<body>
<%= @inner_content %>
</body>
</html>
20 changes: 20 additions & 0 deletions lib/error_tracker/web/live/dashboard.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule ErrorTracker.Web.Live.Dashboard do
@moduledoc false

use ErrorTracker.Web, :live_view

@impl Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, :counter, 0)}
end

@impl Phoenix.LiveView
def handle_params(_params, _uri, socket) do
{:noreply, socket}
end

@impl Phoenix.LiveView
def handle_event("increment", _params, socket) do
{:noreply, assign(socket, :counter, socket.assigns.counter + 1)}
end
end
8 changes: 8 additions & 0 deletions lib/error_tracker/web/live/dashboard.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<p class="font-bold">Hello world!</p>
<p>Number of presses: <%= @counter %></p>
<button
class="h-8 px-4 m-2 text-sm text-indigo-100 transition-colors duration-150 bg-indigo-700 rounded-lg focus:shadow-outline hover:bg-indigo-800"
phx-click="increment"
>
Press me!
</button>
35 changes: 35 additions & 0 deletions lib/error_tracker/web/router.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule ErrorTracker.Web.Router do
@moduledoc false

@doc """
Creates the routes needed to use the `ErrorTracker` web interface.

It requires a path in which you are going to serve the web interface.
"""
defmacro error_tracker_dashboard(path, opts \\ []) do
{session_name, session_opts} = parse_options(opts)

quote do
scope unquote(path), alias: false, as: false do
import Phoenix.LiveView.Router, only: [live: 4, live_session: 3]

live_session unquote(session_name), unquote(session_opts) do
live "/", ErrorTracker.Web.Live.Dashboard, :index, as: unquote(session_name)
end
end
end
end

@doc false
def parse_options(opts) do
on_mount = Keyword.get(opts, :on_mount, [])
session_name = Keyword.get(opts, :as, :error_tracker_dashboard)

session_opts = [
on_mount: on_mount,
root_layout: {ErrorTracker.Web.Layouts, :root}
]

{session_name, session_opts}
end
end
5 changes: 4 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ defmodule ErrorTracker.MixProject do
{:plug, "~> 1.10"},
{:postgrex, ">= 0.0.0"},
# Dev dependencies
{:bun, "~> 1.3", only: :dev},
{:credo, "~> 1.7", only: [:dev, :test]},
{:ex_doc, "~> 0.33", only: :dev},
{:phoenix_live_reload, ">= 0.0.0", only: :dev},
Expand All @@ -62,7 +63,9 @@ defmodule ErrorTracker.MixProject do
[
setup: ["deps.get", "cmd --cd assets npm install"],
dev: "run --no-halt dev.exs",
"assets.build": ["esbuild default --minify"]
"assets.install": ["bun.install", "cmd _build/bun install --cwd assets/"],
"assets.watch": ["tailwind default --watch"],
"assets.build": ["bun default --minify", "tailwind default --minify"]
]
end
end
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
%{
"bun": {:hex, :bun, "1.3.0", "6833722da5b073777e043aec42091b0cf8bbacb84262ec6d348a914dda4c6a98", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "dde1b8116ba57269a9f398b4b28492b16fb29a78800c9533b7c9fb036793d62a"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
Expand Down