defmodule LivebookWeb.Hub.NewLive do use LivebookWeb, :live_view alias Livebook.Teams alias Livebook.Teams.Org alias LivebookWeb.LayoutHelpers on_mount LivebookWeb.SidebarHook @check_completion_data_interval Application.compile_env( :livebook, :check_completion_data_interval, 3000 ) @impl true def mount(_params, _session, socket) do socket = assign(socket, selected_option: "new-org", page_title: "Hub - Livebook", requested_code: false, org: nil, verification_uri: nil, form: nil, button_label: nil, request_code_info: nil ) socket = assign_form(socket, "new-org") {:ok, socket} end @impl true def render(assigns) do ~H""" Beware! You are running Livebook in development but this page communicates with production servers.

Manage your Livebooks in the cloud with Hubs.

    <.tab_button id="new-org" selected={@selected_option} title="Create a new organization" icon="lightbulb-flash-line" /> <.tab_button id="join-org" selected={@selected_option} title="Join an existing organization" icon="organization-chart" />
<.form :let={f} id={"#{@selected_option}-form"} class="flex flex-col space-y-4" for={@form} phx-submit="save" phx-change="validate" >
<.text_field field={f[:name]} label="Organization name" /> <.emoji_field field={f[:emoji]} label="Emoji" />
<.password_field :if={@selected_option == "join-org"} field={f[:teams_key]} label="Livebook Teams Key" />
<%= @request_code_info %>
1. Copy the code:
<.copyclip content={@org.user_code} />
2. Sign in to Livebook Teams and paste the code:
""" end defp copyclip(assigns) do ~H"""
<%= @content %>
""" end defp tab_button(assigns) do ~H"""
  • """ end defp selected_tab_button(id, id), do: "border-black/10 bg-white drop-shadow-sm hover:!opacity-100" defp selected_tab_button(_, _), do: "border-transparent text-gray-500 hover:text-gray-800" @impl true def handle_event("select_option", %{"option" => option}, socket) do {:noreply, socket |> assign(selected_option: option, requested_code: false, verification_uri: nil) |> assign_form(option)} end def handle_event("validate", %{"org" => attrs}, socket) do changeset = socket.assigns.org |> Teams.change_org(attrs) |> Map.replace!(:action, :validate) {:noreply, assign_form(socket, changeset)} end def handle_event("save", %{"org" => attrs}, socket) do result = case socket.assigns.selected_option do "new-org" -> Teams.create_org(socket.assigns.org, attrs) "join-org" -> Teams.join_org(socket.assigns.org, attrs) end case result do {:ok, %{"device_code" => device_code} = response} -> attrs = Map.merge(attrs, response) changeset = Teams.change_org(socket.assigns.org, attrs) org = Ecto.Changeset.apply_action!(changeset, :insert) Process.send_after( self(), {:check_completion_data, device_code}, @check_completion_data_interval ) {:noreply, socket |> assign(requested_code: true, org: org, verification_uri: response["verification_uri"]) |> assign_form(changeset)} {:error, changeset} -> changeset = Map.replace!(changeset, :action, :validate) {:noreply, assign_form(socket, changeset)} {:transport_error, message} -> {:noreply, put_flash(socket, :error, message)} end end @impl true def handle_info({:check_completion_data, device_code}, %{assigns: %{org: org}} = socket) do case Teams.get_org_request_completion_data(org, device_code) do {:ok, :awaiting_confirmation} -> Process.send_after( self(), {:check_completion_data, device_code}, @check_completion_data_interval ) {:noreply, socket} {:ok, %{"id" => _id, "session_token" => _session_token} = response} -> hub = Teams.create_hub!(%{ org_id: response["id"], user_id: response["user_id"], org_key_id: response["org_key_id"], org_public_key: response["org_public_key"], session_token: response["session_token"], teams_key: org.teams_key, hub_name: org.name, hub_emoji: org.emoji }) {:noreply, socket |> put_flash(:success, "Hub added successfully") |> push_navigate(to: ~p"/hub/#{hub.id}?show-key=true")} {:error, :expired} -> changeset = org |> Teams.change_org(%{user_code: nil}) |> Map.replace!(:action, :validate) {:noreply, socket |> assign(requested_code: false, org: org, verification_uri: nil) |> put_flash(:error, "Oh no! Your org request expired, could you please try again?") |> assign_form(changeset)} {:transport_error, message} -> Process.send_after( self(), {:check_completion_data, device_code}, @check_completion_data_interval ) {:noreply, put_flash(socket, :error, message)} end end def handle_info(_any, socket), do: {:noreply, socket} defp assign_form(socket, "join-org") do org = %Org{emoji: random_emoji()} changeset = Teams.change_org(org) socket |> assign( org: org, button_label: "Join", request_code_info: "Authenticate with your organization" ) |> assign_form(changeset) end defp assign_form(socket, "new-org") do org = %Org{emoji: random_emoji(), teams_key: Org.teams_key()} changeset = Teams.change_org(org) socket |> assign( org: org, button_label: "Create", request_code_info: "Verify your new organization" ) |> assign_form(changeset) end defp assign_form(socket, %Ecto.Changeset{} = changeset) do assign(socket, form: to_form(changeset)) end defp random_emoji do Enum.random(~w[💡 🚀 🌈 🦄 🐱 👩‍💻 ⚽️ ⭐️]) end end