mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(portal): UX improvements (#7013)
This PR accumulates lots of small UX fixes from #6645. --------- Co-authored-by: Jamil Bou Kheir <jamilbk@users.noreply.github.com>
This commit is contained in:
@@ -661,7 +661,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
}, otel_ctx}
|
||||
)
|
||||
|
||||
assert_push "request_connection", %{}
|
||||
assert_push "request_connection", %{}, 200
|
||||
|
||||
{:updated, resource} =
|
||||
Domain.Resources.update_or_replace_resource(
|
||||
@@ -670,7 +670,7 @@ defmodule API.Gateway.ChannelTest do
|
||||
subject
|
||||
)
|
||||
|
||||
assert_push "resource_updated", payload
|
||||
assert_push "resource_updated", payload, 200
|
||||
|
||||
assert payload == %{
|
||||
address: resource.address,
|
||||
|
||||
@@ -194,7 +194,7 @@ defmodule Domain.Actors.Actor.Query do
|
||||
title: "Status",
|
||||
type: :string,
|
||||
values: [
|
||||
{"Enabled", "enabled"},
|
||||
{"Active", "active"},
|
||||
{"Disabled", "disabled"}
|
||||
],
|
||||
fun: &filter_by_status/2
|
||||
@@ -234,7 +234,7 @@ defmodule Domain.Actors.Actor.Query do
|
||||
}
|
||||
]
|
||||
|
||||
def filter_by_status(queryable, "enabled") do
|
||||
def filter_by_status(queryable, "active") do
|
||||
{queryable, dynamic([actors: actors], is_nil(actors.disabled_at))}
|
||||
end
|
||||
|
||||
|
||||
@@ -100,4 +100,27 @@ defmodule Domain.Flows.Flow.Query do
|
||||
{:flows, :desc, :inserted_at},
|
||||
{:flows, :asc, :id}
|
||||
]
|
||||
|
||||
@impl Domain.Repo.Query
|
||||
def filters,
|
||||
do: [
|
||||
%Domain.Repo.Filter{
|
||||
name: :expiration,
|
||||
title: "Expired",
|
||||
type: :string,
|
||||
values: [
|
||||
{"Expired", "expired"},
|
||||
{"Not Expired", "not_expired"}
|
||||
],
|
||||
fun: &filter_by_expired/2
|
||||
}
|
||||
]
|
||||
|
||||
def filter_by_expired(queryable, "expired") do
|
||||
{queryable, dynamic([flows: flows], flows.expires_at < fragment("NOW()"))}
|
||||
end
|
||||
|
||||
def filter_by_expired(queryable, "not_expired") do
|
||||
{queryable, dynamic([flows: flows], flows.expires_at >= fragment("NOW()"))}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,6 +39,9 @@ defmodule Domain.Repo.Changeset do
|
||||
Keyword.has_key?(changeset.errors, field)
|
||||
end
|
||||
|
||||
def empty?(%Ecto.Changeset{} = changeset), do: Enum.empty?(changeset.changes)
|
||||
def empty?(%{}), do: true
|
||||
|
||||
def empty?(%Ecto.Changeset{} = changeset, field) do
|
||||
case fetch_field(changeset, field) do
|
||||
:error -> true
|
||||
@@ -505,7 +508,7 @@ defmodule Domain.Repo.Changeset do
|
||||
data = Map.get(changeset.data, field)
|
||||
changes = get_change(changeset, field)
|
||||
|
||||
if required? and is_nil(changes) and empty?(data) do
|
||||
if required? and is_nil(changes) and empty_value?(data) do
|
||||
add_error(changeset, field, "can't be blank", validation: :required)
|
||||
else
|
||||
%Changeset{} = nested_changeset = on_cast.(data || %{}, changes || %{})
|
||||
@@ -546,7 +549,7 @@ defmodule Domain.Repo.Changeset do
|
||||
Keyword.has_key?(changeset.errors, field)
|
||||
end
|
||||
|
||||
defp empty?(term), do: is_nil(term) or term == %{}
|
||||
defp empty_value?(term), do: is_nil(term) or term == %{}
|
||||
|
||||
defp dump(changeset, field, original_type) do
|
||||
map =
|
||||
|
||||
@@ -472,6 +472,12 @@ defmodule Web.CoreComponents do
|
||||
"""
|
||||
end
|
||||
|
||||
def icon(%{name: "firezone"} = assigns) do
|
||||
~H"""
|
||||
<img src={~p"/images/logo.svg"} class={["inline-block", @class]} {@rest} />
|
||||
"""
|
||||
end
|
||||
|
||||
def icon(%{name: "spinner"} = assigns) do
|
||||
~H"""
|
||||
<svg
|
||||
@@ -783,14 +789,19 @@ defmodule Web.CoreComponents do
|
||||
"""
|
||||
attr :datetime, DateTime, default: nil
|
||||
attr :relative_to, DateTime, required: false
|
||||
attr :negative_class, :string, default: ""
|
||||
|
||||
def relative_datetime(assigns) do
|
||||
assigns = assign_new(assigns, :relative_to, fn -> DateTime.utc_now() end)
|
||||
assigns =
|
||||
assign_new(assigns, :relative_to, fn -> DateTime.utc_now() end)
|
||||
|
||||
~H"""
|
||||
<.popover :if={not is_nil(@datetime)}>
|
||||
<:target>
|
||||
<span class="underline underline-offset-2 decoration-dashed">
|
||||
<span class={[
|
||||
"underline underline-offset-2 decoration-1 decoration-dotted",
|
||||
DateTime.compare(@datetime, @relative_to) == :lt && @negative_class
|
||||
]}>
|
||||
<%= Cldr.DateTime.Relative.to_string!(@datetime, Web.CLDR, relative_to: @relative_to)
|
||||
|> String.capitalize() %>
|
||||
</span>
|
||||
@@ -1021,8 +1032,8 @@ defmodule Web.CoreComponents do
|
||||
text-xs
|
||||
rounded-l
|
||||
py-0.5 px-1.5
|
||||
text-neutral-900
|
||||
bg-neutral-50
|
||||
text-neutral-800
|
||||
bg-neutral-100
|
||||
border-neutral-100
|
||||
border
|
||||
]}
|
||||
@@ -1035,7 +1046,7 @@ defmodule Web.CoreComponents do
|
||||
rounded-r
|
||||
mr-2 py-0.5 pl-1.5 pr-2.5
|
||||
text-neutral-900
|
||||
bg-neutral-100
|
||||
bg-neutral-50
|
||||
]}>
|
||||
<span class="block truncate" title={get_identity_email(@identity)}>
|
||||
<%= get_identity_email(@identity) %>
|
||||
@@ -1068,14 +1079,25 @@ defmodule Web.CoreComponents do
|
||||
class={~w[
|
||||
rounded-l
|
||||
py-0.5 px-1.5
|
||||
text-neutral-900
|
||||
bg-neutral-50
|
||||
text-neutral-800
|
||||
bg-neutral-100
|
||||
border-neutral-100
|
||||
border
|
||||
]}
|
||||
>
|
||||
<.provider_icon adapter={@group.provider.adapter} class="h-3.5 w-3.5" />
|
||||
</.link>
|
||||
<div :if={not Actors.group_synced?(@group)} title="Manually managed in Firezone" class={~w[
|
||||
inline-flex
|
||||
rounded-l
|
||||
py-0.5 px-1.5
|
||||
text-neutral-800
|
||||
bg-neutral-100
|
||||
border-neutral-100
|
||||
border
|
||||
]}>
|
||||
<.icon name="firezone" class="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<.link
|
||||
title={"View Group \"#{@group.name}\""}
|
||||
navigate={~p"/#{@account}/groups/#{@group}"}
|
||||
@@ -1083,10 +1105,10 @@ defmodule Web.CoreComponents do
|
||||
text-xs
|
||||
truncate
|
||||
min-w-0
|
||||
#{if(Actors.group_synced?(@group), do: "rounded-r pl-1.5 pr-2.5", else: "rounded px-1.5")}
|
||||
rounded-r pl-1.5 pr-2.5
|
||||
py-0.5
|
||||
text-neutral-800
|
||||
bg-neutral-100
|
||||
text-neutral-900
|
||||
bg-neutral-50
|
||||
]}
|
||||
>
|
||||
<%= @group.name %>
|
||||
|
||||
@@ -387,9 +387,11 @@ defmodule Web.FormComponents do
|
||||
attr :id, :string, required: true, doc: "The id of the dialog"
|
||||
attr :class, :string, default: "", doc: "Custom classes to be added to the button"
|
||||
attr :style, :string, default: "danger", doc: "The style of the button"
|
||||
attr :confirm_style, :string, default: "danger", doc: "The style of the confirm button"
|
||||
attr :icon, :string, default: nil, doc: "The icon of the button"
|
||||
attr :size, :string, default: "md", doc: "The size of the button"
|
||||
attr :on_confirm, :string, required: true, doc: "The phx event to broadcast on confirm"
|
||||
attr :disabled, :boolean, default: false, doc: "Whether the button is disabled"
|
||||
|
||||
attr :on_confirm_id, :string,
|
||||
default: nil,
|
||||
@@ -418,7 +420,7 @@ defmodule Web.FormComponents do
|
||||
<%= render_slot(@dialog_title) %>
|
||||
</h3>
|
||||
<button
|
||||
class="text-neutral-400 bg-transparent hover:text-accent-900"
|
||||
class="text-neutral-400 bg-transparent hover:text-accent-900 ml-2"
|
||||
type="submit"
|
||||
value="cancel"
|
||||
>
|
||||
@@ -444,7 +446,7 @@ defmodule Web.FormComponents do
|
||||
phx-click={@on_confirm}
|
||||
phx-value-id={@on_confirm_id}
|
||||
type="submit"
|
||||
style="danger"
|
||||
style={@confirm_style}
|
||||
value="confirm"
|
||||
class="py-2.5 px-5 ms-3"
|
||||
>
|
||||
@@ -454,7 +456,15 @@ defmodule Web.FormComponents do
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<.button id={@id} style={@style} size={@size} icon={@icon} class={@class} phx-hook="ConfirmDialog">
|
||||
<.button
|
||||
id={@id}
|
||||
style={@style}
|
||||
size={@size}
|
||||
icon={@icon}
|
||||
class={@class}
|
||||
disabled={@disabled}
|
||||
phx-hook="ConfirmDialog"
|
||||
>
|
||||
<%= render_slot(@inner_block) %>
|
||||
</.button>
|
||||
"""
|
||||
@@ -635,6 +645,15 @@ defmodule Web.FormComponents do
|
||||
]
|
||||
end
|
||||
|
||||
def button_style("disabled") do
|
||||
button_style() ++
|
||||
[
|
||||
"text-neutral-200",
|
||||
"border border-neutral-200",
|
||||
"cursor-not-allowed"
|
||||
]
|
||||
end
|
||||
|
||||
def button_style(_style) do
|
||||
button_style() ++
|
||||
[
|
||||
|
||||
@@ -347,4 +347,30 @@ defmodule Web.NavigationComponents do
|
||||
</.link>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders links to the docs based off documentation portal path.
|
||||
|
||||
## Examples
|
||||
|
||||
<.website_link href="/pricing>Pricing</.website_link>
|
||||
<.website_link href="/kb/deploy/gateways" class="text-neutral-900">Deploy Gateway(s)</.website_link>
|
||||
<.website_link href={~p"/contact/sales"}>Contact Sales</.website_link>
|
||||
"""
|
||||
attr :path, :string, required: true
|
||||
attr :fragment, :string, required: false, default: ""
|
||||
attr :rest, :global
|
||||
|
||||
def docs_action(assigns) do
|
||||
~H"""
|
||||
<.link
|
||||
title="View documentation for this page"
|
||||
href={"https://www.firezone.dev/kb#{@path}?utm_source=product##{@fragment}"}
|
||||
target="_blank"
|
||||
{@rest}
|
||||
>
|
||||
<.icon name="hero-question-mark-circle" class="mr-2 w-5 h-5" />
|
||||
</.link>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,7 +18,10 @@ defmodule Web.TableComponents do
|
||||
~H"""
|
||||
<thead id={"#{@table_id}-header"} class="text-xs text-neutral-700 uppercase bg-neutral-50">
|
||||
<tr>
|
||||
<th :for={col <- @columns} class={["px-4 py-3 font-medium", Map.get(col, :class, "")]}>
|
||||
<th
|
||||
:for={col <- @columns}
|
||||
class={["px-4 py-3 font-medium whitespace-nowrap", Map.get(col, :class, "")]}
|
||||
>
|
||||
<%= col[:label] %>
|
||||
<.table_header_order_buttons
|
||||
:if={col[:field]}
|
||||
|
||||
@@ -9,15 +9,31 @@ defmodule Web.Actors.Components do
|
||||
def actor_role(:account_user), do: "user"
|
||||
def actor_role(:account_admin_user), do: "admin"
|
||||
|
||||
attr :token, :any, required: true
|
||||
attr :class, :string, default: ""
|
||||
|
||||
def token_type_icon(assigns) do
|
||||
~H"""
|
||||
<.icon name={token_type_icon_name(@token.type)} class={@class} />
|
||||
"""
|
||||
end
|
||||
|
||||
defp token_type_icon_name(:browser), do: "hero-computer-window"
|
||||
defp token_type_icon_name(:client), do: "hero-device-phone-mobile"
|
||||
defp token_type_icon_name(:api_client), do: "hero-command-line"
|
||||
|
||||
attr :actor, :any, required: true
|
||||
|
||||
def actor_status(assigns) do
|
||||
~H"""
|
||||
<span :if={Actors.actor_disabled?(@actor)} class="text-red-800">
|
||||
(Disabled)
|
||||
Disabled
|
||||
</span>
|
||||
<span :if={Actors.actor_deleted?(@actor)} class="text-red-800">
|
||||
(Deleted)
|
||||
Deleted
|
||||
</span>
|
||||
<span :if={not Actors.actor_disabled?(@actor) and not Actors.actor_deleted?(@actor)}>
|
||||
Active
|
||||
</span>
|
||||
"""
|
||||
end
|
||||
@@ -53,9 +69,11 @@ defmodule Web.Actors.Components do
|
||||
<div>
|
||||
<.input
|
||||
:if={not Actors.actor_synced?(@actor)}
|
||||
label="Name"
|
||||
label="Full Name"
|
||||
field={@form[:name]}
|
||||
placeholder="Full Name"
|
||||
placeholder={
|
||||
if @type == :service_account, do: "E.g. My Backend Service", else: "E.g. John Smith"
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -75,9 +93,9 @@ defmodule Web.Actors.Components do
|
||||
required
|
||||
/>
|
||||
<p class="mt-2 text-xs text-gray-500">
|
||||
Select <strong>Admin</strong>
|
||||
to grant this user access to the admin portal. Otherwise, select <strong>User</strong>
|
||||
to limit access to the Client apps only.
|
||||
<strong>Admin</strong>
|
||||
grants full access to the admin portal and client applications. <strong>User</strong>
|
||||
grants access to client applications only.
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
@@ -91,7 +109,7 @@ defmodule Web.Actors.Components do
|
||||
<div>
|
||||
<.input
|
||||
label="Email"
|
||||
placeholder="Email"
|
||||
placeholder="Enter an email address"
|
||||
field={@form[:provider_identifier]}
|
||||
autocomplete="off"
|
||||
/>
|
||||
@@ -99,7 +117,7 @@ defmodule Web.Actors.Components do
|
||||
<div>
|
||||
<.input
|
||||
label="Email Confirmation"
|
||||
placeholder="Email Confirmation"
|
||||
placeholder="Enter the same email as above"
|
||||
field={@form[:provider_identifier_confirmation]}
|
||||
autocomplete="off"
|
||||
/>
|
||||
@@ -112,7 +130,7 @@ defmodule Web.Actors.Components do
|
||||
<div>
|
||||
<.input
|
||||
label="Email"
|
||||
placeholder="Email"
|
||||
placeholder="Enter an email address"
|
||||
field={@form[:provider_identifier]}
|
||||
autocomplete="off"
|
||||
/>
|
||||
@@ -124,7 +142,7 @@ defmodule Web.Actors.Components do
|
||||
<div>
|
||||
<.input
|
||||
label="Email Confirmation"
|
||||
placeholder="Email Confirmation"
|
||||
placeholder="Enter the same email as above"
|
||||
field={@form[:provider_identifier_confirmation]}
|
||||
autocomplete="off"
|
||||
/>
|
||||
|
||||
@@ -44,7 +44,6 @@ defmodule Web.Actors.Edit do
|
||||
</:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Edit User Details</h2>
|
||||
<.flash kind={:error} flash={@flash} />
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
|
||||
@@ -48,14 +48,20 @@ defmodule Web.Actors.Index do
|
||||
<.section>
|
||||
<:title><%= @page_title %></:title>
|
||||
|
||||
<:action>
|
||||
<.docs_action path="/deploy/users" />
|
||||
</:action>
|
||||
|
||||
<:action>
|
||||
<.add_button navigate={~p"/#{@account}/actors/new"}>
|
||||
Add Actor
|
||||
</.add_button>
|
||||
</:action>
|
||||
|
||||
<:help>
|
||||
Actors are the people and services that can access your Resources.
|
||||
</:help>
|
||||
|
||||
<:content>
|
||||
<.flash_group flash={@flash} />
|
||||
<.live_table
|
||||
@@ -103,7 +109,11 @@ defmodule Web.Actors.Index do
|
||||
</.peek>
|
||||
</:col>
|
||||
|
||||
<:col :let={actor} label="last signed in">
|
||||
<:col :let={actor} label="status" class="w-1/12">
|
||||
<.actor_status actor={actor} />
|
||||
</:col>
|
||||
|
||||
<:col :let={actor} label="last signed in" class="w-1/12">
|
||||
<.relative_datetime datetime={actor.last_seen_at} />
|
||||
</:col>
|
||||
</.live_table>
|
||||
|
||||
@@ -27,9 +27,6 @@ defmodule Web.Actors.ServiceAccounts.New do
|
||||
<:title><%= @page_title %></:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">
|
||||
Service Account details
|
||||
</h2>
|
||||
<.flash kind={:error} flash={@flash} />
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
|
||||
@@ -35,7 +35,7 @@ defmodule Web.Actors.ServiceAccounts.NewIdentity do
|
||||
<%= @actor.name %>
|
||||
</.breadcrumb>
|
||||
<.breadcrumb path={~p"/#{@account}/actors/service_accounts/#{@actor}/new_identity"}>
|
||||
Add Token
|
||||
Create Token
|
||||
</.breadcrumb>
|
||||
</.breadcrumbs>
|
||||
|
||||
@@ -43,7 +43,6 @@ defmodule Web.Actors.ServiceAccounts.NewIdentity do
|
||||
<:title><%= @page_title %></:title>
|
||||
<:content>
|
||||
<div :if={is_nil(@encoded_token)} class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Token details</h2>
|
||||
<.flash kind={:error} flash={@flash} />
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
|
||||
@@ -145,7 +145,8 @@ defmodule Web.Actors.Show do
|
||||
<:title>
|
||||
<%= actor_type(@actor.type) %>: <span class="font-medium"><%= @actor.name %></span>
|
||||
<span :if={@actor.id == @subject.actor.id} class="text-sm text-neutral-400">(you)</span>
|
||||
<span :if={not is_nil(@actor.deleted_at)} class="text-red-600">(deleted)</span>
|
||||
<span :if={Actors.actor_deleted?(@actor)} class="text-red-600">(deleted)</span>
|
||||
<span :if={Actors.actor_disabled?(@actor)} class="text-red-600">(disabled)</span>
|
||||
</:title>
|
||||
<:action :if={is_nil(@actor.deleted_at)}>
|
||||
<.edit_button navigate={~p"/#{@account}/actors/#{@actor}/edit"}>
|
||||
@@ -196,8 +197,9 @@ defmodule Web.Actors.Show do
|
||||
<.vertical_table id="actor">
|
||||
<.vertical_table_row>
|
||||
<:label>Name</:label>
|
||||
<:value><%= @actor.name %>
|
||||
<.actor_status actor={@actor} /></:value>
|
||||
<:value>
|
||||
<%= @actor.name %>
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
|
||||
<.vertical_table_row>
|
||||
@@ -376,24 +378,7 @@ defmodule Web.Actors.Show do
|
||||
<:col :let={token} :if={@actor.type != :service_account} label="identity" class="w-3/12">
|
||||
<.identity_identifier account={@account} identity={token.identity} />
|
||||
</:col>
|
||||
<:col :let={token} :if={@actor.type == :service_account} label="name" class="w-2/12">
|
||||
<%= token.name %>
|
||||
</:col>
|
||||
<:col :let={token} label="created">
|
||||
<.created_by account={@account} schema={token} />
|
||||
</:col>
|
||||
<:col :let={token} label="last used">
|
||||
<p>
|
||||
<.relative_datetime datetime={token.last_seen_at} />
|
||||
</p>
|
||||
<p :if={not is_nil(token.last_seen_at)}>
|
||||
<code class="text-xs"><.last_seen schema={token} /></code>
|
||||
</p>
|
||||
</:col>
|
||||
<:col :let={token} label="expires">
|
||||
<.relative_datetime datetime={token.expires_at} />
|
||||
</:col>
|
||||
<:col :let={token} label="client">
|
||||
<:col :let={token} label="client" class="w-1/12">
|
||||
<.intersperse_blocks :if={token.type == :client}>
|
||||
<:separator>, </:separator>
|
||||
|
||||
@@ -407,6 +392,34 @@ defmodule Web.Actors.Show do
|
||||
</.intersperse_blocks>
|
||||
<span :if={token.type != :client}>N/A</span>
|
||||
</:col>
|
||||
<:col :let={token} :if={@actor.type == :service_account} label="name" class="w-2/12">
|
||||
<%= token.name %>
|
||||
</:col>
|
||||
<:col :let={token} label="created" class="w-2/12">
|
||||
<.created_by account={@account} schema={token} />
|
||||
</:col>
|
||||
<:col :let={token} label="expires" class="w-1/12">
|
||||
<%= if DateTime.compare(token.expires_at, DateTime.utc_now()) == :lt do %>
|
||||
<.popover>
|
||||
<:target>
|
||||
expired
|
||||
</:target>
|
||||
<:content>
|
||||
<.datetime datetime={token.expires_at} />
|
||||
</:content>
|
||||
</.popover>
|
||||
<% else %>
|
||||
<.relative_datetime datetime={token.expires_at} negative_class="text-red-600" />
|
||||
<% end %>
|
||||
</:col>
|
||||
<:col :let={token} label="last used" class="w-3/12">
|
||||
<p>
|
||||
<.relative_datetime datetime={token.last_seen_at} />
|
||||
</p>
|
||||
<p :if={not is_nil(token.last_seen_at)}>
|
||||
<code class="text-xs"><.last_seen schema={token} /></code>
|
||||
</p>
|
||||
</:col>
|
||||
<:action :let={token}>
|
||||
<.button_with_confirmation
|
||||
id={"revoke_token_#{token.id}"}
|
||||
@@ -538,9 +551,7 @@ defmodule Web.Actors.Show do
|
||||
metadata={@groups_metadata}
|
||||
>
|
||||
<:col :let={group} label="name">
|
||||
<.link navigate={~p"/#{@account}/groups/#{group.id}"} class={[link_style()]}>
|
||||
<%= group.name %>
|
||||
</.link>
|
||||
<.group account={@account} group={group} />
|
||||
</:col>
|
||||
<:empty>
|
||||
<div class="text-center text-neutral-500 p-4">No Groups to display.</div>
|
||||
|
||||
@@ -27,7 +27,6 @@ defmodule Web.Actors.Users.New do
|
||||
<:title><%= @page_title %></:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">User details</h2>
|
||||
<.flash kind={:error} flash={@flash} />
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
|
||||
@@ -54,14 +54,13 @@ defmodule Web.Actors.Users.NewIdentity do
|
||||
<:title><%= @page_title %></:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Identity details</h2>
|
||||
<.flash kind={:error} flash={@flash} />
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
<.input
|
||||
type="select"
|
||||
label="Provider"
|
||||
label="Identity Provider"
|
||||
field={@form[:provider_id]}
|
||||
options={
|
||||
Enum.map(@providers, fn provider ->
|
||||
@@ -72,9 +71,6 @@ defmodule Web.Actors.Users.NewIdentity do
|
||||
placeholder="Provider"
|
||||
required
|
||||
/>
|
||||
<p class="mt-2 text-xs text-gray-500">
|
||||
Select the provider to use for signing in.
|
||||
</p>
|
||||
</div>
|
||||
<.provider_form :if={@provider} form={@form} provider={@provider} />
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,6 @@ defmodule Web.Clients.Edit do
|
||||
</:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Edit client details</h2>
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
defmodule Web.Clients.Index do
|
||||
use Web, :live_view
|
||||
import Web.Actors.Components
|
||||
alias Domain.Clients
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
@@ -54,6 +55,9 @@ defmodule Web.Clients.Index do
|
||||
<:help>
|
||||
Clients are end-user devices and servers that access your protected Resources.
|
||||
</:help>
|
||||
<:action>
|
||||
<.docs_action path="/deploy/clients" />
|
||||
</:action>
|
||||
<:content>
|
||||
<.flash_group flash={@flash} />
|
||||
<.live_table
|
||||
@@ -70,12 +74,17 @@ defmodule Web.Clients.Index do
|
||||
<.link navigate={~p"/#{@account}/clients/#{client.id}"} class={[link_style()]}>
|
||||
<%= client.name %>
|
||||
</.link>
|
||||
<.icon :if={not is_nil(client.verified_at)} name="hero-shield-check" class="w-4 h-4" />
|
||||
<.icon
|
||||
:if={not is_nil(client.verified_at)}
|
||||
name="hero-shield-check"
|
||||
class="w-4 h-4"
|
||||
title="Device attributes of this client are manually verified"
|
||||
/>
|
||||
</div>
|
||||
</:col>
|
||||
<:col :let={client} label="user">
|
||||
<.link navigate={~p"/#{@account}/actors/#{client.actor.id}"} class={[link_style()]}>
|
||||
<%= client.actor.name %>
|
||||
<.actor_name_and_role account={@account} actor={client.actor} />
|
||||
</.link>
|
||||
</:col>
|
||||
<:col :let={client} label="status">
|
||||
|
||||
@@ -75,51 +75,12 @@ defmodule Web.Clients.Show do
|
||||
<span :if={not is_nil(@client.deleted_at)} class="text-red-600">(deleted)</span>
|
||||
</:title>
|
||||
|
||||
<:action :if={is_nil(@client.deleted_at) and not is_nil(@client.verified_at)}>
|
||||
<.button_with_confirmation
|
||||
id="remove_client_verification"
|
||||
style="danger"
|
||||
icon="hero-shield-exclamation"
|
||||
on_confirm="remove_client_verification"
|
||||
>
|
||||
<:dialog_title>Remove verification</:dialog_title>
|
||||
<:dialog_content>
|
||||
Are you sure you want to remove verification of this Client?
|
||||
</:dialog_content>
|
||||
<:dialog_confirm_button>
|
||||
Remove
|
||||
</:dialog_confirm_button>
|
||||
<:dialog_cancel_button>
|
||||
Cancel
|
||||
</:dialog_cancel_button>
|
||||
Remove verification
|
||||
</.button_with_confirmation>
|
||||
</:action>
|
||||
<:action :if={is_nil(@client.deleted_at) and is_nil(@client.verified_at)}>
|
||||
<.button_with_confirmation
|
||||
id="verify_client"
|
||||
style="warning"
|
||||
icon="hero-shield-check"
|
||||
on_confirm="verify_client"
|
||||
>
|
||||
<:dialog_title>Verify Client</:dialog_title>
|
||||
<:dialog_content>
|
||||
Are you sure you want to verify this Client?
|
||||
</:dialog_content>
|
||||
<:dialog_confirm_button>
|
||||
Verify
|
||||
</:dialog_confirm_button>
|
||||
<:dialog_cancel_button>
|
||||
Cancel
|
||||
</:dialog_cancel_button>
|
||||
Verify
|
||||
</.button_with_confirmation>
|
||||
</:action>
|
||||
<:action :if={is_nil(@client.deleted_at)}>
|
||||
<.edit_button navigate={~p"/#{@account}/clients/#{@client}/edit"}>
|
||||
Edit Client
|
||||
</.edit_button>
|
||||
</:action>
|
||||
|
||||
<:content>
|
||||
<.vertical_table id="client">
|
||||
<.vertical_table_row>
|
||||
@@ -143,12 +104,6 @@ defmodule Web.Clients.Show do
|
||||
<:label>Status</:label>
|
||||
<:value><.connection_status class="ml-1/2" schema={@client} /></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Verification</:label>
|
||||
<:value>
|
||||
<.verified_by account={@account} schema={@client} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Owner</:label>
|
||||
<:value>
|
||||
@@ -188,7 +143,7 @@ defmodule Web.Clients.Show do
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
<:label>Client version</:label>
|
||||
<:label>Version</:label>
|
||||
<:value><%= @client.last_seen_version %></:value>
|
||||
</.vertical_table_row>
|
||||
<.vertical_table_row>
|
||||
@@ -208,11 +163,60 @@ defmodule Web.Clients.Show do
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
</.vertical_table>
|
||||
</:content>
|
||||
</.section>
|
||||
|
||||
<h2 class="mt-6 mb-4 text-xl leading-none tracking-tight text-neutral-900">
|
||||
Device Attributes
|
||||
</h2>
|
||||
<.section>
|
||||
<:title>
|
||||
Device Attributes
|
||||
</:title>
|
||||
|
||||
<:help>
|
||||
Information about the device that the Client is running on.
|
||||
</:help>
|
||||
|
||||
<:action :if={is_nil(@client.deleted_at) and not is_nil(@client.verified_at)}>
|
||||
<.button_with_confirmation
|
||||
id="remove_client_verification"
|
||||
style="danger"
|
||||
icon="hero-shield-exclamation"
|
||||
on_confirm="remove_client_verification"
|
||||
>
|
||||
<:dialog_title>Remove verification</:dialog_title>
|
||||
<:dialog_content>
|
||||
Are you sure you want to remove verification of this Client?
|
||||
</:dialog_content>
|
||||
<:dialog_confirm_button>
|
||||
Remove
|
||||
</:dialog_confirm_button>
|
||||
<:dialog_cancel_button>
|
||||
Cancel
|
||||
</:dialog_cancel_button>
|
||||
Remove verification
|
||||
</.button_with_confirmation>
|
||||
</:action>
|
||||
<:action :if={is_nil(@client.deleted_at) and is_nil(@client.verified_at)}>
|
||||
<.button_with_confirmation
|
||||
id="verify_client"
|
||||
style="warning"
|
||||
icon="hero-shield-check"
|
||||
on_confirm="verify_client"
|
||||
>
|
||||
<:dialog_title>Verify Client</:dialog_title>
|
||||
<:dialog_content>
|
||||
Are you sure you want to verify this Client?
|
||||
</:dialog_content>
|
||||
<:dialog_confirm_button>
|
||||
Verify
|
||||
</:dialog_confirm_button>
|
||||
<:dialog_cancel_button>
|
||||
Cancel
|
||||
</:dialog_cancel_button>
|
||||
Verify
|
||||
</.button_with_confirmation>
|
||||
</:action>
|
||||
|
||||
<:content>
|
||||
<.vertical_table id="posture">
|
||||
<.vertical_table_row>
|
||||
<:label>
|
||||
@@ -250,6 +254,23 @@ defmodule Web.Clients.Show do
|
||||
<.last_seen schema={@client} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
|
||||
<.vertical_table_row>
|
||||
<:label>
|
||||
<.popover>
|
||||
<:target>
|
||||
Verification
|
||||
<.icon name="hero-question-mark-circle" class="w-3 h-3 mb-1 text-neutral-400" />
|
||||
</:target>
|
||||
<:content>
|
||||
Policies can be configured to require verification in order to access a Resource.
|
||||
</:content>
|
||||
</.popover>
|
||||
</:label>
|
||||
<:value>
|
||||
<.verified_by account={@account} schema={@client} />
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
</.vertical_table>
|
||||
</:content>
|
||||
</.section>
|
||||
|
||||
@@ -43,7 +43,6 @@ defmodule Web.Groups.Edit do
|
||||
</:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Edit group details</h2>
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
|
||||
@@ -141,13 +141,26 @@ defmodule Web.Groups.EditActors do
|
||||
</.live_table>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<.button_with_confirmation id="save_changes" style="primary" class="m-4" on_confirm="submit">
|
||||
<:dialog_title>Apply changes to Group Actors</:dialog_title>
|
||||
<.button_with_confirmation
|
||||
id="save_changes"
|
||||
style={(@added == %{} and @removed == %{} && "disabled") || "primary"}
|
||||
confirm_style="primary"
|
||||
class="m-4"
|
||||
on_confirm="submit"
|
||||
disabled={@added == %{} and @removed == %{}}
|
||||
>
|
||||
<:dialog_title>Confirm changes to Group Actors</:dialog_title>
|
||||
<:dialog_content>
|
||||
<%= confirm_message(@added, @removed) %>
|
||||
<div class="mb-2">
|
||||
You're about to apply the following membership changes for the
|
||||
<strong><%= @group.name %></strong>
|
||||
group:
|
||||
</div>
|
||||
|
||||
<.confirm_message added={@added} removed={@removed} />
|
||||
</:dialog_content>
|
||||
<:dialog_confirm_button>
|
||||
Save
|
||||
Confirm
|
||||
</:dialog_confirm_button>
|
||||
<:dialog_cancel_button>
|
||||
Cancel
|
||||
@@ -234,20 +247,19 @@ defmodule Web.Groups.EditActors do
|
||||
Map.has_key?(added, actor.id)
|
||||
end
|
||||
|
||||
# TODO: this should be replaced by a new state of a form which will render impact of a change
|
||||
defp confirm_message(added, removed) do
|
||||
added_names = Enum.map(added, fn {_id, name} -> name end)
|
||||
removed_names = Enum.map(removed, fn {_id, name} -> name end)
|
||||
defp confirm_message(assigns) do
|
||||
~H"""
|
||||
<ul>
|
||||
<li :for={{_id, name} <- @added} class="mb-2">
|
||||
<.icon name="hero-plus" class="h-3.5 w-3.5 mr-2 text-green-500" />
|
||||
<%= name %>
|
||||
</li>
|
||||
|
||||
add = if added_names != [], do: "add #{Enum.join(added_names, ", ")}"
|
||||
remove = if removed_names != [], do: "remove #{Enum.join(removed_names, ", ")}"
|
||||
change = [add, remove] |> Enum.reject(&is_nil/1) |> Enum.join(" and ")
|
||||
|
||||
if change == "" do
|
||||
# Don't show confirmation message if no changes were made
|
||||
nil
|
||||
else
|
||||
"Are you sure you want to #{change}?"
|
||||
end
|
||||
<li :for={{_id, name} <- @removed} class="mb-2">
|
||||
<.icon name="hero-minus" class="h-3.5 w-3.5 mr-2 text-red-500" />
|
||||
<%= name %>
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,14 +47,21 @@ defmodule Web.Groups.Index do
|
||||
<:title>
|
||||
Groups
|
||||
</:title>
|
||||
|
||||
<:action>
|
||||
<.docs_action path="/deploy/groups" />
|
||||
</:action>
|
||||
|
||||
<:action>
|
||||
<.add_button navigate={~p"/#{@account}/groups/new"}>
|
||||
Add Group
|
||||
</.add_button>
|
||||
</:action>
|
||||
|
||||
<:help>
|
||||
Groups organize Actors and form the basis of the Firezone access control model.
|
||||
</:help>
|
||||
|
||||
<:content>
|
||||
<.flash_group flash={@flash} />
|
||||
<.live_table
|
||||
@@ -67,9 +74,7 @@ defmodule Web.Groups.Index do
|
||||
metadata={@groups_metadata}
|
||||
>
|
||||
<:col :let={group} field={{:groups, :name}} label="name" class="w-2/4">
|
||||
<.link navigate={~p"/#{@account}/groups/#{group.id}"} class={[link_style()]}>
|
||||
<%= group.name %>
|
||||
</.link>
|
||||
<.group account={@account} group={group} />
|
||||
|
||||
<span :if={Actors.group_deleted?(group)} class="text-xs text-neutral-100">
|
||||
(deleted)
|
||||
|
||||
@@ -24,17 +24,16 @@ defmodule Web.Groups.New do
|
||||
<:title><%= @page_title %></:title>
|
||||
<:content>
|
||||
<div class="py-8 px-4 mx-auto max-w-2xl lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">
|
||||
Group details
|
||||
</h2>
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<.input type="hidden" field={@form[:type]} value="static" />
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
<.input label="Name" field={@form[:name]} placeholder="E.g. Engineering" required />
|
||||
<p class="text-sm mt-2 text-neutral-600">
|
||||
Enter a name for this Group.
|
||||
</p>
|
||||
<.input
|
||||
label="Group Name"
|
||||
field={@form[:name]}
|
||||
placeholder="E.g. Engineering"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<.submit_button>
|
||||
|
||||
@@ -48,7 +48,7 @@ defmodule Web.Policies.Edit do
|
||||
<:title><%= @page_title %></:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Policy details</h2>
|
||||
<legend class="mb-4 text-xl text-neutral-900">Details</legend>
|
||||
|
||||
<.form for={@form} phx-submit="submit" phx-change="validate">
|
||||
<.base_error form={@form} field={:base} />
|
||||
|
||||
@@ -50,6 +50,9 @@ defmodule Web.Policies.Index do
|
||||
|
||||
<.section>
|
||||
<:title><%= @page_title %></:title>
|
||||
<:action>
|
||||
<.docs_action path="/deploy/policies" />
|
||||
</:action>
|
||||
<:action>
|
||||
<.add_button navigate={~p"/#{@account}/policies/new"}>
|
||||
Add Policy
|
||||
@@ -91,7 +94,7 @@ defmodule Web.Policies.Index do
|
||||
<%= if is_nil(policy.disabled_at) do %>
|
||||
Active
|
||||
<% else %>
|
||||
Disabled
|
||||
<span class="text-red-800">Disabled</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
Deleted
|
||||
|
||||
@@ -35,7 +35,7 @@ defmodule Web.Policies.New do
|
||||
<:title><%= @page_title %></:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Policy details</h2>
|
||||
<legend class="mb-4 text-xl text-neutral-900">Details</legend>
|
||||
|
||||
<.form for={@form} phx-submit="submit" phx-change="validate">
|
||||
<.base_error form={@form} field={:base} />
|
||||
|
||||
@@ -116,7 +116,7 @@ defmodule Web.Resources.Components do
|
||||
~H"""
|
||||
<fieldset class="flex flex-col gap-2">
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<legend>Traffic Restriction</legend>
|
||||
<legend class="text-xl">Traffic Restriction</legend>
|
||||
|
||||
<%= if @traffic_filters_enabled? == false do %>
|
||||
<.link navigate={~p"/#{@account}/settings/billing"} class="text-sm text-primary-500">
|
||||
@@ -136,7 +136,7 @@ defmodule Web.Resources.Components do
|
||||
@traffic_filters_enabled? == false && "opacity-50",
|
||||
"mt-4"
|
||||
]}>
|
||||
<div class="flex items-top h-20">
|
||||
<div class="flex items-top mb-4">
|
||||
<.input type="hidden" name={"#{@form.name}[tcp][protocol]"} value="tcp" />
|
||||
<div class="mt-2.5 w-24">
|
||||
<.input
|
||||
@@ -169,7 +169,7 @@ defmodule Web.Resources.Components do
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-top h-20">
|
||||
<div class="flex items-top mb-4">
|
||||
<.input type="hidden" name={"#{@form.name}[udp][protocol]"} value="udp" />
|
||||
<div class="mt-2.5 w-24">
|
||||
<.input
|
||||
@@ -201,7 +201,7 @@ defmodule Web.Resources.Components do
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-top h-20">
|
||||
<div class="flex items-top mb-4">
|
||||
<.input type="hidden" name={"#{@form.name}[icmp][protocol]"} value="icmp" />
|
||||
|
||||
<div class="mt-2.5 w-24">
|
||||
@@ -287,7 +287,11 @@ defmodule Web.Resources.Components do
|
||||
|
||||
~H"""
|
||||
<fieldset class="flex flex-col gap-2" {@rest}>
|
||||
<legend class="mb-2">Sites</legend>
|
||||
<legend class="text-xl mb-4">Sites</legend>
|
||||
|
||||
<p class="text-sm text-neutral-500">
|
||||
When multiple sites are selected, the client will automatically connect to the closest one based on its geographical location.
|
||||
</p>
|
||||
|
||||
<.error :for={error <- @errors} data-validation-error-for="connections">
|
||||
<%= error %>
|
||||
|
||||
@@ -47,8 +47,6 @@ defmodule Web.Resources.Edit do
|
||||
</:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Edit Resource details</h2>
|
||||
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit} class="space-y-4 lg:space-y-6">
|
||||
<div :if={@resource.type != :internet}>
|
||||
<p class="mb-2 text-sm text-neutral-900">
|
||||
|
||||
@@ -57,6 +57,9 @@ defmodule Web.Resources.Index do
|
||||
Resources define the subnets, hosts, and applications for which you want to manage access. You can manage Resources per Site
|
||||
in the <.link navigate={~p"/#{@account}/sites"} class={link_style()}>Sites</.link> section.
|
||||
</:help>
|
||||
<:action>
|
||||
<.docs_action path="/deploy/resources" />
|
||||
</:action>
|
||||
<:action>
|
||||
<.add_button navigate={~p"/#{@account}/resources/new"}>
|
||||
Add Resource
|
||||
|
||||
@@ -32,7 +32,7 @@ defmodule Web.Resources.New do
|
||||
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Resource details</h2>
|
||||
<legend class="text-xl mb-4">Details</legend>
|
||||
<.form for={@form} class="space-y-4 lg:space-y-6" phx-submit="submit" phx-change="change">
|
||||
<div>
|
||||
<p class="mb-2 text-sm text-neutral-900">
|
||||
|
||||
@@ -30,7 +30,6 @@ defmodule Web.Settings.Account.Edit do
|
||||
</:title>
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto lg:py-16">
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Edit account details</h2>
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
|
||||
@@ -63,6 +63,9 @@ defmodule Web.Settings.ApiClients.Index do
|
||||
for more information.
|
||||
</:help>
|
||||
|
||||
<:action>
|
||||
<.docs_action path="/reference/rest-api" />
|
||||
</:action>
|
||||
<:action>
|
||||
<.add_button navigate={~p"/#{@account}/settings/api_clients/new"}>
|
||||
Add API Client
|
||||
|
||||
@@ -32,24 +32,35 @@ defmodule Web.Settings.DNS do
|
||||
<:title>
|
||||
DNS
|
||||
</:title>
|
||||
|
||||
<:action>
|
||||
<.docs_action path="/deploy/dns" />
|
||||
</:action>
|
||||
|
||||
<:help>
|
||||
Configure the default resolver used by connected Clients.
|
||||
Queries for Resources will <strong>always</strong>
|
||||
use Firezone's internal DNS.
|
||||
All other queries will use the DNS servers configured here or the Client's
|
||||
system resolvers if no servers are configured.
|
||||
<p class="mt-2">
|
||||
<.website_link path="/kb/deploy/dns">
|
||||
Read more about configuring DNS in Firezone.
|
||||
</.website_link>
|
||||
</p>
|
||||
Configure the upstream resolver used by connected Clients.
|
||||
Queries for Resources will <strong>always</strong> use Firezone's internal DNS.
|
||||
All other queries will use the resolvers configured here or the Client's
|
||||
system resolvers if none are configured.
|
||||
</:help>
|
||||
|
||||
<:content>
|
||||
<div class="max-w-2xl px-4 py-8 mx-auto">
|
||||
<.flash kind={:success} flash={@flash} phx-click="lv:clear-flash" />
|
||||
<h2 class="mb-4 text-xl text-neutral-900">Client DNS</h2>
|
||||
<p class="mb-4 text-neutral-500">
|
||||
DNS servers will be used in the order they are listed below.
|
||||
|
||||
<.flash kind={:success} flash={@flash} phx-click="lv:clear-flash" />
|
||||
|
||||
<% empty? =
|
||||
Domain.Repo.Changeset.empty?(@form.source) and
|
||||
Enum.empty?(@account.config.clients_upstream_dns) %>
|
||||
|
||||
<p :if={not empty?} class="mb-4 text-neutral-500">
|
||||
Upstream resolvers will be used by Clients in the order they are listed below.
|
||||
</p>
|
||||
|
||||
<p :if={empty?} class="text-neutral-500">
|
||||
No upstream resolvers have been configured. Click <strong>New Resolver</strong>
|
||||
to add one.
|
||||
</p>
|
||||
|
||||
<.form for={@form} phx-submit={:submit} phx-change={:change}>
|
||||
@@ -74,12 +85,8 @@ defmodule Web.Settings.DNS do
|
||||
value={dns[:protocol].value}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-8/12">
|
||||
<.input
|
||||
label="Address"
|
||||
field={dns[:address]}
|
||||
placeholder="DNS Server Address"
|
||||
/>
|
||||
<div class="w-3/4">
|
||||
<.input label="Address" field={dns[:address]} placeholder="E.g. 1.1.1.1" />
|
||||
</div>
|
||||
<div class="w-1/12 flex">
|
||||
<div class="pt-7">
|
||||
@@ -89,7 +96,10 @@ defmodule Web.Settings.DNS do
|
||||
value={dns.index}
|
||||
phx-click={JS.dispatch("change")}
|
||||
>
|
||||
<.icon name="hero-trash" class="text-red-500 w-6 h-6 relative top-2" />
|
||||
<.icon
|
||||
name="hero-trash"
|
||||
class="-ml-1 text-red-500 w-5 h-5 relative top-2"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,7 +114,7 @@ defmodule Web.Settings.DNS do
|
||||
value="new"
|
||||
phx-click={JS.dispatch("change")}
|
||||
>
|
||||
New DNS Server
|
||||
New Resolver
|
||||
</.button>
|
||||
<.error
|
||||
:for={error <- dns_config_errors(@form.source.changes)}
|
||||
@@ -117,7 +127,7 @@ defmodule Web.Settings.DNS do
|
||||
<p class="text-sm text-neutral-500">
|
||||
<strong>Note:</strong>
|
||||
It is highly recommended to to specify <strong>both</strong>
|
||||
IPv4 and IPv6 addresses when adding custom resolvers. Otherwise, Clients without IPv4
|
||||
IPv4 and IPv6 addresses when adding upstream resolvers. Otherwise, Clients without IPv4
|
||||
or IPv6 connectivity may not be able to resolve DNS queries.
|
||||
</p>
|
||||
<.submit_button>
|
||||
|
||||
@@ -55,16 +55,16 @@ defmodule Web.Settings.IdentityProviders.Index do
|
||||
<:title>
|
||||
Identity Providers
|
||||
</:title>
|
||||
<:action>
|
||||
<.docs_action path="/authenticate" />
|
||||
</:action>
|
||||
<:action>
|
||||
<.add_button navigate={~p"/#{@account}/settings/identity_providers/new"}>
|
||||
Add Identity Provider
|
||||
</.add_button>
|
||||
</:action>
|
||||
<:help>
|
||||
<.website_link path="/kb/authenticate">
|
||||
Read more
|
||||
</.website_link>
|
||||
about how authentication works in Firezone.
|
||||
Identity providers authenticate and sync your users and groups with an external source.
|
||||
</:help>
|
||||
<:content>
|
||||
<.flash_group flash={@flash} />
|
||||
|
||||
@@ -65,9 +65,8 @@ defmodule Web.SignIn.Email do
|
||||
|
||||
<div>
|
||||
<p>
|
||||
If <strong><%= @provider_identifier %></strong> is registered, a sign in token has
|
||||
been sent to that email. Please copy and paste this into the form below to proceed
|
||||
with your login.
|
||||
If <strong><%= @provider_identifier %></strong>
|
||||
is registered, a sign-in token has been sent.
|
||||
</p>
|
||||
<form
|
||||
id="verify-sign-in-token"
|
||||
|
||||
@@ -133,7 +133,7 @@ defmodule Web.SignUp do
|
||||
~H"""
|
||||
<div class="space-y-6">
|
||||
<div class="text-center text-neutral-900">
|
||||
Your account has been created!
|
||||
<p class="text-xl font-medium">Your account has been created!</p>
|
||||
<p>Please check your email for sign in instructions.</p>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
@@ -141,7 +141,7 @@ defmodule Web.SignUp do
|
||||
<table class="border-collapse w-full text-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900]}>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900 font-bold]}>
|
||||
Account Name:
|
||||
</td>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900]}>
|
||||
@@ -149,7 +149,7 @@ defmodule Web.SignUp do
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900]}>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900 font-bold]}>
|
||||
Account Slug:
|
||||
</td>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900]}>
|
||||
@@ -157,7 +157,7 @@ defmodule Web.SignUp do
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900]}>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900 font-bold]}>
|
||||
Sign In URL:
|
||||
</td>
|
||||
<td class={~w[border-b border-neutral-100 py-4 text-neutral-900]}>
|
||||
@@ -204,8 +204,8 @@ defmodule Web.SignUp do
|
||||
<.input
|
||||
field={@form[:email]}
|
||||
type="text"
|
||||
label="Email"
|
||||
placeholder="Enter your work email here"
|
||||
label="Work Email"
|
||||
placeholder="E.g. foo@example.com"
|
||||
required
|
||||
autofocus
|
||||
phx-debounce="300"
|
||||
@@ -215,8 +215,8 @@ defmodule Web.SignUp do
|
||||
<.input
|
||||
field={account[:name]}
|
||||
type="text"
|
||||
label="Account Name"
|
||||
placeholder="Enter an account name"
|
||||
label="Company Name"
|
||||
placeholder="E.g. Example Corp"
|
||||
required
|
||||
phx-debounce="300"
|
||||
/>
|
||||
@@ -228,7 +228,7 @@ defmodule Web.SignUp do
|
||||
field={actor[:name]}
|
||||
type="text"
|
||||
label="Your Name"
|
||||
placeholder="Enter your name here"
|
||||
placeholder="E.g. John Smith"
|
||||
required
|
||||
phx-debounce="300"
|
||||
/>
|
||||
@@ -293,8 +293,6 @@ defmodule Web.SignUp do
|
||||
|
||||
changeset =
|
||||
attrs
|
||||
|> maybe_put_default_account_name(account_name_changed?)
|
||||
|> maybe_put_default_actor_name(actor_name_changed?)
|
||||
|> Registration.changeset()
|
||||
|> Map.put(:action, :validate)
|
||||
|
||||
@@ -313,8 +311,6 @@ defmodule Web.SignUp do
|
||||
|
||||
changeset =
|
||||
attrs
|
||||
|> maybe_put_default_account_name()
|
||||
|> maybe_put_default_actor_name()
|
||||
|> put_in(["actor", "type"], :account_admin_user)
|
||||
|> Registration.changeset()
|
||||
|> Map.put(:action, :insert)
|
||||
@@ -382,33 +378,6 @@ defmodule Web.SignUp do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_default_account_name(attrs, account_name_changed? \\ true)
|
||||
|
||||
defp maybe_put_default_account_name(attrs, true) do
|
||||
attrs
|
||||
end
|
||||
|
||||
defp maybe_put_default_account_name(attrs, false) do
|
||||
case String.split(attrs["email"], "@", parts: 2) do
|
||||
[default_name | _] when byte_size(default_name) > 0 ->
|
||||
put_in(attrs, ["account", "name"], "#{default_name}'s account")
|
||||
|
||||
_ ->
|
||||
attrs
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_default_actor_name(attrs, actor_name_changed? \\ true)
|
||||
|
||||
defp maybe_put_default_actor_name(attrs, true) do
|
||||
attrs
|
||||
end
|
||||
|
||||
defp maybe_put_default_actor_name(attrs, false) do
|
||||
[default_name | _] = String.split(attrs["email"], "@", parts: 2)
|
||||
put_in(attrs, ["actor", "name"], default_name)
|
||||
end
|
||||
|
||||
defp register_account(socket, registration) do
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.run(
|
||||
|
||||
@@ -49,6 +49,11 @@ defmodule Web.Sites.Index do
|
||||
<:title>
|
||||
Sites
|
||||
</:title>
|
||||
|
||||
<:action>
|
||||
<.docs_action path="/deploy/sites" />
|
||||
</:action>
|
||||
|
||||
<:action>
|
||||
<.add_button navigate={~p"/#{@account}/sites/new"}>
|
||||
Add Site
|
||||
|
||||
@@ -20,16 +20,15 @@ defmodule Web.Sites.New do
|
||||
<:content>
|
||||
<.flash kind={:error} flash={@flash} />
|
||||
<div class="py-8 px-4 mx-auto max-w-2xl lg:py-16">
|
||||
<h2 class="mb-6 text-xl text-neutral-900">
|
||||
Site details
|
||||
</h2>
|
||||
<.form for={@form} phx-change={:change} phx-submit={:submit}>
|
||||
<div class="grid gap-4 mb-4 sm:grid-cols-1 sm:gap-6 sm:mb-6">
|
||||
<div>
|
||||
<.input label="Name" field={@form[:name]} placeholder="Name of this Site" required />
|
||||
<p class="mt-2 text-xs text-neutral-500">
|
||||
Enter a name for this Site.
|
||||
</p>
|
||||
<.input
|
||||
label="Name"
|
||||
field={@form[:name]}
|
||||
placeholder="Enter a name for this Site"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<.submit_button>
|
||||
|
||||
@@ -129,6 +129,9 @@ defmodule Web.Sites.Show do
|
||||
see all <.icon name="hero-arrow-right" class="w-2 h-2" />
|
||||
</.link>
|
||||
</:title>
|
||||
<:action>
|
||||
<.docs_action path="/deploy/gateways" />
|
||||
</:action>
|
||||
<:action :if={is_nil(@group.deleted_at)}>
|
||||
<.add_button navigate={~p"/#{@account}/sites/#{@group}/new_token"}>
|
||||
Deploy Gateway
|
||||
@@ -245,9 +248,14 @@ defmodule Web.Sites.Show do
|
||||
<:col :let={resource} label="Authorized groups">
|
||||
<.peek peek={Map.fetch!(@resource_actor_groups_peek, resource.id)}>
|
||||
<:empty>
|
||||
None -
|
||||
<div class="mr-1">
|
||||
<.icon
|
||||
name="hero-exclamation-triangle-solid"
|
||||
class="inline-block w-3.5 h-3.5 text-red-500"
|
||||
/> None.
|
||||
</div>
|
||||
<.link
|
||||
class={["px-1", link_style()]}
|
||||
class={[link_style(), "mr-1"]}
|
||||
navigate={~p"/#{@account}/policies/new?resource_id=#{resource}&site_id=#{@group}"}
|
||||
>
|
||||
Create a Policy
|
||||
|
||||
@@ -186,7 +186,7 @@ defmodule Web.LiveTable do
|
||||
|
||||
defp filter(%{filter: %{type: {:string, :websearch}}} = assigns) do
|
||||
~H"""
|
||||
<div class="flex items-center order-last md:w-56">
|
||||
<div class="flex items-center order-last">
|
||||
<div class="relative w-full" phx-feedback-for={@form[@filter.name].name}>
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<.icon name="hero-magnifying-glass" class="w-5 h-5 text-neutral-500" />
|
||||
@@ -200,7 +200,7 @@ defmodule Web.LiveTable do
|
||||
placeholder={"Search by " <> @filter.title}
|
||||
class={[
|
||||
"bg-neutral-50 border border-neutral-300 text-neutral-900 text-sm rounded",
|
||||
"block w-full pl-10 p-2",
|
||||
"block w-full md:w-72 pl-10 p-2",
|
||||
"disabled:bg-neutral-50 disabled:text-neutral-500 disabled:border-neutral-300 disabled:shadow-none",
|
||||
"focus:outline-none focus:border-1 focus:ring-0",
|
||||
@form[@filter.name].errors != [] && "border-rose-400"
|
||||
@@ -219,7 +219,7 @@ defmodule Web.LiveTable do
|
||||
|
||||
defp filter(%{filter: %{type: {:string, :email}}} = assigns) do
|
||||
~H"""
|
||||
<div class="flex items-center order-last md:w-56">
|
||||
<div class="flex items-center order-last">
|
||||
<div class="relative w-full" phx-feedback-for={@form[@filter.name].name}>
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<.icon name="hero-magnifying-glass" class="w-5 h-5 text-neutral-500" />
|
||||
@@ -233,7 +233,7 @@ defmodule Web.LiveTable do
|
||||
placeholder={"Search by " <> @filter.title}
|
||||
class={[
|
||||
"bg-neutral-50 border border-neutral-300 text-neutral-900 text-sm rounded",
|
||||
"block w-full pl-10 p-2",
|
||||
"block w-full md:w-72 pl-10 p-2",
|
||||
"disabled:bg-neutral-50 disabled:text-neutral-500 disabled:border-neutral-300 disabled:shadow-none",
|
||||
"focus:outline-none focus:border-1 focus:ring-0",
|
||||
@form[@filter.name].errors != [] && "border-rose-400"
|
||||
|
||||
@@ -53,7 +53,7 @@ defmodule Web.Live.Actors.ServiceAccounts.NewIdentityTest do
|
||||
breadcrumbs = String.trim(Floki.text(item))
|
||||
assert breadcrumbs =~ "Actors"
|
||||
assert breadcrumbs =~ actor.name
|
||||
assert breadcrumbs =~ "Add Token"
|
||||
assert breadcrumbs =~ "Create Token"
|
||||
end
|
||||
|
||||
test "renders form", %{
|
||||
|
||||
@@ -92,8 +92,7 @@ defmodule Web.Live.Clients.ShowTest do
|
||||
assert table["status"] =~ "Offline"
|
||||
assert table["created"]
|
||||
assert table["last started"]
|
||||
assert table["verification"] =~ "Not Verified"
|
||||
assert table["client version"] =~ client.last_seen_version
|
||||
assert table["version"] =~ client.last_seen_version
|
||||
assert table["user agent"] =~ client.last_seen_user_agent
|
||||
|
||||
table =
|
||||
@@ -104,6 +103,7 @@ defmodule Web.Live.Clients.ShowTest do
|
||||
|
||||
assert table["file id"] == client.external_id
|
||||
|
||||
assert table["verification"] =~ "Not Verified"
|
||||
assert table["device serial"] =~ to_string(client.device_serial)
|
||||
assert table["device uuid"] =~ to_string(client.device_uuid)
|
||||
assert table["app installation id"] =~ to_string(client.firebase_installation_id)
|
||||
@@ -322,7 +322,7 @@ defmodule Web.Live.Clients.ShowTest do
|
||||
|
||||
table =
|
||||
lv
|
||||
|> element("#client")
|
||||
|> element("#posture")
|
||||
|> render()
|
||||
|> vertical_table_to_map()
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ defmodule Web.Live.Groups.EditActorsTest do
|
||||
|> render_click()
|
||||
|
||||
lv
|
||||
|> element("button[type=submit]", "Save")
|
||||
|> element("button[type=submit]", "Confirm")
|
||||
|> render_click()
|
||||
|
||||
assert_redirected(lv, ~p"/#{account}/groups/#{group}")
|
||||
|
||||
@@ -237,7 +237,7 @@ defmodule Web.Live.Sites.ShowTest do
|
||||
Enum.each(resource_rows, fn row ->
|
||||
assert row["name"] =~ resource.name
|
||||
assert row["address"] =~ resource.address
|
||||
assert row["authorized groups"] == "None - Create a Policy to grant access."
|
||||
assert row["authorized groups"] == "None. Create a Policy to grant access."
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user