diff --git a/assets/js/app.js b/assets/js/app.js
index 7cdf64bc8..1b5a24221 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -17,8 +17,9 @@ import LiveReact, { initLiveReact } from "phoenix_live_react";
import sourceLiveViewHooks from "./source_lv_hooks";
import logsLiveViewHooks from "./log_event_live_hooks";
-
+import $ from "jquery"
import moment from "moment";
+
// set moment globally before daterangepicker
window.moment = moment;
@@ -100,4 +101,24 @@ window.liveSocket = liveSocket;
document.addEventListener("DOMContentLoaded", (e) => {
initLiveReact();
+
+});
+
+// Use `:text` on the `:detail` optoin to pass values to event listener
+window.addEventListener("logflare:copy-to-clipboard", (event) => {
+ if ("clipboard" in navigator) {
+ const text = event.detail?.text || event.target.textContent;
+ navigator.clipboard.writeText(text);
+ } else {
+ console.error("Your browser does not support clipboard copy.");
+ }
});
+
+
+window.addEventListener("phx:page-loading-stop", _info => {
+ // enable all tooltips
+ $(function () {
+ $('[data-toggle="tooltip"]').tooltip({delay: {show: 100, hide: 200}})
+ });
+
+})
diff --git a/lib/logflare_web.ex b/lib/logflare_web.ex
index eec7a72a7..f9db24f9a 100644
--- a/lib/logflare_web.ex
+++ b/lib/logflare_web.ex
@@ -123,6 +123,7 @@ defmodule LogflareWeb do
quote do
use LogflareWeb.LiveCommons
use LogflareWeb.ModalLiveHelpers
+ alias Phoenix.LiveView.JS
end
end
diff --git a/lib/logflare_web/core_components.ex b/lib/logflare_web/core_components.ex
index c006fe298..b5a8cfa31 100644
--- a/lib/logflare_web/core_components.ex
+++ b/lib/logflare_web/core_components.ex
@@ -8,11 +8,12 @@ defmodule LogflareWeb.CoreComponents do
attr :variant, :string,
values: ["primary", "secondary", "success", "danger", "warning", "info", "light", "dark"]
+ attr :class, :string, required: false, default: ""
slot :inner_block, required: true
def alert(assigns) do
~H"""
-
+
<%= render_slot(@inner_block) %>
"""
diff --git a/lib/logflare_web/live/access_tokens_live.ex b/lib/logflare_web/live/access_tokens_live.ex
index c5ba06904..ecc98fd3c 100644
--- a/lib/logflare_web/live/access_tokens_live.ex
+++ b/lib/logflare_web/live/access_tokens_live.ex
@@ -6,69 +6,113 @@ defmodule LogflareWeb.AccessTokensLive do
def render(assigns) do
~H"""
-
-
-
~/account/access tokens
+ <.subheader>
+ <:path>
+ ~/accounts/<.subheader_path_link live_patch to={~p"/access-tokens"}>access tokens
+
+ <.subheader_link to="https://docs.logflare.app/concepts/access-tokens/" text="docs" fa_icon="book" />
+
+
+
+
+
+ Create access token
+
-
-
-
-
-
- Accesss tokens are only supported for Logflare Endpoints for now.
-
-
Theree 3 ways of authenticating with the API: in the Authorization
header, the X-API-KEY
header, or the api_key
query parameter.
+
+
There are 3 ways of authenticating with the API: in the Authorization
header, the X-API-KEY
header, or the api_key
query parameter.
The Authorization
header method expects the header format Authorization: Bearer your-access-token
.
The X-API-KEY
header method expects the header format X-API-KEY: your-access-token
.
The api_key
query parameter method expects the search format ?api_key=your-access-token
.
-
- Create access token
-
-
+ <.form for={%{}} action="#" phx-submit="create-token" class={["mt-4", "jumbotron jumbotron-fluid tw-p-4", if(@show_create_form == false, do: "hidden")]}>
+
New Access Token
+
+ Description
+
+ A short description for identifying what this access token is to be used for.
+
+
+
+
Cancel
+ <%= submit("Create", class: "btn btn-primary") %>
+
<%= if @created_token do %>
-
+ <.alert variant="success">
Access token created successfully, copy this token to a safe location. For security purposes, this token will not be shown again.
<%= @created_token.token %>
-
+
+ Copy
+
+
Dismiss
-
+
<% end %>
<%= if @access_tokens == [] do %>
-
You do not have any access tokens yet.
+ <.alert variant="dark" class="tw-max-w-md">
+
Legacy Ingest API Key
+
Deprecated , use access tokens instead.
+
+ Copy
+
+
<% end %>
-
+
Description
+ Scope
Created on
-
+ Actions
<%= for token <- @access_tokens do %>
- <%= token.description %>
+
+ <%= if token.description do %>
+ <%= token.description %>
+ <% else %>
+ No description
+ <% end %>
+
-
+
+ <%= scope %>
+
+
<%= Calendar.strftime(token.inserted_at, "%d %b %Y, %I:%M:%S %p") %>
+
-
- Revoke
+
+ Copy
+
+
+ Revoke
@@ -102,14 +146,16 @@ defmodule LogflareWeb.AccessTokensLive do
def handle_event(
"create-token",
- %{"description" => description} = params,
+ params,
%{assigns: %{user: user}} = socket
) do
Logger.debug(
"Creating access token for user, user_id=#{inspect(user.id)}, params: #{inspect(params)}"
)
- {:ok, token} = Auth.create_access_token(user, %{description: description})
+ attrs = Map.take(params, ["description", "scopes"])
+
+ {:ok, token} = Auth.create_access_token(user, attrs)
socket =
socket
@@ -141,5 +187,6 @@ defmodule LogflareWeb.AccessTokensLive do
socket
|> assign(access_tokens: tokens)
+ |> assign(created_token: nil)
end
end
diff --git a/lib/logflare_web/templates/source/dashboard.html.eex b/lib/logflare_web/templates/source/dashboard.html.eex
index f259d80ba..04a00653b 100644
--- a/lib/logflare_web/templates/source/dashboard.html.eex
+++ b/lib/logflare_web/templates/source/dashboard.html.eex
@@ -5,10 +5,15 @@
- Ingest API key is
+ ingest API key
CLICK ME
+
+ <%= link to: ~p"/access-tokens" do %>
+ access tokens
+ <% end %>
+
<%= link to: Routes.vercel_log_drains_path(@conn, :edit) do %>▲ vercel
integration <% end %>
<%= link to: Routes.billing_account_path(@conn, :edit) do %> put_session(:user_id, user.id) |> assign(:user, user)
+
+ {:ok, user: user, conn: conn}
+ end
+
+ test "subheader", %{conn: conn} do
+ {:ok, view, _html} = live(conn, ~p"/access-tokens")
+
+ assert view
+ |> element("a", "docs")
+ |> has_element?()
+ end
+
+ test "legacy api key - show only when no access tokens", %{conn: conn} do
+ {:ok, view, _html} = live(conn, ~p"/access-tokens")
+ html = render(view)
+ # able to copy, visible
+ assert view
+ |> element("button", "Copy")
+ |> has_element?()
+
+ # able to see legacy user token
+ assert html =~ "Deprecated"
+ assert html =~ "Copy"
+ end
+
+ test "public token", %{conn: conn, user: user} do
+ token = insert(:access_token, scopes: "public", resource_owner: user)
+ {:ok, view, _html} = live(conn, ~p"/access-tokens")
+ html = render(view)
+ # able to copy, visible
+ assert view
+ |> element("button", "Copy")
+ |> has_element?()
+
+ assert html =~ token.token
+ assert html =~ "public"
+ refute html =~ "Deprecated"
+ assert html =~ "No description"
+ end
+
+ test "create private token", %{conn: conn} do
+ {:ok, view, _html} = live(conn, ~p"/access-tokens")
+
+ assert view
+ |> element("button", "Create access token")
+ |> render_click()
+
+ assert view |> element("button", "Create") |> has_element?()
+ assert view |> element("label", "Scope") |> has_element?()
+
+ assert view
+ |> element("form")
+ |> render_submit(%{
+ description: "some description",
+ scopes: "private"
+ }) =~ "created successfully"
+
+ html = view |> element("table") |> render()
+ assert html =~ "some description"
+ assert html =~ "private"
+ end
+
+ test "show private token", %{conn: conn, user: user} do
+ token = insert(:access_token, scopes: "private", resource_owner: user)
+ {:ok, view, _html} = live(conn, ~p"/access-tokens")
+
+ # not able to copy, not visible
+ refute view
+ |> element("button", "Copy")
+ |> has_element?()
+
+ html = render(view)
+ refute html =~ token.token
+ assert html =~ "private"
+ end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 1daf00c46..0ed79baf2 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -197,7 +197,8 @@ defmodule Logflare.Factory do
def access_token_factory do
%OauthAccessToken{
token: TestUtils.random_string(20),
- resource_owner: build(:user)
+ resource_owner: build(:user),
+ scopes: "public"
}
end