fix: warn about insecure custom API server connections (#16616)

* fix: warn about insecure custom API server connections

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: limit API server warning to HTTP addresses

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: resolve API warnings against effective server configuration

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: reuse common sync for API server previews

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: keep server settings usable on API preview errors

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou authored and GitHub committed 2026-10-20 22:52:48 +08:00
1 parent 7f5a401947
commit c7f212df2c
59 files changed
+291 -2

No files matched your search

@@ -0,0 +1,72 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/platform_model.dart';
String _resolveApiServer({required String api, required String custom}) {
final key = 'resolve-api-server:${jsonEncode([api, custom])}';
final response = jsonDecode(bind.mainGetCommonSync(key: key));
if (response['error'] != null) {
throw FormatException('Failed to resolve API server: ${response['error']}');
}
return response['url'] as String;
}
bool shouldWarnApiServer(String? apiServer) {
final uri = Uri.tryParse(apiServer?.trim() ?? '');
if (uri == null || uri.host.isEmpty || uri.scheme != 'http') {
return false;
}
return !const {'localhost', '127.0.0.1', '::1'}.contains(uri.host);
}
class ApiServerWarning extends StatelessWidget {
final TextEditingController controller;
final TextEditingController idController;
final String message;
final String emptyMessage;
final String Function({required String api, required String custom})
resolveApiServer;
const ApiServerWarning({
super.key,
required this.controller,
required this.idController,
required this.message,
required this.emptyMessage,
this.resolveApiServer = _resolveApiServer,
});
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: Listenable.merge([controller, idController]),
builder: (context, child) {
final apiServer = controller.text;
final idServer = idController.text;
final String effectiveApiServer;
try {
effectiveApiServer =
resolveApiServer(api: apiServer.trim(), custom: idServer.trim());
} catch (error, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: error,
stack: stack,
context: ErrorDescription('while resolving the API server warning'),
));
return const SizedBox.shrink();
}
if (!shouldWarnApiServer(effectiveApiServer)) {
return const SizedBox.shrink();
}
return Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
controller.text.trim().isEmpty ? emptyMessage : message,
style: TextStyle(color: Colors.orange.shade700),
),
);
},
);
}
}
+16 -2
View File
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/api_server_warning.dart';
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:flutter_hbb/common/widgets/toolbar.dart';
import 'package:get/get.dart';
@@ -69,6 +70,8 @@ void showServerSettingsWithValue(
ServerConfig serverConfig,
OverlayDialogManager dialogManager,
void Function(VoidCallback)? upSetState) async {
const labelWidth = 120.0;
const labelSpacing = 8.0;
var isInProgress = false;
final idCtrl = TextEditingController(text: serverConfig.idServer);
final relayCtrl = TextEditingController(text: serverConfig.relayServer);
@@ -112,10 +115,10 @@ void showServerSettingsWithValue(
return Row(
children: [
SizedBox(
width: 120,
width: labelWidth,
child: Text(label),
),
SizedBox(width: 8),
SizedBox(width: labelSpacing),
Expanded(
child: serverSettingsTextFormField(
label: label,
@@ -175,6 +178,17 @@ void showServerSettingsWithValue(
return null;
},
),
Padding(
padding: EdgeInsetsDirectional.only(
start: isDesktop || isWeb ? labelWidth + labelSpacing : 0,
),
child: ApiServerWarning(
controller: apiCtrl,
idController: idCtrl,
message: translate('api_server_connection_tip'),
emptyMessage: translate('api_server_empty_tip'),
),
),
SizedBox(height: 8),
buildField('Key', keyCtrl, ''),
if (isInProgress)
+5
View File
@@ -1928,6 +1928,11 @@ class RustdeskImpl {
}
String mainGetCommonSync({required String key, dynamic hint}) {
const prefix = 'resolve-api-server:';
if (key.startsWith(prefix)) {
return js.context
.callMethod('getByName', ['api_server', key.substring(prefix.length)]);
}
throw UnimplementedError("mainGetCommonSync");
}
+80
View File
@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_hbb/common/widgets/api_server_warning.dart';
void main() {
test('warns only for remote HTTP endpoints', () {
final cases = <String?, bool>{
null: false,
'': false,
'https://api.example.com': false,
'http://api.example.com': true,
'http://192.168.1.13:13000': true,
'http://localhost:21114': false,
'http://127.0.0.1:21114': false,
'http://[::1]:21114': false,
};
for (final entry in cases.entries) {
expect(shouldWarnApiServer(entry.key), entry.value, reason: entry.key);
}
});
testWidgets('warning follows API and ID changes using the resolved endpoint',
(tester) async {
final api = TextEditingController();
final id = TextEditingController(text: 'id.example.com');
addTearDown(api.dispose);
addTearDown(id.dispose);
await tester.pumpWidget(MaterialApp(
home: ApiServerWarning(
controller: api,
idController: id,
message: 'HTTP warning',
emptyMessage: 'Default HTTP warning',
resolveApiServer: ({required api, required custom}) =>
api.isNotEmpty ? api : 'http://$custom',
),
));
expect(find.text('Default HTTP warning'), findsOneWidget);
id.text = 'localhost';
await tester.pump();
expect(find.text('Default HTTP warning'), findsNothing);
api.text = 'http://api.example.com';
await tester.pump();
expect(find.text('HTTP warning'), findsOneWidget);
api.text = 'https://api.example.com';
await tester.pump();
expect(find.text('HTTP warning'), findsNothing);
});
testWidgets('resolver failures are reported without breaking the server form',
(tester) async {
final api = TextEditingController();
final id = TextEditingController();
addTearDown(api.dispose);
addTearDown(id.dispose);
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Column(children: [
TextField(controller: api),
Expanded(
child: ApiServerWarning(
controller: api,
idController: id,
message: 'HTTP warning',
emptyMessage: 'Default HTTP warning',
resolveApiServer: ({required api, required custom}) =>
throw const FormatException('API preview failed'),
)),
]),
),
));
expect(tester.takeException(), isA<FormatException>());
expect(find.byType(ErrorWidget), findsNothing);
expect(find.text('Default HTTP warning'), findsNothing);
expect(find.byType(TextField), findsOneWidget);
});
}
+10
View File
@@ -2763,6 +2763,16 @@ pub fn main_get_common(key: String) -> String {
}
pub fn main_get_common_sync(key: String) -> SyncReturn<String> {
if let Some(args) = key.strip_prefix("resolve-api-server:") {
let response = match serde_json::from_str::<(String, String)>(args) {
Ok((api, custom)) => serde_json::json!({"url": crate::get_api_server(api, custom)}),
Err(err) => {
log::trace!("Invalid API server preview arguments: {err}");
serde_json::json!({"error": err.to_string()})
}
};
return SyncReturn(response.to_string());
}
// Metadata verification performs blocking network requests. Keep this key on
// the asynchronous FFI path to avoid blocking the calling Flutter isolate.
if key.starts_with("verified-download-url-") {
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", "“{}” 不能用作快捷键。"),
("On", "开"),
("Off", "关"),
("api_server_connection_tip", "自定义服务器正在使用未加密的 HTTP。请联系管理员启用 HTTPS。"),
("api_server_empty_tip", "API 服务器留空时,自定义服务器默认使用 HTTP。请联系管理员启用 HTTPS。"),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -288,5 +288,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-already-bound-to", "Already bound to"),
("shortcut-mobile-physical-keyboard-tip", "Recording requires a physical keyboard. Soft keyboards are not supported."),
("shortcut-key-not-supported", "\"{}\" can't be used as a shortcut."),
("api_server_connection_tip", "Your custom server uses unencrypted HTTP. Contact your administrator to enable HTTPS."),
("api_server_empty_tip", "With API Server empty, your custom server defaults to HTTP. Contact your administrator to enable HTTPS."),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", "'{}' non può essere usato come scorciatoia."),
("On", "ON"),
("Off", "OFF"),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", "Sneltoets wordt niet ondersteund"),
("On", "Aan"),
("Off", "Uit"),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", "Não é possível usar \"{}\" como atalho."),
("On", "Ativado"),
("Off", "Desativado"),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", "\"{}\" невозможно использовать как сочетание клавиш."),
("On", "вкл"),
("Off", "откл"),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", "自訂伺服器正在使用未加密的 HTTP。請聯絡管理員啟用 HTTPS。"),
("api_server_empty_tip", "API 伺服器留空時,自訂伺服器預設使用 HTTP。請聯絡管理員啟用 HTTPS。"),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,6 +813,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -813,5 +813,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("shortcut-key-not-supported", ""),
("On", ""),
("Off", ""),
("api_server_connection_tip", ""),
("api_server_empty_tip", ""),
].iter().cloned().collect();
}