Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SetDPC request and implement SetServer request and UI #3

Merged
merged 25 commits into from
Nov 18, 2024

Conversation

rucoder
Copy link
Contributor

@rucoder rucoder commented Oct 31, 2024

  • Add NTP server to IpDialog
  • handle IpNet correctly
  • Update to the latest ratatui/crossterm
  • Implement better panic handling
  • Disable some keystrokes in release mode e.g CTRL+a to trigger panic
  • many more

raw-model is really redundant and has to be removed
Also remove dummy set_dpc function in preparation for real
implementation

Signed-off-by: Mikhail Malyshev <[email protected]>
- Also fix some tests. NOTE: not all tests are passed because test data
  has to be updated

Signed-off-by: Mikhail Malyshev <[email protected]>
Signed-off-by: Mikhail Malyshev <[email protected]>
Signed-off-by: Mikhail Malyshev <[email protected]>
- Linux assigns a Node Local IPv6 to every interface. They are not
  interesting for end users so filter them out

- fix a bug with IPs not displayed if only a single IP is assigned

Signed-off-by: Mikhail Malyshev <[email protected]>
- Move IpDialog state into a separate struct
- Save initial state to compare later and make decision whether we need
  to change settings or not

Signed-off-by: Mikhail Malyshev <[email protected]>
- use subnet instead of hard-coded values
- use NtpServer instead of NtpServers for static IP configuration

Signed-off-by: Mikhail Malyshev <[email protected]>
- add human friendli panic printing in debug mode
- add panic logging in release mode
- rework TerminalWrapper to reset terminam settings on panic

Signed-off-by: Mikhail Malyshev <[email protected]>
Signed-off-by: Mikhail Malyshev <[email protected]>
 - it was used for debugging debug

Signed-off-by: Mikhail Malyshev <[email protected]>
- ratatui is a false-positive since it is a package name

Signed-off-by: Mikhail Malyshev <[email protected]>
- use serde_as to simplify deserialization. Especially IpNet from GO
- get rid of manually written tests and load real json files from the
  device

Signed-off-by: Mikhail Malyshev <[email protected]>
- also pass old and new state to understand the difference and make a
  decision

Signed-off-by: Mikhail Malyshev <[email protected]>
- Called on CTRL+s
- TODO: prompt user to reboot the device if it was already onboarded

Signed-off-by: Mikhail Malyshev <[email protected]>
Signed-off-by: Mikhail Malyshev <[email protected]>
- it is better to use constants than magic numbers

Signed-off-by: Mikhail Malyshev <[email protected]>
@rucoder rucoder force-pushed the rucoder/set-dpc-new branch from 529d673 to 8e6c46a Compare October 31, 2024 13:04
Copy link
Member

@uncleDecart uncleDecart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big PR, will need a second time to go through it, do I get it right, that some tests were removed completely?

