Merge pull request #2649 from 21pages/audit

audit && remove  ReorderedListView
This commit is contained in:
RustDesk 2022-12-26 11:05:48 +08:00 committed by GitHub
commit 0e6e27b0ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 299 additions and 201 deletions

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
@ -22,10 +21,7 @@ const String defaultGroupTabname = 'Group';
class StatePeerTab { class StatePeerTab {
final RxInt currentTab = 0.obs; final RxInt currentTab = 0.obs;
static const List<int> tabIndexs = [0, 1, 2, 3, 4]; final RxInt tabHiddenFlag = 0.obs;
List<int> tabOrder = List.empty(growable: true);
final RxList<int> visibleTabOrder = RxList.empty(growable: true);
int tabHiddenFlag = 0;
final RxList<String> tabNames = [ final RxList<String> tabNames = [
'Recent Sessions', 'Recent Sessions',
'Favorites', 'Favorites',
@ -35,44 +31,25 @@ class StatePeerTab {
].obs; ].obs;
StatePeerTab._() { StatePeerTab._() {
tabHiddenFlag = (int.tryParse( tabHiddenFlag.value = (int.tryParse(
bind.getLocalFlutterConfig(k: 'hidden-peer-card'), bind.getLocalFlutterConfig(k: 'hidden-peer-card'),
radix: 2) ?? radix: 2) ??
0); 0);
var tabs = _notHiddenTabs();
currentTab.value = currentTab.value =
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0; int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0;
if (!tabIndexs.contains(currentTab.value)) { if (!tabs.contains(currentTab.value)) {
currentTab.value = tabIndexs[0]; 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<int> 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._(); static final StatePeerTab instance = StatePeerTab._();
check() { check() {
List<int> oldOrder = visibleTabOrder; var tabs = _notHiddenTabs();
if (filterGroupCard()) { if (filterGroupCard()) {
visibleTabOrder.remove(groupTabIndex);
if (currentTab.value == groupTabIndex) { if (currentTab.value == groupTabIndex) {
currentTab.value = currentTab.value =
visibleTabOrder.firstWhereOrNull((e) => e != groupTabIndex) ?? 0; tabs.firstWhereOrNull((e) => e != groupTabIndex) ?? 0;
bind.setLocalFlutterConfig( bind.setLocalFlutterConfig(
k: 'peer-tab-index', v: currentTab.value.toString()); k: 'peer-tab-index', v: currentTab.value.toString());
} }
@ -83,26 +60,22 @@ class StatePeerTab {
} else { } else {
tabNames[groupTabIndex] = defaultGroupTabname; tabNames[groupTabIndex] = defaultGroupTabname;
} }
if (isTabHidden(groupTabIndex)) { if (tabs.contains(groupTabIndex) &&
visibleTabOrder.remove(groupTabIndex);
} else {
if (!visibleTabOrder.contains(groupTabIndex)) {
addTabInOrder(visibleTabOrder, groupTabIndex);
}
}
if (visibleTabOrder.contains(groupTabIndex) &&
int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) == int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ==
groupTabIndex) { groupTabIndex) {
currentTab.value = groupTabIndex; currentTab.value = groupTabIndex;
} }
} }
if (oldOrder != visibleTabOrder) {
saveTabOrder();
}
} }
bool isTabHidden(int tabindex) { List<int> currentTabs() {
return tabHiddenFlag & (1 << tabindex) != 0; var v = List<int>.empty(growable: true);
for (int i = 0; i < tabNames.length; i++) {
if (!_isTabHidden(i) && !_isTabFilter(i)) {
v.add(i);
}
}
return v;
} }
bool filterGroupCard() { bool filterGroupCard() {
@ -114,50 +87,25 @@ class StatePeerTab {
} }
} }
addTabInOrder(List<int> list, int tabIndex) { bool _isTabHidden(int tabindex) {
if (!tabOrder.contains(tabIndex) || list.contains(tabIndex)) { return tabHiddenFlag & (1 << tabindex) != 0;
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);
}
} }
saveTabOrder() { bool _isTabFilter(int tabIndex) {
var list = statePeerTab.visibleTabOrder.toList(); if (tabIndex == groupTabIndex) {
var left = tabOrder return filterGroupCard();
.where((e) => !statePeerTab.visibleTabOrder.contains(e))
.toList();
for (var t in left) {
addTabInOrder(list, t);
} }
statePeerTab.tabOrder = list; return false;
bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(list)); }
List<int> _notHiddenTabs() {
var v = List<int>.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<PeerTabPage>
Widget _createSwitchBar(BuildContext context) { Widget _createSwitchBar(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color; final textColor = Theme.of(context).textTheme.titleLarge?.color;
statePeerTab.visibleTabOrder
.removeWhere((e) => !StatePeerTab.tabIndexs.contains(e));
return Obx(() { return Obx(() {
int indexCounter = -1; var tabs = statePeerTab.currentTabs();
return ReorderableListView( return ListView(
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();
},
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
shrinkWrap: true, physics: NeverScrollableScrollPhysics(),
scrollController: ScrollController(), controller: ScrollController(),
children: statePeerTab.visibleTabOrder.map((t) { children: tabs.map((t) {
indexCounter++; return InkWell(
return ReorderableDragStartListener( child: Container(
key: ValueKey(t), padding: const EdgeInsets.symmetric(horizontal: 8),
index: indexCounter, decoration: BoxDecoration(
child: InkWell( color: statePeerTab.currentTab.value == t
child: Container( ? Theme.of(context).backgroundColor
padding: const EdgeInsets.symmetric(horizontal: 8), : null,
decoration: BoxDecoration( borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
color: statePeerTab.currentTab.value == t ),
? Theme.of(context).backgroundColor child: Align(
: null, alignment: Alignment.center,
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), 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, onTap: () async {
child: Text( await handleTabSelection(t);
translatedTabname(t), await bind.setLocalFlutterConfig(
textAlign: TextAlign.center, k: 'peer-tab-index', v: t.toString());
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());
},
),
); );
}).toList()); }).toList());
}); });
@ -343,20 +273,18 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget _createPeersView() { Widget _createPeersView() {
final verticalMargin = isDesktop ? 12.0 : 6.0; final verticalMargin = isDesktop ? 12.0 : 6.0;
statePeerTab.visibleTabOrder
.removeWhere((e) => !StatePeerTab.tabIndexs.contains(e));
return Expanded( return Expanded(
child: Obx(() { child: Obx(() {
if (statePeerTab.visibleTabOrder.isEmpty) { var tabs = statePeerTab.currentTabs();
if (tabs.isEmpty) {
return visibleContextMenuListener(Center( return visibleContextMenuListener(Center(
child: Text(translate('Right click to select tabs')), child: Text(translate('Right click to select tabs')),
)); ));
} else { } else {
if (statePeerTab.visibleTabOrder if (tabs.contains(statePeerTab.currentTab.value)) {
.contains(statePeerTab.currentTab.value)) {
return entries[statePeerTab.currentTab.value].widget; return entries[statePeerTab.currentTab.value].widget;
} else { } else {
statePeerTab.currentTab.value = statePeerTab.visibleTabOrder[0]; statePeerTab.currentTab.value = tabs[0];
return entries[statePeerTab.currentTab.value].widget; return entries[statePeerTab.currentTab.value].widget;
} }
} }
@ -394,13 +322,9 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
adjustTab() { adjustTab() {
if (statePeerTab.visibleTabOrder.isNotEmpty) { var tabs = statePeerTab.currentTabs();
if (!statePeerTab.visibleTabOrder if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) {
.contains(statePeerTab.currentTab.value)) { statePeerTab.currentTab.value = tabs[0];
handleTabSelection(statePeerTab.visibleTabOrder[0]);
}
} else {
statePeerTab.currentTab.value = 0;
} }
} }
@ -438,22 +362,13 @@ class _PeerTabPageState extends State<PeerTabPage>
}, },
setter: (show) async { setter: (show) async {
if (show) { if (show) {
statePeerTab.tabHiddenFlag &= ~bitMask; statePeerTab.tabHiddenFlag.value &= ~bitMask;
} else { } else {
statePeerTab.tabHiddenFlag |= bitMask; statePeerTab.tabHiddenFlag.value |= bitMask;
} }
await bind.setLocalFlutterConfig( await bind.setLocalFlutterConfig(
k: 'hidden-peer-card', k: 'hidden-peer-card',
v: statePeerTab.tabHiddenFlag.toRadixString(2)); v: statePeerTab.tabHiddenFlag.value.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();
cancelFunc(); cancelFunc();
adjustTab(); adjustTab();
})); }));

View File

@ -125,6 +125,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
scrollController: controller, scrollController: controller,
child: PageView( child: PageView(
controller: controller, controller: controller,
physics: NeverScrollableScrollPhysics(),
children: const [ children: const [
_General(), _General(),
_Safety(), _Safety(),

View File

@ -571,7 +571,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
), ),
]); ]);
// {handler.get_audit_server() && <li #note>{translate('Note')}</li>} // {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
final auditServer = bind.sessionGetAuditServerSync(id: widget.id); final auditServer =
bind.sessionGetAuditServerSync(id: widget.id, typ: "conn");
if (auditServer.isNotEmpty) { if (auditServer.isNotEmpty) {
displayMenu.add( displayMenu.add(
MenuEntryButton<String>( MenuEntryButton<String>(

View File

@ -331,6 +331,7 @@ class DesktopTab extends StatelessWidget {
return _buildBlock( return _buildBlock(
child: Obx(() => PageView( child: Obx(() => PageView(
controller: state.value.pageController, controller: state.value.pageController,
physics: NeverScrollableScrollPhysics(),
children: state.value.tabs children: state.value.tabs
.map((tab) => tab.page) .map((tab) => tab.page)
.toList(growable: false)))); .toList(growable: false))));

View File

@ -45,8 +45,9 @@ class GroupModel {
var uri0 = Uri.parse(api); var uri0 = Uri.parse(api);
final pageSize = 20; final pageSize = 20;
var total = 0; var total = 0;
int current = 1; int current = 0;
do { do {
current += 1;
var uri = Uri( var uri = Uri(
scheme: uri0.scheme, scheme: uri0.scheme,
host: uri0.host, host: uri0.host,
@ -58,7 +59,6 @@ class GroupModel {
if (gFFI.userModel.isAdmin.isFalse) if (gFFI.userModel.isAdmin.isFalse)
'grp': gFFI.userModel.groupName.value, 'grp': gFFI.userModel.groupName.value,
}); });
current += pageSize;
final resp = await http.get(uri, headers: await getHttpHeaders()); final resp = await http.get(uri, headers: await getHttpHeaders());
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(resp.body); Map<String, dynamic> json = jsonDecode(resp.body);
@ -76,7 +76,7 @@ class GroupModel {
} }
} }
} }
} while (current < total + 1); } while (current * pageSize < total);
} catch (err) { } catch (err) {
debugPrint('$err'); debugPrint('$err');
userLoadError.value = err.toString(); userLoadError.value = err.toString();
@ -96,8 +96,9 @@ class GroupModel {
var uri0 = Uri.parse(api); var uri0 = Uri.parse(api);
final pageSize = 20; final pageSize = 20;
var total = 0; var total = 0;
int current = 1; int current = 0;
do { do {
current += 1;
var uri = Uri( var uri = Uri(
scheme: uri0.scheme, scheme: uri0.scheme,
host: uri0.host, host: uri0.host,
@ -109,7 +110,6 @@ class GroupModel {
'grp': gFFI.userModel.groupName.value, 'grp': gFFI.userModel.groupName.value,
'target_user': username 'target_user': username
}); });
current += pageSize;
final resp = await http.get(uri, headers: await getHttpHeaders()); final resp = await http.get(uri, headers: await getHttpHeaders());
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(resp.body); Map<String, dynamic> json = jsonDecode(resp.body);
@ -129,7 +129,7 @@ class GroupModel {
} }
} }
} }
} while (current < total + 1); } while (current * pageSize < total);
} catch (err) { } catch (err) {
debugPrint('$err'); debugPrint('$err');
peerLoadError.value = err.toString(); peerLoadError.value = err.toString();

View File

@ -620,12 +620,12 @@ pub fn get_api_server(api: String, custom: String) -> String {
"https://admin.rustdesk.com".to_owned() "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); let url = get_api_server(api, custom);
if url.is_empty() || url.contains("rustdesk.com") { if url.is_empty() || url.contains("rustdesk.com") {
return "".to_owned(); return "".to_owned();
} }
format!("{}/api/audit", url) format!("{}/api/audit/{}", url, typ)
} }
pub async fn post_request(url: String, body: String, header: &str) -> ResultType<String> { pub async fn post_request(url: String, body: String, header: &str) -> ResultType<String> {

View File

@ -894,9 +894,9 @@ pub fn session_restart_remote_device(id: String) {
} }
} }
pub fn session_get_audit_server_sync(id: String) -> SyncReturn<String> { pub fn session_get_audit_server_sync(id: String, typ: String) -> SyncReturn<String> {
let res = if let Some(session) = SESSIONS.read().unwrap().get(&id) { let res = if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.get_audit_server() session.get_audit_server(typ)
} else { } else {
"".to_owned() "".to_owned()
}; };

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -406,5 +406,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "添加到地址簿"), ("Add to Address Book", "添加到地址簿"),
("Group", "小组"), ("Group", "小组"),
("Search", "搜索"), ("Search", "搜索"),
("Closed manually by the web console", "被web控制台手动关闭"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Zum Adressbuch hinzufügen"), ("Add to Address Book", "Zum Adressbuch hinzufügen"),
("Group", "Gruppe"), ("Group", "Gruppe"),
("Search", "Suchen"), ("Search", "Suchen"),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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"), ("Add to Address Book", "Añadir a la libreta de direcciones"),
("Group", "Grupo"), ("Group", "Grupo"),
("Search", "Búsqueda"), ("Search", "Búsqueda"),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "افزودن به دفترچه آدرس"), ("Add to Address Book", "افزودن به دفترچه آدرس"),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Ajouter au carnet d'adresses"), ("Add to Address Book", "Ajouter au carnet d'adresses"),
("Group", "Groupe"), ("Group", "Groupe"),
("Search", "Rechercher"), ("Search", "Rechercher"),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Προσθήκη στο Βιβλίο Διευθύνσεων"), ("Add to Address Book", "Προσθήκη στο Βιβλίο Διευθύνσεων"),
("Group", "Ομάδα"), ("Group", "Ομάδα"),
("Search", "Αναζήτηση"), ("Search", "Αναζήτηση"),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Aggiungi alla rubrica"), ("Add to Address Book", "Aggiungi alla rubrica"),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Dodaj do Książki Adresowej"), ("Add to Address Book", "Dodaj do Książki Adresowej"),
("Group", "Grypy"), ("Group", "Grypy"),
("Search", "Szukaj"), ("Search", "Szukaj"),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Добавить в адресную книгу"), ("Add to Address Book", "Добавить в адресную книгу"),
("Group", "Группа"), ("Group", "Группа"),
("Search", "Поиск"), ("Search", "Поиск"),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -410,5 +410,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Dodaj u adresar"), ("Add to Address Book", "Dodaj u adresar"),
("Group", "Grupa"), ("Group", "Grupa"),
("Search", "Pretraga"), ("Search", "Pretraga"),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "添加到地址簿"), ("Add to Address Book", "添加到地址簿"),
("Group", "小組"), ("Group", "小組"),
("Search", "搜索"), ("Search", "搜索"),
("Closed manually by the web console", "被web控制台手動關閉"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", "Додати IP до Адресної книги"), ("Add to Address Book", "Додати IP до Адресної книги"),
("Group", "Група"), ("Group", "Група"),
("Search", "Пошук"), ("Search", "Пошук"),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -405,5 +405,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add to Address Book", ""), ("Add to Address Book", ""),
("Group", ""), ("Group", ""),
("Search", ""), ("Search", ""),
("Closed manually by the web console", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -26,6 +26,7 @@ use hbb_common::{
}; };
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
use scrap::android::call_main_service_mouse_input; use scrap::android::call_main_service_mouse_input;
use serde::Deserialize;
use serde_json::{json, value::Value}; use serde_json::{json, value::Value};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -72,6 +73,8 @@ pub struct Connection {
hash: Hash, hash: Hash,
read_jobs: Vec<fs::TransferJob>, read_jobs: Vec<fs::TransferJob>,
timer: Interval, timer: Interval,
file_timer: Interval,
http_timer: Interval,
file_transfer: Option<(String, bool)>, file_transfer: Option<(String, bool)>,
port_forward_socket: Option<Framed<TcpStream, BytesCodec>>, port_forward_socket: Option<Framed<TcpStream, BytesCodec>>,
port_forward_address: String, port_forward_address: String,
@ -93,7 +96,8 @@ pub struct Connection {
tx_input: std_mpsc::Sender<MessageInput>, // handle input messages tx_input: std_mpsc::Sender<MessageInput>, // handle input messages
video_ack_required: bool, video_ack_required: bool,
peer_info: (String, String), peer_info: (String, String),
api_server: String, server_audit_conn: String,
server_audit_file: String,
lr: LoginRequest, lr: LoginRequest,
last_recv_time: Arc<Mutex<Instant>>, last_recv_time: Arc<Mutex<Instant>>,
chat_unanswered: bool, chat_unanswered: bool,
@ -163,6 +167,8 @@ impl Connection {
hash, hash,
read_jobs: Vec::new(), read_jobs: Vec::new(),
timer: time::interval(SEC30), timer: time::interval(SEC30),
file_timer: time::interval(SEC30),
http_timer: time::interval(Duration::from_secs(3)),
file_transfer: None, file_transfer: None,
port_forward_socket: None, port_forward_socket: None,
port_forward_address: "".to_owned(), port_forward_address: "".to_owned(),
@ -184,7 +190,8 @@ impl Connection {
tx_input, tx_input,
video_ack_required: false, video_ack_required: false,
peer_info: Default::default(), peer_info: Default::default(),
api_server: "".to_owned(), server_audit_conn: "".to_owned(),
server_audit_file: "".to_owned(),
lr: Default::default(), lr: Default::default(),
last_recv_time: Arc::new(Mutex::new(Instant::now())), last_recv_time: Arc::new(Mutex::new(Instant::now())),
chat_unanswered: false, chat_unanswered: false,
@ -255,7 +262,7 @@ impl Connection {
} }
} }
ipc::Data::Close => { ipc::Data::Close => {
conn.on_close_manually("connection manager").await; conn.on_close_manually("connection manager", "peer").await;
break; break;
} }
ipc::Data::ChatMessage{text} => { ipc::Data::ChatMessage{text} => {
@ -375,16 +382,21 @@ impl Connection {
break; break;
} }
}, },
_ = conn.timer.tick() => { _ = conn.file_timer.tick() => {
if !conn.read_jobs.is_empty() { if !conn.read_jobs.is_empty() {
if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await { if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await {
conn.on_close(&err.to_string(), false).await; conn.on_close(&err.to_string(), false).await;
break; break;
} }
} else { } 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() => { Some((instant, value)) = rx_video.recv() => {
if !conn.video_ack_required { if !conn.video_ack_required {
@ -412,7 +424,7 @@ impl Connection {
Some(message::Union::Misc(m)) => { Some(message::Union::Misc(m)) => {
match &m.union { match &m.union {
Some(misc::Union::StopService(_)) => { Some(misc::Union::StopService(_)) => {
conn.on_close_manually("stop service").await; conn.on_close_manually("stop service", "peer").await;
break; break;
} }
_ => {}, _ => {},
@ -497,7 +509,7 @@ impl Connection {
conn.on_close(&err.to_string(), false).await; conn.on_close(&err.to_string(), false).await;
} }
conn.post_audit(json!({ conn.post_conn_audit(json!({
"action": "close", "action": "close",
})); }));
log::info!("#{} connection loop exited", id); log::info!("#{} connection loop exited", id);
@ -601,7 +613,7 @@ impl Connection {
if last_recv_time.elapsed() >= H1 { if last_recv_time.elapsed() >= H1 {
bail!("Timeout"); 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") self.send_login_error("Your ip is blocked by the peer")
.await; .await;
Self::post_alarm_audit(
AlarmAuditType::IpWhiltelist, //"ip whiltelist",
true,
json!({
"ip":addr.ip(),
}),
);
sleep(1.).await; sleep(1.).await;
return false; return false;
} }
@ -650,7 +669,7 @@ impl Connection {
msg_out.set_hash(self.hash.clone()); msg_out.set_hash(self.hash.clone());
self.send(msg_out).await; self.send(msg_out).await;
self.get_api_server(); self.get_api_server();
self.post_audit(json!({ self.post_conn_audit(json!({
"ip": addr.ip(), "ip": addr.ip(),
"action": "new", "action": "new",
})); }));
@ -658,30 +677,109 @@ impl Connection {
} }
fn get_api_server(&mut self) { 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("api-server"),
Config::get_option("custom-rendezvous-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) { fn post_conn_audit(&self, v: Value) {
if self.api_server.is_empty() { if self.server_audit_conn.is_empty() {
return; return;
} }
let url = self.api_server.clone(); let url = self.server_audit_conn.clone();
let mut v = v; let mut v = v;
v["id"] = json!(Config::get_id()); v["id"] = json!(Config::get_id());
v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); 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::<ConnAuditResponse>(&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 { tokio::spawn(async move {
allow_err!(Self::post_audit_async(url, v).await); allow_err!(Self::post_audit_async(url, v).await);
}); });
} }
#[inline] #[inline]
async fn post_audit_async(url: String, v: Value) -> ResultType<()> { async fn post_audit_async(url: String, v: Value) -> ResultType<String> {
crate::post_request(url, v.to_string(), "").await?; crate::post_request(url, v.to_string(), "").await
Ok(())
} }
async fn send_logon_response(&mut self) { async fn send_logon_response(&mut self) {
@ -695,7 +793,7 @@ impl Connection {
} else { } else {
0 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)] #[allow(unused_mut)]
let mut username = crate::platform::get_active_username(); let mut username = crate::platform::get_active_username();
let mut res = LoginResponse::new(); let mut res = LoginResponse::new();
@ -1086,8 +1184,22 @@ impl Connection {
if failure.2 > 30 { if failure.2 > 30 {
self.send_login_error("Too many wrong password attempts") self.send_login_error("Too many wrong password attempts")
.await; .await;
Self::post_alarm_audit(
AlarmAuditType::ManyWrongPassword,
true,
json!({
"ip":self.ip,
}),
);
} else if time == failure.0 && failure.1 > 6 { } else if time == failure.0 && failure.1 > 6 {
self.send_login_error("Please try 1 minute later").await; 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() { } else if !self.validate_password() {
if failure.0 == time { if failure.0 == time {
failure.1 += 1; failure.1 += 1;
@ -1225,8 +1337,18 @@ impl Connection {
Ok(job) => { Ok(job) => {
self.send(fs::new_dir(id, path, job.files().to_vec())) self.send(fs::new_dir(id, path, job.files().to_vec()))
.await; .await;
let mut files = job.files().to_owned();
self.read_jobs.push(job); 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.lr.version,
)); ));
self.send_fs(ipc::FS::NewWrite { self.send_fs(ipc::FS::NewWrite {
path: r.path, path: r.path.clone(),
id: r.id, id: r.id,
file_num: r.file_num, file_num: r.file_num,
files: r files: r
@ -1248,6 +1370,16 @@ impl Connection {
.collect(), .collect(),
overwrite_detection: od, 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)) => { Some(file_action::Union::RemoveDir(d)) => {
self.send_fs(ipc::FS::RemoveDir { self.send_fs(ipc::FS::RemoveDir {
@ -1541,10 +1673,10 @@ impl Connection {
self.port_forward_socket.take(); 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; self.close_manually = true;
let mut misc = Misc::new(); 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(); let mut msg_out = Message::new();
msg_out.set_misc(misc); msg_out.set_misc(misc);
self.send(msg_out).await; 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,
}

View File

@ -208,7 +208,7 @@ class Header: Reactor.Component {
{keyboard_enabled ? <li #os-password>{translate('OS Password')}<EditOsPassword /></li> : ""} {keyboard_enabled ? <li #os-password>{translate('OS Password')}<EditOsPassword /></li> : ""}
<li #transfer-file>{translate('Transfer File')}</li> <li #transfer-file>{translate('Transfer File')}</li>
<li #tunnel>{translate('TCP Tunneling')}</li> <li #tunnel>{translate('TCP Tunneling')}</li>
{handler.get_audit_server() && <li #note>{translate('Note')}</li>} {handler.get_audit_server("conn") && <li #note>{translate('Note')}</li>}
<div .separator /> <div .separator />
{keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>{translate('Insert')} Ctrl + Alt + Del</li> : ""} {keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ? <li #ctrl-alt-del>{translate('Insert')} Ctrl + Alt + Del</li> : ""}
{restart_enabled && (pi.platform == "Linux" || pi.platform == "Windows" || pi.platform == "Mac OS") ? <li #restart_remote_device>{translate('Restart Remote Device')}</li> : ""} {restart_enabled && (pi.platform == "Linux" || pi.platform == "Windows" || pi.platform == "Mac OS") ? <li #restart_remote_device>{translate('Restart Remote Device')}</li> : ""}

View File

@ -233,12 +233,12 @@ impl InvokeUiSession for SciterHandler {
fn on_connected(&self, conn_type: ConnType) { fn on_connected(&self, conn_type: ConnType) {
match conn_type { match conn_type {
ConnType::RDP => {}, ConnType::RDP => {}
ConnType::PORT_FORWARD => {}, ConnType::PORT_FORWARD => {}
ConnType::FILE_TRANSFER => {}, ConnType::FILE_TRANSFER => {}
ConnType::DEFAULT_CONN => { ConnType::DEFAULT_CONN => {
crate::keyboard::client::start_grab_loop(); crate::keyboard::client::start_grab_loop();
}, }
} }
} }
@ -348,7 +348,7 @@ impl sciter::EventHandler for SciterSession {
} }
sciter::dispatch_script_call! { sciter::dispatch_script_call! {
fn get_audit_server(); fn get_audit_server(String);
fn send_note(String); fn send_note(String);
fn is_xfce(); fn is_xfce();
fn get_id(); fn get_id();

View File

@ -173,7 +173,7 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Message(msg)); 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 if self.lc.read().unwrap().conn_id <= 0
|| LocalConfig::get_option("access_token").is_empty() || LocalConfig::get_option("access_token").is_empty()
{ {
@ -182,11 +182,12 @@ impl<T: InvokeUiSession> Session<T> {
crate::get_audit_server( crate::get_audit_server(
Config::get_option("api-server"), Config::get_option("api-server"),
Config::get_option("custom-rendezvous-server"), Config::get_option("custom-rendezvous-server"),
typ,
) )
} }
pub fn send_note(&self, note: String) { 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 id = self.id.clone();
let conn_id = self.lc.read().unwrap().conn_id; let conn_id = self.lc.read().unwrap().conn_id;
std::thread::spawn(move || { std::thread::spawn(move || {