Connected Devices: Added sync feature

This commit is contained in:
Ultradesu
2026-07-24 00:54:27 +03:00
parent 7fe6ddfe05
commit adbb76b031
13 changed files with 3095 additions and 16 deletions
+127 -2
View File
@@ -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);
}
+61
View File
@@ -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(