@@ -38,7 +41,8 @@ pub struct Application {
ui: Ui,
// this is our model :)
model: Rc<Model>,
raw_model: RawModel,
// pending requests
pending_requests: HashMap<u64, Rc<dyn Fn(&mut Application)>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rc, because you're not sharing this via threads, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because dyn object doesn't have size. Rc does. you need something to wrap your closure into. Since we do not pass across threads Rc is enough

debug!("Pending response for: {:?}", request);
self.pending_requests.insert(*id, Rc::new(handle_response));
}
_ => {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to send any message here through the channel or just Request one? Or you just want to put Requests inside pending_requests? If former then you need to print error and return here, if latter -- you might want to use

if let IpcMessage::Request(request, id) = msg {
  ...
}

},
);
}
_ => {} // do nothing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print log message that there's some weird combination that is not handled?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, makes sense. In theory we could have DHCP->DHCP transition with manual DNS for example but it is not supported in UI anyways

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I'd say better lay hay for future self in this case, things gets forgotten :) And in this case this won't add any overhead, neither computational, nor readability

}
_ => {} // do nothing
}
self.send_ipc_message(IpcMessage::new_request(Request::SetDPC(new_dpc)), |_| {});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so if nothing changes you still want to send message with "new" dpc, which hasn't been changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, this function is called here

            UiActions::AppAction(app_action) => match app_action {
                MonActions::NetworkInterfaceUpdated(old, new) => {
                    debug!("Setting DPC for {}", &old.iface_name);
                    debug!("OLD DPC: {:#?}", &old);
                    debug!("NEW DPC: {:#?}", &new);
                    if old == new {
                        debug!("Not changed, not sending DPC");
                    } else {
                        self.send_dpc(old, new);
                    }
                    self.ui.pop_layer();
                }
                MonActions::ServerUpdated(url) => {
                    debug!("Setting server URL to: {}", &url);
                    self.send_ipc_message(
                        IpcMessage::new_request(Request::SetServer(url.clone())),
                        move |app| {
                            app.model.borrow_mut().node_status.server = Some(url.clone());
                        },
                    );
                    self.ui.pop_layer();
                }
            },

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, missed that, you handle it there, makes sense

