Compare commits

...

8 commits

7 changed files with 274 additions and 83 deletions

View file

@ -3,9 +3,12 @@ defmodule FreediveWeb.HomeLive do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<.section> <.block class="px-2 py-4">
<.panel is-info> <.panel is-info>
<.panel_heading>Home</.panel_heading> <.panel_heading>
Home
</.panel_heading>
<.panel_tabs is-hidden-mobile> <.panel_tabs is-hidden-mobile>
<a class="is-active">All</a> <a class="is-active">All</a>
<a>Compute</a> <a>Compute</a>
@ -13,21 +16,22 @@ defmodule FreediveWeb.HomeLive do
<a>Network</a> <a>Network</a>
<a>System</a> <a>System</a>
</.panel_tabs> </.panel_tabs>
<.panel_tabs is-hidden-tablet> <.panel_tabs is-hidden-tablet>
<a class="is-active" title="All"> <a title="All" is-active>
<.icon for="infinity" color="auto" /> <.icon for="all" color="auto" />
</a> </a>
<a title="Compute"> <a title="Compute">
<.icon for="binary" color="auto" /> <.icon for="compute" color="auto" />
</a> </a>
<a title="Storage"> <a title="Storage">
<.icon for="hard-drive" color="auto" /> <.icon for="storage" color="auto" />
</a> </a>
<a title="Network"> <a title="Network">
<.icon for="earth" color="auto" /> <.icon for="network" color="auto" />
</a> </a>
<a title="System"> <a title="System">
<.icon for="bot" color="auto" /> <.icon for="system" color="auto" />
</a> </a>
</.panel_tabs> </.panel_tabs>
@ -40,11 +44,10 @@ defmodule FreediveWeb.HomeLive do
name="search" name="search"
value={@query} value={@query}
/> />
<span class="icon is-left"> <.icon for="search" size="1.5rem" aria-hidden="true" is-left />
<Lucideicons.search aria-hidden="true" />
</span>
</.control> </.control>
</.panel_block> </.panel_block>
<.link patch={~p"/services"} class="panel-block pt-1"> <.link patch={~p"/services"} class="panel-block pt-1">
<span class="panel-icon"> <span class="panel-icon">
<.icon for="puzzle" color="auto" aria-hidden="true" /> <.icon for="puzzle" color="auto" aria-hidden="true" />
@ -64,12 +67,32 @@ defmodule FreediveWeb.HomeLive do
<span class="mt-2 ml-2">Software updates</span> <span class="mt-2 ml-2">Software updates</span>
</.link> </.link>
</.panel> </.panel>
</.block>
<.section>
<.box>
<.button phx-click="color" phx-value-enable="true">
Color
</.button>
<.button phx-click="color" phx-value-enable="false">
Grayscale
</.button>
</.box>
</.section> </.section>
""" """
end end
def mount(_params, _session, socket) do def mount(_params, _session, socket) do
socket = assign(socket, query: "") socket = assign(socket, query: "all")
{:ok, socket} {:ok, socket}
end end
def handle_event("color", %{"enable" => "true"}, socket) do
Freedive.Features.enable(:colorhash)
{:noreply, assign(socket, query: "color")}
end
def handle_event("color", %{"enable" => "false"}, socket) do
Freedive.Features.disable(:colorhash)
{:noreply, assign(socket, query: "grayscale")}
end
end end

View file

