Connected Devices: Added sync feature
This commit is contained in:
+127
-2
@@ -6,7 +6,7 @@ use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Paragraph};
|
||||
|
||||
use super::theme;
|
||||
use crate::app::state::{AppState, FedRow};
|
||||
use crate::app::state::{AppState, FedRow, settings_rows};
|
||||
|
||||
pub fn draw(frame: &mut Frame, area: Rect, state: &AppState) {
|
||||
let block = Block::bordered()
|
||||
@@ -16,7 +16,7 @@ pub fn draw(frame: &mut Frame, area: Rect, state: &AppState) {
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
|
||||
let rows_height = (FedRow::ALL.len() + state.visualizer.scripts.len() + 6) as u16;
|
||||
let rows_height = (settings_rows(state).len() + 5) as u16;
|
||||
let [rows_area, _, status_area] = Layout::vertical([
|
||||
Constraint::Length(rows_height.min(inner.height)),
|
||||
Constraint::Length(1),
|
||||
@@ -66,6 +66,83 @@ fn draw_settings_rows(frame: &mut Frame, area: Rect, state: &AppState) {
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
y = y.saturating_add(1);
|
||||
draw_section(frame, area, &mut y, "Connected Devices");
|
||||
let devices = state.federation.devices.as_ref();
|
||||
draw_row(
|
||||
frame,
|
||||
area,
|
||||
&mut y,
|
||||
cursor,
|
||||
state.settings_cursor,
|
||||
"This device name",
|
||||
devices
|
||||
.map(|status| status.this_device_name.clone())
|
||||
.unwrap_or_else(|| "loading…".to_string()),
|
||||
);
|
||||
cursor += 1;
|
||||
draw_row(
|
||||
frame,
|
||||
area,
|
||||
&mut y,
|
||||
cursor,
|
||||
state.settings_cursor,
|
||||
"Generate device invite",
|
||||
"↵".to_string(),
|
||||
);
|
||||
cursor += 1;
|
||||
draw_row(
|
||||
frame,
|
||||
area,
|
||||
&mut y,
|
||||
cursor,
|
||||
state.settings_cursor,
|
||||
"Connect device by invite…",
|
||||
"↵".to_string(),
|
||||
);
|
||||
cursor += 1;
|
||||
draw_row(
|
||||
frame,
|
||||
area,
|
||||
&mut y,
|
||||
cursor,
|
||||
state.settings_cursor,
|
||||
"Sync devices now",
|
||||
"↵".to_string(),
|
||||
);
|
||||
cursor += 1;
|
||||
if let Some(status) = devices {
|
||||
for device in &status.devices {
|
||||
let label = if device.is_self {
|
||||
format!("* {}", device.name)
|
||||
} else if device.revoked {
|
||||
format!(" {} (revoked)", device.name)
|
||||
} else {
|
||||
format!(" {}", device.name)
|
||||
};
|
||||
let version = if device.client_version.is_empty() {
|
||||
"unknown".to_string()
|
||||
} else {
|
||||
format!("v{}", device.client_version)
|
||||
};
|
||||
let value = if device.is_self || device.revoked {
|
||||
version
|
||||
} else {
|
||||
format!("{version} · revoke ↵")
|
||||
};
|
||||
draw_row(
|
||||
frame,
|
||||
area,
|
||||
&mut y,
|
||||
cursor,
|
||||
state.settings_cursor,
|
||||
&label,
|
||||
value,
|
||||
);
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
|
||||
y = y.saturating_add(1);
|
||||
draw_section(frame, area, &mut y, "Visualizations");
|
||||
draw_row(
|
||||
@@ -273,5 +350,53 @@ fn draw_status(frame: &mut Frame, area: Rect, state: &AppState) {
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.push(Line::default());
|
||||
lines.push(Line::styled("Connected Devices", theme::header()));
|
||||
match &state.federation.devices {
|
||||
None => lines.push(Line::styled("loading…", theme::dim())),
|
||||
Some(status) => {
|
||||
lines.push(status_line("This device", status.this_device_id.clone()));
|
||||
lines.push(status_line("Sync group", status.group_id.clone()));
|
||||
lines.push(status_line(
|
||||
"Active devices",
|
||||
status.active_devices.to_string(),
|
||||
));
|
||||
lines.push(status_line(
|
||||
"Revoked devices",
|
||||
status.revoked_devices.to_string(),
|
||||
));
|
||||
lines.push(status_line(
|
||||
"Pending requests",
|
||||
status.pending_requests.to_string(),
|
||||
));
|
||||
lines.push(status_line("Ops in log", status.ops_total.to_string()));
|
||||
lines.push(status_line(
|
||||
"Tombstones",
|
||||
format!(
|
||||
"{} ({} compactable)",
|
||||
status.tombstone_ops, status.compactable_tombstones
|
||||
),
|
||||
));
|
||||
lines.push(status_line("Outbox ops", status.outbox_ops.to_string()));
|
||||
lines.push(status_line(
|
||||
"Snapshot",
|
||||
format!(
|
||||
"{} likes, {} playlists, {} items",
|
||||
status.snapshot_likes, status.snapshot_playlists, status.snapshot_items
|
||||
),
|
||||
));
|
||||
lines.push(status_line(
|
||||
"Unresolved items",
|
||||
status.unresolved_playlist_items.to_string(),
|
||||
));
|
||||
lines.push(status_line("Peer ack floor", status.peer_ack_floor.clone()));
|
||||
if let Some(last_sync) = &status.last_sync {
|
||||
lines.push(status_line("Last device sync", last_sync.clone()));
|
||||
}
|
||||
if let Some(last_error) = &status.last_error {
|
||||
lines.push(status_line("Device error", last_error.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
frame.render_widget(Paragraph::new(lines), area);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,15 @@ pub fn draw(frame: &mut Frame, state: &AppState) {
|
||||
Some(Popup::LogDetail(entry)) => draw_log_detail(frame, entry),
|
||||
Some(Popup::FedInput { field, input }) => draw_fed_input(frame, field.title(), input),
|
||||
Some(Popup::FedText { title, text }) => draw_fed_text(frame, title, text),
|
||||
Some(Popup::DevicePairing {
|
||||
device_id,
|
||||
name,
|
||||
client_version,
|
||||
..
|
||||
}) => draw_device_pairing(frame, device_id, name, client_version),
|
||||
Some(Popup::ConfirmDeviceRevoke { device_id, name }) => {
|
||||
draw_device_revoke(frame, device_id, name)
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +136,58 @@ fn draw_fed_text(frame: &mut Frame, title: &str, text: &str) {
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_device_pairing(frame: &mut Frame, device_id: &str, name: &str, client_version: &str) {
|
||||
let area = centered(frame.area(), 64, 8);
|
||||
let block = Block::bordered()
|
||||
.title(" Pair device ")
|
||||
.title_style(theme::header())
|
||||
.border_style(theme::accent());
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(Clear, area);
|
||||
frame.render_widget(block, area);
|
||||
let lines = vec![
|
||||
Line::from(vec![
|
||||
Span::styled("Name ", theme::dim()),
|
||||
Span::raw(name.to_string()),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("Version ", theme::dim()),
|
||||
Span::raw(client_version.to_string()),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("Device ", theme::dim()),
|
||||
Span::raw(device_id.chars().take(24).collect::<String>()),
|
||||
]),
|
||||
Line::default(),
|
||||
Line::styled("enter/y accept · n/esc deny", theme::dim()),
|
||||
];
|
||||
frame.render_widget(Paragraph::new(lines), inner);
|
||||
}
|
||||
|
||||
fn draw_device_revoke(frame: &mut Frame, device_id: &str, name: &str) {
|
||||
let area = centered(frame.area(), 64, 7);
|
||||
let block = Block::bordered()
|
||||
.title(" Revoke device ")
|
||||
.title_style(theme::header())
|
||||
.border_style(theme::accent());
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(Clear, area);
|
||||
frame.render_widget(block, area);
|
||||
let lines = vec![
|
||||
Line::from(vec![
|
||||
Span::styled("Device ", theme::dim()),
|
||||
Span::raw(name.to_string()),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("ID ", theme::dim()),
|
||||
Span::raw(device_id.chars().take(24).collect::<String>()),
|
||||
]),
|
||||
Line::default(),
|
||||
Line::styled("enter/y revoke · n/esc cancel", theme::dim()),
|
||||
];
|
||||
frame.render_widget(Paragraph::new(lines), inner);
|
||||
}
|
||||
|
||||
/// Metadata edit form: one bordered input per field, the focused field gets
|
||||
/// the accent border and a cursor block.
|
||||
fn draw_edit(
|
||||
|
||||
Reference in New Issue
Block a user