Skip to content
Open
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
2 changes: 2 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ config :tailwind,
cd: Path.expand("../assets", __DIR__)
]

config :tesla, :adapter, {Tesla.Adapter.Finch, name: SituationRoom.Finch}

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"
3 changes: 2 additions & 1 deletion lib/situation_room/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ defmodule SituationRoom.Application do
# Start the PubSub system
{Phoenix.PubSub, name: SituationRoom.PubSub},
# Start the Endpoint (http/https)
SituationRoomWeb.Endpoint
SituationRoomWeb.Endpoint,
# Start a worker by calling: SituationRoom.Worker.start_link(arg)
# {SituationRoom.Worker, arg}
{Finch, name: SituationRoom.Finch}
]

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
8 changes: 8 additions & 0 deletions lib/situation_room/check/client.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule SituationRoom.Check.Client do
@moduledoc """
This is a client setup for Tesla
"""
use Tesla

adapter(Tesla.Adapter.Finch, name: SituationRoom.Finch)
end
8 changes: 8 additions & 0 deletions lib/situation_room/check/lib/behavior.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule SituationRoom.Check.Lib.Behaviour do
@moduledoc """
Behavior for all site checking modules
"""
alias SituationRoom.DTO.Check
@type t :: %Check{}
@callback run(check :: Check.t()) :: :ok | :error
end
14 changes: 14 additions & 0 deletions lib/situation_room/check/lib/status_code.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule SituationRoom.Check.Lib.StatusCode do
@moduledoc """
Module that checks the status code
"""
@behaviour SituationRoom.Check.Lib.Behaviour

@impl true
def run(%SituationRoom.DTO.Check{} = check) do
case check.status_code do
x when x in 100..399 -> :ok
_ -> :error
end
end
end
13 changes: 13 additions & 0 deletions lib/situation_room/check/manager.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule SituationRoom.Check.Manager do
@moduledoc """
Logic for sending requests to site URLs
"""
alias SituationRoom.Check.{Request, Validate}
alias SituationRoom.Checks

def head_check(%SituationRoom.Site{} = site) do
dto = Request.head(site)
Checks.create_check(dto)
Validate.head(dto)
end
end
32 changes: 32 additions & 0 deletions lib/situation_room/check/request.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule SituationRoom.Check.Request do
@moduledoc """
Module that sends a head request to a site's url
Retrieves a status code and a response time
"""
alias SituationRoom.DTO.Check

def head(%SituationRoom.Site{} = site) do
start = :os.system_time(:microsecond)
response = SituationRoom.Check.Client.head(site.endpoint)
time = (:os.system_time(:microsecond) - start) / 1000

case response do
{:ok, %Tesla.Env{} = env} ->
%Check{
site_id: site.id,
type: "head",
headers: Enum.map(env.headers, fn {k, v} -> %{k => v} end),
response_time: time,
status_code: env.status
}

{:error, _reason} ->
%Check{
site_id: site.id,
type: "head",
response_time: time,
status_code: -1
}
end
end
end
17 changes: 17 additions & 0 deletions lib/situation_room/check/validate.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule SituationRoom.Check.Validate do
@moduledoc """
Module that checks if there is an outage
"""
alias SituationRoom.Outages

def head(%SituationRoom.DTO.Check{} = dto) do
# This will hold a map of each check that is performed.
head_checks = [
SituationRoom.Check.Lib.StatusCode.run(dto)
]

Outages.analyze(dto.site_id, head_checks)

:ok
end
end
31 changes: 31 additions & 0 deletions lib/situation_room/checks.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
defmodule SituationRoom.Checks do
@moduledoc """
Common repository methods for Site Checks
"""
alias SituationRoom.Site.Check
alias SituationRoom.Repo

def get_all_checks() do
Repo.all(Check)
end

def create_check(%SituationRoom.DTO.Check{} = dto) do
create_check(%{
"site_id" => dto.site_id,
"status_code" => dto.status_code,
"response_time" => dto.response_time,
"type" => dto.type,
"headers" => dto.headers
})
end

def create_check(%Check{} = check) do
Repo.insert!(check)
end

def create_check(params) do
%Check{}
|> Check.changeset(params)
|> Repo.insert()
end
end
25 changes: 25 additions & 0 deletions lib/situation_room/notifications.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule SituationRoom.Notifications do
@moduledoc """
Common Repository Methods for the Notification Data model
"""
alias SituationRoom.Repo
alias SituationRoom.Site.Notification

def get_all() do
Repo.all(Notification)
end

def create(params) do
%Notification{}
|> Notification.changeset(params)
|> Repo.insert()
end

def send_outage(_site_id) do
nil
end

def send_resolved(_site_id) do
nil
end
end
81 changes: 81 additions & 0 deletions lib/situation_room/outages.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
defmodule SituationRoom.Outages do
@moduledoc """
Common repository methods for Outages
"""
import Ecto.Query
alias SituationRoom.{Notifications, Repo}
alias SituationRoom.Site.Outage

def create(params) do
%Outage{}
|> Outage.changeset(params)
|> Repo.insert()
end

def get(param) do
Repo.get_by(Outage, param)
end

def get_last(site_id) do
from(o in Outage, where: o.site_id == ^site_id)
|> last()
|> Repo.one()
end

def resolve_last(last_outage) do
last_outage
|> Outage.changeset(%{resolved: true})
|> Repo.update!()

Notifications.send_resolved(last_outage.site_id)
end

def analyze(site_id, complete_checks) do
for result <- complete_checks do
case result do
:error ->
execute(site_id)

:ok ->
resolve(site_id)
end
end
end

def execute(site_id) do
case get_last(site_id) do
%SituationRoom.Site.Outage{resolved: result} ->
case result do
true ->
report(site_id)

false ->
nil
end

nil ->
report(site_id)
end
end

def report(site_id) do
create(%{"site_id" => site_id})
Notifications.send_outage(site_id)
end

def resolve(site_id) do
case get_last(site_id) do
%SituationRoom.Site.Outage{resolved: result} = last_outage ->
case result do
true ->
nil

false ->
resolve_last(last_outage)
end

nil ->
nil
end
end
end
15 changes: 15 additions & 0 deletions lib/situation_room/schema/dto/check.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule SituationRoom.DTO.Check do
@moduledoc """
DTO that holds the data when a check is performed
"""
use Ecto.Schema

defstruct [
:site_id,
:status_code,
:response_time,
:type,
:headers,
:body
]
end
14 changes: 4 additions & 10 deletions lib/situation_room/schema/site/site_check.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ defmodule SituationRoom.Site.Check do
@derive {Jason.Encoder, except: [:__meta__, :site]}
use Ecto.Schema
import Ecto.Changeset
alias SituationRoom.Repo

schema "site_checks" do
field(:status_code, :integer)
field(:response_time, :float)
field(:type, :string, default: "head", null: false)
field(:headers, {:array, :map})
belongs_to(:site, SituationRoom.Site)

timestamps()
Expand All @@ -21,14 +22,7 @@ defmodule SituationRoom.Site.Check do
|> validate_required([:site_id, :status_code, :response_time])
end

def get_all_checks() do
Repo.all(SituationRoom.Site.Check)
end

@spec create_check(any()) :: any()
def create_check(params) do
%SituationRoom.Site.Check{}
|> changeset(params)
|> Repo.insert()
def changeset(whole_check) do
validate_required(whole_check, [:site_id, :status_code, :response_time])
end
end
8 changes: 6 additions & 2 deletions lib/situation_room/schema/site/site_outage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ defmodule SituationRoom.Site.Outage do
@doc false
def changeset(outage, attrs) do
outage
|> cast(attrs, [:resolved, :notified_outage, :notified_resolved])
|> validate_required([:resolved, :notified_outage, :notified_resolved])
|> cast(attrs, [:site_id, :resolved, :notified_outage, :notified_resolved])
|> validate_required([:site_id, :resolved, :notified_outage, :notified_resolved])
end

def changeset(whole_outage) do
validate_required(whole_outage, [:site_id, :resolved, :notified_outage, :notified_resolved])
end
end
24 changes: 22 additions & 2 deletions lib/situation_room_web/controllers/site_check_controller.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
defmodule SituationRoomWeb.SiteCheckController do
use SituationRoomWeb, :controller
alias SituationRoom.Site.Check
alias SituationRoom.{Checks, Outages, Sites}
alias SituationRoom.Check.Manager

def check(conn, _params) do
conn
|> json(Check.get_all_checks())
|> json(Checks.get_all_checks())
end

def edit(conn, %{"id" => id}) do
site = Sites.get_site!(id)
Manager.head_check(site)

case Outages.get_last(id) do
%SituationRoom.Site.Outage{resolved: result} ->
case result do
true ->
conn |> render("edit.html", site: site, check: "resolved")

false ->
conn |> render("edit.html", site: site, check: "down")
end

nil ->
conn |> render("edit.html", site: site, check: "okay")
end
end
end
1 change: 1 addition & 0 deletions lib/situation_room_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule SituationRoomWeb.Router do
pipe_through(:browser)

resources("/sites", SiteController)
resources("/site_checks", SiteCheckController)

live("/", SiteLive)
end
Expand Down
9 changes: 8 additions & 1 deletion lib/situation_room_web/templates/site/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@
<td class="py-2 pr-4 text-left">
<.link to={site.endpoint} label={site.endpoint} title="Go to Endpoint" target="_blank" />
</td>
<td class="py-2 pr-4 text-left">Coming Soon...</td>
<td class="py-2">
<span>
<%# <.button label="Run Check" color="primary" method={SituationRoom.Sites.get_site!(site.id) |> SituationRoom.Check.Manager.all_checks()}/> %>
<%# <.button label="Run Check" color="primary" phx-click="run_check"}/> %>
<.button label="Run Check" color="primary" link_type="a" to={Routes.site_check_path(@conn, :edit, site)} class="mt-2"/>
</span>

</td>
<td class="py-2 pr-4 text-left"><%= site.interval |> SituationRoom.Sites.CheckIntervals.get_interval_string() %></td>
<td class="py-2">
<span>
Expand Down
9 changes: 9 additions & 0 deletions lib/situation_room_web/templates/site_check/edit.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<h2 class="mb-4 text-xl">Result of latest check for <%= @site.name %>:</h2>

<%# <%= render "form.html", Map.put(assigns, :action, Routes.site_path(@conn, :update, @site)) %>

<%# <div class="my-4"><.alert color="danger" label="Oops, something went wrong! Please check the errors below." /></div> %>
<div>
<strong><%= @check %></strong>
</div>
<.button label="Back to My Sites" color="gray" link_type="a" to={Routes.site_path(@conn, :index)} class="mt-6" />
3 changes: 3 additions & 0 deletions lib/situation_room_web/views/site_check_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule SituationRoomWeb.SiteCheckView do
use SituationRoomWeb, :view
end
Loading