diff --git a/tunet-cui/src/view.rs b/tunet-cui/src/view.rs index 77f34d1d..446dbf1c 100644 --- a/tunet-cui/src/view.rs +++ b/tunet-cui/src/view.rs @@ -19,7 +19,7 @@ pub fn draw(m: &Model, f: &mut Frame) { .split(f.size()); let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Length(34), Constraint::Percentage(100)]) + .constraints([Constraint::Length(51), Constraint::Percentage(100)]) .split(global_chunks[0]); let title_chunks = Layout::default() .direction(Direction::Vertical) @@ -73,7 +73,7 @@ pub fn draw(m: &Model, f: &mut Frame) { .map(|u| { ListItem::new(Text::from(vec![ Line::from(vec![ - Span::styled("IP 地址 ", subtitle_style), + Span::styled("IP地址 ", subtitle_style), Span::styled( u.address.to_string(), Style::default() @@ -81,6 +81,15 @@ pub fn draw(m: &Model, f: &mut Frame) { .add_modifier(Modifier::BOLD), ), ]), + Line::from(vec![ + Span::styled("IPv6地址 ", subtitle_style), + Span::styled( + u.address_v6.to_string(), + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + ), + ]), Line::from(vec![ Span::styled("登录时间 ", subtitle_style), Span::styled(u.login_time.to_string(), Style::default().fg(Color::Green)), @@ -94,7 +103,7 @@ pub fn draw(m: &Model, f: &mut Frame) { ]), Line::from({ let mut spans = vec![ - Span::styled("MAC 地址 ", subtitle_style), + Span::styled("MAC地址 ", subtitle_style), Span::styled( u.mac_address.map(|a| a.to_string()).unwrap_or_default(), Style::default().fg(Color::LightCyan), diff --git a/tunet-flutter/lib/views/onlines_card.dart b/tunet-flutter/lib/views/onlines_card.dart index f948a47a..6f5f0517 100644 --- a/tunet-flutter/lib/views/onlines_card.dart +++ b/tunet-flutter/lib/views/onlines_card.dart @@ -75,7 +75,7 @@ class _OnlinesCardState extends State { rows = [ DataRow( cells: List.filled( - 5, + 6, DataCell(Shimmer(child: const Text(' '))), ), onSelectChanged: (_) {}, @@ -104,6 +104,7 @@ class _OnlinesCardState extends State { child: DataTable( columns: const [ DataColumn(label: Text('IP地址')), + DataColumn(label: Text('IPv6地址')), DataColumn(label: Text('登录时间')), DataColumn(label: Text('流量')), DataColumn(label: Text('MAC地址')), @@ -133,6 +134,10 @@ DataRow _netUserToRow( Uint8List.fromList(u.address.octets), type: InternetAddressType.IPv4, ).address)), + DataCell(Text(InternetAddress.fromRawAddress( + Uint8List.fromList(u.addressV6.octets), + type: InternetAddressType.IPv6, + ).address)), DataCell(Text(DateFormat('MM-dd HH:mm').format(u.loginTime.field0))), DataCell(Text(u.flux.field0.formatByteSize())), DataCell(Text(u.macAddress)), diff --git a/tunet-flutter/native/Cargo.toml b/tunet-flutter/native/Cargo.toml index 3277af76..f5952734 100644 --- a/tunet-flutter/native/Cargo.toml +++ b/tunet-flutter/native/Cargo.toml @@ -17,7 +17,7 @@ anyhow = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } log = { workspace = true } chrono = { workspace = true } -flutter_rust_bridge = { version = "2.0.0-dev.25", features = ["chrono"] } +flutter_rust_bridge = { version = "=2.0.0-dev.26", features = ["chrono"] } [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.13" diff --git a/tunet-flutter/native/src/api.rs b/tunet-flutter/native/src/api.rs index 674c6cfb..064157d4 100644 --- a/tunet-flutter/native/src/api.rs +++ b/tunet-flutter/native/src/api.rs @@ -4,6 +4,7 @@ use chrono::Datelike; use flutter_rust_bridge::frb; pub use netstatus::NetStatus; +use std::net::Ipv6Addr; pub use std::{net::Ipv4Addr, sync::Mutex}; pub use tokio::{runtime::Handle, sync::mpsc}; pub use tunet_helper::{ @@ -83,6 +84,7 @@ pub struct DetailDailyWrap { pub struct NetUserWrap { pub address: Ipv4AddrWrap, + pub address_v6: Ipv6AddrWrap, pub login_time: NetDateTime, pub mac_address: String, pub flux: Flux, @@ -101,6 +103,18 @@ impl From for Ipv4AddrWrap { } } +pub struct Ipv6AddrWrap { + pub octets: [u8; 16], +} + +impl From for Ipv6AddrWrap { + fn from(value: Ipv6Addr) -> Self { + Self { + octets: value.octets(), + } + } +} + pub struct RuntimeStartConfig { pub status: NetStatus, pub username: String, @@ -168,6 +182,7 @@ impl Runtime { .iter() .map(|u| NetUserWrap { address: u.address.into(), + address_v6: u.address_v6.into(), login_time: u.login_time, mac_address: u .mac_address diff --git a/tunet-flutter/pubspec.yaml b/tunet-flutter/pubspec.yaml index a14fa484..ef5c35a6 100644 --- a/tunet-flutter/pubspec.yaml +++ b/tunet-flutter/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: sdk: flutter ffi: ^2.1.0 - flutter_rust_bridge: ^2.0.0-dev.24 + flutter_rust_bridge: 2.0.0-dev.26 meta: ^1.9.1 uuid: ^4.1.0 freezed_annotation: ^2.2.0 diff --git a/tunet-gui/src/context.rs b/tunet-gui/src/context.rs index dbb481db..c5122606 100644 --- a/tunet-gui/src/context.rs +++ b/tunet-gui/src/context.rs @@ -247,6 +247,7 @@ fn update_online(app: App, onlines: Vec, is_local: Vec) { for (user, is_local) in onlines.into_iter().zip(is_local) { let items: Rc> = Rc::new(VecModel::default()); items.push(user.address.to_string().as_str().into()); + items.push(user.address_v6.to_string().as_str().into()); items.push(user.login_time.to_string().as_str().into()); items.push(user.flux.to_string().as_str().into()); items.push( diff --git a/tunet-gui/ui/settings.slint b/tunet-gui/ui/settings.slint index a344d6a9..9d9f0d44 100644 --- a/tunet-gui/ui/settings.slint +++ b/tunet-gui/ui/settings.slint @@ -23,6 +23,7 @@ export component SettingsPage inherits VerticalBox { font-size: 1.5rem; font-weight: 700; } + GridBox { un := LineEdit { col: 0; @@ -30,12 +31,14 @@ export component SettingsPage inherits VerticalBox { text: SettingsModel.username; placeholder-text: "用户名"; } + pw := LineEdit { col: 0; row: 1; input-type: password; placeholder-text: "密码"; } + Button { col: 1; row: 0; @@ -46,12 +49,16 @@ export component SettingsPage inherits VerticalBox { pw.text = ""; } } + del_at_exit_btn := Button { col: 1; row: 1; text: "删除并退出"; - clicked => { msgbox.show() } + clicked => { + msgbox.show() + } } + msgbox := PopupWindow { x: del_at_exit_btn.x + del_at_exit_btn.width - 100pt; y: del_at_exit_btn.y + del_at_exit_btn.height; @@ -60,44 +67,58 @@ export component SettingsPage inherits VerticalBox { height: confirm-layout.height; background: confirm_layout.background; } + confirm_layout := Dialog { width: 100pt; title: "删除凭据并退出"; Text { text: "删除后程序将会退出。"; } + StandardButton { kind: yes; - clicked => { SettingsModel.del_and_exit() } + clicked => { + SettingsModel.del_and_exit() + } } } } } + Text { text: "管理连接"; horizontal-alignment: center; font-size: 1.5rem; font-weight: 700; } + HorizontalBox { iptext := LineEdit { placeholder-text: "IP地址"; } + Button { text: "认证IP"; enabled: !SettingsModel.busy; - clicked => { SettingsModel.connect-ip(iptext.text) } + clicked => { + SettingsModel.connect-ip(iptext.text) + } } + Button { text: "下线IP"; enabled: !SettingsModel.busy; - clicked => { SettingsModel.drop-ip(iptext.text) } + clicked => { + SettingsModel.drop-ip(iptext.text) + } } } + VerticalBox { StandardTableView { columns: [ { title: "IP地址" }, + { title: "IPv6地址" }, { title: "登录时间" }, { title: "流量" }, { title: "MAC地址" }, @@ -105,10 +126,13 @@ export component SettingsPage inherits VerticalBox { ]; rows: SettingsModel.onlines; } + Button { text: "刷新"; enabled: !SettingsModel.busy; - clicked => { SettingsModel.refresh() } + clicked => { + SettingsModel.refresh() + } } } } diff --git a/tunet-helper/src/usereg.rs b/tunet-helper/src/usereg.rs index c34cc9d3..4ddbecc2 100644 --- a/tunet-helper/src/usereg.rs +++ b/tunet-helper/src/usereg.rs @@ -6,7 +6,7 @@ use mac_address2::MacAddress; use md5::{Digest, Md5}; use select::document::Document; use select::predicate::*; -use std::net::Ipv4Addr; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::ops::Deref; use std::str::FromStr; use url::Url; @@ -39,15 +39,23 @@ impl Deref for NetDateTime { #[derive(Debug, Clone, Copy)] pub struct NetUser { pub address: Ipv4Addr, + pub address_v6: Ipv6Addr, pub login_time: NetDateTime, pub mac_address: Option, pub flux: Flux, } impl NetUser { - pub fn from_detail(a: Ipv4Addr, t: NetDateTime, m: Option, f: Flux) -> Self { + pub fn from_detail( + a: Ipv4Addr, + a6: Ipv6Addr, + t: NetDateTime, + m: Option, + f: Flux, + ) -> Self { NetUser { address: a, + address_v6: a6, login_time: t, mac_address: m, flux: f, @@ -110,7 +118,8 @@ pub struct UseregHelper { } static USEREG_LOG_URI: &str = "https://usereg.tsinghua.edu.cn/do.php"; -static USEREG_INFO_URI: &str = "https://usereg.tsinghua.edu.cn/online_user_ipv4.php"; +static USEREG_INFO_URI: &str = "https://usereg.tsinghua.edu.cn/import_online_user.php"; +static USEREG_INFO_URI2: &str = "https://usereg.tsinghua.edu.cn/1x_online_user.php"; static USEREG_CONNECT_URI: &str = "https://usereg.tsinghua.edu.cn/ip_login.php"; static USEREG_DETAIL_URI: &str = "https://usereg.tsinghua.edu.cn/user_detail_list.php"; static DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; @@ -184,24 +193,29 @@ impl UseregHelper { pub fn users(&self) -> impl Stream> { let client = self.client.clone(); try_stream! { - let res = client.get(USEREG_INFO_URI).send().await?; - let doc = { - let doc = Document::from(res.text().await?.as_str()); - doc - .find(Name("tr").descendant(Attr("align", "center"))) - .skip(1) - .map(|node| node.find(Name("td")).skip(1).map(|n| n.text()).collect::>()) - .collect::>() - }; - for tds in doc { - yield NetUser::from_detail( - tds[0] - .parse() - .unwrap_or_else(|_| Ipv4Addr::new(0, 0, 0, 0)), - tds[1].parse().unwrap_or_default(), - tds[6].parse().ok(), - tds[2].parse().unwrap_or_default(), - ); + for uri in [USEREG_INFO_URI, USEREG_INFO_URI2] { + let res = client.get(uri).send().await?; + let doc = { + let doc = Document::from(res.text().await?.as_str()); + doc + .find(Name("tr").descendant(Attr("align", "center"))) + .skip(1) + .map(|node| node.find(Name("td")).skip(1).map(|n| n.text()).collect::>()) + .collect::>() + }; + for tds in doc { + yield NetUser::from_detail( + tds[0] + .parse() + .unwrap_or_else(|_| Ipv4Addr::new(0, 0, 0, 0)), + tds[1] + .parse() + .unwrap_or_else(|_| Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), + tds[2].parse().unwrap_or_default(), + tds[7].parse().ok(), + tds[3].parse().unwrap_or_default(), + ); + } } } } diff --git a/tunet/src/commands.rs b/tunet/src/commands.rs index 857a3f4e..7741f10a 100644 --- a/tunet/src/commands.rs +++ b/tunet/src/commands.rs @@ -150,11 +150,12 @@ impl TUNetCommand for Online { .unwrap_or_default(); pin_mut!(us); if self.nuon { - print!("[[address login_time flux mac_address is_self]; "); + print!("[[address address_v6 login_time flux mac_address is_self]; "); while let Some(u) = us.try_next().await? { print!( - "[\"{}\" {} {}b \"{}\" {}] ", + "[\"{}\" \"{}\" {} {}b \"{}\" {}] ", u.address, + u.address_v6, naive_rfc3339(u.login_time), u.flux.0, u.mac_address.map(|a| a.to_string()).unwrap_or_default(), @@ -163,12 +164,13 @@ impl TUNetCommand for Online { } println!("]"); } else { - println!(" IP地址 登录时间 流量 MAC地址"); + println!(" IP地址 IPv6地址 登录时间 流量 MAC地址"); while let Some(u) = us.try_next().await? { let is_self = is_self(&mac_addrs, &u); println!( - "{:15} {:20} {:>8} {} {}", + "{:15} {:39} {:20} {:>8} {} {}", style(u.address).yellow(), + style(u.address_v6).yellow(), style(u.login_time).green(), style(u.flux).fg(get_flux_color(&u.flux, true)), style(u.mac_address.map(|a| a.to_string()).unwrap_or_default()).cyan(),