@@ -363,6 +461,7 @@ impl Application {
// send initial redraw event
self.invalidate();

#[allow(unused_assignments)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read the comment above.

@@ -50,6 +50,54 @@ fn init_logging() -> log2::Handle {
handle
}

pub fn initialize_panic_handler() -> Result<()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 👏 👏

panic handlers are good thing

@@ -173,7 +173,7 @@ impl IPresenter for ApplicationsPage {
area: &ratatui::prelude::Rect,
frame: &mut ratatui::Frame<'_>,
model: &std::rc::Rc<Model>,
focused: bool,
_focused: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why keep it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it is used in other implementation of this trait but not in this one

@@ -90,11 +90,13 @@ impl<D> WindowBuilder<D> {
self
}

#[allow(dead_code)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you want to keep them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an API of UI widgets. I have plans for them

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, gotcha

@OhmSpectator
Copy link
Member

I guess we should ask @jsfakian to take a look. At least he knows how to read rust code...

@jsfakian
Copy link

jsfakian commented Nov 1, 2024

I guess we should ask @jsfakian to take a look. At least he knows how to read rust code...

I will do my best.

hint: String,
}

fn on_init(w: &mut Window<InputDialogState>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think that it makes sense to make input dialog as a trait? Say you want to input ip addr which has some checks to be valid, mac addr, which also has some other checks, etc. etc. It's more about near future implementations rather than current PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uncleDecart if that would be so simple... PRs are always welcome ;)

Copy link
Member

@uncleDecart uncleDecart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, some questions which are more discussions and one fix with printing error.

- in theory crossterm shouldn't depend on libc but there is a bug when
  crossterm cannot be built with event-stream and dev-tty without libc

Signed-off-by: Mikhail Malyshev <[email protected]>
- we need to update some fields in the model only if our request was
  successful
- on error we can show a message to the user

Signed-off-by: Mikhail Malyshev <[email protected]>
- this is a simple dialog with Label that can show a message to the user
- do not allow changing of server URL if the device is onboarded

Signed-off-by: Mikhail Malyshev <[email protected]>
- We do not want user to accidentally cause a panic or exit the application
- Do not strip symbol names from release build to see readable stack
  trace

Signed-off-by: Mikhail Malyshev <[email protected]>
Signed-off-by: Mikhail Malyshev <[email protected]>
- this tab is useful for debugging only
- cleanup some warnings

Signed-off-by: Mikhail Malyshev <[email protected]>
@rucoder rucoder force-pushed the rucoder/set-dpc-new branch from 8e6c46a to 2a407bf Compare November 6, 2024 16:19
- Such transition may be valid if we ever support DHCP with e.g. static
  DNS. For now log an error

Signed-off-by: Mikhail Malyshev <[email protected]>
@rucoder rucoder requested a review from uncleDecart November 6, 2024 16:30
debug!("Got response: {:?}", result);
match result {
Ok(_) => {
debug!("Response OK");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does debug! macros adds function name? Would be useful to have it IMO

@@ -48,6 +50,7 @@ impl Application {
let terminal = TerminalWrapper::open_terminal()?;
let mut ui = Ui::new(action_tx.clone(), terminal)?;
let model = Rc::new(RefCell::new(MonitorModel::default()));
Copy link
Member

@uncleDecart uncleDecart Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Rc<RefCell> can't it be just Rc<MonitorModel> ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uncleDecart no, we need interior mutability here or we cannot pass model to other functions because 'it is already borrowed as mut'.

@@ -137,20 +174,85 @@ impl Application {
self.model.borrow_mut().update_app_list(app_list);
}

IpcMessage::ZedAgentStatus(status) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment on code above, but there's IpcMessage::LedBlinkCounter(led) case which just prints that we have LedBlinkCounter, is that intended?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want to keep variable you can just write _led to silent compiler warning

let current_dpc = self.model.borrow().get_current_dpc().cloned();
if let Some(current_dpc) = current_dpc {
info!("send_dpc: Sending DPC for iface {}", &new.iface_name);
let dpc = current_dpc.clone();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you clone current_dpc twice here, once

let current_dpc = self.model.borrow().get_current_dpc().cloned();

once here, doesn't make sense to clone twice, maybe even to clone at all actually

@@ -310,7 +412,7 @@ impl Application {
}

fn create_terminal_task(&mut self) -> (JoinHandle<()>, CancellationToken) {
let mut terminal_event_stream = self.ui.terminal.get_stream();
let mut terminal_event_stream = TerminalWrapper::get_stream();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so now stream is singleton?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, it always was de-facto. the ownership of the stream is pretty complex here but the real underlining stream is a singleton in crossterm. I can explain offline if you wish, very long explanation

UiActions::ChangeServer => {
let is_onboarded = self.model.borrow().node_status.is_onboarded();

if is_onboarded {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're not using the variable here, why not

if self.model.borrow().node_status.is_onboarded() {
// ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no specific reason really. I can change it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no reason :) i'll change

self.send_ipc_message(
IpcMessage::new_request(Request::SetServer(url.clone())),
move |app| {
app.model.borrow_mut().node_status.server = Some(url.clone());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clone is not needed, it's the last place where you use url variable in this branch

@@ -182,6 +192,7 @@ impl MonitorModel {
}

pub fn update_network_status(&mut self, net_status: DeviceNetworkStatus) {
self.dpc_key = Some(net_status.dpc_key.clone());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why clone dpc_key?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, the answer to "why clone()..." question in 90% of cases is "to satisfy borrow checker". But this is a really good catch! I rewrote it without cloning.

@@ -289,7 +324,7 @@ fn update_current_layout(w: &mut Window<IpDialogState>, rect: &Rect) {
}
}

fn ip_dialog_layout(w: &mut Window<IpDialogState>, rect: &Rect, model: &Rc<Model>) {
fn ip_dialog_layout(w: &mut Window<IpDialogState>, rect: &Rect, _model: &Rc<Model>) {
Copy link
Member

@uncleDecart uncleDecart Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a trait, right? Why keep model as a parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it is a function which is used as closure later. in .with_layout(....) call

.collect::<Vec<_>>()
.join("\n");

// cell #3 IP address list
if height > 1 {
if height > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not that good with the code base (yet :D) why 0 instead of 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a real bug here: if we have a single IP (height 1) we display ... nothing :)

Copy link
Member

@uncleDecart uncleDecart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, some small comments removing unnecessary cloning and clarifications about code base :)

- remove two unnecessary clone() calls
- remove verbose log

Signed-off-by: Mikhail Malyshev <[email protected]>
@rucoder rucoder merged commit 79e1f65 into lf-edge:main Nov 18, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants