diff --git a/lapdev-api/src/workspace.rs b/lapdev-api/src/workspace.rs index 690df84..9e029bc 100644 --- a/lapdev-api/src/workspace.rs +++ b/lapdev-api/src/workspace.rs @@ -290,6 +290,7 @@ pub async fn workspace_ports( .map(|p| WorkspacePort { port: p.port as u16, shared: p.shared, + public: p.public, }) .collect::>(), ) @@ -344,6 +345,7 @@ pub async fn update_workspace_port( entities::workspace_port::ActiveModel { id: ActiveValue::Set(port.id), shared: ActiveValue::Set(update_workspace_port.shared), + public: ActiveValue::Set(update_workspace_port.public), ..Default::default() } .update(&txn) diff --git a/lapdev-common/src/lib.rs b/lapdev-common/src/lib.rs index 66502d5..6d40944 100644 --- a/lapdev-common/src/lib.rs +++ b/lapdev-common/src/lib.rs @@ -320,11 +320,13 @@ pub struct WorkspaceInfo { pub struct WorkspacePort { pub port: u16, pub shared: bool, + pub public: bool, } #[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] pub struct UpdateWorkspacePort { pub shared: bool, + pub public: bool, } #[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] diff --git a/lapdev-conductor/src/server.rs b/lapdev-conductor/src/server.rs index 42aad78..78ca518 100644 --- a/lapdev-conductor/src/server.rs +++ b/lapdev-conductor/src/server.rs @@ -1862,6 +1862,7 @@ impl Conductor { port: ActiveValue::Set(port as i32), host_port: ActiveValue::Set(host_port as i32), shared: ActiveValue::Set(false), + public: ActiveValue::Set(false), } .insert(&self.db.conn) .await?; diff --git a/lapdev-dashboard/src/workspace.rs b/lapdev-dashboard/src/workspace.rs index 587fcf3..c7e1d64 100644 --- a/lapdev-dashboard/src/workspace.rs +++ b/lapdev-dashboard/src/workspace.rs @@ -8,12 +8,12 @@ use gloo_net::{ }; use lapdev_common::{ console::Organization, utils, ClusterInfo, GitBranch, NewWorkspace, NewWorkspaceResponse, - PrebuildStatus, ProjectInfo, ProjectPrebuild, RepoSource, WorkspaceInfo, WorkspaceStatus, - WorkspaceUpdateEvent, + PrebuildStatus, ProjectInfo, ProjectPrebuild, RepoSource, UpdateWorkspacePort, WorkspaceInfo, + WorkspacePort, WorkspaceStatus, WorkspaceUpdateEvent, }; use leptos::{ component, create_action, create_effect, create_local_resource, create_rw_signal, document, - event_target_value, expect_context, on_cleanup, set_timeout, spawn_local, + event_target_checked, event_target_value, expect_context, on_cleanup, set_timeout, spawn_local, spawn_local_with_current_owner, use_context, view, window, For, IntoView, RwSignal, Show, Signal, SignalGet, SignalGetUntracked, SignalSet, SignalUpdate, SignalWith, SignalWithUntracked, @@ -219,7 +219,7 @@ fn OpenWorkspaceView( class={format!("{open_button_class} px-4 rounded-l-lg inline-block")} disabled=move || workspace_status != WorkspaceStatus::Running target="_blank" - href={format!("http://{workspace_name}.{workspace_hostname}/")} + href={format!("https://{workspace_name}.{workspace_hostname}/")} > Open @@ -1226,6 +1226,7 @@ pub fn WorkspaceDetails() -> impl IntoView { /> + } @@ -1253,3 +1254,229 @@ pub fn WorkspaceDetails() -> impl IntoView { // } } + +#[derive(Copy, Clone, PartialEq, Eq)] +enum TabKind { + Port, +} + +#[component] +pub fn WorkspaceTabView(name: String, workspace_hostname: String) -> impl IntoView { + let tab_kind = create_rw_signal(TabKind::Port); + let active_class = "inline-block p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500"; + let inactive_class = "inline-block p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300"; + let change_tab = move |kind: TabKind| { + tab_kind.set(kind); + }; + + view! { +
+ +
+
+
+ +
+
+ } +} + +async fn workspace_ports(name: &str) -> Result> { + let org = + use_context::>>().ok_or_else(|| anyhow!("can't get org"))?; + let org = org + .get_untracked() + .ok_or_else(|| anyhow!("can't get org"))?; + let resp = Request::get(&format!( + "/api/v1/organizations/{}/workspaces/{name}/ports", + org.id + )) + .send() + .await?; + let ports: Vec = resp.json().await?; + Ok(ports) +} + +async fn update_workspace_port( + name: &str, + port: u16, + shared: bool, + public: bool, +) -> Result<(), ErrorResponse> { + let org = + use_context::>>().ok_or_else(|| anyhow!("can't get org"))?; + let org = org + .get_untracked() + .ok_or_else(|| anyhow!("can't get org"))?; + let resp = Request::put(&format!( + "/api/v1/organizations/{}/workspaces/{name}/ports/{port}", + org.id + )) + .json(&UpdateWorkspacePort { shared, public })? + .send() + .await?; + if resp.status() != 204 { + let error = resp + .json::() + .await + .unwrap_or_else(|_| ErrorResponse { + error: "Internal Server Error".to_string(), + }); + return Err(error); + } + Ok(()) +} + +#[component] +pub fn WorkspacePortsView(name: String, workspace_hostname: String) -> impl IntoView { + let ports = { + let name = name.clone(); + create_local_resource( + || (), + move |_| { + let name = name.clone(); + async move { workspace_ports(&name.clone()).await } + }, + ) + }; + let ports = Signal::derive(move || { + ports + .with(|p| p.as_ref().map(|p| p.as_ref().ok().cloned())) + .flatten() + .unwrap_or_default() + }); + + let success = create_rw_signal(None); + let error = create_rw_signal(None); + + view! { +

Exposed ports of your workspace

+
+

Only you can access the ports by default.

+

If you make the port shared, all members in the same organisation can access it.

+

If you make the port public, everyone who has the url can access it.

+
+ { move || if let Some(success) = success.get() { + view! { +
+ { success } +
+ }.into_view() + } else { + view!{}.into_view() + }} + + { move || if let Some(error) = error.get() { + view! { +
+ { error } +
+ }.into_view() + } else { + view!{}.into_view() + }} + +
+ Port +
+ shared + public +
+ +
+ + + } + } + /> + } +} + +#[component] +fn WorkspacePortView( + name: String, + p: WorkspacePort, + workspace_hostname: String, + success: RwSignal>, + error: RwSignal>, +) -> impl IntoView { + let shared = create_rw_signal(p.shared); + let public = create_rw_signal(p.public); + let action = { + let name = name.clone(); + let port = p.port; + create_action(move |_| { + let name = name.clone(); + async move { + error.set(None); + success.set(None); + if let Err(e) = update_workspace_port( + &name.clone(), + port, + shared.get_untracked(), + public.get_untracked(), + ) + .await + { + error.set(Some(e.error.clone())); + } else { + success.set(Some(format!("Port {port} updated successfully"))); + } + } + }) + }; + + view! { +
+ {p.port} +
+
+ +
+
+ +
+
+
+ + Open + + +
+
+ } +} diff --git a/lapdev-db/src/entities/workspace_port.rs b/lapdev-db/src/entities/workspace_port.rs index 6bac5b0..777afbc 100644 --- a/lapdev-db/src/entities/workspace_port.rs +++ b/lapdev-db/src/entities/workspace_port.rs @@ -11,6 +11,7 @@ pub struct Model { pub port: i32, pub host_port: i32, pub shared: bool, + pub public: bool, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/lapdev-db/src/migration/m20240316_194115_create_workspace_port_table.rs b/lapdev-db/src/migration/m20240316_194115_create_workspace_port_table.rs index 71fe269..68fb519 100644 --- a/lapdev-db/src/migration/m20240316_194115_create_workspace_port_table.rs +++ b/lapdev-db/src/migration/m20240316_194115_create_workspace_port_table.rs @@ -41,6 +41,7 @@ impl MigrationTrait for Migration { .table(WorkspacePort::Table) .col(WorkspacePort::WorkspaceId) .col(WorkspacePort::Port) + .col(WorkspacePort::Public) .to_owned(), ) .await?; @@ -57,4 +58,5 @@ enum WorkspacePort { Port, HostPort, Shared, + Public, }