aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/session-manager
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-06-23 19:19:37 +0200
committerGitHub <noreply@github.com>2025-06-23 19:19:37 +0200
commitc5ac7968809ddb1033b625562eae22019cccc2f4 (patch)
tree932cf07d898dfbcc6ec6a9d4cf5353c06a47accc /default-plugins/session-manager
parentfeat(ui): add optional tooltip with key hints to the compact bar (#4225) (diff)
downloadsl0ppij-c5ac7968809ddb1033b625562eae22019cccc2f4.tar.gz
sl0ppij-c5ac7968809ddb1033b625562eae22019cccc2f4.tar.xz
sl0ppij-c5ac7968809ddb1033b625562eae22019cccc2f4.zip
Feature: web-client/server to share your sessions in the browser (#4242)
* work * moar work * notes * work * separate to terminal and control channels * stdin working * serve html web client initial * serve static assets loaded with include_dir * merge * enable_web_server config parameter * compile time flag to disable web server capability * rustfmt * add license to all xterm.js assets * mouse working except copy/paste * helpful comment * web client improvements - move script to js file - add favicon - add nerd font - change title TODO: investigate if font license embedded in otf is sufficient * get mouse to work properly * kitty keyboard support initial * fix wrong type in preload link * wip axum websocket handlers - upgrade axum to v0.8.1, enable ws feature - begin setup of websocket handlers - tidy up imports * replace control listener * handle terminal websocket with axum * cleanup Cargo.toml * kitty fixes and bracketed paste * fix(mouse): pane not found crash * initial session switching infra * add `web_client_font` option * session switching, creation and resurrection working through the session manager * move session module to zellij-utils and share logic with web-client * some cleanups * require restart for enable-web-server * use session name from router * write config to disk and watch for config changes * rename session name to ipc path * add basic panic handler, make render_to_client exit on channel close * use while let instead of loop * handle websocket close * add mouse motions * make clipboard work * add weblink handling and webgl rendering * add todo * fix: use session name instead of patch on session switch * use "default" layout for new sessions * ui indication for session being shared * share this session ui * plugin assets * Fix process crash on mac with notify watcher. Use poll watcher instead of recommended as a workaround. * make url session switching and creation work * start welcome screen on root url * scaffold control messages, set font from config * set dimensions on session start * bring back session name from url * send bytes on terminal websocket instead of json - create web client os input and id before websocket connection * draft ui * work * refactor ui * remove otf font, remove margins to avoid scrollbar * version query endpoint for server status * web session info query endpoint * refactor: move stuff around * add web client info to session metadata * make tests pass * populate real data in session list * remove unnecessary endpoint * add web_client node to config, add font option * remove web_client_font * allow disabling the web session through the config - WIP * formalize sharing/not-sharing configuration * fix tests * allow shutting down web server * display error when web clients are forbidden to attach * only show sessions that allow web clients if this is a web client * style(fmt): rustfmt * fix: query web server from Zellij rather than from each plugin * remove log spam * handle some error paths better in the web client * allow controlling the web server through the cli * allow configuring the web server's ip/port * fix tests and format code * use direct WebServerStatus event instead of piggy-backing on SessionInfo * plugin revamp initial * make plugin responsive * adjust plugin title * refactor: share plugin * refactor: share plugin * add cors middleware * some fixes for running without a compiled web server capability * display error when starting the share plugin without web server support * clarify config * add pipelines to compile zellij without web support * display error when unable to start web server * only query web server when share plugin is running * refactor(web-client): connection table * give zellij_server_listener access to the control channel * fixes and clarifications * refactor: consolidate generate_unique_session_name * give proper error when trying to attach to a forbidden session * change browser URL when switching sessions * add keyboard shortcut * enforce https when bound to non-loopback ip * initial authentication token implementation * background color from theme * initial web client theme config * basic token generation ui * refactor set config message creation * also set body background * allow editing scrollback for plugins too * set scrollback to 0 * properly parse colors in config * generate token from plugin * nice login modals * initial token management screen * implement token authentication * refactor(share): token management screen * style(fmt): rustfmt * fix(plugin): some minor bugs * refactor(share): main screen * refactor(share): token screen * refactor(share): main * refactor(share): ui components * fix(responsiveness): properly send usage_width to the render function * fix cli commands and add some verbosity * add support for settings ansi and selection colors * add cursor and cursor accent * basic web client tests * fix tests * refactor: web client * use session tokens for authentication * improve modals * move shutdown to ipc * refactor: ipc logic * serialize theme config for web client * update tests * refactor: move some stuff around to prepare for config hot reload * config live reloading for the web clients * change remember-me UI wording * improve xterm.js link handling * make sure terminal is focused on mousemove * remove deprecated sharing indication from compact-bar * gate deps and functionality behind the web_server_compatibility feature * feat(build): add --no-web flag in all the places * fix some other build flows * add new assets * update CI for no-web (untested) * make more dependencies optional * update axum-extra * add web client configuration options * gracefully close connections on server exit * tests for graceful connection closing * handle client-side reconnect when server is down * fix: make sure ipc bus folder exists before starting * add commands to manage login tokens from the cli * style(fmt): rustfmt * some cleanups * fix(ux): allow alt-right-click on the web client without opening the context menu * fix: prevent attaching to welcome screen * fix: reload config issues * fix long socket path on macos * normalize config conversion and fix color gap in browser * revoke session_token cookie if it is not valid * fix: visual bug with multiple clients in extremely small screen sizes * fix: only include rusqlite for the web server capability builds * update e2e snapshots * refactor(web): client side js * some cleanups * moar cleanups * fix(tests): wait for server instead of using a fixed timeout * debug CI * fix(tests): use spawn_blocking for running the test web server * fix(tests): wait for http rather than tcp port * fix(tests): properly pass config path - hopefully this is the issue... * success! bring back the rest of the tests * attempt to fix the macos CI issue * docs(changelog): add PR --------- Co-authored-by: Thomas Linford <linford.t@gmail.com>
Diffstat (limited to 'default-plugins/session-manager')
-rw-r--r--default-plugins/session-manager/src/main.rs48
-rw-r--r--default-plugins/session-manager/src/session_list.rs14
2 files changed, 57 insertions, 5 deletions
diff --git a/default-plugins/session-manager/src/main.rs b/default-plugins/session-manager/src/main.rs
index ba6ef55d..8e1d039f 100644
--- a/default-plugins/session-manager/src/main.rs
+++ b/default-plugins/session-manager/src/main.rs
@@ -46,6 +46,7 @@ struct State {
is_welcome_screen: bool,
show_kill_all_sessions_warning: bool,
request_ids: Vec<String>,
+ is_web_client: bool,
}
register_plugin!(State);
@@ -94,6 +95,7 @@ impl ZellijPlugin for State {
match event {
Event::ModeUpdate(mode_info) => {
self.colors = Colors::new(mode_info.style.colors);
+ self.is_web_client = mode_info.is_web_client.unwrap_or(false);
should_render = true;
},
Event::Key(key) => {
@@ -462,6 +464,12 @@ impl State {
} else if self.new_session_info.name().contains('/') {
self.show_error("Session name cannot contain '/'");
return;
+ } else if self
+ .sessions
+ .has_forbidden_session(self.new_session_info.name())
+ {
+ self.show_error("This session exists and web clients cannot attach to it.");
+ return;
}
self.new_session_info.handle_selection(&self.session_name);
},
@@ -505,6 +513,8 @@ impl State {
}
} else if let Some(tab_position) = selected_tab {
go_to_tab(tab_position as u32);
+ } else {
+ self.show_error("Already attached...");
}
} else {
switch_session_with_focus(
@@ -518,7 +528,13 @@ impl State {
self.search_term.clear();
self.sessions
.update_search_term(&self.search_term, &self.colors);
- hide_self();
+ if !self.is_welcome_screen {
+ // we usually don't want to hide_self() if we're the welcome screen because
+ // unless the user did something odd like opening an extra pane/tab in the
+ // welcome screen, this will result in the current session closing, as this is
+ // the last selectable pane...
+ hide_self();
+ }
},
ActiveScreen::ResurrectSession => {
if let Some(session_name_to_resurrect) =
@@ -547,9 +563,32 @@ impl State {
self.session_name = Some(new_name.to_owned());
}
fn update_session_infos(&mut self, session_infos: Vec<SessionInfo>) {
- let session_infos: Vec<SessionUiInfo> = session_infos
+ let session_ui_infos: Vec<SessionUiInfo> = session_infos
.iter()
- .map(|s| SessionUiInfo::from_session_info(s))
+ .filter_map(|s| {
+ if self.is_web_client && !s.web_clients_allowed {
+ None
+ } else if self.is_welcome_screen && s.is_current_session {
+ // do not display current session if we're the welcome screen
+ // because:
+ // 1. attaching to the welcome screen from the welcome screen is not a thing
+ // 2. it can cause issues on the web (since we're disconnecting and
+ // reconnecting to a session we just closed by disconnecting...)
+ None
+ } else {
+ Some(SessionUiInfo::from_session_info(s))
+ }
+ })
+ .collect();
+ let forbidden_sessions: Vec<SessionUiInfo> = session_infos
+ .iter()
+ .filter_map(|s| {
+ if self.is_web_client && !s.web_clients_allowed {
+ Some(SessionUiInfo::from_session_info(s))
+ } else {
+ None
+ }
+ })
.collect();
let current_session_name = session_infos.iter().find_map(|s| {
if s.is_current_session {
@@ -561,7 +600,8 @@ impl State {
if let Some(current_session_name) = current_session_name {
self.session_name = Some(current_session_name);
}
- self.sessions.set_sessions(session_infos);
+ self.sessions
+ .set_sessions(session_ui_infos, forbidden_sessions);
}
fn main_menu_size(&self, rows: usize, cols: usize) -> (usize, usize, usize, usize) {
// x, y, width, height
diff --git a/default-plugins/session-manager/src/session_list.rs b/default-plugins/session-manager/src/session_list.rs
index 01c75ad5..7a66aabb 100644
--- a/default-plugins/session-manager/src/session_list.rs
+++ b/default-plugins/session-manager/src/session_list.rs
@@ -9,6 +9,7 @@ use crate::ui::{
#[derive(Debug, Default)]
pub struct SessionList {
pub session_ui_infos: Vec<SessionUiInfo>,
+ pub forbidden_sessions: Vec<SessionUiInfo>,
pub selected_index: SelectedIndex,
pub selected_search_index: Option<usize>,
pub search_results: Vec<SearchResult>,
@@ -16,7 +17,11 @@ pub struct SessionList {
}
impl SessionList {
- pub fn set_sessions(&mut self, mut session_ui_infos: Vec<SessionUiInfo>) {
+ pub fn set_sessions(
+ &mut self,
+ mut session_ui_infos: Vec<SessionUiInfo>,
+ mut forbidden_sessions: Vec<SessionUiInfo>,
+ ) {
session_ui_infos.sort_unstable_by(|a, b| {
if a.is_current_session {
std::cmp::Ordering::Less
@@ -26,7 +31,9 @@ impl SessionList {
a.name.cmp(&b.name)
}
});
+ forbidden_sessions.sort_unstable_by(|a, b| a.name.cmp(&b.name));
self.session_ui_infos = session_ui_infos;
+ self.forbidden_sessions = forbidden_sessions;
}
pub fn update_search_term(&mut self, search_term: &str, colors: &Colors) {
let mut flattened_assets = self.flatten_assets(colors);
@@ -317,6 +324,11 @@ impl SessionList {
pub fn has_session(&self, session_name: &str) -> bool {
self.session_ui_infos.iter().any(|s| s.name == session_name)
}
+ pub fn has_forbidden_session(&self, session_name: &str) -> bool {
+ self.forbidden_sessions
+ .iter()
+ .any(|s| s.name == session_name)
+ }
pub fn update_session_name(&mut self, old_name: &str, new_name: &str) {
self.session_ui_infos
.iter_mut()