forked from mirrors/rustdesk
Add Android device deployment flow (#15146)
* Add Android device deployment flow Notify the Android Flutter UI when the server requires deployment, add a deploy dialog with API token/custom ID inputs, and reuse shared deploy logic for CLI and FFI Signed-off-by: 21pages <sunboeasy@gmail.com> * Hide Android deploy API token input Signed-off-by: 21pages <sunboeasy@gmail.com> * add more translations Signed-off-by: 21pages <sunboeasy@gmail.com> * optimize transations Signed-off-by: 21pages <sunboeasy@gmail.com> * Hide deploy action for outgoing-only clients Signed-off-by: 21pages <sunboeasy@gmail.com> * Fix deployment register throttle state reset Signed-off-by: 21pages <sunboeasy@gmail.com> * Move Android deploy dialog out of settings page Signed-off-by: 21pages <sunboeasy@gmail.com> * Use async mutex for deploy register throttle Signed-off-by: 21pages <sunboeasy@gmail.com> --------- Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
parent
32c6e32e04
commit
d99ddf6816
59 changed files with 604 additions and 66 deletions
|
|
@ -27,6 +27,7 @@ import 'common.dart';
|
|||
import 'consts.dart';
|
||||
import 'mobile/pages/home_page.dart';
|
||||
import 'mobile/pages/server_page.dart';
|
||||
import 'mobile/widgets/deploy_dialog.dart';
|
||||
import 'models/platform_model.dart';
|
||||
|
||||
import 'package:flutter_hbb/plugin/handlers.dart'
|
||||
|
|
@ -575,6 +576,14 @@ _registerEventHandler() {
|
|||
NativeUiHandler.instance.onEvent(evt);
|
||||
});
|
||||
}
|
||||
if (isAndroid) {
|
||||
platformFFI.registerEventHandler(
|
||||
'android_needs_deploy', 'android_needs_deploy', (_) async {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showDeployPromptDialog();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget keyListenerBuilder(BuildContext context, Widget? child) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import '../../common/widgets/login.dart';
|
|||
import '../../consts.dart';
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../widgets/deploy_dialog.dart';
|
||||
import '../widgets/dialog.dart';
|
||||
import 'home_page.dart';
|
||||
import 'scan_page.dart';
|
||||
|
|
@ -728,6 +729,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||
onPressed: (context) {
|
||||
changeSocks5Proxy();
|
||||
}),
|
||||
if (isAndroid && !bind.isOutgoingOnly())
|
||||
SettingsTile(
|
||||
title: Text(translate('Deploy')),
|
||||
leading: Icon(Icons.cloud_upload),
|
||||
onPressed: (context) {
|
||||
showDeployDialog();
|
||||
}),
|
||||
if (!disabledSettings && !_hideNetwork && !_hideWebSocket)
|
||||
SettingsTile.switchTile(
|
||||
title: Text(translate('Use WebSocket')),
|
||||
|
|
|
|||
114
flutter/lib/mobile/widgets/deploy_dialog.dart
Normal file
114
flutter/lib/mobile/widgets/deploy_dialog.dart
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
|
||||
const _deployDialogTag = 'android-deploy-device';
|
||||
|
||||
void showDeployPromptDialog() {
|
||||
gFFI.dialogManager.dismissByTag(_deployDialogTag);
|
||||
gFFI.dialogManager.show<bool>((setState, close, context) {
|
||||
submit() => close(true);
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Deploy")),
|
||||
content: Text(translate("server_requires_deployment_tip")),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: submit),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
);
|
||||
}, tag: _deployDialogTag).then((deploy) {
|
||||
if (deploy == true) {
|
||||
showDeployDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void showDeployDialog() {
|
||||
gFFI.dialogManager.dismissByTag(_deployDialogTag);
|
||||
final tokenController = TextEditingController();
|
||||
final idController = TextEditingController();
|
||||
var errorText = "";
|
||||
var isInProgress = false;
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
submit() async {
|
||||
if (isInProgress) return;
|
||||
final token = tokenController.text.trim();
|
||||
if (token.isEmpty) {
|
||||
setState(() {
|
||||
errorText = translate("token is required!");
|
||||
});
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
errorText = "";
|
||||
isInProgress = true;
|
||||
});
|
||||
String res;
|
||||
try {
|
||||
res = await bind.mainDeployDevice(
|
||||
token: token, id: idController.text.trim());
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
errorText = translate(e.toString());
|
||||
isInProgress = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (res.isEmpty) {
|
||||
close();
|
||||
await gFFI.serverModel.fetchID();
|
||||
showToast(translate("Successful"));
|
||||
} else {
|
||||
setState(() {
|
||||
errorText = translate(res.toString());
|
||||
isInProgress = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Deploy")),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: tokenController,
|
||||
decoration: InputDecoration(labelText: translate("API Token")),
|
||||
obscureText: true,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
autofocus: true,
|
||||
).workaroundFreezeLinuxMint(),
|
||||
TextField(
|
||||
controller: idController,
|
||||
decoration:
|
||||
InputDecoration(labelText: translate("Custom ID (optional)")),
|
||||
).workaroundFreezeLinuxMint(),
|
||||
if (errorText.isNotEmpty)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SelectableText(
|
||||
errorText,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
).paddingOnly(top: 8),
|
||||
),
|
||||
if (isInProgress) const LinearProgressIndicator().paddingOnly(top: 8),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel",
|
||||
onPressed: isInProgress ? null : close, isOutline: true),
|
||||
dialogButton("OK", onPressed: isInProgress ? null : submit),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: isInProgress ? null : close,
|
||||
);
|
||||
}, tag: _deployDialogTag);
|
||||
}
|
||||
|
|
@ -2034,7 +2034,14 @@ class RustdeskImpl {
|
|||
}
|
||||
|
||||
String mainResolveAvatarUrl({required String avatar, dynamic hint}) {
|
||||
return js.context.callMethod('getByName', ['resolve_avatar_url', avatar])?.toString() ?? avatar;
|
||||
return js.context.callMethod(
|
||||
'getByName', ['resolve_avatar_url', avatar])?.toString() ??
|
||||
avatar;
|
||||
}
|
||||
|
||||
Future<String> mainDeployDevice(
|
||||
{required String token, required String id, dynamic hint}) {
|
||||
throw UnimplementedError("mainDeployDevice");
|
||||
}
|
||||
|
||||
void dispose() {}
|
||||
|
|
|
|||
|
|
@ -644,6 +644,8 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||
} else if args[0] == "--deploy" {
|
||||
if config::Config::no_register_device() {
|
||||
println!("Cannot deploy an unregistrable device!");
|
||||
} else if config::is_outgoing_only() {
|
||||
println!("Cannot deploy Outgoing-only clients.");
|
||||
} else if crate::platform::is_installed() && is_root() {
|
||||
let max = args.len() - 1;
|
||||
let pos = args.iter().position(|x| x == "--token").unwrap_or(max);
|
||||
|
|
@ -661,72 +663,28 @@ pub fn core_main() -> Option<Vec<String>> {
|
|||
}
|
||||
};
|
||||
let new_id = get_value("--id");
|
||||
let local_id = crate::ipc::get_id();
|
||||
let id_to_deploy = new_id.clone().unwrap_or_else(|| local_id.clone());
|
||||
let uuid = crate::encode64(hbb_common::get_uuid());
|
||||
let pk = crate::encode64(
|
||||
hbb_common::config::Config::get_key_pair().1,
|
||||
);
|
||||
let body = serde_json::json!({
|
||||
"id": id_to_deploy,
|
||||
"uuid": uuid,
|
||||
"pk": pk,
|
||||
});
|
||||
let header = "Authorization: Bearer ".to_owned() + &token;
|
||||
let url = crate::ui_interface::get_api_server() + "/api/devices/deploy";
|
||||
match crate::post_request_sync(url, body.to_string(), &header) {
|
||||
Err(err) => {
|
||||
println!("Request failed: {}", err);
|
||||
std::process::exit(1);
|
||||
match crate::ui_interface::deploy_device(token, new_id) {
|
||||
crate::ui_interface::DeployResult::Ok => {
|
||||
println!("Device deployed.");
|
||||
}
|
||||
Ok(text) => {
|
||||
let parsed: serde_json::Value =
|
||||
serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
|
||||
let result = parsed["result"].as_str().unwrap_or("");
|
||||
match result {
|
||||
"OK" => {
|
||||
if let Some(ref new_id) = new_id {
|
||||
if *new_id != local_id {
|
||||
if let Err(err) =
|
||||
crate::ipc::set_config("id", new_id.clone())
|
||||
{
|
||||
println!(
|
||||
"Failed to persist deployed id locally: {}",
|
||||
err
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Err(err) = crate::ipc::notify_deployed() {
|
||||
log::warn!("Failed to notify deployed state: {}", err);
|
||||
}
|
||||
println!("Device deployed.");
|
||||
}
|
||||
"NOT_ENABLED" => {
|
||||
println!("Server does not require deployment.");
|
||||
std::process::exit(3);
|
||||
}
|
||||
"INVALID_INPUT" => {
|
||||
println!("Invalid input.");
|
||||
std::process::exit(5);
|
||||
}
|
||||
"ID_TAKEN" => {
|
||||
println!(
|
||||
"Id `{}` is already used by another machine on the server.",
|
||||
id_to_deploy
|
||||
);
|
||||
std::process::exit(6);
|
||||
}
|
||||
_ => {
|
||||
if text.is_empty() {
|
||||
println!("Unknown response.");
|
||||
} else {
|
||||
println!("{}", text);
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
crate::ui_interface::DeployResult::NotEnabled => {
|
||||
println!("Server does not require deployment.");
|
||||
std::process::exit(3);
|
||||
}
|
||||
crate::ui_interface::DeployResult::InvalidInput => {
|
||||
println!("Invalid input.");
|
||||
std::process::exit(5);
|
||||
}
|
||||
crate::ui_interface::DeployResult::IdTaken(id) => {
|
||||
println!(
|
||||
"Id `{}` is already used by another machine on the server.",
|
||||
id
|
||||
);
|
||||
std::process::exit(6);
|
||||
}
|
||||
crate::ui_interface::DeployResult::Error(err) => {
|
||||
println!("{}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1153,6 +1153,22 @@ pub fn main_get_api_server() -> String {
|
|||
get_api_server()
|
||||
}
|
||||
|
||||
pub fn main_deploy_device(token: String, id: String) -> String {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
let new_id = match id.trim() {
|
||||
"" => None,
|
||||
id => Some(id.to_owned()),
|
||||
};
|
||||
ui_interface::deploy_device(token, new_id).message()
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
let _ = (token, id);
|
||||
"Deployment is not supported on this platform.".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_resolve_avatar_url(avatar: String) -> SyncReturn<String> {
|
||||
SyncReturn(resolve_avatar_url(avatar))
|
||||
}
|
||||
|
|
@ -2116,6 +2132,7 @@ pub fn main_start_service() {
|
|||
#[cfg(target_os = "android")]
|
||||
{
|
||||
config::Config::set_option("stop-service".into(), "".into());
|
||||
crate::rendezvous_mediator::reset_needs_deploy_notification();
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
}
|
||||
|
|
@ -3055,6 +3072,7 @@ pub mod server_side {
|
|||
pub unsafe extern "system" fn Java_ffi_FFI_startService(_env: JNIEnv, _class: JClass) {
|
||||
log::debug!("startService from jvm");
|
||||
config::Config::set_option("stop-service".into(), "".into());
|
||||
crate::rendezvous_mediator::reset_needs_deploy_notification();
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "كلمة المرور المحددة مسبقًا قيد الاستخدام"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Пададзены пароль цяпер выкарыстоўваецца"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "当前使用预设密码"),
|
||||
("Enable privacy mode", "允许隐私模式"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", "API 令牌"),
|
||||
("Deploy", "部署"),
|
||||
("Custom ID (optional)", "自定义 ID(可选)"),
|
||||
("server_requires_deployment_tip", "服务器要求显式部署此设备。是否立即部署?"),
|
||||
("The server does not require explicit deployment.", "服务器不需要显式部署。"),
|
||||
("Unknown response.", "未知响应。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Das voreingestellte Passwort wird derzeit verwendet."),
|
||||
("Enable privacy mode", "Datenschutzmodus aktivieren"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Andocken der Remote-Symbolleiste an jeden Fensterrand zulassen"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,5 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("password-hidden-tip", "Permanent password is set (hidden)."),
|
||||
("preset-password-in-use-tip", "Preset password is currently in use."),
|
||||
("allow-remote-toolbar-docking-any-edge", "Allow docking remote toolbar to any window edge"),
|
||||
("server_requires_deployment_tip", "The server requires this device to be deployed explicitly. Deploy now?"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Se está usando la contraseña predeterminada."),
|
||||
("Enable privacy mode", "Habilitar modo privado"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Le mot de passe prédéfini est actuellement utilisé."),
|
||||
("Enable privacy mode", "Activer le mode de confidentialité"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Autoriser l’ancrage de la barre d’outils à distance sur n’importe quel bord de la fenêtre"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "પ્રીસેટ પાસવર્ડ વપરાશમાં છે."),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "पूर्व-निर्धारित पासवर्ड उपयोग में है।"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Jelenleg az alapértelmezett jelszót használja."),
|
||||
("Enable privacy mode", "Adatvédelmi mód aktiválása"),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "È attualmente in uso la password preimpostata."),
|
||||
("Enable privacy mode", "Abilita modalità privacy"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Consenti ancoraggio barra strumenti remota a qualsiasi bordo della finestra"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "プリセットパスワードが現在使用されています"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "현재 사전 설정된 비밀번호가 사용 중입니다."),
|
||||
("Enable privacy mode", "개인정보 보호 모드 사용함"),
|
||||
("allow-remote-toolbar-docking-any-edge", "원격 도구 모음을 창 가장자리에 도킹 허용"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Iepriekš iestatītā parole pašlaik tiek izmantota."),
|
||||
("Enable privacy mode", "Iespējot privātuma režīmu"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Atļaut attālās rīkjoslas piestiprināšanu pie jebkuras loga malas"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "പ്രീസെറ്റ് പാസ്വേഡ് ഉപയോഗത്തിലാണ്."),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Het basis wachtwoord is momenteel in gebruik."),
|
||||
("Enable privacy mode", "Privacymodus inschakelen"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Sta toe om de werkbalk-op-afstand aan de rand van het venster te plaatsen"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Obecnie używane jest hasło domyślne."),
|
||||
("Enable privacy mode", "Włącz tryb prywatny"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Zezwalaj na dokowanie zdalnego paska narzędzi do dowolnej krawędzi"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "A senha predefinida está sendo usada."),
|
||||
("Enable privacy mode", "Habilitar modo de privacidade"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Permitir fixar a barra de ferramentas remota em qualquer borda da janela"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Se folosește o parolă prestabilită. Se recomandă setarea unei parole personalizate pentru securitate sporită."),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Установленный пароль сейчас используется."),
|
||||
("Enable privacy mode", "Использовать режим конфиденциальности"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Разрешать прикрепление удалённой панели инструментов к любому краю окна"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "Önceden ayarlanmış parola kullanılıyor"),
|
||||
("Enable privacy mode", "Gizlilik modunu etkinleştir"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Uzak araç çubuğunun pencerenin herhangi bir kenarına sabitlenmesine izin ver"),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", "目前正在使用預設密碼"),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,5 +745,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||
("preset-password-in-use-tip", ""),
|
||||
("Enable privacy mode", ""),
|
||||
("allow-remote-toolbar-docking-any-edge", ""),
|
||||
("API Token", ""),
|
||||
("Deploy", ""),
|
||||
("Custom ID (optional)", ""),
|
||||
("server_requires_deployment_tip", ""),
|
||||
("The server does not require explicit deployment.", ""),
|
||||
("Unknown response.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ static SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
|
|||
static MANUAL_RESTARTED: AtomicBool = AtomicBool::new(false);
|
||||
static SENT_REGISTER_PK: AtomicBool = AtomicBool::new(false);
|
||||
pub(crate) static NEEDS_DEPLOY: AtomicBool = AtomicBool::new(false);
|
||||
#[cfg(target_os = "android")]
|
||||
static NOTIFIED_NEEDS_DEPLOY: AtomicBool = AtomicBool::new(false);
|
||||
// register_pk retry interval (ms) when device is awaiting deployment
|
||||
const DEPLOY_RETRY_INTERVAL: i64 = 30_000;
|
||||
lazy_static::lazy_static! {
|
||||
|
|
@ -66,6 +68,26 @@ async fn deploy_register_throttled() -> bool {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn notify_android_needs_deploy() {
|
||||
if NOTIFIED_NEEDS_DEPLOY.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
let event = serde_json::json!({ "name": "android_needs_deploy" }).to_string();
|
||||
if matches!(
|
||||
crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, event),
|
||||
Some(true)
|
||||
) {
|
||||
NOTIFIED_NEEDS_DEPLOY.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub(crate) fn reset_needs_deploy_notification() {
|
||||
NEEDS_DEPLOY.store(false, Ordering::SeqCst);
|
||||
NOTIFIED_NEEDS_DEPLOY.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RendezvousMediator {
|
||||
addr: TargetAddr<'static>,
|
||||
|
|
@ -117,6 +139,7 @@ impl RendezvousMediator {
|
|||
crate::platform::linux_desktop_manager::start_xdesktop();
|
||||
}
|
||||
scrap::codec::test_av1();
|
||||
*LAST_NOT_DEPLOYED_REGISTER.lock().await = None;
|
||||
loop {
|
||||
let timeout = Arc::new(RwLock::new(CONNECT_TIMEOUT));
|
||||
let conn_start_time = Instant::now();
|
||||
|
|
@ -322,6 +345,8 @@ impl RendezvousMediator {
|
|||
Config::set_host_key_confirmed(&self.host_prefix, true);
|
||||
*SOLVING_PK_MISMATCH.lock().await = "".to_owned();
|
||||
NEEDS_DEPLOY.store(false, Ordering::SeqCst);
|
||||
#[cfg(target_os = "android")]
|
||||
reset_needs_deploy_notification();
|
||||
}
|
||||
Ok(register_pk_response::Result::UUID_MISMATCH) => {
|
||||
self.handle_uuid_mismatch(sink).await?;
|
||||
|
|
@ -336,6 +361,8 @@ impl RendezvousMediator {
|
|||
// was deleted by an admin while running.
|
||||
Config::set_key_confirmed(false);
|
||||
Config::set_host_key_confirmed(&self.host_prefix, false);
|
||||
#[cfg(target_os = "android")]
|
||||
notify_android_needs_deploy();
|
||||
}
|
||||
_ => {
|
||||
log::error!("unknown RegisterPkResponse");
|
||||
|
|
|
|||
|
|
@ -1020,6 +1020,102 @@ pub fn get_api_server() -> String {
|
|||
)
|
||||
}
|
||||
|
||||
pub enum DeployResult {
|
||||
Ok,
|
||||
NotEnabled,
|
||||
InvalidInput,
|
||||
IdTaken(String),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl DeployResult {
|
||||
pub fn message(&self) -> String {
|
||||
match self {
|
||||
Self::Ok => "".to_owned(),
|
||||
Self::NotEnabled => "The server does not require explicit deployment.".to_owned(),
|
||||
Self::InvalidInput => "Invalid input.".to_owned(),
|
||||
Self::IdTaken(id) => {
|
||||
format!(
|
||||
"Id `{}` is already used by another machine on the server.",
|
||||
id
|
||||
)
|
||||
}
|
||||
Self::Error(err) => err.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deploy_device(token: String, new_id: Option<String>) -> DeployResult {
|
||||
if Config::no_register_device() {
|
||||
return DeployResult::Error("Cannot deploy an unregistrable device!".to_owned());
|
||||
}
|
||||
let token = token.trim();
|
||||
if token.is_empty() {
|
||||
return DeployResult::Error("token is required!".to_owned());
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let local_id = Config::get_id();
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let local_id = ipc::get_id();
|
||||
let id_to_deploy = new_id.clone().unwrap_or_else(|| local_id.clone());
|
||||
let uuid = crate::encode64(hbb_common::get_uuid());
|
||||
let pk = crate::encode64(Config::get_key_pair().1);
|
||||
let body = serde_json::json!({
|
||||
"id": id_to_deploy,
|
||||
"uuid": uuid,
|
||||
"pk": pk,
|
||||
});
|
||||
let header = "Authorization: Bearer ".to_owned() + token;
|
||||
let url = get_api_server() + "/api/devices/deploy";
|
||||
let text = match crate::post_request_sync(url, body.to_string(), &header) {
|
||||
Ok(text) => text,
|
||||
Err(err) => return DeployResult::Error(format!("Request failed: {}", err)),
|
||||
};
|
||||
let parsed: serde_json::Value = serde_json::from_str(&text).unwrap_or(serde_json::Value::Null);
|
||||
match parsed["result"].as_str().unwrap_or("") {
|
||||
"OK" => {
|
||||
if let Some(new_id) = new_id {
|
||||
if new_id != local_id {
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
Config::set_key_confirmed(false);
|
||||
Config::set_id(&new_id);
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Err(err) = ipc::set_config("id", new_id) {
|
||||
return DeployResult::Error(format!(
|
||||
"Failed to persist deployed id locally: {}",
|
||||
err
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Err(err) = ipc::notify_deployed() {
|
||||
log::warn!("Failed to notify deployed state: {}", err);
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
crate::rendezvous_mediator::NEEDS_DEPLOY
|
||||
.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
crate::rendezvous_mediator::reset_needs_deploy_notification();
|
||||
crate::rendezvous_mediator::RendezvousMediator::restart();
|
||||
}
|
||||
DeployResult::Ok
|
||||
}
|
||||
"NOT_ENABLED" => DeployResult::NotEnabled,
|
||||
"INVALID_INPUT" => DeployResult::InvalidInput,
|
||||
"ID_TAKEN" => DeployResult::IdTaken(id_to_deploy),
|
||||
_ => {
|
||||
if text.is_empty() {
|
||||
DeployResult::Error("Unknown response.".to_owned())
|
||||
} else {
|
||||
DeployResult::Error(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_hwcodec() -> bool {
|
||||
// Has real hardware codec using gpu
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue