feat(smb): add SMB dialect selection and persistence (#445)
Deploy docs to GitHub Pages / deploy (push) Has been cancelled
Install.sh / build (ubuntu-latest) (push) Has been cancelled
CI / toolchain (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / install-scripts (push) Has been cancelled
Install.sh / build (macos-latest) (push) Has been cancelled
Site / build-site (push) Has been cancelled
CI / crates-macos-latest (push) Has been cancelled
CI / crates-ubuntu-latest (push) Has been cancelled
CI / crates-windows-latest (push) Has been cancelled
CI / doc (push) Has been cancelled
CI / deny (push) Has been cancelled

Support Auto, SMB1, SMB2, and SMB3 selection on Unix, bound negotiation to the selected dialect family, preserve legacy bookmarks as Auto, and document the new option. Windows keeps operating-system-managed negotiation.

Closes #439
This commit is contained in:
Christian Visintin
2026-09-02 14:14:23 +02:00
committed by GitHub
parent e966a83220
commit 751f68f6d4
20 changed files with 802 additions and 121 deletions
+8
View File
@@ -101,6 +101,10 @@ pub enum AuthFormId {
SmbShare,
#[cfg(posix)]
SmbWorkgroup,
#[cfg(posix)]
SmbDialect,
#[cfg(posix)]
SmbDialectWarning,
Username,
WebDAVUri,
}
@@ -209,6 +213,10 @@ pub enum UiAuthFormMsg {
SmbWorkgroupDown,
#[cfg(posix)]
SmbWorkgroupUp,
#[cfg(posix)]
SmbDialectBlurDown,
#[cfg(posix)]
SmbDialectBlurUp,
UsernameBlurDown,
UsernameBlurUp,
WebDAVUriBlurDown,
+2
View File
@@ -315,6 +315,8 @@ impl AuthActivity {
self.mount_smb_share(form_tab, &params.share);
#[cfg(posix)]
self.mount_smb_workgroup(form_tab, params.workgroup.as_deref().unwrap_or(""));
#[cfg(posix)]
self.mount_smb_dialect(form_tab, params.dialect);
}
fn load_bookmark_webdav_into_gui(&mut self, form_tab: FormTab, params: WebDAVProtocolParams) {
+2 -2
View File
@@ -13,8 +13,6 @@ pub use bookmarks::{
BookmarkName, BookmarkSavePassword, BookmarksList, DeleteBookmarkPopup, DeleteRecentPopup,
RecentsList,
};
#[cfg(posix)]
pub use form::InputSmbWorkgroup;
pub use form::{
HostBridgeProtocolRadio, InputAddress, InputGcsBucket, InputGcsEndpoint,
InputGcsServiceAccountKey, InputKubeClientCert, InputKubeClientKey, InputKubeClusterUrl,
@@ -23,6 +21,8 @@ pub use form::{
InputS3Region, InputS3SecretAccessKey, InputS3SecurityToken, InputS3SessionToken,
InputSmbShare, InputUsername, InputWebDAVUri, RadioS3NewPathStyle, RemoteProtocolRadio,
};
#[cfg(posix)]
pub use form::{InputSmbWorkgroup, RadioSmbDialect, SmbDialectWarning};
pub use popup::{
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,
WindowSizeError,
+1 -1
View File
@@ -54,7 +54,7 @@ pub use s3::{
};
pub use smb::InputSmbShare;
#[cfg(posix)]
pub use smb::InputSmbWorkgroup;
pub use smb::{InputSmbWorkgroup, RadioSmbDialect, SmbDialectWarning};
pub use webdav::InputWebDAVUri;
fn handle_input_ev(
@@ -1,7 +1,13 @@
#[cfg(posix)]
use tui_realm_stdlib::components::Span;
use tuirealm::component::{AppComponent, Component};
use tuirealm::event::NoUserEvent;
#[cfg(posix)]
use tuirealm::props::SpanStatic;
use super::*;
#[cfg(posix)]
use crate::filetransfer::params::SmbDialect;
#[derive(Component)]
pub struct InputSmbShare {
@@ -85,3 +91,163 @@ impl AppComponent<Msg, NoUserEvent> for InputSmbWorkgroup {
handle_input_ev(self, ev, on_key_down, on_key_up, form_tab)
}
}
#[cfg(all(test, posix))]
mod test {
use pretty_assertions::assert_eq;
use super::*;
use crate::filetransfer::params::SmbDialect;
#[test]
fn should_map_radio_options_to_dialect() {
assert_eq!(RadioSmbDialect::opt_to_dialect(0), SmbDialect::Auto);
assert_eq!(RadioSmbDialect::opt_to_dialect(1), SmbDialect::Smb1);
assert_eq!(RadioSmbDialect::opt_to_dialect(2), SmbDialect::Smb2);
assert_eq!(RadioSmbDialect::opt_to_dialect(3), SmbDialect::Smb3);
assert_eq!(RadioSmbDialect::opt_to_dialect(99), SmbDialect::Auto);
}
#[test]
fn should_map_dialect_to_radio_options() {
for dialect in [
SmbDialect::Auto,
SmbDialect::Smb1,
SmbDialect::Smb2,
SmbDialect::Smb3,
] {
let opt = RadioSmbDialect::dialect_to_opt(dialect);
assert_eq!(RadioSmbDialect::opt_to_dialect(opt), dialect);
}
}
}
#[cfg(posix)]
const RADIO_SMB_DIALECT_AUTO: usize = 0;
#[cfg(posix)]
const RADIO_SMB_DIALECT_SMB1: usize = 1;
#[cfg(posix)]
const RADIO_SMB_DIALECT_SMB2: usize = 2;
#[cfg(posix)]
const RADIO_SMB_DIALECT_SMB3: usize = 3;
/// Radio to select the SMB protocol family.
#[cfg(posix)]
#[derive(Component)]
pub struct RadioSmbDialect {
component: Radio,
form_tab: FormTab,
}
#[cfg(posix)]
impl RadioSmbDialect {
pub fn new(dialect: SmbDialect, form_tab: FormTab, color: Color) -> Self {
Self {
component: Radio::default()
.highlight_style(
Style::default()
.fg(color)
.add_modifier(TextModifiers::REVERSED),
)
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.choices(["Auto", "SMB1 (insecure)", "SMB2", "SMB3"])
.rewind(true)
.title(Title::from("SMB version").alignment(HorizontalAlignment::Left))
.value(Self::dialect_to_opt(dialect)),
form_tab,
}
}
/// Converts the radio choice index to a dialect. Unknown indexes map to `Auto`.
pub fn opt_to_dialect(opt: usize) -> SmbDialect {
match opt {
RADIO_SMB_DIALECT_SMB1 => SmbDialect::Smb1,
RADIO_SMB_DIALECT_SMB2 => SmbDialect::Smb2,
RADIO_SMB_DIALECT_SMB3 => SmbDialect::Smb3,
_ => SmbDialect::Auto,
}
}
fn dialect_to_opt(dialect: SmbDialect) -> usize {
match dialect {
SmbDialect::Auto => RADIO_SMB_DIALECT_AUTO,
SmbDialect::Smb1 => RADIO_SMB_DIALECT_SMB1,
SmbDialect::Smb2 => RADIO_SMB_DIALECT_SMB2,
SmbDialect::Smb3 => RADIO_SMB_DIALECT_SMB3,
}
}
}
#[cfg(posix)]
impl AppComponent<Msg, NoUserEvent> for RadioSmbDialect {
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: Key::Left, ..
}) => {
self.perform(Cmd::Move(Direction::Left));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Right, ..
}) => {
self.perform(Cmd::Move(Direction::Right));
Some(Msg::None)
}
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => Some(Msg::Form(FormMsg::Connect)),
Event::Keyboard(KeyEvent {
code: Key::Down, ..
}) => Some(if self.form_tab == FormTab::Remote {
Msg::Ui(UiMsg::Remote(UiAuthFormMsg::SmbDialectBlurDown))
} else {
Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::SmbDialectBlurDown))
}),
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
Some(if self.form_tab == FormTab::Remote {
Msg::Ui(UiMsg::Remote(UiAuthFormMsg::SmbDialectBlurUp))
} else {
Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::SmbDialectBlurUp))
})
}
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
Some(if self.form_tab == FormTab::Remote {
Msg::Ui(UiMsg::Remote(UiAuthFormMsg::ParamsFormBlur))
} else {
Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::ParamsFormBlur))
})
}
_ => None,
}
}
}
/// One-line warning shown above the dialect radio while SMB1 is selected.
#[cfg(posix)]
#[derive(Component)]
pub struct SmbDialectWarning {
component: Span,
}
#[cfg(posix)]
impl SmbDialectWarning {
pub fn new(color: Color) -> Self {
Self {
component: Span::default().foreground(color).spans([SpanStatic::from(
"Warning: SMB1 is deprecated and insecure. Use it only for isolated legacy devices.",
)]),
}
}
}
#[cfg(posix)]
impl AppComponent<Msg, NoUserEvent> for SmbDialectWarning {
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
None
}
}
+20 -4
View File
@@ -358,7 +358,7 @@ impl AuthActivity {
InputMask::Localhost => unreachable!(),
InputMask::Generic => Id::HostBridge(AuthFormId::Password),
#[cfg(posix)]
InputMask::Smb => Id::HostBridge(AuthFormId::SmbWorkgroup),
InputMask::Smb => Id::HostBridge(AuthFormId::SmbDialect),
#[cfg(win)]
InputMask::Smb => Id::HostBridge(AuthFormId::Password),
InputMask::Kube => Id::HostBridge(AuthFormId::KubeClientKey),
@@ -483,12 +483,20 @@ impl AuthActivity {
}
#[cfg(posix)]
UiAuthFormMsg::SmbWorkgroupDown => {
self.activate_component(Id::HostBridge(AuthFormId::RemoteDirectory))
self.activate_component(Id::HostBridge(AuthFormId::SmbDialect))
}
#[cfg(posix)]
UiAuthFormMsg::SmbWorkgroupUp => {
self.activate_component(Id::HostBridge(AuthFormId::Password))
}
#[cfg(posix)]
UiAuthFormMsg::SmbDialectBlurDown => {
self.activate_component(Id::HostBridge(AuthFormId::RemoteDirectory))
}
#[cfg(posix)]
UiAuthFormMsg::SmbDialectBlurUp => {
self.activate_component(Id::HostBridge(AuthFormId::SmbWorkgroup))
}
UiAuthFormMsg::UsernameBlurDown => {
self.activate_component(Id::HostBridge(AuthFormId::Password))
}
@@ -601,7 +609,7 @@ impl AuthActivity {
InputMask::Localhost => unreachable!(),
InputMask::Generic => Id::Remote(AuthFormId::Password),
#[cfg(posix)]
InputMask::Smb => Id::Remote(AuthFormId::SmbWorkgroup),
InputMask::Smb => Id::Remote(AuthFormId::SmbDialect),
#[cfg(win)]
InputMask::Smb => Id::Remote(AuthFormId::Password),
InputMask::Kube => Id::Remote(AuthFormId::KubeClientKey),
@@ -726,12 +734,20 @@ impl AuthActivity {
}
#[cfg(posix)]
UiAuthFormMsg::SmbWorkgroupDown => {
self.activate_component(Id::Remote(AuthFormId::RemoteDirectory))
self.activate_component(Id::Remote(AuthFormId::SmbDialect))
}
#[cfg(posix)]
UiAuthFormMsg::SmbWorkgroupUp => {
self.activate_component(Id::Remote(AuthFormId::Password))
}
#[cfg(posix)]
UiAuthFormMsg::SmbDialectBlurDown => {
self.activate_component(Id::Remote(AuthFormId::RemoteDirectory))
}
#[cfg(posix)]
UiAuthFormMsg::SmbDialectBlurUp => {
self.activate_component(Id::Remote(AuthFormId::SmbWorkgroup))
}
UiAuthFormMsg::UsernameBlurDown => {
self.activate_component(Id::Remote(AuthFormId::Password))
}
+156 -58
View File
@@ -3,7 +3,7 @@
//! `auth_activity` is the module which implements the authentication activity
use tuirealm::props::Color;
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
use tuirealm::ratatui::layout::{Constraint, Direction, Layout, Rect};
use tuirealm::ratatui::widgets::Clear;
use tuirealm::terminal::TerminalAdapter;
@@ -12,6 +12,8 @@ use super::{
InputMask, components,
};
use crate::filetransfer::params::DEFAULT_GCS_ENDPOINT;
#[cfg(posix)]
use crate::filetransfer::params::SmbDialect;
use crate::utils::ui::{Popup, Size};
#[path = "view/mounting.rs"]
@@ -73,6 +75,10 @@ impl AuthActivity {
self.mount_smb_share(FormTab::HostBridge, "");
#[cfg(posix)]
self.mount_smb_workgroup(FormTab::HostBridge, "");
#[cfg(posix)]
self.mount_smb_dialect(FormTab::HostBridge, SmbDialect::default());
#[cfg(posix)]
self.mount_smb_dialect_warning(FormTab::HostBridge);
self.mount_webdav_uri(FormTab::HostBridge, "");
let remote_default_protocol = self.context().config().get_default_protocol();
@@ -107,6 +113,10 @@ impl AuthActivity {
self.mount_smb_share(FormTab::Remote, "");
#[cfg(posix)]
self.mount_smb_workgroup(FormTab::Remote, "");
#[cfg(posix)]
self.mount_smb_dialect(FormTab::Remote, SmbDialect::default());
#[cfg(posix)]
self.mount_smb_dialect_warning(FormTab::Remote);
self.mount_webdav_uri(FormTab::Remote, "");
if let Some(version) = self
@@ -247,8 +257,10 @@ impl AuthActivity {
f: &mut tuirealm::ratatui::Frame<'_>,
area: tuirealm::ratatui::layout::Rect,
) {
let input_mask_size = Self::input_mask_size(self.host_bridge_input_mask());
let input_mask = self.host_bridge_input_mask();
let protocol_and_mask_chunks = Layout::default()
.constraints([Constraint::Length(3), Constraint::Length(12)].as_ref())
.constraints([Constraint::Length(3), Constraint::Length(input_mask_size)].as_ref())
.direction(Direction::Vertical)
.split(area);
@@ -258,36 +270,25 @@ impl AuthActivity {
protocol_and_mask_chunks[0],
);
let input_mask = Layout::default()
.constraints(
[
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
]
.as_ref(),
)
.direction(Direction::Vertical)
.split(protocol_and_mask_chunks[1]);
match self.host_bridge_input_mask() {
InputMask::AwsS3 => self.render_view_ids(f, input_mask, self.get_host_bridge_s3_view()),
InputMask::Gcs => self.render_view_ids(f, input_mask, self.get_host_bridge_gcs_view()),
InputMask::Generic => {
self.render_view_ids(f, input_mask, self.get_host_bridge_generic_params_view())
}
InputMask::Kube => {
self.render_view_ids(f, input_mask, self.get_host_bridge_kube_view())
}
let view_ids = match input_mask {
InputMask::AwsS3 => self.get_host_bridge_s3_view(),
InputMask::Gcs => self.get_host_bridge_gcs_view(),
InputMask::Generic => self.get_host_bridge_generic_params_view(),
InputMask::Kube => self.get_host_bridge_kube_view(),
InputMask::Localhost => {
let view_ids = self.get_host_bridge_localhost_view();
self.app.view(&view_ids[0], f, input_mask[0]);
self.app.view(&view_ids[0], f, protocol_and_mask_chunks[1]);
return;
}
InputMask::Smb => self.render_view_ids(f, input_mask, self.get_host_bridge_smb_view()),
InputMask::WebDAV => {
self.render_view_ids(f, input_mask, self.get_host_bridge_webdav_view())
}
}
InputMask::Smb => self.get_host_bridge_smb_view(),
InputMask::WebDAV => self.get_host_bridge_webdav_view(),
};
self.render_form_rows(
f,
protocol_and_mask_chunks[1],
FormTab::HostBridge,
view_ids,
);
}
fn render_remote_input_mask(
@@ -295,8 +296,10 @@ impl AuthActivity {
f: &mut tuirealm::ratatui::Frame<'_>,
area: tuirealm::ratatui::layout::Rect,
) {
let input_mask_size = Self::input_mask_size(self.remote_input_mask());
let input_mask = self.remote_input_mask();
let protocol_and_mask_chunks = Layout::default()
.constraints([Constraint::Length(3), Constraint::Length(12)].as_ref())
.constraints([Constraint::Length(3), Constraint::Length(input_mask_size)].as_ref())
.direction(Direction::Vertical)
.split(area);
@@ -306,40 +309,135 @@ impl AuthActivity {
protocol_and_mask_chunks[0],
);
let input_mask = Layout::default()
.constraints(
[
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
]
.as_ref(),
)
.direction(Direction::Vertical)
.split(protocol_and_mask_chunks[1]);
match self.remote_input_mask() {
InputMask::AwsS3 => self.render_view_ids(f, input_mask, self.get_remote_s3_view()),
InputMask::Gcs => self.render_view_ids(f, input_mask, self.get_remote_gcs_view()),
InputMask::Generic => {
self.render_view_ids(f, input_mask, self.get_remote_generic_params_view())
}
InputMask::Kube => self.render_view_ids(f, input_mask, self.get_remote_kube_view()),
let view_ids = match input_mask {
InputMask::AwsS3 => self.get_remote_s3_view(),
InputMask::Gcs => self.get_remote_gcs_view(),
InputMask::Generic => self.get_remote_generic_params_view(),
InputMask::Kube => self.get_remote_kube_view(),
InputMask::Localhost => unreachable!(),
InputMask::Smb => self.render_view_ids(f, input_mask, self.get_remote_smb_view()),
InputMask::WebDAV => self.render_view_ids(f, input_mask, self.get_remote_webdav_view()),
}
InputMask::Smb => self.get_remote_smb_view(),
InputMask::WebDAV => self.get_remote_webdav_view(),
};
self.render_form_rows(f, protocol_and_mask_chunks[1], FormTab::Remote, view_ids);
}
fn render_view_ids(
/// Splits `area` into four 3-line form rows. When `warning_row` is
/// `Some(index)`, a 1-line row is inserted directly above row `index` and
/// returned as the second tuple element.
fn split_input_mask(area: Rect, warning_row: Option<usize>) -> ([Rect; 4], Option<Rect>) {
let mut constraints = Vec::with_capacity(6);
for row in 0..4 {
if warning_row == Some(row) {
constraints.push(Constraint::Length(1));
}
constraints.push(Constraint::Length(3));
}
constraints.push(Constraint::Min(0));
let chunks = Layout::default()
.constraints(constraints)
.direction(Direction::Vertical)
.split(area);
let mut rows = [Rect::default(); 4];
let mut warning = None;
let mut chunk = 0;
for (row, slot) in rows.iter_mut().enumerate() {
if warning_row == Some(row) {
warning = Some(chunks[chunk]);
chunk += 1;
}
*slot = chunks[chunk];
chunk += 1;
}
(rows, warning)
}
/// Returns the visible row index of the SMB dialect radio when the form
/// shows SMB and SMB1 is selected; `None` otherwise.
#[cfg(posix)]
fn smb_dialect_warning_row(&self, form_tab: FormTab, view_ids: &[Id; 4]) -> Option<usize> {
let input_mask = match form_tab {
FormTab::HostBridge => self.host_bridge_input_mask(),
FormTab::Remote => self.remote_input_mask(),
};
if input_mask != InputMask::Smb || self.get_input_smb_dialect(form_tab) != SmbDialect::Smb1
{
return None;
}
let dialect_id = Self::form_tab_id(form_tab, AuthFormId::SmbDialect);
view_ids.iter().position(|id| *id == dialect_id)
}
#[cfg(win)]
fn smb_dialect_warning_row(&self, _form_tab: FormTab, _view_ids: &[Id; 4]) -> Option<usize> {
None
}
fn render_form_rows(
&mut self,
f: &mut tuirealm::ratatui::Frame<'_>,
input_mask: std::rc::Rc<[tuirealm::ratatui::layout::Rect]>,
area: Rect,
form_tab: FormTab,
view_ids: [Id; 4],
) {
self.app.view(&view_ids[0], f, input_mask[0]);
self.app.view(&view_ids[1], f, input_mask[1]);
self.app.view(&view_ids[2], f, input_mask[2]);
self.app.view(&view_ids[3], f, input_mask[3]);
let warning_row = self.smb_dialect_warning_row(form_tab, &view_ids);
let (rows, warning) = Self::split_input_mask(area, warning_row);
#[cfg(posix)]
if let Some(rect) = warning {
let id = Self::form_tab_id(form_tab, AuthFormId::SmbDialectWarning);
self.app.view(&id, f, rect);
}
#[cfg(win)]
let _ = warning;
for (id, rect) in view_ids.iter().zip(rows) {
self.app.view(id, f, rect);
}
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use tuirealm::ratatui::layout::Rect;
use super::AuthActivity;
#[test]
fn should_split_input_mask_without_warning() {
let area = Rect::new(0, 0, 40, 13);
let (rows, warning) = AuthActivity::split_input_mask(area, None);
assert_eq!(warning, None);
assert_eq!(rows[0], Rect::new(0, 0, 40, 3));
assert_eq!(rows[1], Rect::new(0, 3, 40, 3));
assert_eq!(rows[2], Rect::new(0, 6, 40, 3));
assert_eq!(rows[3], Rect::new(0, 9, 40, 3));
}
#[test]
fn should_split_input_mask_with_middle_warning() {
let area = Rect::new(0, 0, 40, 13);
let (rows, warning) = AuthActivity::split_input_mask(area, Some(2));
assert_eq!(warning, Some(Rect::new(0, 6, 40, 1)));
assert_eq!(rows[0], Rect::new(0, 0, 40, 3));
assert_eq!(rows[1], Rect::new(0, 3, 40, 3));
assert_eq!(rows[2], Rect::new(0, 7, 40, 3));
assert_eq!(rows[3], Rect::new(0, 10, 40, 3));
}
#[test]
fn should_split_input_mask_with_first_row_warning() {
let area = Rect::new(0, 0, 40, 13);
let (rows, warning) = AuthActivity::split_input_mask(area, Some(0));
assert_eq!(warning, Some(Rect::new(0, 0, 40, 1)));
assert_eq!(rows[0], Rect::new(0, 1, 40, 3));
assert_eq!(rows[1], Rect::new(0, 4, 40, 3));
assert_eq!(rows[2], Rect::new(0, 7, 40, 3));
assert_eq!(rows[3], Rect::new(0, 10, 40, 3));
}
}
+32
View File
@@ -1,4 +1,6 @@
use super::*;
#[cfg(posix)]
use crate::filetransfer::params::SmbDialect;
use crate::ui::activities::auth::STORE_KEY_RELEASE_NOTES;
impl AuthActivity {
@@ -705,6 +707,36 @@ impl AuthActivity {
}
}
#[cfg(posix)]
pub(in crate::ui::activities::auth) fn mount_smb_dialect(
&mut self,
form_tab: FormTab,
dialect: SmbDialect,
) {
let color = self.theme().auth_protocol;
let id = Self::form_tab_id(form_tab, AuthFormId::SmbDialect);
if let Err(err) = self.app.remount(
id,
Box::new(components::RadioSmbDialect::new(dialect, form_tab, color)),
vec![],
) {
error!("Failed to remount component: {err}");
}
}
#[cfg(posix)]
pub(in crate::ui::activities::auth) fn mount_smb_dialect_warning(&mut self, form_tab: FormTab) {
let color = self.theme().misc_warn_dialog;
let id = Self::form_tab_id(form_tab, AuthFormId::SmbDialectWarning);
if let Err(err) = self.app.remount(
id,
Box::new(components::SmbDialectWarning::new(color)),
vec![],
) {
error!("Failed to remount component: {err}");
}
}
pub(in crate::ui::activities::auth) fn mount_webdav_uri(
&mut self,
form_tab: FormTab,
+23 -1
View File
@@ -5,10 +5,14 @@ use tuirealm::state::{State, StateValue};
use super::*;
use crate::filetransfer::FileTransferParams;
#[cfg(posix)]
use crate::filetransfer::params::SmbDialect;
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, GoogleCloudStorageParams, KubeProtocolParams,
ProtocolParams, SmbParams, WebDAVProtocolParams,
};
#[cfg(posix)]
use crate::ui::activities::auth::components::RadioSmbDialect;
impl AuthActivity {
pub(in crate::ui::activities::auth) fn get_generic_params_input(
@@ -82,6 +86,7 @@ impl AuthActivity {
) -> SmbParams {
let share = self.get_input_smb_share(form_tab);
let workgroup = self.get_input_smb_workgroup(form_tab);
let dialect = self.get_input_smb_dialect(form_tab);
let address = self.get_input_addr(form_tab);
let port = self.get_input_port(form_tab);
let username = self.get_input_username(form_tab);
@@ -92,6 +97,7 @@ impl AuthActivity {
.username(username)
.password(password)
.workgroup(workgroup)
.dialect(dialect)
}
#[cfg(win)]
@@ -452,6 +458,20 @@ impl AuthActivity {
}
}
#[cfg(posix)]
pub(in crate::ui::activities::auth) fn get_input_smb_dialect(
&self,
form_tab: FormTab,
) -> SmbDialect {
match self
.app
.state(&Self::form_tab_id(form_tab, AuthFormId::SmbDialect))
{
Ok(State::Single(StateValue::Usize(opt))) => RadioSmbDialect::opt_to_dialect(opt),
_ => SmbDialect::default(),
}
}
pub(in crate::ui::activities::auth) fn get_new_bookmark(&self) -> (String, bool) {
let name = match self.app.state(&Id::BookmarkName) {
Ok(State::Single(StateValue::String(name))) => name,
@@ -473,8 +493,10 @@ impl AuthActivity {
+ 3
}
fn input_mask_size(input_mask: InputMask) -> u16 {
pub(in crate::ui::activities::auth) fn input_mask_size(input_mask: InputMask) -> u16 {
match input_mask {
// One extra line for the SMB1 warning above the dialect radio.
InputMask::Smb if cfg!(posix) => 13,
InputMask::AwsS3
| InputMask::Gcs
| InputMask::Generic
+16 -4
View File
@@ -289,15 +289,21 @@ impl AuthActivity {
Id::HostBridge(AuthFormId::Password),
Id::HostBridge(AuthFormId::SmbWorkgroup),
],
Some(&Id::HostBridge(AuthFormId::RemoteDirectory)) => [
Some(&Id::HostBridge(AuthFormId::SmbDialect)) => [
Id::HostBridge(AuthFormId::Username),
Id::HostBridge(AuthFormId::Password),
Id::HostBridge(AuthFormId::SmbWorkgroup),
Id::HostBridge(AuthFormId::SmbDialect),
],
Some(&Id::HostBridge(AuthFormId::RemoteDirectory)) => [
Id::HostBridge(AuthFormId::Password),
Id::HostBridge(AuthFormId::SmbWorkgroup),
Id::HostBridge(AuthFormId::SmbDialect),
Id::HostBridge(AuthFormId::RemoteDirectory),
],
Some(&Id::HostBridge(AuthFormId::LocalDirectory)) => [
Id::HostBridge(AuthFormId::Password),
Id::HostBridge(AuthFormId::SmbWorkgroup),
Id::HostBridge(AuthFormId::SmbDialect),
Id::HostBridge(AuthFormId::RemoteDirectory),
Id::HostBridge(AuthFormId::LocalDirectory),
],
@@ -336,15 +342,21 @@ impl AuthActivity {
Id::Remote(AuthFormId::Password),
Id::Remote(AuthFormId::SmbWorkgroup),
],
Some(&Id::Remote(AuthFormId::RemoteDirectory)) => [
Some(&Id::Remote(AuthFormId::SmbDialect)) => [
Id::Remote(AuthFormId::Username),
Id::Remote(AuthFormId::Password),
Id::Remote(AuthFormId::SmbWorkgroup),
Id::Remote(AuthFormId::SmbDialect),
],
Some(&Id::Remote(AuthFormId::RemoteDirectory)) => [
Id::Remote(AuthFormId::Password),
Id::Remote(AuthFormId::SmbWorkgroup),
Id::Remote(AuthFormId::SmbDialect),
Id::Remote(AuthFormId::RemoteDirectory),
],
Some(&Id::Remote(AuthFormId::LocalDirectory)) => [
Id::Remote(AuthFormId::Password),
Id::Remote(AuthFormId::SmbWorkgroup),
Id::Remote(AuthFormId::SmbDialect),
Id::Remote(AuthFormId::RemoteDirectory),
Id::Remote(AuthFormId::LocalDirectory),
],