mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-02-03 04:38:11 +08:00
Add Offline Deployment section on the Hub Teams page (#2086)
This commit is contained in:
parent
5a79137390
commit
07caffaecb
8 changed files with 143 additions and 86 deletions
|
@ -94,6 +94,7 @@ import "monaco-editor/esm/vs/basic-languages/sql/sql.contribution";
|
|||
import "monaco-editor/esm/vs/basic-languages/css/css.contribution";
|
||||
import "monaco-editor/esm/vs/basic-languages/html/html.contribution";
|
||||
import "monaco-editor/esm/vs/basic-languages/xml/xml.contribution";
|
||||
import "monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution";
|
||||
|
||||
// === Configuration ===
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ config :mime, :types, %{
|
|||
config :plug_cowboy, :log_exceptions_with_status_code, [407..599]
|
||||
|
||||
config :livebook,
|
||||
teams_url: nil,
|
||||
teams_url: "http://localhost:4100",
|
||||
app_service_name: nil,
|
||||
app_service_url: nil,
|
||||
authentication_mode: :token,
|
||||
|
|
|
@ -207,10 +207,15 @@ defmodule Livebook.Application do
|
|||
|
||||
def create_offline_hub() do
|
||||
name = System.get_env("LIVEBOOK_TEAMS_NAME")
|
||||
teams_key = System.get_env("LIVEBOOK_TEAMS_KEY")
|
||||
public_key = System.get_env("LIVEBOOK_TEAMS_OFFLINE_KEY")
|
||||
|
||||
if name && teams_key && public_key do
|
||||
if name && public_key do
|
||||
teams_key =
|
||||
System.get_env("LIVEBOOK_TEAMS_KEY") ||
|
||||
Livebook.Config.abort!(
|
||||
"You specified LIVEBOOK_TEAMS_NAME, but LIVEBOOK_TEAMS_KEY is missing."
|
||||
)
|
||||
|
||||
Livebook.Hubs.set_offline_hub(%Livebook.Hubs.Team{
|
||||
id: "team-#{name}",
|
||||
hub_name: name,
|
||||
|
|
4
lib/livebook_web/errors/not_found_error.ex
Normal file
4
lib/livebook_web/errors/not_found_error.ex
Normal file
|
@ -0,0 +1,4 @@
|
|||
defmodule LivebookWeb.NotFoundError do
|
||||
@moduledoc false
|
||||
defexception [:message, plug_status: 404]
|
||||
end
|
|
@ -4,11 +4,7 @@ defmodule LivebookWeb.Hub.Edit.PersonalComponent do
|
|||
alias Livebook.Hubs
|
||||
alias Livebook.Hubs.Personal
|
||||
alias LivebookWeb.LayoutHelpers
|
||||
|
||||
defmodule NotFoundError do
|
||||
@moduledoc false
|
||||
defexception [:message, plug_status: 404]
|
||||
end
|
||||
alias LivebookWeb.NotFoundError
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
alias Livebook.Hubs.Team
|
||||
alias Livebook.Teams
|
||||
alias LivebookWeb.LayoutHelpers
|
||||
alias LivebookWeb.NotFoundError
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
|
@ -15,7 +16,8 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
|
||||
secret_value =
|
||||
if assigns.live_action == :edit_secret do
|
||||
Enum.find_value(secrets, &(&1.name == secret_name && &1.value))
|
||||
Enum.find_value(secrets, &(&1.name == secret_name and &1.value)) ||
|
||||
raise(NotFoundError, "could not find secret matching #{inspect(secret_name)}")
|
||||
end
|
||||
|
||||
{:ok,
|
||||
|
@ -26,6 +28,7 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
secret_name: secret_name,
|
||||
secret_value: secret_value
|
||||
)
|
||||
|> assign_dockerfile()
|
||||
|> assign_form(changeset)}
|
||||
end
|
||||
|
||||
|
@ -101,86 +104,29 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
target={@myself}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-4">
|
||||
<h2 class="text-xl text-gray-800 font-medium pb-2 border-b border-gray-200">
|
||||
Offline Deployment
|
||||
</h2>
|
||||
|
||||
<p class="text-gray-700">
|
||||
Deploy your stamped notebooks with your Hub
|
||||
using an instance of the Hub using
|
||||
environment variables.
|
||||
</p>
|
||||
|
||||
<.code_preview
|
||||
source_id={"offline-deployment-#{@hub.id}"}
|
||||
source={@dockerfile}
|
||||
language="dockerfile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<.modal show={@show_key} id="show-key-modal" width={:medium} patch={~p"/hub/#{@hub.id}"}>
|
||||
<div class="p-6 flex flex-col space-y-5">
|
||||
<h3 class="text-2xl font-semibold text-gray-800">
|
||||
Teams Key
|
||||
</h3>
|
||||
<div class="justify-center">
|
||||
This is your <strong>Teams Key</strong>. If you want to join or invite others
|
||||
to your organization, you will need to share your Teams Key with them. We
|
||||
recommend storing it somewhere safe:
|
||||
</div>
|
||||
<div class=" w-full">
|
||||
<div id="teams-key-toggle" class="relative flex">
|
||||
<input
|
||||
type="password"
|
||||
id="teams-key"
|
||||
readonly
|
||||
value={@hub.teams_key}
|
||||
class="input font-mono w-full border-neutral-200 bg-neutral-100 py-2 border-2 pr-8"
|
||||
/>
|
||||
|
||||
<div class="flex items-center absolute inset-y-0 right-1">
|
||||
<button
|
||||
class="icon-button"
|
||||
data-copy
|
||||
data-tooltip="Copied to clipboard"
|
||||
type="button"
|
||||
aria-label="copy to clipboard"
|
||||
phx-click={
|
||||
JS.dispatch("lb:clipcopy", to: "#teams-key")
|
||||
|> JS.add_class(
|
||||
"tooltip top",
|
||||
to: "#teams-key-toggle [data-copy]",
|
||||
transition: {"ease-out duration-200", "opacity-0", "opacity-100"}
|
||||
)
|
||||
|> JS.remove_class(
|
||||
"tooltip top",
|
||||
to: "#teams-key-toggle [data-copy]",
|
||||
transition: {"ease-out duration-200", "opacity-0", "opacity-100"},
|
||||
time: 2000
|
||||
)
|
||||
}
|
||||
>
|
||||
<.remix_icon icon="clipboard-line" class="text-xl" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="icon-button"
|
||||
data-show
|
||||
type="button"
|
||||
aria-label="show password"
|
||||
phx-click={
|
||||
JS.remove_attribute("type", to: "#teams-key-toggle input")
|
||||
|> JS.set_attribute({"type", "text"}, to: "#teams-key-toggle input")
|
||||
|> toggle_class("hidden", to: "#teams-key-toggle [data-show]")
|
||||
|> toggle_class("hidden", to: "#teams-key-toggle [data-hide]")
|
||||
}
|
||||
>
|
||||
<.remix_icon icon="eye-line" class="text-xl" />
|
||||
</button>
|
||||
<button
|
||||
class="icon-button hidden"
|
||||
data-hide
|
||||
type="button"
|
||||
aria-label="hide password"
|
||||
phx-click={
|
||||
JS.remove_attribute("type", to: "#teams-key-toggle input")
|
||||
|> JS.set_attribute({"type", "password"}, to: "#teams-key-toggle input")
|
||||
|> toggle_class("hidden", to: "#teams-key-toggle [data-show]")
|
||||
|> toggle_class("hidden", to: "#teams-key-toggle [data-hide]")
|
||||
}
|
||||
>
|
||||
<.remix_icon icon="eye-off-line" class="text-xl" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<.teams_key_modal teams_key={@hub.teams_key} />
|
||||
</.modal>
|
||||
|
||||
<.modal
|
||||
|
@ -203,6 +149,87 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp teams_key_modal(assigns) do
|
||||
~H"""
|
||||
<div class="p-6 flex flex-col space-y-5">
|
||||
<h3 class="text-2xl font-semibold text-gray-800">
|
||||
Teams Key
|
||||
</h3>
|
||||
<div class="justify-center">
|
||||
This is your <strong>Teams Key</strong>. If you want to join or invite others
|
||||
to your organization, you will need to share your Teams Key with them. We
|
||||
recommend storing it somewhere safe:
|
||||
</div>
|
||||
<div class=" w-full">
|
||||
<div id="teams-key-toggle" class="relative flex">
|
||||
<input
|
||||
type="password"
|
||||
id="teams-key"
|
||||
readonly
|
||||
value={@teams_key}
|
||||
class="input font-mono w-full border-neutral-200 bg-neutral-100 py-2 border-2 pr-8"
|
||||
/>
|
||||
|
||||
<div class="flex items-center absolute inset-y-0 right-1">
|
||||
<button
|
||||
class="icon-button"
|
||||
data-copy
|
||||
data-tooltip="Copied to clipboard"
|
||||
type="button"
|
||||
aria-label="copy to clipboard"
|
||||
phx-click={
|
||||
JS.dispatch("lb:clipcopy", to: "#teams-key")
|
||||
|> JS.add_class(
|
||||
"tooltip top",
|
||||
to: "#teams-key-toggle [data-copy]",
|
||||
transition: {"ease-out duration-200", "opacity-0", "opacity-100"}
|
||||
)
|
||||
|> JS.remove_class(
|
||||
"tooltip top",
|
||||
to: "#teams-key-toggle [data-copy]",
|
||||
transition: {"ease-out duration-200", "opacity-0", "opacity-100"},
|
||||
time: 2000
|
||||
)
|
||||
}
|
||||
>
|
||||
<.remix_icon icon="clipboard-line" class="text-xl" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="icon-button"
|
||||
data-show
|
||||
type="button"
|
||||
aria-label="show password"
|
||||
phx-click={
|
||||
JS.remove_attribute("type", to: "#teams-key-toggle input")
|
||||
|> JS.set_attribute({"type", "text"}, to: "#teams-key-toggle input")
|
||||
|> toggle_class("hidden", to: "#teams-key-toggle [data-show]")
|
||||
|> toggle_class("hidden", to: "#teams-key-toggle [data-hide]")
|
||||
}
|
||||
>
|
||||
<.remix_icon icon="eye-line" class="text-xl" />
|
||||
</button>
|
||||
<button
|
||||
class="icon-button hidden"
|
||||
data-hide
|
||||
type="button"
|
||||
aria-label="hide password"
|
||||
phx-click={
|
||||
JS.remove_attribute("type", to: "#teams-key-toggle input")
|
||||
|> JS.set_attribute({"type", "password"}, to: "#teams-key-toggle input")
|
||||
|> toggle_class("hidden", to: "#teams-key-toggle [data-show]")
|
||||
|> toggle_class("hidden", to: "#teams-key-toggle [data-hide]")
|
||||
}
|
||||
>
|
||||
<.remix_icon icon="eye-off-line" class="text-xl" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("save", %{"team" => params}, socket) do
|
||||
case Teams.update_hub(socket.assigns.hub, params) do
|
||||
|
@ -255,4 +282,22 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
|
||||
assign(socket, form: to_form(changeset))
|
||||
end
|
||||
|
||||
defp assign_dockerfile(socket) do
|
||||
version = to_string(Application.spec(:livebook, :vsn))
|
||||
version = if version =~ "dev", do: "edge", else: version
|
||||
|
||||
assign(socket, :dockerfile, """
|
||||
FROM livebook/livebook:#{version}
|
||||
|
||||
COPY /path/to/my/notebooks /data
|
||||
ENV LIVEBOOK_APPS_PATH "/data"
|
||||
|
||||
ENV LIVEBOOK_APPS_PATH_HUB_ID "#{socket.assigns.hub.id}"
|
||||
ENV LIVEBOOK_TEAMS_NAME "#{socket.assigns.hub.hub_name}"
|
||||
ENV LIVEBOOK_TEAMS_OFFLINE_KEY "#{socket.assigns.hub.org_public_key}"
|
||||
|
||||
CMD [ "/app/bin/livebook", "start" ]\
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -175,5 +175,11 @@ defmodule LivebookWeb.Integration.Hub.EditLiveTest do
|
|||
refute render(element(view, "#hub-secrets-list")) =~ secret.name
|
||||
refute secret in Livebook.Hubs.get_secrets(hub)
|
||||
end
|
||||
|
||||
test "raises an error if does not exist secret", %{conn: conn, hub: hub} do
|
||||
assert_raise LivebookWeb.NotFoundError, fn ->
|
||||
live(conn, ~p"/hub/#{hub.id}/secrets/edit/HELLO")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,7 +42,7 @@ defmodule LivebookWeb.Hub.EditLiveTest do
|
|||
end
|
||||
|
||||
test "raises an error if does not exist secret", %{conn: conn, hub: hub} do
|
||||
assert_raise LivebookWeb.Hub.Edit.PersonalComponent.NotFoundError, fn ->
|
||||
assert_raise LivebookWeb.NotFoundError, fn ->
|
||||
live(conn, ~p"/hub/#{hub.id}/secrets/edit/HELLO")
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue