diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index b501bb44b..0c24fe7ea 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:ui' as ui; import 'package:bot_toast/bot_toast.dart'; @@ -22,10 +21,7 @@ const String defaultGroupTabname = 'Group'; class StatePeerTab { final RxInt currentTab = 0.obs; - static const List tabIndexs = [0, 1, 2, 3, 4]; - List tabOrder = List.empty(growable: true); - final RxList visibleTabOrder = RxList.empty(growable: true); - int tabHiddenFlag = 0; + final RxInt tabHiddenFlag = 0.obs; final RxList tabNames = [ 'Recent Sessions', 'Favorites', @@ -35,44 +31,25 @@ class StatePeerTab { ].obs; StatePeerTab._() { - tabHiddenFlag = (int.tryParse( + tabHiddenFlag.value = (int.tryParse( bind.getLocalFlutterConfig(k: 'hidden-peer-card'), radix: 2) ?? 0); + var tabs = _notHiddenTabs(); currentTab.value = int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0; - if (!tabIndexs.contains(currentTab.value)) { - currentTab.value = tabIndexs[0]; + if (!tabs.contains(currentTab.value)) { + currentTab.value = 0; } - tabOrder = tabIndexs.toList(); - try { - final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order'); - if (conf.isNotEmpty) { - final json = jsonDecode(conf); - if (json is List) { - final List list = - json.map((e) => int.tryParse(e.toString()) ?? -1).toList(); - if (list.length == tabOrder.length && - tabOrder.every((e) => list.contains(e))) { - tabOrder = list; - } - } - } - } catch (e) { - debugPrintStack(label: '$e'); - } - visibleTabOrder.value = tabOrder.where((e) => !isTabHidden(e)).toList(); - visibleTabOrder.remove(groupTabIndex); } static final StatePeerTab instance = StatePeerTab._(); check() { - List oldOrder = visibleTabOrder; + var tabs = _notHiddenTabs(); if (filterGroupCard()) { - visibleTabOrder.remove(groupTabIndex); if (currentTab.value == groupTabIndex) { currentTab.value = - visibleTabOrder.firstWhereOrNull((e) => e != groupTabIndex) ?? 0; + tabs.firstWhereOrNull((e) => e != groupTabIndex) ?? 0; bind.setLocalFlutterConfig( k: 'peer-tab-index', v: currentTab.value.toString()); } @@ -83,26 +60,22 @@ class StatePeerTab { } else { tabNames[groupTabIndex] = defaultGroupTabname; } - if (isTabHidden(groupTabIndex)) { - visibleTabOrder.remove(groupTabIndex); - } else { - if (!visibleTabOrder.contains(groupTabIndex)) { - addTabInOrder(visibleTabOrder, groupTabIndex); - } - } - if (visibleTabOrder.contains(groupTabIndex) && + if (tabs.contains(groupTabIndex) && int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) == groupTabIndex) { currentTab.value = groupTabIndex; } } - if (oldOrder != visibleTabOrder) { - saveTabOrder(); - } } - bool isTabHidden(int tabindex) { - return tabHiddenFlag & (1 << tabindex) != 0; + List currentTabs() { + var v = List.empty(growable: true); + for (int i = 0; i < tabNames.length; i++) { + if (!_isTabHidden(i) && !_isTabFilter(i)) { + v.add(i); + } + } + return v; } bool filterGroupCard() { @@ -114,50 +87,25 @@ class StatePeerTab { } } - addTabInOrder(List list, int tabIndex) { - if (!tabOrder.contains(tabIndex) || list.contains(tabIndex)) { - return; - } - bool sameOrder = true; - int lastIndex = -1; - for (int i = 0; i < list.length; i++) { - var index = tabOrder.lastIndexOf(list[i]); - if (index > lastIndex) { - lastIndex = index; - continue; - } else { - sameOrder = false; - break; - } - } - if (sameOrder) { - var indexInTabOrder = tabOrder.indexOf(tabIndex); - var left = List.empty(growable: true); - for (int i = 0; i < indexInTabOrder; i++) { - left.add(tabOrder[i]); - } - int insertIndex = list.lastIndexWhere((e) => left.contains(e)); - if (insertIndex < 0) { - insertIndex = 0; - } else { - insertIndex += 1; - } - list.insert(insertIndex, tabIndex); - } else { - list.add(tabIndex); - } + bool _isTabHidden(int tabindex) { + return tabHiddenFlag & (1 << tabindex) != 0; } - saveTabOrder() { - var list = statePeerTab.visibleTabOrder.toList(); - var left = tabOrder - .where((e) => !statePeerTab.visibleTabOrder.contains(e)) - .toList(); - for (var t in left) { - addTabInOrder(list, t); + bool _isTabFilter(int tabIndex) { + if (tabIndex == groupTabIndex) { + return filterGroupCard(); } - statePeerTab.tabOrder = list; - bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(list)); + return false; + } + + List _notHiddenTabs() { + var v = List.empty(growable: true); + for (int i = 0; i < tabNames.length; i++) { + if (!_isTabHidden(i)) { + v.add(i); + } + } + return v; } } @@ -266,59 +214,41 @@ class _PeerTabPageState extends State Widget _createSwitchBar(BuildContext context) { final textColor = Theme.of(context).textTheme.titleLarge?.color; - statePeerTab.visibleTabOrder - .removeWhere((e) => !StatePeerTab.tabIndexs.contains(e)); return Obx(() { - int indexCounter = -1; - return ReorderableListView( - buildDefaultDragHandles: false, - onReorder: (oldIndex, newIndex) { - var list = statePeerTab.visibleTabOrder.toList(); - if (oldIndex < newIndex) { - newIndex -= 1; - } - final int item = list.removeAt(oldIndex); - list.insert(newIndex, item); - statePeerTab.visibleTabOrder.value = list; - statePeerTab.saveTabOrder(); - }, + var tabs = statePeerTab.currentTabs(); + return ListView( scrollDirection: Axis.horizontal, - shrinkWrap: true, - scrollController: ScrollController(), - children: statePeerTab.visibleTabOrder.map((t) { - indexCounter++; - return ReorderableDragStartListener( - key: ValueKey(t), - index: indexCounter, - child: InkWell( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: statePeerTab.currentTab.value == t - ? Theme.of(context).backgroundColor - : null, - borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), + physics: NeverScrollableScrollPhysics(), + controller: ScrollController(), + children: tabs.map((t) { + return InkWell( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: statePeerTab.currentTab.value == t + ? Theme.of(context).backgroundColor + : null, + borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), + ), + child: Align( + alignment: Alignment.center, + child: Text( + translatedTabname(t), + textAlign: TextAlign.center, + style: TextStyle( + height: 1, + fontSize: 14, + color: statePeerTab.currentTab.value == t + ? textColor + : textColor + ?..withOpacity(0.5)), ), - child: Align( - alignment: Alignment.center, - child: Text( - translatedTabname(t), - textAlign: TextAlign.center, - style: TextStyle( - height: 1, - fontSize: 14, - color: statePeerTab.currentTab.value == t - ? textColor - : textColor - ?..withOpacity(0.5)), - ), - )), - onTap: () async { - await handleTabSelection(t); - await bind.setLocalFlutterConfig( - k: 'peer-tab-index', v: t.toString()); - }, - ), + )), + onTap: () async { + await handleTabSelection(t); + await bind.setLocalFlutterConfig( + k: 'peer-tab-index', v: t.toString()); + }, ); }).toList()); }); @@ -343,20 +273,18 @@ class _PeerTabPageState extends State Widget _createPeersView() { final verticalMargin = isDesktop ? 12.0 : 6.0; - statePeerTab.visibleTabOrder - .removeWhere((e) => !StatePeerTab.tabIndexs.contains(e)); return Expanded( child: Obx(() { - if (statePeerTab.visibleTabOrder.isEmpty) { + var tabs = statePeerTab.currentTabs(); + if (tabs.isEmpty) { return visibleContextMenuListener(Center( child: Text(translate('Right click to select tabs')), )); } else { - if (statePeerTab.visibleTabOrder - .contains(statePeerTab.currentTab.value)) { + if (tabs.contains(statePeerTab.currentTab.value)) { return entries[statePeerTab.currentTab.value].widget; } else { - statePeerTab.currentTab.value = statePeerTab.visibleTabOrder[0]; + statePeerTab.currentTab.value = tabs[0]; return entries[statePeerTab.currentTab.value].widget; } } @@ -394,13 +322,9 @@ class _PeerTabPageState extends State } adjustTab() { - if (statePeerTab.visibleTabOrder.isNotEmpty) { - if (!statePeerTab.visibleTabOrder - .contains(statePeerTab.currentTab.value)) { - handleTabSelection(statePeerTab.visibleTabOrder[0]); - } - } else { - statePeerTab.currentTab.value = 0; + var tabs = statePeerTab.currentTabs(); + if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) { + statePeerTab.currentTab.value = tabs[0]; } } @@ -438,22 +362,13 @@ class _PeerTabPageState extends State }, setter: (show) async { if (show) { - statePeerTab.tabHiddenFlag &= ~bitMask; + statePeerTab.tabHiddenFlag.value &= ~bitMask; } else { - statePeerTab.tabHiddenFlag |= bitMask; + statePeerTab.tabHiddenFlag.value |= bitMask; } await bind.setLocalFlutterConfig( k: 'hidden-peer-card', - v: statePeerTab.tabHiddenFlag.toRadixString(2)); - statePeerTab.visibleTabOrder - .removeWhere((e) => statePeerTab.isTabHidden(e)); - for (int j = 0; j < statePeerTab.tabNames.length; j++) { - if (!statePeerTab.isTabHidden(j) && - !(j == groupTabIndex && statePeerTab.filterGroupCard())) { - statePeerTab.addTabInOrder(statePeerTab.visibleTabOrder, j); - } - } - statePeerTab.saveTabOrder(); + v: statePeerTab.tabHiddenFlag.value.toRadixString(2)); cancelFunc(); adjustTab(); })); diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index a45de24b0..40cd794ab 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -125,6 +125,7 @@ class _DesktopSettingPageState extends State scrollController: controller, child: PageView( controller: controller, + physics: NeverScrollableScrollPhysics(), children: const [ _General(), _Safety(), diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index c776f0e89..1a7e9aeb7 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -571,7 +571,8 @@ class _RemoteMenubarState extends State { ), ]); // {handler.get_audit_server() &&
  • {translate('Note')}
  • } - final auditServer = bind.sessionGetAuditServerSync(id: widget.id); + final auditServer = + bind.sessionGetAuditServerSync(id: widget.id, typ: "conn"); if (auditServer.isNotEmpty) { displayMenu.add( MenuEntryButton( diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 436011cb5..f6a5da819 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -331,6 +331,7 @@ class DesktopTab extends StatelessWidget { return _buildBlock( child: Obx(() => PageView( controller: state.value.pageController, + physics: NeverScrollableScrollPhysics(), children: state.value.tabs .map((tab) => tab.page) .toList(growable: false)))); diff --git a/flutter/lib/models/group_model.dart b/flutter/lib/models/group_model.dart index adc92f182..f220d62f1 100644 --- a/flutter/lib/models/group_model.dart +++ b/flutter/lib/models/group_model.dart @@ -45,8 +45,9 @@ class GroupModel { var uri0 = Uri.parse(api); final pageSize = 20; var total = 0; - int current = 1; + int current = 0; do { + current += 1; var uri = Uri( scheme: uri0.scheme, host: uri0.host, @@ -58,7 +59,6 @@ class GroupModel { if (gFFI.userModel.isAdmin.isFalse) 'grp': gFFI.userModel.groupName.value, }); - current += pageSize; final resp = await http.get(uri, headers: await getHttpHeaders()); if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { Map json = jsonDecode(resp.body); @@ -76,7 +76,7 @@ class GroupModel { } } } - } while (current < total + 1); + } while (current * pageSize < total); } catch (err) { debugPrint('$err'); userLoadError.value = err.toString(); @@ -96,8 +96,9 @@ class GroupModel { var uri0 = Uri.parse(api); final pageSize = 20; var total = 0; - int current = 1; + int current = 0; do { + current += 1; var uri = Uri( scheme: uri0.scheme, host: uri0.host, @@ -109,7 +110,6 @@ class GroupModel { 'grp': gFFI.userModel.groupName.value, 'target_user': username }); - current += pageSize; final resp = await http.get(uri, headers: await getHttpHeaders()); if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { Map json = jsonDecode(resp.body); @@ -129,7 +129,7 @@ class GroupModel { } } } - } while (current < total + 1); + } while (current * pageSize < total); } catch (err) { debugPrint('$err'); peerLoadError.value = err.toString(); diff --git a/src/common.rs b/src/common.rs index 9023780f4..fe76b3168 100644 --- a/src/common.rs +++ b/src/common.rs @@ -620,12 +620,12 @@ pub fn get_api_server(api: String, custom: String) -> String { "https://admin.rustdesk.com".to_owned() } -pub fn get_audit_server(api: String, custom: String) -> String { +pub fn get_audit_server(api: String, custom: String, typ: String) -> String { let url = get_api_server(api, custom); if url.is_empty() || url.contains("rustdesk.com") { return "".to_owned(); } - format!("{}/api/audit", url) + format!("{}/api/audit/{}", url, typ) } pub async fn post_request(url: String, body: String, header: &str) -> ResultType { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index ddfaad06d..dc9d7a04a 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -894,9 +894,9 @@ pub fn session_restart_remote_device(id: String) { } } -pub fn session_get_audit_server_sync(id: String) -> SyncReturn { +pub fn session_get_audit_server_sync(id: String, typ: String) -> SyncReturn { let res = if let Some(session) = SESSIONS.read().unwrap().get(&id) { - session.get_audit_server() + session.get_audit_server(typ) } else { "".to_owned() }; diff --git a/src/lang/ca.rs b/src/lang/ca.rs index a8f39a23f..2f9bf7b25 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 5dce8cd38..be0d7803e 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -406,5 +406,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "添加到地址簿"), ("Group", "小组"), ("Search", "搜索"), + ("Closed manually by the web console", "被web控制台手动关闭"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 52571ef07..eb57edc0a 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 138777e32..c34a31aef 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index d5d75d90b..b551774bf 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Zum Adressbuch hinzufügen"), ("Group", "Gruppe"), ("Search", "Suchen"), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index d22cb2311..3f22f489e 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index ce0254a98..6923029ff 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Añadir a la libreta de direcciones"), ("Group", "Grupo"), ("Search", "Búsqueda"), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index b3850e1f2..20a1663f2 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "افزودن به دفترچه آدرس"), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 124bfc00c..3b81fa1e4 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Ajouter au carnet d'adresses"), ("Group", "Groupe"), ("Search", "Rechercher"), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/gr.rs b/src/lang/gr.rs index 933a84143..c708ce478 100644 --- a/src/lang/gr.rs +++ b/src/lang/gr.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Προσθήκη στο Βιβλίο Διευθύνσεων"), ("Group", "Ομάδα"), ("Search", "Αναζήτηση"), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index f3f1e8fd9..849bfa0af 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 89728b3e6..da69f2c76 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 2237c81db..3b112618d 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Aggiungi alla rubrica"), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index e40c81ae8..3d76f446b 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 426a027db..92bb85bce 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 6acd892f8..90a2730f6 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index eb3a45d53..e5604c442 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Dodaj do Książki Adresowej"), ("Group", "Grypy"), ("Search", "Szukaj"), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 4d3d057ee..2adb4eb9e 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index bc878b680..6256d2e7a 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index f42d3146e..6e08322f3 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Добавить в адресную книгу"), ("Group", "Группа"), ("Search", "Поиск"), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index e1b82d4f4..a65e0519b 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index cbc71d4aa..27f37260d 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 43490c0b2..5d04f0150 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -410,5 +410,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Dodaj u adresar"), ("Group", "Grupa"), ("Search", "Pretraga"), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 4dcababc0..72cc83fce 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 34fe5077f..a92df2b52 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index b0c0686c1..cb7203c2d 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index d8d6c5ba0..6eef3656c 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "添加到地址簿"), ("Group", "小組"), ("Search", "搜索"), + ("Closed manually by the web console", "被web控制台手動關閉"), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 83b5e6984..373c3267e 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", "Додати IP до Адресної книги"), ("Group", "Група"), ("Search", "Пошук"), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 412f04999..7b7808bea 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Add to Address Book", ""), ("Group", ""), ("Search", ""), + ("Closed manually by the web console", ""), ].iter().cloned().collect(); } diff --git a/src/server/connection.rs b/src/server/connection.rs index fdd0ea77a..394d330a9 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -26,6 +26,7 @@ use hbb_common::{ }; #[cfg(any(target_os = "android", target_os = "ios"))] use scrap::android::call_main_service_mouse_input; +use serde::Deserialize; use serde_json::{json, value::Value}; use sha2::{Digest, Sha256}; #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -72,6 +73,8 @@ pub struct Connection { hash: Hash, read_jobs: Vec, timer: Interval, + file_timer: Interval, + http_timer: Interval, file_transfer: Option<(String, bool)>, port_forward_socket: Option>, port_forward_address: String, @@ -93,7 +96,8 @@ pub struct Connection { tx_input: std_mpsc::Sender, // handle input messages video_ack_required: bool, peer_info: (String, String), - api_server: String, + server_audit_conn: String, + server_audit_file: String, lr: LoginRequest, last_recv_time: Arc>, chat_unanswered: bool, @@ -163,6 +167,8 @@ impl Connection { hash, read_jobs: Vec::new(), timer: time::interval(SEC30), + file_timer: time::interval(SEC30), + http_timer: time::interval(Duration::from_secs(3)), file_transfer: None, port_forward_socket: None, port_forward_address: "".to_owned(), @@ -184,7 +190,8 @@ impl Connection { tx_input, video_ack_required: false, peer_info: Default::default(), - api_server: "".to_owned(), + server_audit_conn: "".to_owned(), + server_audit_file: "".to_owned(), lr: Default::default(), last_recv_time: Arc::new(Mutex::new(Instant::now())), chat_unanswered: false, @@ -255,7 +262,7 @@ impl Connection { } } ipc::Data::Close => { - conn.on_close_manually("connection manager").await; + conn.on_close_manually("connection manager", "peer").await; break; } ipc::Data::ChatMessage{text} => { @@ -375,16 +382,21 @@ impl Connection { break; } }, - _ = conn.timer.tick() => { + _ = conn.file_timer.tick() => { if !conn.read_jobs.is_empty() { if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await { conn.on_close(&err.to_string(), false).await; break; } } else { - conn.timer = time::interval_at(Instant::now() + SEC30, SEC30); + conn.file_timer = time::interval_at(Instant::now() + SEC30, SEC30); + } + } + _ = conn.http_timer.tick() => { + if let Err(_) = Connection::post_heartbeat(conn.server_audit_conn.clone(), conn.inner.id).await { + conn.on_close_manually("web console", "web console").await; + break; } - conn.post_audit(json!({})); // heartbeat }, Some((instant, value)) = rx_video.recv() => { if !conn.video_ack_required { @@ -412,7 +424,7 @@ impl Connection { Some(message::Union::Misc(m)) => { match &m.union { Some(misc::Union::StopService(_)) => { - conn.on_close_manually("stop service").await; + conn.on_close_manually("stop service", "peer").await; break; } _ => {}, @@ -497,7 +509,7 @@ impl Connection { conn.on_close(&err.to_string(), false).await; } - conn.post_audit(json!({ + conn.post_conn_audit(json!({ "action": "close", })); log::info!("#{} connection loop exited", id); @@ -601,7 +613,7 @@ impl Connection { if last_recv_time.elapsed() >= H1 { bail!("Timeout"); } - self.post_audit(json!({})); // heartbeat + Connection::post_heartbeat(self.server_audit_conn.clone(), self.inner.id).await?; } } } @@ -642,6 +654,13 @@ impl Connection { { self.send_login_error("Your ip is blocked by the peer") .await; + Self::post_alarm_audit( + AlarmAuditType::IpWhiltelist, //"ip whiltelist", + true, + json!({ + "ip":addr.ip(), + }), + ); sleep(1.).await; return false; } @@ -650,7 +669,7 @@ impl Connection { msg_out.set_hash(self.hash.clone()); self.send(msg_out).await; self.get_api_server(); - self.post_audit(json!({ + self.post_conn_audit(json!({ "ip": addr.ip(), "action": "new", })); @@ -658,30 +677,109 @@ impl Connection { } fn get_api_server(&mut self) { - self.api_server = crate::get_audit_server( + self.server_audit_conn = crate::get_audit_server( Config::get_option("api-server"), Config::get_option("custom-rendezvous-server"), + "conn".to_owned(), + ); + self.server_audit_file = crate::get_audit_server( + Config::get_option("api-server"), + Config::get_option("custom-rendezvous-server"), + "file".to_owned(), ); } - fn post_audit(&self, v: Value) { - if self.api_server.is_empty() { + fn post_conn_audit(&self, v: Value) { + if self.server_audit_conn.is_empty() { return; } - let url = self.api_server.clone(); + let url = self.server_audit_conn.clone(); let mut v = v; v["id"] = json!(Config::get_id()); v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); - v["Id"] = json!(self.inner.id); + v["conn_id"] = json!(self.inner.id); + tokio::spawn(async move { + allow_err!(Self::post_audit_async(url, v).await); + }); + } + + async fn post_heartbeat(server_audit_conn: String, conn_id: i32) -> ResultType<()> { + if server_audit_conn.is_empty() { + return Ok(()); + } + let url = server_audit_conn.clone(); + let mut v = Value::default(); + v["id"] = json!(Config::get_id()); + v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); + v["conn_id"] = json!(conn_id); + if let Ok(rsp) = Self::post_audit_async(url, v).await { + if let Ok(rsp) = serde_json::from_str::(&rsp) { + if rsp.action == "disconnect" { + bail!("disconnect by server"); + } + } + } + return Ok(()); + } + + fn post_file_audit( + &self, + r#type: FileAuditType, + path: &str, + files: Vec<(String, i64)>, + info: Value, + ) { + if self.server_audit_file.is_empty() { + return; + } + let url = self.server_audit_file.clone(); + let file_num = files.len(); + let mut files = files; + files.sort_by(|a, b| b.1.cmp(&a.1)); + files.truncate(10); + let is_file = files.len() == 1 && files[0].0.is_empty(); + let mut info = info; + info["ip"] = json!(self.ip.clone()); + info["name"] = json!(self.lr.my_name.clone()); + info["num"] = json!(file_num); + info["files"] = json!(files); + let v = json!({ + "id":json!(Config::get_id()), + "uuid":json!(base64::encode(hbb_common::get_uuid())), + "peer_id":json!(self.lr.my_id), + "type": r#type as i8, + "path":path, + "is_file":is_file, + "info":json!(info).to_string(), + }); + tokio::spawn(async move { + allow_err!(Self::post_audit_async(url, v).await); + }); + } + + pub fn post_alarm_audit(typ: AlarmAuditType, from_remote: bool, info: Value) { + let url = crate::get_audit_server( + Config::get_option("api-server"), + Config::get_option("custom-rendezvous-server"), + "alarm".to_owned(), + ); + if url.is_empty() { + return; + } + let mut v = Value::default(); + v["id"] = json!(Config::get_id()); + v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); + v["typ"] = json!(typ as i8); + v["from_remote"] = json!(from_remote); + v["info"] = serde_json::Value::String(info.to_string()); tokio::spawn(async move { allow_err!(Self::post_audit_async(url, v).await); }); } #[inline] - async fn post_audit_async(url: String, v: Value) -> ResultType<()> { - crate::post_request(url, v.to_string(), "").await?; - Ok(()) + async fn post_audit_async(url: String, v: Value) -> ResultType { + crate::post_request(url, v.to_string(), "").await } async fn send_logon_response(&mut self) { @@ -695,7 +793,7 @@ impl Connection { } else { 0 }; - self.post_audit(json!({"peer": self.peer_info, "Type": conn_type})); + self.post_conn_audit(json!({"peer": self.peer_info, "type": conn_type})); #[allow(unused_mut)] let mut username = crate::platform::get_active_username(); let mut res = LoginResponse::new(); @@ -1086,8 +1184,22 @@ impl Connection { if failure.2 > 30 { self.send_login_error("Too many wrong password attempts") .await; + Self::post_alarm_audit( + AlarmAuditType::ManyWrongPassword, + true, + json!({ + "ip":self.ip, + }), + ); } else if time == failure.0 && failure.1 > 6 { self.send_login_error("Please try 1 minute later").await; + Self::post_alarm_audit( + AlarmAuditType::FrequentAttempt, + true, + json!({ + "ip":self.ip, + }), + ); } else if !self.validate_password() { if failure.0 == time { failure.1 += 1; @@ -1225,8 +1337,18 @@ impl Connection { Ok(job) => { self.send(fs::new_dir(id, path, job.files().to_vec())) .await; + let mut files = job.files().to_owned(); self.read_jobs.push(job); - self.timer = time::interval(MILLI1); + self.file_timer = time::interval(MILLI1); + self.post_file_audit( + FileAuditType::RemoteSend, + &s.path, + files + .drain(..) + .map(|f| (f.name, f.size as _)) + .collect(), + json!({}), + ); } } } @@ -1237,7 +1359,7 @@ impl Connection { &self.lr.version, )); self.send_fs(ipc::FS::NewWrite { - path: r.path, + path: r.path.clone(), id: r.id, file_num: r.file_num, files: r @@ -1248,6 +1370,16 @@ impl Connection { .collect(), overwrite_detection: od, }); + self.post_file_audit( + FileAuditType::RemoteReceive, + &r.path, + r.files + .to_vec() + .drain(..) + .map(|f| (f.name, f.size as _)) + .collect(), + json!({}), + ); } Some(file_action::Union::RemoveDir(d)) => { self.send_fs(ipc::FS::RemoveDir { @@ -1541,10 +1673,10 @@ impl Connection { self.port_forward_socket.take(); } - async fn on_close_manually(&mut self, close_from: &str) { + async fn on_close_manually(&mut self, close_from: &str, close_by: &str) { self.close_manually = true; let mut misc = Misc::new(); - misc.set_close_reason("Closed manually by the peer".into()); + misc.set_close_reason(format!("Closed manually by the {}", close_by)); let mut msg_out = Message::new(); msg_out.set_misc(misc); self.send(msg_out).await; @@ -1721,3 +1853,21 @@ mod privacy_mode { } } } + +#[derive(Debug, Deserialize)] +struct ConnAuditResponse { + #[allow(dead_code)] + ret: bool, + action: String, +} + +pub enum AlarmAuditType { + IpWhiltelist = 0, + ManyWrongPassword = 1, + FrequentAttempt = 2, +} + +pub enum FileAuditType { + RemoteSend = 0, + RemoteReceive = 1, +} diff --git a/src/ui/header.tis b/src/ui/header.tis index 086696726..d1bb91cb9 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -208,7 +208,7 @@ class Header: Reactor.Component { {keyboard_enabled ?
  • {translate('OS Password')}
  • : ""}
  • {translate('Transfer File')}
  • {translate('TCP Tunneling')}
  • - {handler.get_audit_server() &&
  • {translate('Note')}
  • } + {handler.get_audit_server("conn") &&
  • {translate('Note')}
  • }
    {keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ?
  • {translate('Insert')} Ctrl + Alt + Del
  • : ""} {restart_enabled && (pi.platform == "Linux" || pi.platform == "Windows" || pi.platform == "Mac OS") ?
  • {translate('Restart Remote Device')}
  • : ""} diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 29b1a9eee..df5d98038 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -233,12 +233,12 @@ impl InvokeUiSession for SciterHandler { fn on_connected(&self, conn_type: ConnType) { match conn_type { - ConnType::RDP => {}, - ConnType::PORT_FORWARD => {}, - ConnType::FILE_TRANSFER => {}, + ConnType::RDP => {} + ConnType::PORT_FORWARD => {} + ConnType::FILE_TRANSFER => {} ConnType::DEFAULT_CONN => { crate::keyboard::client::start_grab_loop(); - }, + } } } @@ -348,7 +348,7 @@ impl sciter::EventHandler for SciterSession { } sciter::dispatch_script_call! { - fn get_audit_server(); + fn get_audit_server(String); fn send_note(String); fn is_xfce(); fn get_id(); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 9868b5bb1..55984e343 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -173,7 +173,7 @@ impl Session { self.send(Data::Message(msg)); } - pub fn get_audit_server(&self) -> String { + pub fn get_audit_server(&self, typ: String) -> String { if self.lc.read().unwrap().conn_id <= 0 || LocalConfig::get_option("access_token").is_empty() { @@ -182,11 +182,12 @@ impl Session { crate::get_audit_server( Config::get_option("api-server"), Config::get_option("custom-rendezvous-server"), + typ, ) } pub fn send_note(&self, note: String) { - let url = self.get_audit_server(); + let url = self.get_audit_server("conn".to_string()); let id = self.id.clone(); let conn_id = self.lc.read().unwrap().conn_id; std::thread::spawn(move || {