able to accept or reject upload requests from the tui

This commit is contained in:
Joe Ardent 2025-08-01 14:55:17 -07:00
parent f9efd37d00
commit a7cfe419b4
4 changed files with 102 additions and 22 deletions

View file

@ -4,11 +4,10 @@ use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind};
use futures::{FutureExt, StreamExt};
use joecalsend::{
Config, JoecalState, JoecalUploadRequest, Listeners, TransferEvent, UploadDialog,
error::{LocalSendError, Result},
models::Device,
error::Result, models::Device,
};
use julid::Julid;
use log::{LevelFilter, debug, error, info};
use log::{LevelFilter, debug, error, info, warn};
use ratatui::{DefaultTerminal, Frame, widgets::TableState};
use tokio::{
sync::mpsc::{UnboundedReceiver, unbounded_channel},
@ -147,6 +146,15 @@ impl App {
KeyCode::Char('q') => self.exit(),
_ => {}
},
CurrentScreen::Receiving => match key_event.code {
KeyCode::Up => self.upload_state.select_previous(),
KeyCode::Down => self.upload_state.select_next(),
KeyCode::Char('a') => self.accept(),
KeyCode::Char('d') => self.deny(),
KeyCode::Esc => self.pop(),
KeyCode::Char('q') => self.exit(),
_ => {}
},
_ => match key_event.code {
KeyCode::Char('q') => self.exit(),
KeyCode::Char('s') => self.send(),
@ -158,6 +166,43 @@ impl App {
}
}
fn accept(&mut self) {
let Some(idx) = self.upload_state.selected() else {
return;
};
// keys are sorted, so we can use the table selection index
let keys: Vec<_> = self.uploads.keys().collect();
let Some(key) = keys.get(idx) else {
warn!("could not get id from selection index {idx}");
return;
};
let Some(req) = self.uploads.get(key) else {
return;
};
if let Err(e) = req.tx.send(UploadDialog::UploadConfirm) {
error!("got error sending upload confirmation: {e:?}");
};
}
fn deny(&mut self) {
let Some(idx) = self.upload_state.selected() else {
return;
};
// keys are sorted, so we can use the table selection index
let keys: Vec<_> = self.uploads.keys().cloned().collect();
let Some(key) = keys.get(idx) else {
warn!("could not get id from selection index {idx}");
return;
};
let Some(req) = self.uploads.get(key).cloned() else {
return;
};
if let Err(e) = req.tx.send(UploadDialog::UploadDeny) {
error!("got error sending upload confirmation: {e:?}");
};
self.uploads.remove(key);
}
fn draw(&mut self, frame: &mut Frame) {
frame.render_widget(self, frame.area());
}

View file

@ -5,7 +5,7 @@ use log::LevelFilter;
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Margin, Rect},
style::{Style, Stylize},
style::{Color, Style, Stylize},
symbols::border,
text::{Line, Text, ToLine},
widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Row, Table, TableState, Widget},
@ -42,6 +42,23 @@ static LOGGING_MENU: LazyLock<Line> = LazyLock::new(|| {
])
});
static UPLOADS_MENU: LazyLock<Line> = LazyLock::new(|| {
Line::from(vec![
" Select Previous ".into(),
"<UP>".blue().bold(),
" Select Next ".into(),
"<DOWN>".blue().bold(),
" Approve Selection ".into(),
"<A>".blue().bold(),
" Deny Selection ".into(),
"<D>".blue().bold(),
" Previous Screen ".into(),
"<ESC>".blue().bold(),
" Quit ".into(),
"<Q>".blue().bold(),
])
});
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
let main_layout =
@ -59,6 +76,7 @@ impl Widget for &mut App {
let header_margin = Margin::new(1, 2);
let mode = self.screen.last().unwrap();
let ups: Vec<_> = self.uploads.values().collect();
match mode {
CurrentScreen::Main => {
main_page(*mode, &MAIN_MENU, area, buf);
@ -66,7 +84,6 @@ impl Widget for &mut App {
let peers = PeersWidget { peers: &self.peers };
peers.render(footer_right.inner(footer_margin), buf);
NetworkInfoWidget.render(footer_left.inner(footer_margin), buf);
let ups: Vec<_> = self.uploads.values().collect();
uploads(
&ups,
&mut self.upload_state,
@ -79,7 +96,13 @@ impl Widget for &mut App {
logger(area.inner(Margin::new(2, 4)), buf);
}
CurrentScreen::Receiving => {
main_page(*mode, &MAIN_MENU, area, buf);
main_page(*mode, &UPLOADS_MENU, area, buf);
uploads(
&ups,
&mut self.upload_state,
area.inner(Margin::new(2, 4)),
buf,
);
}
_ => {
main_page(*mode, &MAIN_MENU, area, buf);
@ -97,11 +120,7 @@ fn logger(area: Rect, buf: &mut Buffer) {
.output_target(true)
.output_file(false)
.output_line(false)
.block(
Block::bordered()
.border_set(border::THICK)
.title(title.centered()),
)
.block(Block::bordered().title(title.centered()))
.style(Style::default())
.state(&TuiWidgetState::new().set_default_display_level(LevelFilter::Debug));
logger.render(area, buf);
@ -130,9 +149,8 @@ fn uploads(
buf: &mut Buffer,
) {
let title = Line::from(" Upload Requests ").bold();
let block = Block::bordered()
.title(title.centered())
.border_set(border::THICK);
let block = Block::bordered().title(title.centered());
let mut rows = Vec::new();
for &req in requests {
let src = req.alias.to_line().left_aligned();
@ -150,16 +168,27 @@ fn uploads(
let size = Line::from(format!("{size}")).centered();
rows.push(Row::new([src, size, files]));
}
if state.selected().is_none() && !rows.is_empty() {
state.select(Some(0));
} else if rows.is_empty() {
state.select(None);
};
let widths = [
Constraint::Max(20),
Constraint::Max(15),
Constraint::Min(50),
];
let table = Table::new(rows, widths).block(block).header(Row::new([
"Sender".bold().into_left_aligned_line(),
"Bytes".bold().into_centered_line(),
"Files".bold().into_centered_line(),
]));
let table = Table::new(rows, widths)
.block(block)
.header(Row::new([
"Sender".bold().into_left_aligned_line(),
"Bytes".bold().into_centered_line(),
"Files".bold().into_centered_line(),
]))
.row_highlight_style(Style::new().bg(Color::Rgb(99, 99, 99)));
ratatui::widgets::StatefulWidget::render(table, area, buf, state);
}

View file

@ -8,7 +8,7 @@ use axum::{
Json,
extract::{ConnectInfo, State},
};
use log::{debug, error, warn};
use log::{debug, error, trace, warn};
use tokio::net::UdpSocket;
use crate::{Config, JoecalState, RunningState, models::Device};
@ -19,7 +19,7 @@ impl JoecalState {
socket: Option<SocketAddr>,
config: &Config,
) -> crate::error::Result<()> {
debug!("announcing");
trace!("announcing");
announce_http(&self.device, socket, self.client.clone()).await?;
announce_multicast(&self.device, config.multicast_addr, self.socket.clone()).await?;
Ok(())
@ -44,7 +44,7 @@ impl JoecalState {
}
},
r = self.socket.recv_from(&mut buf) => {
debug!("received multicast datagram");
trace!("received multicast datagram");
match r {
Ok((size, src)) => {
let received_msg = String::from_utf8_lossy(&buf[..size]);

View file

@ -321,6 +321,12 @@ pub async fn register_upload(
.into_response();
}
if let Ok(id) = Julid::from_str(session_id)
&& let Err(e) = state.transfer_event_tx.send(TransferEvent::Received(id))
{
error!("got error sending upload received event: {e:?}");
};
StatusCode::OK.into_response()
}