@ -1,79 +1,80 @@
defmodule FreediveWeb.LiliformLive do defmodule FreediveWeb.LiliformLive do
use FreediveWeb, :live_view defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
use FreediveWeb, :live_view
@behaviour FreediveWeb.LiliformLive
@filters_all [
%{
title: "All",
icon: "all",
active: false,
key: :all
}
]
def render(assigns) do def(mount(_params, _session, socket)) do
~H""" items = items()
<.block class="px-2 py-4"> filters = filters()
<.panel is-info> socket = assign(socket, :opts, Keyword.get(unquote(opts), :opts, []))
<.panel_heading> socket = assign(socket, :items_all, items)
Home
</.panel_heading>
<.panel_tabs is-hidden-mobile> socket =
<a class="is-active">All</a> assign(
<a>Compute</a> socket,
<a>Storage</a> :items,
<a>Network</a> FreediveWeb.LiliformLive.filter(
<a>System</a> items,
</.panel_tabs> FreediveWeb.LiliformLive.get_active_filter(filters)
)
)
<.panel_tabs is-hidden-tablet> socket = assign(socket, :filters, @filters_all ++ filters)
<a title="All" is-active> socket = assign(socket, :query, "")
<.icon for="all" color="auto" /> {:ok, socket}
</a> end
<a title="Compute">
<.icon for="compute" color="auto" />
</a>
<a title="Storage">
<.icon for="storage" color="auto" />
</a>
<a title="Network">
<.icon for="network" color="auto" />
</a>
<a title="System">
<.icon for="system" color="auto" />
</a>
</.panel_tabs>
<.panel_block> def(handle_event("search", %{"value" => query}, socket)) do
<.control has-icons-left> items = search(query)
<input socket = assign(socket, :items, items)
class="input is-info" {:noreply, socket}
type="text" end
placeholder="Search"
name="search" def(handle_event("filter", %{"key" => key}, socket)) do
value={@query} key = String.to_existing_atom(key)
/> items = FreediveWeb.LiliformLive.filter(socket.assigns.items_all, key)
<.icon for="search" size="1.5rem" aria-hidden="true" is-left /> socket = assign(socket, :items, items)
</.control>
</.panel_block> socket =
</.panel> assign(
</.block> socket,
<.section> :filters,
<.box> Enum.map(socket.assigns.filters, fn filter ->
<.button phx-click="color" phx-value-enable="true"> Map.put(filter, :active, filter.key == key)
Color end)
</.button> )
<.button phx-click="color" phx-value-enable="false">
Grayscale {:noreply, socket}
</.button> end
</.box> end
</.section>
"""
end end
def mount(_params, _session, socket) do @callback items() :: [map]
socket = assign(socket, query: "all") @callback filters() :: [map]
{:ok, socket} @callback search(query :: String.t()) :: [map]
def filter(items, key) do
case key do
:all ->
items
_ ->
Enum.filter(items, fn item ->
Map.get(item, key) == true
end)
end
end end
def handle_event("color", %{"enable" => "true"}, socket) do def get_active_filter(filters) do
Freedive.Features.enable(:colorhash) filters |> Enum.find(& &1.active) |> Map.get(:key)
{:noreply, assign(socket, query: "color")}
end
def handle_event("color", %{"enable" => "false"}, socket) do
Freedive.Features.disable(:colorhash)
{:noreply, assign(socket, query: "grayscale")}
end end
end end

View file

@ -0,0 +1,97 @@
defmodule FreediveWeb.ServiceLive do
use FreediveWeb.LiliformLive
def render(assigns) do
~H"""
<.block class="px-2 py-4">
<.page name="Services" filters={@filters}>
<FreediveWeb.ServiceLive.Item.items_block items={@items} />
</.page>
</.block>
"""
end
def items() do
[
%{
name: "sshd",
path: "/services/ssh",
icon: "blocks",
description: "Secure Shell Daemon",
enabled: true,
running: true
},
%{
name: "pf",
path: "/services/pf",
icon: "shield",
description: "Packet Filter",
enabled: true,
running: true
},
%{
name: "ntpdate",
path: "/services/ntp",
icon: "clock",
description: "Network Time Protocol Daemon",
enabled: true,
running: false
},
%{
name: "httpd",
path: "/services/httpd",
icon: "globe",
description: "Hypertext Transfer Protocol Daemon",
enabled: false,
running: false
}
]
end
def filters() do
[
%{
title: "Running",
key: :running,
icon: "circle-play",
active: true
},
%{
title: "Enabled",
key: :enabled,
icon: "circle-check",
active: false
}
]
end
def search(query) do
items()
|> Enum.filter(fn item ->
String.contains?(String.downcase(item.name), String.downcase(query))
end)
end
end
defmodule FreediveWeb.ServiceLive.Item do
use Liliform.Component
import Liliform.Icon
@doc """
Returns a list of items.
"""
attr :items, :list, default: [], doc: "items"
def items_block(assigns) do
~H"""
<%= for item <- @items do %>
<.link patch={item.path} class="panel-block pt-1">
<span class="panel-icon">
<.icon for={item.icon} color="auto" aria-hidden="true" />
</span>
<div class="mt-2 ml-2"><%= item.name %></div>
</.link>
<% end %>
"""
end
end

