Commit 4565ea38 authored by Pascal Wiedenbeck's avatar Pascal Wiedenbeck
Browse files

Small fixes to the frontend html

parent d9635f03
......@@ -32,14 +32,14 @@ RUN cd assets/ && \
# Copy other static assets
COPY priv/gettext priv/gettext/
# Digest and compress static files (Disabled atm)
RUN if [ "$ENV" = "prod" ] ; then mix phx.digest ; fi
# Copy source code to container
COPY config config/
COPY lib lib/
COPY test test/
# Digest and compress static files
RUN mix phx.digest
# Compile source code
RUN MIX_ENV=$ENV mix compile
......
......@@ -20,7 +20,9 @@ config :logger, :console,
metadata: [:request_id]
# Use Poison for JSON parsing in Phoenix
config :phoenix, :json_library, Poison
config :phoenix, :json_library, Jason
config :tesla, adapter: Tesla.Adapter.Hackney
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
......
......@@ -10,8 +10,7 @@ use Mix.Config
# which you should run after static files are built and
# before starting your production server.
config :frontend, FrontendWeb.Endpoint,
http: [:inet6, port: System.get_env("PORT") || 80],
url: [host: "example.com", port: 80],
http: [:inet6, port: 4000],
cache_static_manifest: "priv/static/cache_manifest.json"
# Do not print debug messages in production
......
......@@ -95,5 +95,6 @@
<orderEntry type="library" name="plug_crypto" level="project" />
<orderEntry type="library" name="ranch" level="project" />
<orderEntry type="library" name="mixunit" level="project" />
<orderEntry type="library" name="tesla" level="project" />
</component>
</module>
\ No newline at end of file
......@@ -31,15 +31,22 @@ spec:
httpGet:
path: /healthz
port: {{service.targetPort|default('80')}}
initialDelaySeconds: 40
periodSeconds: 5
initialDelaySeconds: 120
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: {{service.targetPort|default('80')}}
initialDelaySeconds: 5
periodSeconds: 3
initialDelaySeconds: 20
periodSeconds: 10
env:
- name: RABBITMQ_USER
value: rabbit
- name: RABBITMQ_PASS
valueFrom:
secretKeyRef:
name: rabbitmq
key: rabbitmq-password
{% if not build.profile.debug|default(false) %}
- name: MIX_ENV
value: prod
......
......@@ -8,8 +8,9 @@ metadata:
kind: Mapping
name: {{build.name}}_mapping
prefix: /
tls: upstream
service: {{build.name}}:{{service.port|default('8080')}}
service: {{service.name}}:{{service.port|default('8080')}}
use_websocket: true
timeout_ms: 100000
{%- if "weight" in build.profile %}
weight: {{build.profile.weight}}
{%- endif %}
......
defmodule Frontend.CheckEventListener do
use GenServer
@recurring_time 30*1000 # Send every 30 seconds a dummy notification
use AMQP
def start_link(opts) do
GenServer.start_link(__MODULE__, %{}, opts)
end
@exchange "icinga_checks"
def init(_opts) do
{:ok, %{}, @recurring_time}
rabbitmq_connect()
end
def handle_info(:timeout, state) do
measurement = Frontend.MeasurementsService.create_random_measurement(1, Timex.now() |> Timex.to_unix(), 0)
# Confirmation sent by the broker after registering this process as a consumer
def handle_info({:basic_consume_ok, %{consumer_tag: _consumer_tag}}, chan) do
{:noreply, chan}
end
event =
%{
"hostId" => 1,
"serviceId" => measurement["serviceId"],
"success" => measurement["responseTime"] != nil,
"timeTaken" => measurement["responseTime"],
"timestamp" => measurement["endTime"]
}
# Sent by the broker when the consumer is unexpectedly cancelled (such as after a queue deletion)
def handle_info({:basic_cancel, %{consumer_tag: _consumer_tag}}, chan) do
{:stop, :normal, chan}
end
FrontendWeb.MeasurementsChannel.handle_new_check_event(event)
# Confirmation sent by the broker to the consumer process after a Basic.cancel
def handle_info({:basic_cancel_ok, %{consumer_tag: _consumer_tag}}, chan) do
{:noreply, chan}
end
{:noreply, state, @recurring_time}
def handle_info({:basic_deliver, payload, %{delivery_tag: tag, redelivered: redelivered}}, chan) do
spawn fn -> consume(chan, tag, redelivered, payload) end
{:noreply, chan}
end
def handle_call(_msg, _from, state) do
{:reply, :ok, state}
def handle_info({:DOWN, _, :process, _pid, _reason}, _) do
{:ok, chan} = rabbitmq_connect()
{:noreply, chan}
end
def handle_cast(_msg, state) do
{:noreply, state}
defp setup_queue(channel) do
{:ok, %{queue: queue_name}} = AMQP.Queue.declare(channel, "", exclusive: true)
AMQP.Exchange.fanout(channel, @exchange, durable: true)
AMQP.Queue.bind(channel, queue_name, @exchange)
queue_name
end
defp consume(channel, tag, _redelivered, payload) do
payload
|> Jason.decode!()
|> FrontendWeb.MeasurementsChannel.handle_new_check_event()
Basic.ack(channel, tag)
end
defp rabbitmq_connect() do
case Connection.open("amqp://#{System.get_env("RABBITMQ_USER")}:#{System.get_env("RABBITMQ_PASS")}@rabbitmq") do
{:ok, conn} ->
# Get notifications when the connection goes down
Process.monitor(conn.pid)
{:ok, chan} = Channel.open(conn)
queue_name = setup_queue(chan)
{:ok, _consumer_tag} = Basic.consume(chan, queue_name)
{:ok, chan}
{:error, _} ->
# Reconnection loop
:timer.sleep(10000)
rabbitmq_connect()
end
end
end
\ No newline at end of file
defmodule Frontend.MeasurementsService do
use Tesla
require Logger
def get_measurements(start_time, end_time, "10m", services) do
create_measurements(start_time + 10*60, 10*60, end_time, services) # Create dummy measurements
end
defp create_measurements(cur_time, interval, end_time, services) do
if cur_time > end_time do
[]
else
measurements = services |> Enum.map(fn id -> create_random_measurement(id, cur_time, interval) end)
measurements ++ create_measurements(cur_time + interval, interval, end_time, services)
end
end
def create_random_measurement(service_id, cur_time, interval) do
count = 10
outages = :rand.uniform(count)
resp_time =
if count == outages do
nil
else
:math.pow(6, :rand.uniform() * 1.5 + 2)
end
plug Tesla.Middleware.BaseUrl, "http://measurements-service"
plug Tesla.Middleware.JSON
plug Tesla.Middleware.FollowRedirects
plug Tesla.Middleware.Logger
%{
"serviceId" => service_id,
"responseTime" => resp_time,
"startTime" => cur_time - interval,
"endTime" => cur_time,
"measurementsCount" => count,
"outages" => outages,
"tries" => 1
}
def get_measurements(start_time, end_time, interval, service_id) do
get!("/measurements/", query: [serviceId: service_id, startTime: start_time, endTime: end_time, groupBy: interval])
|> Map.get(:body)
|> Jason.decode!()
end
end
defmodule Frontend.MonitoredService do
use Tesla
require Logger
plug Tesla.Middleware.BaseUrl, "http://monitored-service"
plug Tesla.Middleware.JSON
def get_host_info(host_id) do
%{
"id" => host_id,
"name" => "Test host",
"icon" =>
"https://www.uni-paderborn.de/typo3conf/ext/upb/Resources/Public/Files/gfx/logo.png"
}
get!("/host", query: [id: host_id])
|> Map.get(:body)
|> Jason.decode!()
end
def get_services(host_id) do
[
%{
"id" => 1,
"name" => "Test http service",
"host_id" => host_id,
"type" => "mayor"
},
%{
"id" => 2,
"name" => "Test ping service",
"host_id" => host_id,
"type" => "mayor"
},
%{
"id" => 3,
"name" => "Test minor ping service",
"host_id" => host_id,
"type" => "minor"
},
%{
"id" => 4,
"name" => "Test minor http service",
"host_id" => host_id,
"type" => "minor"
}
]
get!("/host/services", query: [id: host_id])
|> Map.get(:body)
|> Jason.decode!()
end
def search(search_string) do
if :rand.uniform() > 0.8 do
[]
else
[
%{
"id" => 1,
"name" => "UPB",
"icon" =>
"https://www.uni-paderborn.de/typo3conf/ext/upb/Resources/Public/Files/gfx/logo.png"
},
%{
"id" => 42,
"name" => "Reddit",
"icon" =>
"https://cdn0.iconfinder.com/data/icons/social-media-2092/100/social-36-512.png"
}
]
end
get!("/search", query: [search: search_string])
|> Map.get(:body)
|> Jason.decode!()
end
end
......@@ -15,4 +15,9 @@ defmodule FrontendWeb.MeasurementsChannel do
FrontendWeb.Endpoint.broadcast!("measurements:#{id}", "measurement", event)
end
def handle_new_check_event(event) do
Logger.debug("Received invalid check event. Event was #{inspect(event)}")
end
end
......@@ -4,6 +4,9 @@ defmodule FrontendWeb.HostController do
def index(conn, %{"id" => id}) do
host_info = Frontend.MonitoredService.get_host_info(id)
render(conn, "index.html", host: host_info)
services =
Frontend.MonitoredService.get_services(host_info["id"])
|> Enum.sort_by(fn service -> service["name"] |> String.downcase() end)
render(conn, "index.html", host: host_info, services: services)
end
end
<% services = get_services(@host["id"]) %>
<% measurements = get_measurements(services) %>
<div class="container host-status-header text-center clearfix">
<img src="<%= @host["icon"] %>" class="float-sm-left d-inline-block" alt="Responsive image"/>
<img src="<%= @host["icon"] %>" class="float-sm-left d-inline-block img-fluid" style="max-height: 100px" alt="Icon"/>
<h1 class="float-sm-right d-block d-md-inline-block h-100" style="transform: translateY(45%);">
<%= gettext "%{name}", name: @host["name"] %>
</h1>
......@@ -23,10 +20,9 @@
</div>
<div class="row" style="margin-bottom: 70px;">
<ul class="list-group col">
<%= for service <- services |> get_mayor_services() do %>
<%= for service <- @services |> get_mayor_services() do %>
<li class="list-group-item">
<%= render @view_module, "mayor_service.html",
assigns |> Map.put(:measurements, measurements) |> Map.put(:service, service) %>
<%= render @view_module, "mayor_service.html", assigns |> Map.put(:service, service) %>
</li>
<% end %>
</ul>
......@@ -37,10 +33,9 @@
</div>
<div class="row">
<ul class="list-group col">
<%= for service <- services |> get_minor_services() do %>
<%= for service <- @services |> get_minor_services() do %>
<li class="list-group-item">
<%= render @view_module, "minor_service.html",
assigns |> Map.put(:measurements, measurements) |> Map.put(:service, service) %>
<%= render @view_module, "minor_service.html", assigns |> Map.put(:service, service) %>
</li>
<% end %>
</ul>
......
......@@ -10,7 +10,7 @@
<canvas id="<%= "service-chart-#{@service["id"]}" %>" height="90"></canvas>
<script>
document.addEventListener("DOMContentLoaded", function() {
let measurements = <%= get_measurements_json_data(@measurements, @service["id"]) |> raw() %>;
let measurements = <%= get_measurements_json_data(@service["id"]) |> raw() %>;
let canvas = document.getElementById("<%= "service-chart-#{@service["id"]}" %>");
let statusContainer = document.getElementById("<%= "service-status-#{@service["id"]}" %>");
let service = createService(<%= @service["id"] %>, canvas, statusContainer, measurements);
......
......@@ -9,7 +9,7 @@
</h5>
<script>
document.addEventListener("DOMContentLoaded", function() {
let measurements = <%= get_measurements_json_data(@measurements, @service["id"]) |> raw() %>;
let measurements = <%= get_measurements_json_data(@service["id"]) |> raw() %>;
let statusContainer = document.getElementById("<%= "service-status-#{@service["id"]}" %>");
let service = createService(<%= @service["id"] %>, null, statusContainer, measurements);
service.startListeningForUpdates();
......
......@@ -25,12 +25,12 @@
<tbody>
<tr>
<th scope="row">1</th>
<td>Test Host</td>
<td>Uni Paderborn</td>
<td><a class="btn btn-info" href="/1">Status</a></td>
</tr>
<tr>
<th scope="row">2</th>
<td>Test Host 2</td>
<td>Cluster Status</td>
<td><a class="btn btn-info" href="/2">Status</a></td>
</tr>
</tbody>
......
defmodule FrontendWeb.HostView do
use FrontendWeb, :view
def get_services(host_id) do
Frontend.MonitoredService.get_services(host_id) |> Enum.sort_by(fn service -> service["name"] end)
end
require Logger
def get_mayor_services(services) do
services |> Enum.filter(fn %{"type" => type} -> type == "mayor" end)
......@@ -13,18 +10,28 @@ defmodule FrontendWeb.HostView do
services |> Enum.filter(fn %{"type" => type} -> type == "minor" end)
end
def get_measurements(services) do
def get_measurements_json_data(service_id) do
start_time = Timex.now() |> Timex.shift(hours: -3) |> Timex.to_unix()
end_time = Timex.now() |> Timex.to_unix()
service_ids = services |> Enum.map(fn %{"id" => id} -> id end)
Frontend.MeasurementsService.get_measurements(start_time, end_time, "10m", service_ids)
Frontend.MeasurementsService.get_measurements(start_time, end_time, "5m", service_id)
|> Enum.to_list()
|> List.insert_at(0, create_dummy_data(service_id, start_time))
|> Jason.encode!()
end
def get_measurements_json_data(measurements, service_id) do
measurements
|> Enum.filter(fn %{"serviceId" => id} -> id == service_id end)
|> Jason.encode!()
# Used so the chart is always 3 hours long
defp create_dummy_data(service_id, start_time) do
%{
"serviceId" => service_id,
"responseTime" => nil,
"startTime" => start_time,
"endTime" => start_time,
"measurementsCount" => 0,
"outages" => 0,
"tries" => 1
}
end
def get_service_status_text(measurements, service_id) do
......@@ -56,11 +63,10 @@ defmodule FrontendWeb.HostView do
end
end
@doc """
Returns the status of the specified status. Status can be either :ok, :warning, :offline or :unkown.
"""
defp service_status(service_id, measurements) do
"""
Returns the status of the specified status. Status can be either :ok, :warning, :offline or :unkown.
"""
last_measurement =
measurements
|> Enum.filter(fn %{"serviceId" => id} -> id == service_id end)
......
......@@ -19,7 +19,7 @@ defmodule Frontend.MixProject do
def application do
[
mod: {Frontend.Application, []},
extra_applications: [:logger, :runtime_tools, :httpoison, :timex]
extra_applications: [:logger, :runtime_tools, :timex, :amqp]
]
end
......@@ -39,8 +39,8 @@ defmodule Frontend.MixProject do
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:poison, "~> 3.1"},
{:httpoison, "~> 1.4"},
{:tesla, "~> 1.2.0"},
{:hackney, "~> 1.14.0"},
{:timex, "~> 3.5.0"},
{:amqp, "~> 1.1.0"}
]
......
name: frontend
port: 80
targetPort: 4000
......
name: measurements-service
port: 80
targetPort: 5000
istio: true
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment