Integrations Settings section (#3427)
* Extend the Tokens context module * Extract GA Import to separate component * Extract Search Console settings to separate component * Remove Search Console from the router * Stop counting imported pageviews in general settings * Remove search console controller action * Add settings_integrations controller action * Fix remaining redirects * Add Integrations route * Replace SC sidebar item with Integrations * Update site controller tests * Implement Plugins API Tokens LV * Apply universal heroicon to docs info links * Add flash on token creation * Update CHANGELOG * Redirect to integrations upon forgetting GA import * Update moduledocs * Remove unnecessary wildcards * WIP: attempt at fixing broken oauth flow * Fix post-import redirect * Fixup missing attribute * Format * Seed random google auth * Use example.com for seeded e-mails * Tweak Google integrations layout * Remove dangling IO.inspect * Bugfix: copy to clipboard breaking LV form bindings * Update lib/plausible/plugins/api/tokens.ex Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com> * Update lib/plausible_web/controllers/site_controller.ex Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com> * Update lib/plausible_web/live/plugins/api/settings.ex Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com> * Update test/plausible/plugins/api/tokens_test.exs Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com> --------- Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>
This commit is contained in:
parent
127a9ef9ba
commit
2cc80ebd7a
|
|
@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Improve password validation in registration and password reset forms
|
||||
- Adds Gravatar profile image to navbar
|
||||
- Enforce email reverification on update
|
||||
- Add Plugins API Tokens provisioning UI
|
||||
|
||||
### Removed
|
||||
- Removed the nested custom event property breakdown UI when filtering by a goal in Goal Conversions
|
||||
|
|
@ -20,6 +21,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Limit the number of Goal Conversions shown on the dashboard and render a "Details" link when there are more entries to show
|
||||
- Show Outbound Links / File Downloads / 404 Pages / Cloaked Links instead of Goal Conversions when filtering by the corresponding goal
|
||||
- Require custom properties to be explicitly added from Site Settings > Custom Properties in order for them to show up on the dashboard
|
||||
- GA/SC sections moved to new settings: Integrations
|
||||
|
||||
### Fixed
|
||||
- Only return `(none)` values in custom property breakdown for the first page (pagination) of results
|
||||
|
|
|
|||
|
|
@ -36,4 +36,20 @@ defmodule Plausible.Plugins.API.Tokens do
|
|||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete(Site.t(), String.t()) :: :ok
|
||||
def delete(site, token_id) do
|
||||
Repo.delete_all(from t in Token, where: t.site_id == ^site.id and t.id == ^token_id)
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec list(Site.t()) :: {:ok, [Token.t()]}
|
||||
def list(site) do
|
||||
Repo.all(from t in Token, where: t.site_id == ^site.id, order_by: [desc: t.id])
|
||||
end
|
||||
|
||||
@spec any?(Site.t()) :: boolean()
|
||||
def any?(site) do
|
||||
Repo.exists?(from(t in Token, where: t.site_id == ^site.id))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,10 +4,20 @@ defmodule PlausibleWeb.Components.Generic do
|
|||
"""
|
||||
use Phoenix.Component
|
||||
|
||||
attr :title, :string, default: "Notice"
|
||||
attr :size, :atom, default: :sm
|
||||
attr :rest, :global
|
||||
slot :inner_block
|
||||
attr(:slug, :string, required: true)
|
||||
|
||||
def docs_info(assigns) do
|
||||
~H"""
|
||||
<a href={"https://plausible.io/docs/#{@slug}"} rel="noreferrer" target="_blank">
|
||||
<Heroicons.information_circle class="text-gray-400 w-6 h-6 absolute top-0 right-0 text-gray-400" />
|
||||
</a>
|
||||
"""
|
||||
end
|
||||
|
||||
attr(:title, :string, default: "Notice")
|
||||
attr(:size, :atom, default: :sm)
|
||||
attr(:rest, :global)
|
||||
slot(:inner_block)
|
||||
|
||||
def notice(assigns) do
|
||||
~H"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
defmodule PlausibleWeb.Components.Google do
|
||||
@moduledoc """
|
||||
Google-related components
|
||||
"""
|
||||
use Phoenix.Component
|
||||
use Phoenix.HTML
|
||||
|
||||
attr(:to, :string, required: true)
|
||||
attr(:id, :string, required: true)
|
||||
|
||||
def button(assigns) do
|
||||
~H"""
|
||||
<%= button(id: @id, to: @to, class: "inline-flex pr-4 items-center border border-gray-100 shadow rounded-md focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-gray-200 mt-8 hover:bg-gray-50 dark:hover:bg-gray-700") do %>
|
||||
<.logo />
|
||||
<span
|
||||
style="font-family: Roboto, system-ui"
|
||||
class="text-sm font-medium text-gray-600 dark:text-gray-50"
|
||||
>
|
||||
Continue with Google
|
||||
</span>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
def logo(assigns \\ %{}) do
|
||||
~H"""
|
||||
<svg
|
||||
width="46px"
|
||||
height="46px"
|
||||
viewBox="0 0 46 46"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-1.000000, -1.000000)">
|
||||
<g transform="translate(15.000000, 15.000000)">
|
||||
<path
|
||||
d="M17.64,9.20454545 C17.64,8.56636364 17.5827273,7.95272727 17.4763636,7.36363636 L9,7.36363636 L9,10.845 L13.8436364,10.845 C13.635,11.97 13.0009091,12.9231818 12.0477273,13.5613636 L12.0477273,15.8195455 L14.9563636,15.8195455 C16.6581818,14.2527273 17.64,11.9454545 17.64,9.20454545 L17.64,9.20454545 Z"
|
||||
fill="#4285F4"
|
||||
>
|
||||
</path>
|
||||
<path
|
||||
d="M9,18 C11.43,18 13.4672727,17.1940909 14.9563636,15.8195455 L12.0477273,13.5613636 C11.2418182,14.1013636 10.2109091,14.4204545 9,14.4204545 C6.65590909,14.4204545 4.67181818,12.8372727 3.96409091,10.71 L0.957272727,10.71 L0.957272727,13.0418182 C2.43818182,15.9831818 5.48181818,18 9,18 L9,18 Z"
|
||||
fill="#34A853"
|
||||
>
|
||||
</path>
|
||||
<path
|
||||
d="M3.96409091,10.71 C3.78409091,10.17 3.68181818,9.59318182 3.68181818,9 C3.68181818,8.40681818 3.78409091,7.83 3.96409091,7.29 L3.96409091,4.95818182 L0.957272727,4.95818182 C0.347727273,6.17318182 0,7.54772727 0,9 C0,10.4522727 0.347727273,11.8268182 0.957272727,13.0418182 L3.96409091,10.71 L3.96409091,10.71 Z"
|
||||
fill="#FBBC05"
|
||||
>
|
||||
</path>
|
||||
<path
|
||||
d="M9,3.57954545 C10.3213636,3.57954545 11.5077273,4.03363636 12.4404545,4.92545455 L15.0218182,2.34409091 C13.4631818,0.891818182 11.4259091,0 9,0 C5.48181818,0 2.43818182,2.01681818 0.957272727,4.95818182 L3.96409091,7.29 C4.67181818,5.16272727 6.65590909,3.57954545 9,3.57954545 L9,3.57954545 Z"
|
||||
fill="#EA4335"
|
||||
>
|
||||
</path>
|
||||
<path d="M0,0 L18,0 L18,18 L0,18 L0,0 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
defmodule PlausibleWeb.Components.Settings do
|
||||
@moduledoc """
|
||||
An umbrella module for the Integrations settings section
|
||||
"""
|
||||
use Phoenix.Component
|
||||
use Phoenix.HTML
|
||||
|
||||
import PlausibleWeb.Components.Generic
|
||||
|
||||
embed_templates("../templates/site/settings_search_console.html")
|
||||
embed_templates("../templates/site/settings_google_import.html")
|
||||
end
|
||||
|
|
@ -516,7 +516,7 @@ defmodule PlausibleWeb.AuthController do
|
|||
|
||||
site = Repo.get(Plausible.Site, site_id)
|
||||
|
||||
redirect(conn, to: "/#{URI.encode_www_form(site.domain)}/settings/#{redirect_to}")
|
||||
redirect(conn, to: "/#{URI.encode_www_form(site.domain)}/settings/integrations")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -166,17 +166,9 @@ defmodule PlausibleWeb.SiteController do
|
|||
conn.assigns[:site]
|
||||
|> Repo.preload([:custom_domain])
|
||||
|
||||
imported_pageviews =
|
||||
if site.imported_data do
|
||||
Plausible.Stats.Clickhouse.imported_pageview_count(site)
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
conn
|
||||
|> render("settings_general.html",
|
||||
site: site,
|
||||
imported_pageviews: imported_pageviews,
|
||||
changeset: Plausible.Site.changeset(site, %{}),
|
||||
dogfood_page_path: "/:dashboard/settings/general",
|
||||
layout: {PlausibleWeb.LayoutView, "site_settings.html"}
|
||||
|
|
@ -251,25 +243,6 @@ defmodule PlausibleWeb.SiteController do
|
|||
)
|
||||
end
|
||||
|
||||
def settings_search_console(conn, _params) do
|
||||
site =
|
||||
conn.assigns[:site]
|
||||
|> Repo.preload([:google_auth, :custom_domain])
|
||||
|
||||
search_console_domains =
|
||||
if site.google_auth do
|
||||
Plausible.Google.Api.fetch_verified_properties(site.google_auth)
|
||||
end
|
||||
|
||||
conn
|
||||
|> render("settings_search_console.html",
|
||||
site: site,
|
||||
search_console_domains: search_console_domains,
|
||||
dogfood_page_path: "/:dashboard/settings/search-console",
|
||||
layout: {PlausibleWeb.LayoutView, "site_settings.html"}
|
||||
)
|
||||
end
|
||||
|
||||
def settings_email_reports(conn, _params) do
|
||||
site = conn.assigns[:site] |> Repo.preload(:custom_domain)
|
||||
|
||||
|
|
@ -308,6 +281,37 @@ defmodule PlausibleWeb.SiteController do
|
|||
)
|
||||
end
|
||||
|
||||
def settings_integrations(conn, _params) do
|
||||
site =
|
||||
conn.assigns.site
|
||||
|> Repo.preload([:google_auth, :custom_domain])
|
||||
|
||||
search_console_domains =
|
||||
if site.google_auth do
|
||||
Plausible.Google.Api.fetch_verified_properties(site.google_auth)
|
||||
end
|
||||
|
||||
imported_pageviews =
|
||||
if site.imported_data do
|
||||
Plausible.Stats.Clickhouse.imported_pageview_count(site)
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
has_plugins_tokens? = Plausible.Plugins.API.Tokens.any?(site)
|
||||
|
||||
conn
|
||||
|> render("settings_integrations.html",
|
||||
site: site,
|
||||
imported_pageviews: imported_pageviews,
|
||||
has_plugins_tokens?: has_plugins_tokens?,
|
||||
search_console_domains: search_console_domains,
|
||||
dogfood_page_path: "/:dashboard/settings/integrations",
|
||||
connect_live_socket: true,
|
||||
layout: {PlausibleWeb.LayoutView, "site_settings.html"}
|
||||
)
|
||||
end
|
||||
|
||||
def update_google_auth(conn, %{"google_auth" => attrs}) do
|
||||
site = conn.assigns[:site] |> Repo.preload(:google_auth)
|
||||
|
||||
|
|
@ -316,7 +320,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|
||||
conn
|
||||
|> put_flash(:success, "Google integration saved successfully")
|
||||
|> redirect(to: Routes.site_path(conn, :settings_search_console, site.domain))
|
||||
|> redirect(to: Routes.site_path(conn, :settings_integrations, site.domain))
|
||||
end
|
||||
|
||||
def delete_google_auth(conn, _params) do
|
||||
|
|
@ -328,19 +332,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|
||||
conn = put_flash(conn, :success, "Google account unlinked from Plausible")
|
||||
|
||||
panel =
|
||||
conn.path_info
|
||||
|> List.last()
|
||||
|> String.split("-")
|
||||
|> List.last()
|
||||
|
||||
case panel do
|
||||
"search" ->
|
||||
redirect(conn, to: Routes.site_path(conn, :settings_search_console, site.domain))
|
||||
|
||||
"import" ->
|
||||
redirect(conn, to: Routes.site_path(conn, :settings_general, site.domain))
|
||||
end
|
||||
redirect(conn, to: Routes.site_path(conn, :settings_integrations, site.domain))
|
||||
end
|
||||
|
||||
def update_settings(conn, %{"site" => site_params}) do
|
||||
|
|
@ -862,7 +854,7 @@ defmodule PlausibleWeb.SiteController do
|
|||
|
||||
conn
|
||||
|> put_flash(:success, "Import scheduled. An email will be sent when it completes.")
|
||||
|> redirect(to: Routes.site_path(conn, :settings_general, site.domain))
|
||||
|> redirect(to: Routes.site_path(conn, :settings_integrations, site.domain))
|
||||
end
|
||||
|
||||
def forget_imported(conn, _params) do
|
||||
|
|
@ -885,12 +877,12 @@ defmodule PlausibleWeb.SiteController do
|
|||
|
||||
conn
|
||||
|> put_flash(:success, "Imported data has been cleared")
|
||||
|> redirect(to: Routes.site_path(conn, :settings_general, site.domain))
|
||||
|> redirect(to: Routes.site_path(conn, :settings_integrations, site.domain))
|
||||
|
||||
true ->
|
||||
conn
|
||||
|> put_flash(:error, "No data has been imported")
|
||||
|> redirect(to: Routes.site_path(conn, :settings_general, site.domain))
|
||||
|> redirect(to: Routes.site_path(conn, :settings_integrations, site.domain))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,40 @@ defmodule PlausibleWeb.Live.Components.Form do
|
|||
"""
|
||||
end
|
||||
|
||||
attr(:rest, :global)
|
||||
attr(:id, :string, required: true)
|
||||
attr(:class, :string, default: "")
|
||||
attr(:name, :string, required: true)
|
||||
attr(:label, :string, required: true)
|
||||
attr(:value, :string, default: "")
|
||||
|
||||
def input_with_clipboard(assigns) do
|
||||
~H"""
|
||||
<div class="my-4">
|
||||
<div class="relative mt-1">
|
||||
<.input
|
||||
id={@id}
|
||||
name={@name}
|
||||
label={@label}
|
||||
value={@value}
|
||||
type="text"
|
||||
readonly="readonly"
|
||||
class={[@class, "pr-20"]}
|
||||
{@rest}
|
||||
/>
|
||||
<a
|
||||
onclick={"var input = document.getElementById('#{@id}'); input.focus(); input.select(); document.execCommand('copy'); event.stopPropagation();"}
|
||||
href="javascript:void(0)"
|
||||
class="absolute flex items-center text-xs font-medium text-indigo-600 no-underline hover:underline"
|
||||
style="top: 42px; right: 12px;"
|
||||
>
|
||||
<Heroicons.document_duplicate class="pr-1 text-indigo-600 dark:text-indigo-500 w-5 h-5" />COPY
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
attr(:id, :any, default: nil)
|
||||
attr(:label, :string, default: nil)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
defmodule PlausibleWeb.Live.Plugins.API.Settings do
|
||||
@moduledoc """
|
||||
LiveView allowing listing, creating and revoking Plugins API tokens.
|
||||
"""
|
||||
use Phoenix.LiveView
|
||||
use Phoenix.HTML
|
||||
|
||||
alias Plausible.Sites
|
||||
alias Plausible.Plugins.API.Tokens
|
||||
|
||||
def mount(
|
||||
_params,
|
||||
%{"domain" => domain, "current_user_id" => user_id} = session,
|
||||
socket
|
||||
) do
|
||||
socket =
|
||||
socket
|
||||
|> assign_new(:site, fn ->
|
||||
Sites.get_for_user!(user_id, domain, [:owner, :admin, :super_admin])
|
||||
end)
|
||||
|> assign_new(:displayed_tokens, fn %{site: site} ->
|
||||
Tokens.list(site)
|
||||
end)
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
domain: domain,
|
||||
add_token?: not is_nil(session["new_token"]),
|
||||
token_description: String.capitalize(session["new_token"] || ""),
|
||||
current_user_id: user_id
|
||||
)}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.live_component id="embedded_liveview_flash" module={PlausibleWeb.Live.Flash} flash={@flash} />
|
||||
|
||||
<%= if @add_token? do %>
|
||||
<%= live_render(
|
||||
@socket,
|
||||
PlausibleWeb.Live.Plugins.API.TokenForm,
|
||||
id: "token-form",
|
||||
session: %{
|
||||
"current_user_id" => @current_user_id,
|
||||
"domain" => @domain,
|
||||
"token_description" => @token_description,
|
||||
"rendered_by" => self()
|
||||
}
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="border-t border-gray-200 pt-4 grid">
|
||||
<div class="mt-4 sm:ml-4 sm:mt-0 justify-self-end">
|
||||
<button type="button" phx-click="add-token" class="button">
|
||||
+ Add Token
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
:if={not Enum.empty?(@displayed_tokens)}
|
||||
class="mt-8 overflow-hidden border-b border-gray-200 shadow dark:border-gray-900 sm:rounded-lg"
|
||||
>
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-900">
|
||||
<thead class="bg-gray-50 dark:bg-gray-900">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-gray-100"
|
||||
>
|
||||
Description
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-gray-100"
|
||||
>
|
||||
Hint
|
||||
</th>
|
||||
<th scope="col" class="relative px-6 py-3">
|
||||
<span class="sr-only">Revoke</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for token <- @displayed_tokens do %>
|
||||
<tr class="bg-white dark:bg-gray-800">
|
||||
<td class="px-6 py-4 text-sm font-medium text-gray-900 dark:text-gray-100 whitespace-nowrap">
|
||||
<span class="token-description"><%= token.description %></span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-100 whitespace-nowrap font-mono">
|
||||
**********<%= token.hint %>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm font-medium text-right whitespace-nowrap">
|
||||
<button
|
||||
id={"revoke-token-#{token.id}"}
|
||||
phx-click="revoke-token"
|
||||
phx-value-token-id={token.id}
|
||||
class="text-sm text-red-600"
|
||||
data-confirm="Are you sure you want to revoke this Token? This action cannot be reversed."
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def handle_event("add-token", _params, socket) do
|
||||
{:noreply, assign(socket, :add_token?, true)}
|
||||
end
|
||||
|
||||
def handle_event("revoke-token", %{"token-id" => token_id}, socket) do
|
||||
:ok = Tokens.delete(socket.assigns.site, token_id)
|
||||
displayed_tokens = Enum.reject(socket.assigns.displayed_tokens, &(&1.id == token_id))
|
||||
{:noreply, assign(socket, add_token?: false, displayed_tokens: displayed_tokens)}
|
||||
end
|
||||
|
||||
def handle_info(:cancel_add_token, socket) do
|
||||
{:noreply, assign(socket, add_token?: false)}
|
||||
end
|
||||
|
||||
def handle_info({:token_added, token}, socket) do
|
||||
displayed_tokens = [token | socket.assigns.displayed_tokens]
|
||||
|
||||
socket = put_flash(socket, :success, "Plugins API Token created successfully")
|
||||
|
||||
Process.send_after(self(), :clear_flash, 5000)
|
||||
|
||||
{:noreply,
|
||||
assign(socket,
|
||||
displayed_tokens: displayed_tokens,
|
||||
add_token?: false,
|
||||
token_description: ""
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_info(:clear_flash, socket) do
|
||||
{:noreply, clear_flash(socket)}
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
defmodule PlausibleWeb.Live.Plugins.API.TokenForm do
|
||||
@moduledoc """
|
||||
Live view for the goal creation form
|
||||
"""
|
||||
use Phoenix.LiveView
|
||||
import PlausibleWeb.Live.Components.Form
|
||||
|
||||
alias Plausible.Repo
|
||||
alias Plausible.Sites
|
||||
alias Plausible.Plugins.API.{Token, Tokens}
|
||||
|
||||
def mount(
|
||||
_params,
|
||||
%{
|
||||
"token_description" => token_description,
|
||||
"current_user_id" => user_id,
|
||||
"domain" => domain,
|
||||
"rendered_by" => pid
|
||||
},
|
||||
socket
|
||||
) do
|
||||
socket =
|
||||
socket
|
||||
|> assign_new(:site, fn ->
|
||||
Sites.get_for_user!(user_id, domain, [:owner, :admin, :super_admin])
|
||||
end)
|
||||
|
||||
token = Token.generate()
|
||||
form = to_form(Token.insert_changeset(socket.assigns.site, token))
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
token_description: token_description,
|
||||
token: token,
|
||||
current_user: Repo.get(Plausible.Auth.User, user_id),
|
||||
form: form,
|
||||
domain: domain,
|
||||
rendered_by: pid,
|
||||
tabs: %{custom_events: true, pageviews: false}
|
||||
)}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div
|
||||
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity z-50"
|
||||
phx-window-keydown="cancel-add-token"
|
||||
phx-key="Escape"
|
||||
>
|
||||
</div>
|
||||
<div class="fixed inset-0 flex items-center justify-center mt-16 z-50 overflow-y-auto overflow-x-hidden">
|
||||
<div class="w-1/2 h-full">
|
||||
<.form
|
||||
:let={f}
|
||||
for={@form}
|
||||
class="max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"
|
||||
phx-submit="save-token"
|
||||
phx-click-away="cancel-add-token"
|
||||
>
|
||||
<h2 class="text-xl font-black dark:text-gray-100 mb-8">Add Token for <%= @domain %></h2>
|
||||
|
||||
<.input
|
||||
autofocus
|
||||
field={f[:description]}
|
||||
label="Description"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-900 dark:text-gray-300 block w-7/12 rounded-md sm:text-sm border-gray-300 dark:border-gray-500 w-full p-2 mt-2"
|
||||
placeholder="e.g. Signup"
|
||||
value={@token_description}
|
||||
autocomplete="off"
|
||||
/>
|
||||
|
||||
<.input_with_clipboard
|
||||
id="token-clipboard"
|
||||
name="token_clipboard"
|
||||
label="API Token"
|
||||
value={@token.raw}
|
||||
onfocus="this.value = this.value;"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 bg-gray-50 dark:bg-gray-850 dark:text-gray-300 block w-7/12 rounded-md sm:text-sm border-gray-300 dark:border-gray-500 w-full p-2 mt-2"
|
||||
/>
|
||||
|
||||
<p class="text-sm mt-2 text-gray-500 dark:text-gray-200">
|
||||
Once created, we will not be able to show the Token again.
|
||||
Please copy the Token now and store it in a secure place.
|
||||
<span :if={@token_description == "Wordpress"}>
|
||||
You'll need to paste it in the settings area of the Plausible WordPress plugin.
|
||||
</span>
|
||||
</p>
|
||||
<div class="py-4 mt-8">
|
||||
<button type="submit" class="button text-base font-bold w-full">
|
||||
Add Token →
|
||||
</button>
|
||||
</div>
|
||||
</.form>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def handle_event("save-token", %{"token" => %{"description" => description}}, socket) do
|
||||
case Tokens.create(socket.assigns.site, description, socket.assigns.token) do
|
||||
{:ok, token, _} ->
|
||||
send(socket.assigns.rendered_by, {:token_added, token})
|
||||
{:noreply, socket}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, form: to_form(changeset))}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("cancel-add-token", _value, socket) do
|
||||
send(socket.assigns.rendered_by, :cancel_add_token)
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
|
@ -284,10 +284,10 @@ defmodule PlausibleWeb.Router do
|
|||
get "/:website/settings/properties", SiteController, :settings_props
|
||||
get "/:website/settings/funnels", SiteController, :settings_funnels
|
||||
|
||||
get "/:website/settings/search-console", SiteController, :settings_search_console
|
||||
get "/:website/settings/email-reports", SiteController, :settings_email_reports
|
||||
get "/:website/settings/custom-domain", SiteController, :settings_custom_domain
|
||||
get "/:website/settings/danger-zone", SiteController, :settings_danger_zone
|
||||
get "/:website/settings/integrations", SiteController, :settings_integrations
|
||||
|
||||
put "/:website/settings/features/visibility/:setting",
|
||||
SiteController,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
<div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Email Reports</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Send weekly/monthly analytics reports to as many addresses as you wish</p>
|
||||
<%= link(to: "https://plausible.io/docs/email-reports", target: "_blank", rel: "noferrer") do %>
|
||||
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Send weekly/monthly analytics reports to as many addresses as you wish
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="email-reports" />
|
||||
</header>
|
||||
|
||||
<div class="my-8 flex items-center">
|
||||
<%= if @weekly_report do %>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/weekly-report/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
|
||||
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200">
|
||||
</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/weekly-report/enable", method: :post, class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
|
||||
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200">
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<span class="ml-2 dark:text-gray-100">Send a weekly email report every Monday</span>
|
||||
|
|
@ -25,13 +28,34 @@
|
|||
<%= for recipient <- @weekly_report.recipients do %>
|
||||
<div class="p-2 pl-3 flex justify-between bg-gray-100 dark:bg-gray-900 rounded my-2 max-w-md">
|
||||
<span>
|
||||
<svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 inline mr-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg><%= recipient %>
|
||||
</svg>
|
||||
<%= recipient %>
|
||||
</span>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/weekly-report/recipients/#{recipient}", method: :delete) do %>
|
||||
<svg class="w-4 h-4 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
||||
<svg
|
||||
class="w-4 h-4 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -40,16 +64,35 @@
|
|||
<div class="mt-1 flex rounded-md shadow-sm">
|
||||
<div class="relative flex items-stretch flex-grow focus-within:z-10">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg>
|
||||
</div>
|
||||
<%= email_input f, :recipient, class: "focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100", placeholder: "recipient@example.com", required: "true" %>
|
||||
<%= email_input(f, :recipient,
|
||||
class:
|
||||
"focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100",
|
||||
placeholder: "recipient@example.com",
|
||||
required: "true"
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<%= submit class: "-ml-px relative button rounded-l-none" do %>
|
||||
<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"></path></svg>
|
||||
<svg
|
||||
class="w-5 h-5 mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z">
|
||||
</path>
|
||||
</svg>
|
||||
<span>Add recipient</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -61,11 +104,13 @@
|
|||
<div class="my-8 flex items-center">
|
||||
<%= if @monthly_report do %>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
|
||||
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200">
|
||||
</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/enable", method: :post, class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
|
||||
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200">
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<span class="ml-2 dark:text-gray-100">Send a monthly email report on 1st of the month</span>
|
||||
|
|
@ -76,13 +121,34 @@
|
|||
<%= for recipient <- @monthly_report.recipients do %>
|
||||
<div class="p-2 pl-3 flex justify-between bg-gray-100 dark:bg-gray-900 rounded my-2 max-w-md">
|
||||
<span>
|
||||
<svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 inline mr-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg><%= recipient %>
|
||||
</svg>
|
||||
<%= recipient %>
|
||||
</span>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/recipients/#{recipient}", method: :delete) do %>
|
||||
<svg class="w-4 h-4 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
||||
<svg
|
||||
class="w-4 h-4 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -91,16 +157,35 @@
|
|||
<div class="mt-1 flex rounded-md shadow-sm">
|
||||
<div class="relative flex items-stretch flex-grow focus-within:z-10">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg>
|
||||
</div>
|
||||
<%= email_input f, :recipient, class: "focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100", placeholder: "recipient@example.com", required: "true" %>
|
||||
<%= email_input(f, :recipient,
|
||||
class:
|
||||
"focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100",
|
||||
placeholder: "recipient@example.com",
|
||||
required: "true"
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<%= submit class: "-ml-px relative button rounded-l-none" do %>
|
||||
<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"></path></svg>
|
||||
<svg
|
||||
class="w-5 h-5 mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z">
|
||||
</path>
|
||||
</svg>
|
||||
<span>Add recipient</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -112,21 +197,26 @@
|
|||
|
||||
<div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Traffic Spike Notifications</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Get notified when your site has unusually high number of current visitors</p>
|
||||
<%= link(to: "https://plausible.io/docs/traffic-spikes", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
Traffic Spike Notifications
|
||||
</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Get notified when your site has unusually high number of current visitors
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="traffic-spikes" />
|
||||
</header>
|
||||
|
||||
<div class="my-8 flex items-center">
|
||||
<%= if @spike_notification do %>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
|
||||
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200">
|
||||
</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification/enable", method: :post, class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
|
||||
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200">
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<span class="ml-2 dark:text-gray-100">Send notifications of traffic spikes</span>
|
||||
|
|
@ -134,18 +224,26 @@
|
|||
|
||||
<%= if @spike_notification do %>
|
||||
<div class="text-sm text-gray-700 dark:text-gray-300 mt-6">
|
||||
|
||||
<%= form_for Plausible.Site.SpikeNotification.changeset(@spike_notification, %{}), "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification", fn f -> %>
|
||||
<h4 class="font-bold my-2">Current visitor threshold</h4>
|
||||
<div class="mt-1 flex rounded-md shadow-sm max-w-md">
|
||||
<div class="relative flex items-stretch flex-grow focus-within:z-10">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<!-- Heroicon name: users -->
|
||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<%= number_input f, :threshold, class: "focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:text-gray-100" %>
|
||||
<%= number_input(f, :threshold,
|
||||
class:
|
||||
"focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:text-gray-100"
|
||||
) %>
|
||||
</div>
|
||||
<button class="-ml-px relative button rounded-l-none">
|
||||
<span>Save threshold</span>
|
||||
|
|
@ -156,13 +254,34 @@
|
|||
<%= for recipient <- @spike_notification.recipients do %>
|
||||
<div class="p-2 pl-3 flex justify-between bg-gray-100 dark:bg-gray-900 rounded my-2 max-w-md">
|
||||
<span>
|
||||
<svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 inline mr-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg><%= recipient %>
|
||||
</svg>
|
||||
<%= recipient %>
|
||||
</span>
|
||||
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification/recipients/#{recipient}", method: :delete) do %>
|
||||
<svg class="w-4 h-4 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
||||
<svg
|
||||
class="w-4 h-4 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -171,16 +290,35 @@
|
|||
<div class="mt-1 flex rounded-md shadow-sm">
|
||||
<div class="relative flex items-stretch flex-grow focus-within:z-10">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg>
|
||||
</div>
|
||||
<%= email_input f, :recipient, class: "focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100", placeholder: "recipient@example.com", required: "true" %>
|
||||
<%= email_input(f, :recipient,
|
||||
class:
|
||||
"focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100",
|
||||
placeholder: "recipient@example.com",
|
||||
required: "true"
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<%= submit class: "-ml-px relative button rounded-l-none" do %>
|
||||
<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"></path></svg>
|
||||
<svg
|
||||
class="w-5 h-5 mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z">
|
||||
</path>
|
||||
</svg>
|
||||
<span>Add recipient</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -12,21 +12,7 @@
|
|||
Compose Goals into Funnels
|
||||
</p>
|
||||
|
||||
<%= link(to: "https://plausible.io/docs/funnel-analysis", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg
|
||||
class="w-6 h-6 absolute top-0 right-0 text-gray-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clip-rule="evenodd"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<% end %>
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="funnel-analysis" />
|
||||
</header>
|
||||
|
||||
<PlausibleWeb.Components.Site.Feature.toggle
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
<div class="shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div class="bg-white dark:bg-gray-800 py-6 px-4 space-y-6 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Site Domain</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Moving your Site to a different Domain? We got you!</p>
|
||||
<%= link(to: "https://plausible.io/docs/change-domain-name/", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
</header>
|
||||
<div class="grid grid-cols-4 gap-6">
|
||||
<div class="col-span-4 sm:col-span-2">
|
||||
<%= label nil, "Domain", class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300" %>
|
||||
<%= text_input nil, :domain, value: @site.domain, disabled: "disabled", class: "dark:bg-gray-900 w-full mt-1 block pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100 text-gray-500" %>
|
||||
</div>
|
||||
</div>
|
||||
<span class="inline-flex rounded-md shadow-sm">
|
||||
<%= link "Change Domain", to: Routes.site_path(@conn, :change_domain, @site.domain), class: "button" %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= form_for @changeset, "/#{URI.encode_www_form(@site.domain)}/settings", fn f -> %>
|
||||
<div class="shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div class="bg-white dark:bg-gray-800 py-6 px-4 space-y-6 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Site Timezone</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Update your reporting Timezone.</p>
|
||||
<%= link(to: "https://plausible.io/docs/general/", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
</header>
|
||||
<div class="grid grid-cols-4 gap-6">
|
||||
<div class="col-span-4 sm:col-span-2">
|
||||
<%= label f, :timezone, "Reporting Timezone", class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300" %>
|
||||
<%= select f, :timezone, Plausible.Timezones.options(), class: "dark:bg-gray-900 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100 cursor-pointer" %>
|
||||
</div>
|
||||
</div>
|
||||
<span class="inline-flex rounded-md shadow-sm">
|
||||
<%= submit "Save", class: "button" %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_for @conn, "/", [class: "shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"], fn f -> %>
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">JavaScript Snippet</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Include this Snippet in the <code><head></code> of your Website.</p>
|
||||
|
||||
<%= link(to: "https://plausible.io/docs/plausible-script", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<div class="my-4">
|
||||
<div class="relative">
|
||||
<code><%= textarea f, :domain, id: "snippet_code", class: "transition overflow-hidden bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white focus:border-gray-300 dark:focus:border-gray-500 text-xs mt-2 resize-none", value: render_snippet(@site), rows: 2 %></code>
|
||||
<a onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="no-underline text-indigo-500 text-sm hover:underline">
|
||||
<svg class="absolute text-indigo-500" style="top: 24px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
|
||||
<header class="relative mt-4">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Data Import from Google Analytics</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Import existing data from your Google Analytics account.</p>
|
||||
<%= link(to: "https://plausible.io/docs/google-analytics-import", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<%= if Keyword.get(Application.get_env(:plausible, :google), :client_id) do %>
|
||||
<%= cond do %>
|
||||
<% @site.imported_data && @site.imported_data.status == "importing" -> %>
|
||||
<li class="py-4 flex items-center justify-between space-x-4">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
|
||||
Import from <%= @site.imported_data.source %>
|
||||
<svg class="animate-spin -mr-1 ml-1 h-4 w-4 inline text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</p>
|
||||
<p class="text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
From <%= PlausibleWeb.EmailView.date_format(@site.imported_data.start_date) %> to <%= PlausibleWeb.EmailView.date_format(@site.imported_data.end_date) %>
|
||||
</p>
|
||||
</div>
|
||||
<%= link("Cancel import", to: "/#{URI.encode_www_form(@site.domain)}/settings/forget-imported", method: :delete, class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150") %>
|
||||
</li>
|
||||
|
||||
<% @site.imported_data && @site.imported_data.status == "ok" -> %>
|
||||
<li class="py-4 flex items-center justify-between space-x-4">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
|
||||
Import from <%= @site.imported_data.source %>
|
||||
<svg class="h-4 w-4 inline ml-1 -mt-1 text-green-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</p>
|
||||
<p class="text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
From <%= PlausibleWeb.EmailView.date_format(@site.imported_data.start_date) %> to <%= PlausibleWeb.EmailView.date_format(@site.imported_data.end_date) %>
|
||||
</p>
|
||||
</div>
|
||||
<%= link("Clear " <> PlausibleWeb.StatsView.large_number_format(@imported_pageviews) <> " Imported Pageviews", to: "/#{URI.encode_www_form(@site.domain)}/settings/forget-imported", method: :delete, class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150") %>
|
||||
</li>
|
||||
|
||||
<% true -> %>
|
||||
<%= if @site.imported_data && @site.imported_data.status == "error" do %>
|
||||
<div class="text-sm mt-2 text-gray-900 dark:text-gray-100">Your latest import has failed. You can try importing again by clicking the button below. If you try multiple times and the import keeps failing, please contact support.</div>
|
||||
<% end %>
|
||||
<div class="flex mt-2">
|
||||
<%= button(to: Plausible.Google.Api.import_authorize_url(@site.id, "import"), class: "inline-flex pr-4 items-center border border-gray-100 shadow rounded-md focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-gray-200 mt-8 hover:bg-gray-50 dark:hover:bg-gray-700") do %>
|
||||
<%= google_logo() %>
|
||||
<span style="font-family: Roboto, system-ui" class="text-sm font-medium text-gray-600 dark:text-gray-50">Continue with Google<span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="my-8 text-center text-lg">
|
||||
<svg class="block mx-auto mb-4 w-6 h-6 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
||||
<p class="text-gray-900 dark:text-gray-200">An extra step is needed to set up your Plausible Analytics Self Hosted for the Google Search Console integration.
|
||||
Find instructions <%= link("here", to: "https://plausible.io/docs/self-hosting-configuration#google-search-integration", class: "text-indigo-500") %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<div class="shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div class="bg-white dark:bg-gray-800 py-6 px-4 space-y-6 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Site Domain</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Moving your Site to a different Domain? We got you!
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="change-domain-name" />
|
||||
</header>
|
||||
<div class="grid grid-cols-4 gap-6">
|
||||
<div class="col-span-4 sm:col-span-2">
|
||||
<%= label(nil, "Domain",
|
||||
class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300"
|
||||
) %>
|
||||
<%= text_input(nil, :domain,
|
||||
value: @site.domain,
|
||||
disabled: "disabled",
|
||||
class:
|
||||
"dark:bg-gray-900 w-full mt-1 block pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100 text-gray-500"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
<span class="inline-flex rounded-md shadow-sm">
|
||||
<%= link("Change Domain",
|
||||
to: Routes.site_path(@conn, :change_domain, @site.domain),
|
||||
class: "button"
|
||||
) %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= form_for @changeset, "/#{URI.encode_www_form(@site.domain)}/settings", fn f -> %>
|
||||
<div class="shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div class="bg-white dark:bg-gray-800 py-6 px-4 space-y-6 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
Site Timezone
|
||||
</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Update your reporting Timezone.
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="general" />
|
||||
</header>
|
||||
<div class="grid grid-cols-4 gap-6">
|
||||
<div class="col-span-4 sm:col-span-2">
|
||||
<%= label(f, :timezone, "Reporting Timezone",
|
||||
class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300"
|
||||
) %>
|
||||
<%= select(f, :timezone, Plausible.Timezones.options(),
|
||||
class:
|
||||
"dark:bg-gray-900 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100 cursor-pointer"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
<span class="inline-flex rounded-md shadow-sm">
|
||||
<%= submit("Save", class: "button") %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_for @conn, "/", [class: "shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"], fn f -> %>
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
JavaScript Snippet
|
||||
</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Include this Snippet in the <code><head></code> of your Website.
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="plausible-script" />
|
||||
</header>
|
||||
|
||||
<div class="my-4">
|
||||
<div class="relative">
|
||||
<code>
|
||||
<%= textarea(f, :domain,
|
||||
id: "snippet_code",
|
||||
class:
|
||||
"transition overflow-hidden bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white focus:border-gray-300 dark:focus:border-gray-500 text-xs mt-2 resize-none",
|
||||
value: render_snippet(@site),
|
||||
rows: 2
|
||||
) %>
|
||||
</code>
|
||||
<a
|
||||
onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');"
|
||||
href="javascript:void(0)"
|
||||
class="no-underline text-indigo-500 text-sm hover:underline"
|
||||
>
|
||||
<svg
|
||||
class="absolute text-indigo-500"
|
||||
style="top: 24px; right: 12px;"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -11,21 +11,7 @@
|
|||
>compose Goals into Funnels</a>.
|
||||
</p>
|
||||
|
||||
<%= link(to: "https://plausible.io/docs/goal-conversions", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg
|
||||
class="w-6 h-6 absolute top-0 right-0 text-gray-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clip-rule="evenodd"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<% end %>
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="goal-conversions" />
|
||||
</header>
|
||||
|
||||
<PlausibleWeb.Components.Site.Feature.toggle
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
<div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
|
||||
<header class="relative border-b border-gray-200 pb-4">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
Google Analytics Data Import
|
||||
</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Import existing data from your Google Analytics account.
|
||||
</p>
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="google-analytics-import" />
|
||||
</header>
|
||||
|
||||
<%= if Keyword.get(Application.get_env(:plausible, :google), :client_id) do %>
|
||||
<%= cond do %>
|
||||
<% @site.imported_data && @site.imported_data.status == "importing" -> %>
|
||||
<li class="py-4 flex items-center justify-between space-x-4">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
|
||||
Import from <%= @site.imported_data.source %>
|
||||
<svg
|
||||
class="animate-spin -mr-1 ml-1 h-4 w-4 inline text-indigo-600"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
>
|
||||
</circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</p>
|
||||
<p class="text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
From <%= PlausibleWeb.EmailView.date_format(@site.imported_data.start_date) %> to <%= PlausibleWeb.EmailView.date_format(
|
||||
@site.imported_data.end_date
|
||||
) %>
|
||||
</p>
|
||||
</div>
|
||||
<%= link("Cancel import",
|
||||
to: "/#{URI.encode_www_form(@site.domain)}/settings/forget-imported",
|
||||
method: :delete,
|
||||
class:
|
||||
"inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150"
|
||||
) %>
|
||||
</li>
|
||||
<% @site.imported_data && @site.imported_data.status == "ok" -> %>
|
||||
<li class="py-4 flex items-center justify-between space-x-4">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
|
||||
Import from <%= @site.imported_data.source %>
|
||||
<svg
|
||||
class="h-4 w-4 inline ml-1 -mt-1 text-green-600"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</p>
|
||||
<p class="text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
From <%= PlausibleWeb.EmailView.date_format(@site.imported_data.start_date) %> to <%= PlausibleWeb.EmailView.date_format(
|
||||
@site.imported_data.end_date
|
||||
) %>
|
||||
</p>
|
||||
</div>
|
||||
<%= link(
|
||||
"Clear " <>
|
||||
PlausibleWeb.StatsView.large_number_format(@imported_pageviews) <>
|
||||
" Imported Pageviews",
|
||||
to: "/#{URI.encode_www_form(@site.domain)}/settings/forget-imported",
|
||||
method: :delete,
|
||||
class:
|
||||
"inline-block mt-4 px-4 py-2 text-sm leading-5 font-medium text-red-600 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150"
|
||||
) %>
|
||||
</li>
|
||||
<% true -> %>
|
||||
<%= if @site.imported_data && @site.imported_data.status == "error" do %>
|
||||
<div class="text-sm mt-2 text-gray-900 dark:text-gray-100">
|
||||
Your latest import has failed. You can try importing again by clicking the button below. If you try multiple times and the import keeps failing, please contact support.
|
||||
</div>
|
||||
<% end %>
|
||||
<PlausibleWeb.Components.Google.button
|
||||
id="analytics-connect"
|
||||
to={Plausible.Google.Api.import_authorize_url(@site.id, "import")}
|
||||
/>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="my-8 text-center text-lg">
|
||||
<svg
|
||||
class="block mx-auto mb-4 w-6 h-6 text-yellow-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<p class="text-gray-900 dark:text-gray-200">
|
||||
An extra step is needed to set up your Plausible Analytics Self Hosted for the Google Search Console integration.
|
||||
Find instructions <%= link("here",
|
||||
to: "https://plausible.io/docs/self-hosting-configuration#google-search-integration",
|
||||
class: "text-indigo-500"
|
||||
) %>
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<PlausibleWeb.Components.Settings.settings_search_console
|
||||
site={@site}
|
||||
search_console_domains={@search_console_domains}
|
||||
/>
|
||||
<PlausibleWeb.Components.Settings.settings_google_import
|
||||
site={@site}
|
||||
imported_pageviews={@imported_pageviews}
|
||||
/>
|
||||
|
||||
<section
|
||||
:if={@has_plugins_tokens? || @conn.query_params["new_token"]}
|
||||
class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden"
|
||||
>
|
||||
<div class="py-6 px-4 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
Plugins API Tokens
|
||||
</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Control Plugins API Access
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<%= live_render(@conn, PlausibleWeb.Live.Plugins.API.Settings,
|
||||
session: %{
|
||||
"site_id" => @site.id,
|
||||
"domain" => @site.domain,
|
||||
"new_token" => @conn.query_params["new_token"]
|
||||
}
|
||||
) %>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
<div class="shadow bg-white dark:bg-gray-800 sm:rounded-md py-6 px-4 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">People</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Invite your friends or coworkers</p>
|
||||
<%= link(to: "https://plausible.io/docs/users-roles", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
Invite your friends or coworkers
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="users-roles" />
|
||||
</header>
|
||||
<div class="flow-root mt-6">
|
||||
<ul class="-my-5 divide-y divide-gray-200 dark:divide-gray-400">
|
||||
|
|
@ -12,7 +13,9 @@
|
|||
<li class="py-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<%= img_tag(Plausible.Auth.User.profile_img_url(membership.user), class: "h-8 w-8 rounded-full") %>
|
||||
<%= img_tag(Plausible.Auth.User.profile_img_url(membership.user),
|
||||
class: "h-8 w-8 rounded-full"
|
||||
) %>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-50 truncate">
|
||||
|
|
@ -24,60 +27,125 @@
|
|||
</div>
|
||||
|
||||
<div x-data="{open: false}" @click.away="open = false" x-cloak class="relative">
|
||||
<button @click="open = !open" class="inline-flex items-center shadow-sm px-2.5 py-0.5 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-full bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
<button
|
||||
@click="open = !open"
|
||||
class="inline-flex items-center shadow-sm px-2.5 py-0.5 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-full bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
>
|
||||
<%= membership.role |> Atom.to_string() |> String.capitalize() %>
|
||||
<svg class="w-4 h-4 pt-px ml-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
|
||||
<svg
|
||||
class="w-4 h-4 pt-px ml-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
<ul
|
||||
x-show="open"
|
||||
x-transition:leave="transition ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="origin-top-right absolute z-10 right-0 mt-2 w-72 rounded-md shadow-lg overflow-hidden bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-400 ring-1 ring-black ring-opacity-5 focus:outline-none" tabindex="-1" role="listbox" aria-labelledby="listbox-label" aria-activedescendant="listbox-option-0"
|
||||
class="origin-top-right absolute z-10 right-0 mt-2 w-72 rounded-md shadow-lg overflow-hidden bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-400 ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
tabindex="-1"
|
||||
role="listbox"
|
||||
aria-labelledby="listbox-label"
|
||||
aria-activedescendant="listbox-option-0"
|
||||
>
|
||||
<%= if membership.role == :owner do %>
|
||||
<li class="p-4 text-sm cursor-default group flex justify-between" role="option">
|
||||
<div>
|
||||
<p class="text-base font-medium text-gray-900 dark:text-gray-100">Owner</p>
|
||||
<p class="mt-1 text-sm text-gray-500">Site owner cannot be assigned to any other role</p>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Site owner cannot be assigned to any other role
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<span class="text-indigo-500">
|
||||
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</li>
|
||||
<%= if @conn.assigns[:current_user_role] == :owner do %>
|
||||
<li class="select-none hover:bg-gray-100 dark:hover:bg-gray-900 text-red-600" role="option">
|
||||
<%= link("Transfer ownership →", to: Routes.membership_path(@conn, :transfer_ownership_form, @site.domain), class: "inline-block w-full p-4 text-sm text-red-600 font-medium") %>
|
||||
<li
|
||||
class="select-none hover:bg-gray-100 dark:hover:bg-gray-900 text-red-600"
|
||||
role="option"
|
||||
>
|
||||
<%= link("Transfer ownership →",
|
||||
to: Routes.membership_path(@conn, :transfer_ownership_form, @site.domain),
|
||||
class: "inline-block w-full p-4 text-sm text-red-600 font-medium"
|
||||
) %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link(to: Routes.membership_path(@conn, :update_role, @site.domain, membership.id, "admin"), method: :put, class: "p-4 flex justify-between text-sm group hover:bg-indigo-500") do %>
|
||||
<div>
|
||||
<p class="text-base font-medium text-gray-900 dark:text-gray-100 group-hover:text-white">Admin</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-300 group-hover:text-gray-100 dark:group-hover:text-white">View stats and edit site settings</p>
|
||||
<p class="text-base font-medium text-gray-900 dark:text-gray-100 group-hover:text-white">
|
||||
Admin
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-300 group-hover:text-gray-100 dark:group-hover:text-white">
|
||||
View stats and edit site settings
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<%= if membership.role == :admin do %>
|
||||
<span class="text-indigo-500 group-hover:text-white">
|
||||
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= link(to: Routes.membership_path(@conn, :update_role, @site.domain, membership.id, "viewer"), method: :put, class: "p-4 flex justify-between text-sm group hover:bg-indigo-500") do %>
|
||||
<div>
|
||||
<p class="text-base font-medium text-gray-900 dark:text-gray-100 group-hover:text-white">Viewer</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-300 group-hover:text-gray-100 dark:group-hover:text-white">View stats only</p>
|
||||
<p class="text-base font-medium text-gray-900 dark:text-gray-100 group-hover:text-white">
|
||||
Viewer
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-300 group-hover:text-gray-100 dark:group-hover:text-white">
|
||||
View stats only
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<%= if membership.role == :viewer do %>
|
||||
<span class="text-indigo-500 group-hover:text-white">
|
||||
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<% end %>
|
||||
|
|
@ -96,7 +164,9 @@
|
|||
|
||||
<%= if Enum.count(@site.invitations) > 0 do %>
|
||||
<header class="mt-12">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Pending invitations</h2>
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
Pending invitations
|
||||
</h2>
|
||||
</header>
|
||||
<div class="flex flex-col mt-4">
|
||||
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
|
|
@ -105,10 +175,16 @@
|
|||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-400">
|
||||
<thead class="bg-gray-50 dark:bg-gray-900">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
<th
|
||||
scope="col"
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider"
|
||||
>
|
||||
Email
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
|
||||
<th
|
||||
scope="col"
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider"
|
||||
>
|
||||
Role
|
||||
</th>
|
||||
<th scope="col" class="relative px-6 py-3">
|
||||
|
|
@ -123,10 +199,20 @@
|
|||
<%= invitation.email %>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-200">
|
||||
<%= invitation.role |> Atom.to_string |> String.capitalize %>
|
||||
<%= invitation.role |> Atom.to_string() |> String.capitalize() %>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<%= link("Remove", to: Routes.invitation_path(@conn, :remove_invitation, @site.domain, invitation.invitation_id), method: :delete, class: "text-red-600 hover:text-red-900") %>
|
||||
<%= link("Remove",
|
||||
to:
|
||||
Routes.invitation_path(
|
||||
@conn,
|
||||
:remove_invitation,
|
||||
@site.domain,
|
||||
invitation.invitation_id
|
||||
),
|
||||
method: :delete,
|
||||
class: "text-red-600 hover:text-red-900"
|
||||
) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
|
@ -141,7 +227,15 @@
|
|||
|
||||
<div class="mt-8">
|
||||
<%= link(to: Routes.membership_path(@conn, :invite_member_form, @site.domain), class: "button") do %>
|
||||
<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"></path></svg>
|
||||
<svg
|
||||
class="w-5 h-5 mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z">
|
||||
</path>
|
||||
</svg>
|
||||
Invite
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
/>
|
||||
|
||||
<div class="py-6 px-4 sm:p-6">
|
||||
<header class="w-full flex">
|
||||
<header class="w-full flex relative">
|
||||
<span class="flex-1">
|
||||
<h1 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
Custom Properties
|
||||
|
|
@ -23,25 +23,7 @@
|
|||
</p>
|
||||
</span>
|
||||
|
||||
<.link
|
||||
href="https://plausible.io/docs/custom-props/introduction"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6 text-gray-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clip-rule="evenodd"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</.link>
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="custom-props/introduction" />
|
||||
</header>
|
||||
|
||||
<PlausibleWeb.Components.Site.Feature.toggle
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
<div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Google Search Console integration</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">You can integrate with Google Search Console to get all of your important search results stats such as keyword phrases people find your site with.</p>
|
||||
<%= link(to: "https://plausible.io/docs/google-search-console-integration", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<%= if Keyword.get(Application.get_env(:plausible, :google), :client_id) do %>
|
||||
<%= if @site.google_auth do %>
|
||||
<div class="py-2"></div>
|
||||
<span class="text-gray-700 dark:text-gray-300">Linked Google account: <b><%= @site.google_auth.email %></b></span>
|
||||
|
||||
<%= link("Unlink Google account", to: "/#{URI.encode_www_form(@site.domain)}/settings/google-search", class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete") %>
|
||||
|
||||
<%= case @search_console_domains do %>
|
||||
<% {:ok, domains} -> %>
|
||||
<%= if @site.google_auth.property && !(@site.google_auth.property in domains) do %>
|
||||
<p class="text-gray-700 dark:text-gray-300 mt-6 font-bold">
|
||||
NB: Your Google account does not have access to your currently configured property, <%= @site.google_auth.property %>. Please select a verified property from the list below.
|
||||
</p>
|
||||
<% else %>
|
||||
<p class="text-gray-700 dark:text-gray-300 mt-6">
|
||||
Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://plausible.io/docs/google-search-console-integration", class: "text-indigo-500", target: "_blank", rel: "noreferrer") %> on Search Console first.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= form_for Plausible.Site.GoogleAuth.changeset(@site.google_auth), "/#{URI.encode_www_form(@site.domain)}/settings/google", [class: "max-w-xs"], fn f -> %>
|
||||
<div class="my-6">
|
||||
<div class="inline-block relative w-full">
|
||||
<%= select f, :property, domains, prompt: "(Choose property)", class: "dark:bg-gray-800 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 outline-none focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= submit "Save", class: "button" %>
|
||||
<% end %>
|
||||
<% {:error, error} -> %>
|
||||
<p class="text-gray-700 dark:text-gray-300 mt-6">The following error happened when fetching your Google Search Console domains:</p>
|
||||
|
||||
<%= case error do %>
|
||||
<% "invalid_grant" -> %>
|
||||
<p class="text-red-700 font-medium mt-3">
|
||||
<a href="https://plausible.io/docs/google-search-console-integration#i-get-the-invalid-grant-error">
|
||||
Invalid Grant error returned from Google. <span class="text-indigo-500">See here on how to fix it</span>.
|
||||
</a>
|
||||
</p>
|
||||
<% "google_auth_error" -> %>
|
||||
<p class="text-red-700 font-medium mt-3">
|
||||
Your Search Console account hasn't been connected successfully. Please unlink your Google account and try linking it again.
|
||||
</p>
|
||||
|
||||
<% _ -> %>
|
||||
<p class="text-red-700 font-medium mt-3">
|
||||
Something went wrong, but looks temporary. If the problem persists, try re-linking your Google account.
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button("Continue with Google", to: Plausible.Google.Api.search_console_authorize_url(@site.id, "search-console"), class: "button mt-8") %>
|
||||
|
||||
<div class="text-gray-700 dark:text-gray-300 mt-8">
|
||||
NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://plausible.io/docs/google-search-console-integration", class: "text-indigo-500", rel: "noreferrer") %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="my-8 text-center text-lg">
|
||||
<svg class="block mx-auto mb-4 w-6 h-6 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
||||
<p class="text-gray-900 dark:text-gray-200">An extra step is needed to set up your Plausible Analytics Self Hosted for the Google Search Console integration.
|
||||
Find instructions <%= link("here", to: "https://plausible.io/docs/self-hosting-configuration#google-search-integration", class: "text-indigo-500") %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
|
||||
<header class="relative border-b border-gray-200 pb-4">
|
||||
<h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
|
||||
Google Search Console Integration
|
||||
</h2>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
|
||||
You can integrate with Google Search Console to get all of your important search results stats such as keyword phrases people find your site with.
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="google-search-console-integration" />
|
||||
</header>
|
||||
|
||||
<%= if Keyword.get(Application.get_env(:plausible, :google), :client_id) do %>
|
||||
<%= if @site.google_auth do %>
|
||||
<div class="flex py-8">
|
||||
<span class="flex-1 text-gray-700 dark:text-gray-300">
|
||||
Linked Google account: <b><%= @site.google_auth.email %></b>
|
||||
</span>
|
||||
|
||||
<%= link("Unlink Google account",
|
||||
to: "/#{URI.encode_www_form(@site.domain)}/settings/google-search",
|
||||
class:
|
||||
"inline-block px-4 text-sm leading-5 font-medium text-red-600 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150",
|
||||
method: "delete"
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<%= case @search_console_domains do %>
|
||||
<% {:ok, domains} -> %>
|
||||
<%= if @site.google_auth.property && !(@site.google_auth.property in domains) do %>
|
||||
<p class="text-gray-700 dark:text-gray-300 mt-6 font-bold">
|
||||
NB: Your Google account does not have access to your currently configured property, <%= @site.google_auth.property %>. Please select a verified property from the list below.
|
||||
</p>
|
||||
<% else %>
|
||||
<p class="text-gray-700 dark:text-gray-300 mt-6">
|
||||
Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain,
|
||||
<.styled_link
|
||||
href="https://plausible.io/docs/google-search-console-integration"
|
||||
new_tab={true}
|
||||
>
|
||||
set it up and verify
|
||||
</.styled_link>
|
||||
on Search Console first.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= form_for Plausible.Site.GoogleAuth.changeset(@site.google_auth), "/#{URI.encode_www_form(@site.domain)}/settings/google", [class: "max-w-xs"], fn f -> %>
|
||||
<div class="my-6">
|
||||
<div class="inline-block relative w-full">
|
||||
<%= select(f, :property, domains,
|
||||
prompt: "(Choose property)",
|
||||
class:
|
||||
"dark:bg-gray-800 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 outline-none focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= submit("Save", class: "button") %>
|
||||
<% end %>
|
||||
<% {:error, error} -> %>
|
||||
<p class="text-gray-700 dark:text-gray-300 mt-6">
|
||||
The following error happened when fetching your Google Search Console domains:
|
||||
</p>
|
||||
|
||||
<%= case error do %>
|
||||
<% "invalid_grant" -> %>
|
||||
<p class="text-red-700 font-medium mt-3">
|
||||
<a href="https://plausible.io/docs/google-search-console-integration#i-get-the-invalid-grant-error">
|
||||
Invalid Grant error returned from Google. <span class="text-indigo-500">See here on how to fix it</span>.
|
||||
</a>
|
||||
</p>
|
||||
<% "google_auth_error" -> %>
|
||||
<p class="text-red-700 font-medium mt-3">
|
||||
Your Search Console account hasn't been connected successfully. Please unlink your Google account and try linking it again.
|
||||
</p>
|
||||
<% _ -> %>
|
||||
<p class="text-red-700 font-medium mt-3">
|
||||
Something went wrong, but looks temporary. If the problem persists, try re-linking your Google account.
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<PlausibleWeb.Components.Google.button
|
||||
id="search-console-connect"
|
||||
to={Plausible.Google.Api.search_console_authorize_url(@site.id, "search-console")}
|
||||
/>
|
||||
<div class="text-gray-700 dark:text-gray-300 mt-8">
|
||||
NB: You also need to set up your site on
|
||||
<.styled_link href="https://search.google.com/search-console/about" new_tab={true}>
|
||||
Google Search Console
|
||||
</.styled_link>
|
||||
for the integration to work.
|
||||
<.styled_link
|
||||
href="https://plausible.io/docs/google-search-console-integration"
|
||||
new_tab={true}
|
||||
>
|
||||
Read the docs
|
||||
</.styled_link>,
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="my-8 text-center text-lg">
|
||||
<svg
|
||||
class="block mx-auto mb-4 w-6 h-6 text-yellow-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<p class="text-gray-900 dark:text-gray-200">
|
||||
An extra step is needed to set up your Plausible Analytics Self Hosted for the Google Search Console integration.
|
||||
Find instructions <%= link("here",
|
||||
to: "https://plausible.io/docs/self-hosting-configuration#google-search-integration",
|
||||
class: "text-indigo-500"
|
||||
) %>
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
<div class="px-4 py-6 bg-white shadow dark:bg-gray-800 sm:rounded-md sm:overflow-hidden sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg font-medium text-gray-900 leading-6 dark:text-gray-100">Public dashboard</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 leading-5 dark:text-gray-200">Share your stats publicly or keep them private</p>
|
||||
<%= link(to: "https://plausible.io/docs/visibility", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="absolute top-0 right-0 w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<%= if @site.public do %>
|
||||
<div class="flex items-center mt-4 space-x-3">
|
||||
<%= button(to: Routes.site_path(@conn, :make_private, @site.domain), method: "POST", class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="inline-block w-5 h-5 bg-white rounded-full shadow translate-x-5 dark:bg-gray-800 transform transition ease-in-out duration-200"></span>
|
||||
<% end %>
|
||||
<span class="text-sm font-medium text-gray-900 leading-5 dark:text-gray-100">
|
||||
Stats are publicly available on <%= link(PlausibleWeb.StatsView.pretty_stats_url(@site), to: Routes.stats_path(@conn, :stats, @site.domain, []), class: "text-indigo-500") %>
|
||||
</span>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="flex items-center mt-4 space-x-3">
|
||||
<%= button(to: Routes.site_path(@conn, :make_public, @site.domain), method: "POST", class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="inline-block w-5 h-5 bg-white rounded-full shadow translate-x-0 dark:bg-gray-800 transform transition ease-in-out duration-200"></span>
|
||||
<% end %>
|
||||
<span class="text-sm font-medium text-gray-900 leading-5 dark:text-gray-100">
|
||||
Make stats publicly available on <%= link(PlausibleWeb.StatsView.pretty_stats_url(@site), to: Routes.stats_path(@conn, :stats, @site.domain, []), class: "text-indigo-500") %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-6 bg-white shadow dark:bg-gray-800 sm:rounded-md sm:overflow-hidden sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg font-medium text-gray-900 leading-6 dark:text-gray-100">Shared Links</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 leading-5 dark:text-gray-200">You can share your stats privately by generating a shared link. The links are impossible to guess and you can add password protection for extra security.</p>
|
||||
<%= link(to: "https://plausible.io/docs/shared-links", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="absolute top-0 right-0 w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<div class="mt-6 flex flex-col divide-y divide-gray-200">
|
||||
<%= for link <- @shared_links do %>
|
||||
<div class="py-4">
|
||||
<label for="<%= link.slug %>" class="flex content-center text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<%= link.name %>
|
||||
<%= if link.password_hash do %>
|
||||
<svg class="ml-1 w-4 h-4 mt-px" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg>
|
||||
<% else %>
|
||||
<svg class="ml-1 w-4 h-4 mt-px" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2H7V7a3 3 0 015.905-.75 1 1 0 001.937-.5A5.002 5.002 0 0010 2z"></path></svg>
|
||||
<% end %>
|
||||
</label>
|
||||
<div class="relative flex w-full mt-2 text-sm">
|
||||
<input type="text" id="<%= link.slug %>" readonly="readonly" value="<%= shared_link_dest(@site, link) %>" class="w-full p-2 text-gray-700 bg-gray-100 border-none rounded rounded-r-none outline-none appearance-none transition dark:bg-gray-900 dark:text-gray-300 focus:outline-none focus:border-gray-300 dark:focus:border-gray-500" />
|
||||
<button onclick="var input = document.getElementById('<%= link.slug %>'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="px-4 py-2 inline-flex items-center text-indigo-800 bg-gray-200 border-r border-gray-300 rounded-none dark:bg-gray-850 dark:text-indigo-500 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-825">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"></path><path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z"></path></svg>
|
||||
<span class="ml-1">Copy</span>
|
||||
</button>
|
||||
|
||||
<%= link(to: Routes.site_path(@conn, :edit_shared_link, @site.domain, link.slug), class: "px-4 py-2 inline-flex items-center text-indigo-800 bg-gray-200 border-r border-gray-300 rounded-none dark:bg-gray-850 dark:text-indigo-500 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-825") do %>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path><path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
<%= button(to: Routes.site_path(@conn, :delete_shared_link, @site.domain, link.slug), method: :delete, class: "py-2 px-4 inline-flex items-center bg-gray-200 dark:bg-gray-850 text-red-600 dark:text-red-500 rounded-l-none hover:bg-gray-300 dark:hover:bg-gray-825", data: [confirm: "Are you sure you want to delete this shared link? The stats will not be accessible with this link anymore."]) do %>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= link("+ New Link", to: Routes.site_path(@conn, :new_shared_link, @site.domain), class: "button mt-4") %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-6 bg-white shadow dark:bg-gray-800 sm:rounded-md sm:overflow-hidden sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg font-medium text-gray-900 leading-6 dark:text-gray-100">Embed Dashboard</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 leading-5 dark:text-gray-200">You can use shared links to embed your stats in any other webpage using an <code>iframe</code>. Copy & paste a shared link into the form below to generate the embed code.</p>
|
||||
<%= link(to: "https://plausible.io/docs/embed-dashboard", target: "_blank", rel: "noreferrer") do %>
|
||||
<svg class="absolute top-0 right-0 w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<div class="max-w-xl mt-4">
|
||||
<div>
|
||||
<label for="embed-link" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Enter Shared Link</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-200">Only public shared links without password protection can be embedded</p>
|
||||
<div class="mt-1">
|
||||
<input type="text" name="embed-link" id="embed-link" onclick="this.select()" class="block w-full border-gray-300 dark:border-gray-700 rounded-md focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="theme" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Select Theme</label>
|
||||
<select id="theme" name="theme" class="block w-full py-2 pl-3 pr-10 mt-1 text-base border-gray-300 dark:border-gray-700 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-900 dark:text-gray-300">
|
||||
<option selected>Light</option>
|
||||
<option>Dark</option>
|
||||
<option>System</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="background" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Custom Background Colour (optional)</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-200">Hint: try using `transparent` background to blend the dashboard with your site background</p>
|
||||
<div class="mt-1">
|
||||
<input type="text" name="background" id="background" class="block w-full border-gray-300 dark:border-gray-700 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-900 dark:text-gray-300" placeholder="#F9FAFB">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="base-url" value="<%= plausible_url() %>" />
|
||||
<button id="generate-embed" class="my-4 button">Generate Embed Code 👇</button>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="max-w-xl">
|
||||
<label for="embed-code" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Embed Code</label>
|
||||
|
||||
<div class="relative mt-1">
|
||||
<textarea id="embed-code" name="embed-code" rows="3" readonly="readonly" onclick="this.select()" class="block w-full max-w-xl border-gray-300 dark:border-gray-700 resize-none shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-900 dark:text-gray-300"></textarea>
|
||||
<a onclick="var textarea = document.getElementById('embed-code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="text-sm text-indigo-500 no-underline hover:underline">
|
||||
<svg class="absolute text-indigo-800" style="top: 12px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
<div class="px-4 py-6 bg-white shadow dark:bg-gray-800 sm:rounded-md sm:overflow-hidden sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg font-medium text-gray-900 leading-6 dark:text-gray-100">
|
||||
Public dashboard
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 leading-5 dark:text-gray-200">
|
||||
Share your stats publicly or keep them private
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="visibility" />
|
||||
</header>
|
||||
|
||||
<%= if @site.public do %>
|
||||
<div class="flex items-center mt-4 space-x-3">
|
||||
<%= button(to: Routes.site_path(@conn, :make_private, @site.domain), method: "POST", class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="inline-block w-5 h-5 bg-white rounded-full shadow translate-x-5 dark:bg-gray-800 transform transition ease-in-out duration-200">
|
||||
</span>
|
||||
<% end %>
|
||||
<span class="text-sm font-medium text-gray-900 leading-5 dark:text-gray-100">
|
||||
Stats are publicly available on <%= link(PlausibleWeb.StatsView.pretty_stats_url(@site),
|
||||
to: Routes.stats_path(@conn, :stats, @site.domain, []),
|
||||
class: "text-indigo-500"
|
||||
) %>
|
||||
</span>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="flex items-center mt-4 space-x-3">
|
||||
<%= button(to: Routes.site_path(@conn, :make_public, @site.domain), method: "POST", class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
|
||||
<span class="inline-block w-5 h-5 bg-white rounded-full shadow translate-x-0 dark:bg-gray-800 transform transition ease-in-out duration-200">
|
||||
</span>
|
||||
<% end %>
|
||||
<span class="text-sm font-medium text-gray-900 leading-5 dark:text-gray-100">
|
||||
Make stats publicly available on <%= link(PlausibleWeb.StatsView.pretty_stats_url(@site),
|
||||
to: Routes.stats_path(@conn, :stats, @site.domain, []),
|
||||
class: "text-indigo-500"
|
||||
) %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-6 bg-white shadow dark:bg-gray-800 sm:rounded-md sm:overflow-hidden sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg font-medium text-gray-900 leading-6 dark:text-gray-100">Shared Links</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 leading-5 dark:text-gray-200">
|
||||
You can share your stats privately by generating a shared link. The links are impossible to guess and you can add password protection for extra security.
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="shared-links" />
|
||||
</header>
|
||||
|
||||
<div class="mt-6 flex flex-col divide-y divide-gray-200">
|
||||
<%= for link <- @shared_links do %>
|
||||
<div class="py-4">
|
||||
<label
|
||||
for={link.slug}
|
||||
class="flex content-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<%= link.name %>
|
||||
<%= if link.password_hash do %>
|
||||
<svg
|
||||
class="ml-1 w-4 h-4 mt-px"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
|
||||
clip-rule="evenodd"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<% else %>
|
||||
<svg
|
||||
class="ml-1 w-4 h-4 mt-px"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2H7V7a3 3 0 015.905-.75 1 1 0 001.937-.5A5.002 5.002 0 0010 2z">
|
||||
</path>
|
||||
</svg>
|
||||
<% end %>
|
||||
</label>
|
||||
<div class="relative flex w-full mt-2 text-sm">
|
||||
<input
|
||||
type="text"
|
||||
id={link.slug}
|
||||
readonly="readonly"
|
||||
value={shared_link_dest(@site, link)}
|
||||
class="w-full p-2 text-gray-700 bg-gray-100 border-none rounded rounded-r-none outline-none appearance-none transition dark:bg-gray-900 dark:text-gray-300 focus:outline-none focus:border-gray-300 dark:focus:border-gray-500"
|
||||
/>
|
||||
<button
|
||||
onclick={"var input = document.getElementById('#{link.slug}'); input.focus(); input.select(); document.execCommand('copy');"}
|
||||
href="javascript:void(0)"
|
||||
class="px-4 py-2 inline-flex items-center text-indigo-800 bg-gray-200 border-r border-gray-300 rounded-none dark:bg-gray-850 dark:text-indigo-500 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-825"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"></path>
|
||||
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="ml-1">Copy</span>
|
||||
</button>
|
||||
|
||||
<%= link(to: Routes.site_path(@conn, :edit_shared_link, @site.domain, link.slug), class: "px-4 py-2 inline-flex items-center text-indigo-800 bg-gray-200 border-r border-gray-300 rounded-none dark:bg-gray-850 dark:text-indigo-500 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-825") do %>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z">
|
||||
</path>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<% end %>
|
||||
<%= button(to: Routes.site_path(@conn, :delete_shared_link, @site.domain, link.slug), method: :delete, class: "py-2 px-4 inline-flex items-center bg-gray-200 dark:bg-gray-850 text-red-600 dark:text-red-500 rounded-l-none hover:bg-gray-300 dark:hover:bg-gray-825", data: [confirm: "Are you sure you want to delete this shared link? The stats will not be accessible with this link anymore."]) do %>
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= link("+ New Link",
|
||||
to: Routes.site_path(@conn, :new_shared_link, @site.domain),
|
||||
class: "button mt-4"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-6 bg-white shadow dark:bg-gray-800 sm:rounded-md sm:overflow-hidden sm:p-6">
|
||||
<header class="relative">
|
||||
<h2 class="text-lg font-medium text-gray-900 leading-6 dark:text-gray-100">
|
||||
Embed Dashboard
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 leading-5 dark:text-gray-200">
|
||||
You can use shared links to embed your stats in any other webpage using an <code>iframe</code>. Copy & paste a shared link into the form below to generate the embed code.
|
||||
</p>
|
||||
|
||||
<PlausibleWeb.Components.Generic.docs_info slug="embed-dashboard" />
|
||||
</header>
|
||||
|
||||
<div class="max-w-xl mt-4">
|
||||
<div>
|
||||
<label for="embed-link" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Enter Shared Link
|
||||
</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-200">
|
||||
Only public shared links without password protection can be embedded
|
||||
</p>
|
||||
<div class="mt-1">
|
||||
<input
|
||||
type="text"
|
||||
name="embed-link"
|
||||
id="embed-link"
|
||||
onclick="this.select()"
|
||||
class="block w-full border-gray-300 dark:border-gray-700 rounded-md focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-gray-900 dark:text-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="theme" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Select Theme
|
||||
</label>
|
||||
<select
|
||||
id="theme"
|
||||
name="theme"
|
||||
class="block w-full py-2 pl-3 pr-10 mt-1 text-base border-gray-300 dark:border-gray-700 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
<option selected>Light</option>
|
||||
<option>Dark</option>
|
||||
<option>System</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="background" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Custom Background Colour (optional)
|
||||
</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-200">
|
||||
Hint: try using `transparent` background to blend the dashboard with your site background
|
||||
</p>
|
||||
<div class="mt-1">
|
||||
<input
|
||||
type="text"
|
||||
name="background"
|
||||
id="background"
|
||||
class="block w-full border-gray-300 dark:border-gray-700 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-900 dark:text-gray-300"
|
||||
placeholder="#F9FAFB"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="base-url" value={plausible_url()} />
|
||||
<button id="generate-embed" class="my-4 button">Generate Embed Code 👇</button>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="max-w-xl">
|
||||
<label for="embed-code" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Embed Code
|
||||
</label>
|
||||
|
||||
<div class="relative mt-1">
|
||||
<textarea
|
||||
id="embed-code"
|
||||
name="embed-code"
|
||||
rows="3"
|
||||
readonly="readonly"
|
||||
onclick="this.select()"
|
||||
class="block w-full max-w-xl border-gray-300 dark:border-gray-700 resize-none shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-900 dark:text-gray-300"
|
||||
>
|
||||
</textarea>
|
||||
<a
|
||||
onclick="var textarea = document.getElementById('embed-code'); textarea.focus(); textarea.select(); document.execCommand('copy');"
|
||||
href="javascript:void(0)"
|
||||
class="text-sm text-indigo-500 no-underline hover:underline"
|
||||
>
|
||||
<svg
|
||||
class="absolute text-indigo-800"
|
||||
style="top: 12px; right: 12px;"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -77,7 +77,7 @@ defmodule PlausibleWeb.LayoutView do
|
|||
[key: "Goals", value: "goals"],
|
||||
[key: "Funnels", value: "funnels"],
|
||||
[key: "Custom Properties", value: "properties"],
|
||||
[key: "Search Console", value: "search-console"],
|
||||
[key: "Integrations", value: "integrations"],
|
||||
[key: "Email Reports", value: "email-reports"],
|
||||
if !is_selfhost() && conn.assigns[:site].custom_domain do
|
||||
[key: "Custom domain", value: "custom-domain"]
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@ site =
|
|||
]
|
||||
)
|
||||
|
||||
Plausible.Factory.insert(:google_auth,
|
||||
user: user,
|
||||
site: site,
|
||||
property: "sc-domain:dummy.test",
|
||||
expires: NaiveDateTime.add(NaiveDateTime.utc_now(), 3600)
|
||||
)
|
||||
|
||||
# Plugins API: on dev environment, use "plausible-plugin-dev-seed-token" for "dummy.site" to authenticate
|
||||
seeded_token = Plausible.Plugins.API.Token.generate("seed-token")
|
||||
|
||||
|
|
|
|||
|
|
@ -40,4 +40,37 @@ defmodule Plausible.Plugins.API.TokensTest do
|
|||
assert {:error, :not_found} = Tokens.find("non-existing")
|
||||
end
|
||||
end
|
||||
|
||||
describe "any?/2" do
|
||||
test "returns if a site has any tokens" do
|
||||
site1 = insert(:site, domain: "foo1.example.com")
|
||||
site2 = insert(:site, domain: "foo2.example.com")
|
||||
assert Tokens.any?(site1) == false
|
||||
assert Tokens.any?(site2) == false
|
||||
assert {:ok, _, _} = Tokens.create(site1, "My test token")
|
||||
assert Tokens.any?(site1) == true
|
||||
assert Tokens.any?(site2) == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete/2" do
|
||||
test "deletes a token" do
|
||||
site1 = insert(:site, domain: "foo1.example.com")
|
||||
site2 = insert(:site, domain: "foo2.example.com")
|
||||
|
||||
assert {:ok, t1, _} = Tokens.create(site1, "My test token")
|
||||
assert {:ok, t2, _} = Tokens.create(site1, "My test token")
|
||||
assert {:ok, _, _} = Tokens.create(site2, "My test token")
|
||||
|
||||
:ok = Tokens.delete(site1, t1.id)
|
||||
# idempotent
|
||||
:ok = Tokens.delete(site1, t1.id)
|
||||
|
||||
assert Tokens.any?(site1)
|
||||
:ok = Tokens.delete(site1, t2.id)
|
||||
refute Tokens.any?(site1)
|
||||
|
||||
assert Tokens.any?(site2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
|
||||
import ExUnit.CaptureLog
|
||||
import Mox
|
||||
import Plausible.Test.Support.HTML
|
||||
|
||||
setup :verify_on_exit!
|
||||
|
||||
|
|
@ -327,10 +328,8 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
resp = html_response(conn, 200)
|
||||
|
||||
assert resp =~ "Site Timezone"
|
||||
assert resp =~ "Data Import from Google Analytics"
|
||||
assert resp =~ "https://accounts.google.com/o/oauth2/v2/auth?"
|
||||
assert resp =~ "analytics.readonly"
|
||||
refute resp =~ "webmasters.readonly"
|
||||
assert resp =~ "Site Domain"
|
||||
assert resp =~ "JavaScript Snippet"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -476,7 +475,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
|
||||
updated_auth = Repo.one(Plausible.Site.GoogleAuth)
|
||||
assert updated_auth.property == "some-new-property.com"
|
||||
assert redirected_to(conn, 302) == "/#{site.domain}/settings/search-console"
|
||||
assert redirected_to(conn, 302) == "/#{site.domain}/settings/integrations"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -488,7 +487,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
conn = delete(conn, "/#{site.domain}/settings/google-search")
|
||||
|
||||
refute Repo.exists?(Plausible.Site.GoogleAuth)
|
||||
assert redirected_to(conn, 302) == "/#{site.domain}/settings/search-console"
|
||||
assert redirected_to(conn, 302) == "/#{site.domain}/settings/integrations"
|
||||
end
|
||||
|
||||
test "fails to delete associated google auth from the outside", %{
|
||||
|
|
@ -504,11 +503,11 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GET /:website/settings/search-console for self-hosting" do
|
||||
describe "GET /:website/settings/integrations for self-hosting" do
|
||||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
test "display search console settings", %{conn: conn, site: site} do
|
||||
conn = get(conn, "/#{site.domain}/settings/search-console")
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
assert resp =~ "An extra step is needed"
|
||||
assert resp =~ "Google Search Console integration"
|
||||
|
|
@ -516,7 +515,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GET /:website/settings/search-console" do
|
||||
describe "GET /:website/integrations (search-console)" do
|
||||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
setup_patch_env(:google, client_id: "some", api_url: "https://www.googleapis.com")
|
||||
|
|
@ -529,12 +528,14 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
test "displays Continue with Google link", %{conn: conn, user: user} do
|
||||
site = insert(:site, domain: "notconnectedyet.example.com", members: [user])
|
||||
|
||||
conn = get(conn, "/#{site.domain}/settings/search-console")
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
assert resp =~ "Continue with Google"
|
||||
assert resp =~ "https://accounts.google.com/o/oauth2/v2/auth?"
|
||||
assert resp =~ "webmasters.readonly"
|
||||
refute resp =~ "analytics.readonly"
|
||||
|
||||
assert button = find(resp, "button#search-console-connect")
|
||||
assert text(button) == "Continue with Google"
|
||||
assert text_of_attr(button, "data-to") =~ "https://accounts.google.com/o/oauth2/v2/auth?"
|
||||
assert text_of_attr(button, "data-to") =~ "webmasters.readonly"
|
||||
refute text_of_attr(button, "data-to") =~ "analytics.readonly"
|
||||
end
|
||||
|
||||
test "displays appropriate error in case of google account `google_auth_error`", %{
|
||||
|
|
@ -551,7 +552,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
end
|
||||
)
|
||||
|
||||
conn = get(conn, "/#{site.domain}/settings/search-console")
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
assert resp =~ "Your Search Console account hasn't been connected successfully"
|
||||
assert resp =~ "Please unlink your Google account and try linking it again"
|
||||
|
|
@ -571,7 +572,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
end
|
||||
)
|
||||
|
||||
conn = get(conn, "/#{site.domain}/settings/search-console")
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
assert resp =~
|
||||
|
|
@ -592,7 +593,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
end
|
||||
)
|
||||
|
||||
conn = get(conn, "/#{site.domain}/settings/search-console")
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
assert resp =~ "Something went wrong, but looks temporary"
|
||||
|
|
@ -616,7 +617,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
|||
|
||||
log =
|
||||
capture_log(fn ->
|
||||
conn = get(conn, "/#{site.domain}/settings/search-console")
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
assert resp =~ "Something went wrong, but looks temporary"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
defmodule PlausibleWeb.Live.PluginsAPISettingsTest do
|
||||
use PlausibleWeb.ConnCase, async: true
|
||||
import Phoenix.LiveViewTest
|
||||
import Plausible.Test.Support.HTML
|
||||
|
||||
alias Plausible.Plugins.API.Tokens
|
||||
|
||||
describe "GET /:website/settings/integrations" do
|
||||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
test "does not display the Plugins API section by default", %{conn: conn, site: site} do
|
||||
conn = get(conn, "/#{site.domain}/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
refute resp =~ "Plugins API Tokens"
|
||||
end
|
||||
|
||||
test "does display the Plugins API section on ?new_token=....", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations?new_token=test")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
assert resp =~ "Plugins API Tokens"
|
||||
end
|
||||
|
||||
test "does display the Plugins API section when there are tokens already created", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
{:ok, _, _} = Tokens.create(site, "test")
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
assert resp =~ "Plugins API Tokens"
|
||||
end
|
||||
|
||||
test "lists tokens with revoke actions", %{conn: conn, site: site} do
|
||||
{:ok, t1, _} = Tokens.create(site, "test-token-1")
|
||||
{:ok, t2, _} = Tokens.create(site, "test-token-2")
|
||||
{:ok, _, _} = Tokens.create(build(:site), "test-token-3")
|
||||
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
assert resp =~ "test-token-1"
|
||||
assert resp =~ "test-token-2"
|
||||
assert resp =~ "**********" <> t1.hint
|
||||
assert resp =~ "**********" <> t2.hint
|
||||
refute resp =~ "test-token-3"
|
||||
|
||||
assert element_exists?(
|
||||
resp,
|
||||
~s/button[phx-click="revoke-token"][phx-value-token-id=#{t1.id}]#revoke-token-#{t1.id}/
|
||||
)
|
||||
|
||||
assert element_exists?(
|
||||
resp,
|
||||
~s/button[phx-click="revoke-token"][phx-value-token-id=#{t2.id}]#revoke-token-#{t2.id}/
|
||||
)
|
||||
end
|
||||
|
||||
test "add token button is rendered", %{conn: conn, site: site} do
|
||||
conn = get(conn, "/#{site.domain}/settings/integrations?new_token=Wordpress")
|
||||
resp = html_response(conn, 200)
|
||||
|
||||
assert element_exists?(resp, ~s/button[phx-click="add-token"]/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Plugins.API.Settings live view" do
|
||||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
test "create token form shows up invoked via URL", %{conn: conn, site: site} do
|
||||
{_lv, html} =
|
||||
get_liveview(conn, site, with_html?: true, query_params: "?new_token=Wordpress")
|
||||
|
||||
assert element_exists?(html, "#token-form")
|
||||
assert text_of_element(html, "label[for=token_description]") == "Description"
|
||||
assert element_exists?(html, "input[value=Wordpress]#token_description")
|
||||
assert text_of_element(html, "label[for=token-clipboard]") == "API Token"
|
||||
assert element_exists?(html, "input#token-clipboard")
|
||||
|
||||
assert element_exists?(
|
||||
html,
|
||||
~s/div#token-form form[phx-submit="save-token"][phx-click-away="cancel-add-token"]/
|
||||
)
|
||||
end
|
||||
|
||||
test "adds token", %{conn: conn, site: site} do
|
||||
refute Tokens.any?(site)
|
||||
|
||||
lv = get_liveview(conn, site, query_params: "?new_token=Wordpress")
|
||||
|
||||
lv
|
||||
|> find_live_child("token-form")
|
||||
|> element("form")
|
||||
|> render_submit()
|
||||
|
||||
assert Tokens.any?(site)
|
||||
|
||||
html = render(lv)
|
||||
assert text_of_element(html, "span.token-description") == "Wordpress"
|
||||
end
|
||||
|
||||
test "fails to add token with no description", %{conn: conn, site: site} do
|
||||
{:ok, _, _} = Tokens.create(site, "test")
|
||||
|
||||
lv = get_liveview(conn, site)
|
||||
|
||||
lv |> render_click("add-token")
|
||||
|
||||
lv
|
||||
|> find_live_child("token-form")
|
||||
|> element("form")
|
||||
|> render_submit()
|
||||
|
||||
assert [_] = Tokens.list(site)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_liveview(conn, site, opts \\ []) do
|
||||
query_params = Keyword.get(opts, :query_params, "")
|
||||
conn = assign(conn, :live_module, PlausibleWeb.Live.Plugins.API.Settings)
|
||||
{:ok, lv, html} = live(conn, "/#{site.domain}/settings/integrations#{query_params}")
|
||||
|
||||
if Keyword.get(opts, :with_html?) do
|
||||
{lv, html}
|
||||
else
|
||||
lv
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -127,7 +127,7 @@ defmodule Plausible.Factory do
|
|||
|
||||
def google_auth_factory do
|
||||
%Plausible.Site.GoogleAuth{
|
||||
email: sequence(:google_auth_email, &"email-#{&1}@email.com"),
|
||||
email: sequence(:google_auth_email, &"email-#{&1}@example.com"),
|
||||
refresh_token: "123",
|
||||
access_token: "123",
|
||||
expires: Timex.now() |> Timex.shift(days: 1)
|
||||
|
|
|
|||
Loading…
Reference in New Issue