View file

@ -69,7 +69,7 @@ defmodule FreediveWeb.Router do
live_session :require_authenticated_user, live_session :require_authenticated_user,
on_mount: [{FreediveWeb.UserAuth, :ensure_authenticated}] do on_mount: [{FreediveWeb.UserAuth, :ensure_authenticated}] do
live "/", HomeLive live "/", HomeLive
live "/liliform", LiliformLive live "/services", ServiceLive
live "/users/settings", UserSettingsLive, :edit live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
end end

View file

@ -15,6 +15,7 @@ defmodule Liliform do
import Liliform.Label import Liliform.Label
import Liliform.Media import Liliform.Media
import Liliform.Navbar import Liliform.Navbar
import Liliform.Page
import Liliform.Panel import Liliform.Panel
import Liliform.Section import Liliform.Section
import Liliform.SimpleForm import Liliform.SimpleForm

View file

@ -21,7 +21,6 @@ defmodule Liliform.Colorhash do
""" """
def hsl(input, raw: true) do def hsl(input, raw: true) do
if Features.enabled?(:colorhash) do if Features.enabled?(:colorhash) do
Logger.debug("Colorhash enabled")
input = String.downcase(input) <> @seed input = String.downcase(input) <> @seed
hash = :erlang.phash2(input, 2_147_483_647) hash = :erlang.phash2(input, 2_147_483_647)
@ -31,7 +30,6 @@ defmodule Liliform.Colorhash do
{hue, saturation, lightness} {hue, saturation, lightness}
else else
Logger.debug("Colorhash disabled")
@default_color @default_color
end end
end end

71
lib/liliform/page.ex Normal file
View file

@ -0,0 +1,71 @@
defmodule Liliform.Page do
use Liliform.Component
import Liliform.Icon
import Liliform.Panel
import Liliform.Control
@doc """
Renders a panel as page.
"""
attr :name, :string, required: true, doc: "page name"
attr :class, :string, default: "", doc: "additional classes"
attr :filters, :list, default: [], doc: "filters"
attr :query, :string, default: "", doc: "search query"
attr :rest, :global
slot :inner_block, required: true
def page(assigns) do
assigns =
assigns
|> set_bulma_classes()
~H"""
<.panel class={@class} {@rest} is-info>
<.panel_heading>
<%= @name %>
</.panel_heading>
<.panel_tabs is-hidden-mobile>
<%= for filter <- @filters do %>
<a
class={if filter.active, do: "is-active"}
phx-click="filter"
phx-value-key={filter.key}
>
<%= filter.title %>
</a>
<% end %>
</.panel_tabs>
<.panel_tabs is-hidden-tablet>
<%= for filter <- @filters do %>
<a
title={filter.title}
class={if filter.active, do: "is-active"}
phx-click="filter"
phx-value-key={filter.key}
>
<.icon for={filter.icon} color="auto" />
</a>
<% end %>
</.panel_tabs>
<.panel_block>
<.control has-icons-left>
<input
class="input is-info"
type="text"
placeholder="Search"
name="search"
value={@query}
phx-keyup="search"
/>
<.icon for="search" size="1.5rem" aria-hidden="true" is-left />
</.control>
</.panel_block>
<%= render_slot(@inner_block) %>
</.panel>
"""
end
end