mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-16 07:06:45 +00:00
feat(GODT-2772): implemented internal help links.
This commit is contained in:
@ -1099,6 +1099,28 @@ void QMLBackend::setUpdateTrayIcon(QString const &stateString, QString const &st
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] helpFileName The name of the help file with extension (e.g. "WhyBridge.html").
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
void QMLBackend::showHelpOverlay(QString const &helpFileName) {
|
||||||
|
QDir const basePath(":/qml/Resources/Help");
|
||||||
|
QString const templatePath = basePath.filePath("Template.html");
|
||||||
|
QFile templateFile(templatePath);
|
||||||
|
if (!templateFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
app().log().error("Could not load help overlay HTML template");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile helpFile(basePath.filePath(helpFileName));
|
||||||
|
if (!helpFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
app().log().error(QString("Could not load help overlay HTML file %1").arg(helpFileName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit showWebFrameOverlayHTML(QString::fromUtf8(templateFile.readAll()).arg(QString::fromUtf8(helpFile.readAll())));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] isOn Does bridge consider internet as on.
|
/// \param[in] isOn Does bridge consider internet as on.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
|
|||||||
@ -211,6 +211,7 @@ public slots: // slots for functions that need to be processed locally.
|
|||||||
void setErrorTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'error' state.
|
void setErrorTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'error' state.
|
||||||
void setWarnTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'warn' state.
|
void setWarnTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'warn' state.
|
||||||
void setUpdateTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'update' state.
|
void setUpdateTrayIcon(QString const& stateString, QString const &statusIcon); ///< Set the tray icon to 'update' state.
|
||||||
|
void showHelpOverlay(QString const &helpFileName); ///< Slot triggering the display of help content as an overlay.
|
||||||
|
|
||||||
public slots: // slot for signals received from gRPC that need transformation instead of simple forwarding
|
public slots: // slot for signals received from gRPC that need transformation instead of simple forwarding
|
||||||
void internetStatusChanged(bool isOn); ///< Check if bridge considers internet as on.
|
void internetStatusChanged(bool isOn); ///< Check if bridge considers internet as on.
|
||||||
@ -279,6 +280,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML
|
|||||||
void showSettings(); ///< Signal for the 'showHelp' event (from the context menu).
|
void showSettings(); ///< Signal for the 'showHelp' event (from the context menu).
|
||||||
void showWebFrameWindow(QString const &url); ///< Signal the the 'showWebFrameWindow' event
|
void showWebFrameWindow(QString const &url); ///< Signal the the 'showWebFrameWindow' event
|
||||||
void showWebFrameOverlay(QString const &url); ////< Signal for the 'showWebFrameOverlay' event.
|
void showWebFrameOverlay(QString const &url); ////< Signal for the 'showWebFrameOverlay' event.
|
||||||
|
void showWebFrameOverlayHTML(QString const &html); ///< Signal to display HTML content in a web frame overlay.
|
||||||
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list.
|
void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list.
|
||||||
void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event.
|
void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event.
|
||||||
void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account.
|
void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account.
|
||||||
|
|||||||
@ -110,6 +110,10 @@
|
|||||||
<file>qml/Proton/WebFrame.qml</file>
|
<file>qml/Proton/WebFrame.qml</file>
|
||||||
<file>qml/QuestionItem.qml</file>
|
<file>qml/QuestionItem.qml</file>
|
||||||
<file>qml/Resources/bug_report_flow.json</file>
|
<file>qml/Resources/bug_report_flow.json</file>
|
||||||
|
<file>qml/Resources/Help/Template.html</file>
|
||||||
|
<file>qml/Resources/Help/WhyBridge.html</file>
|
||||||
|
<file>qml/Resources/Help/WhyCertificate.html</file>
|
||||||
|
<file>qml/Resources/Help/WhyProfileWarning.html</file>
|
||||||
<file>qml/SettingsItem.qml</file>
|
<file>qml/SettingsItem.qml</file>
|
||||||
<file>qml/SettingsView.qml</file>
|
<file>qml/SettingsView.qml</file>
|
||||||
<file>qml/SetupWizard/ClientListItem.qml</file>
|
<file>qml/SetupWizard/ClientListItem.qml</file>
|
||||||
|
|||||||
@ -53,6 +53,9 @@ QtObject {
|
|||||||
function onShowWebFrameOverlay(url) {
|
function onShowWebFrameOverlay(url) {
|
||||||
mainWindow.showWebFrameOverlay(url);
|
mainWindow.showWebFrameOverlay(url);
|
||||||
}
|
}
|
||||||
|
function onShowWebFrameOverlayHTML(html) {
|
||||||
|
mainWindow.showWebFrameOverlayHTML(html)
|
||||||
|
}
|
||||||
function onShowWebFrameWindow(url) {
|
function onShowWebFrameWindow(url) {
|
||||||
webFrameWindow.url = url;
|
webFrameWindow.url = url;
|
||||||
webFrameWindow.show();
|
webFrameWindow.show();
|
||||||
|
|||||||
@ -72,6 +72,11 @@ ApplicationWindow {
|
|||||||
webFrameOverlay.url = url;
|
webFrameOverlay.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showWebFrameOverlayHTML(html) {
|
||||||
|
webFrameOverlay.loadHTML(html);
|
||||||
|
webFrameOverlay.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
colorScheme: ProtonStyle.currentStyle
|
colorScheme: ProtonStyle.currentStyle
|
||||||
height: ProtonStyle.window_default_height
|
height: ProtonStyle.window_default_height
|
||||||
minimumHeight:ProtonStyle.window_minimum_height
|
minimumHeight:ProtonStyle.window_minimum_height
|
||||||
@ -176,9 +181,6 @@ ApplicationWindow {
|
|||||||
onWizardEnded: {
|
onWizardEnded: {
|
||||||
contentLayout.currentIndex = 0;
|
contentLayout.currentIndex = 0;
|
||||||
}
|
}
|
||||||
onShowUnderConstruction: {
|
|
||||||
webFrameOverlay.showUnderConstruction();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WebFrame {
|
WebFrame {
|
||||||
|
|||||||
@ -363,10 +363,11 @@ QtObject {
|
|||||||
property int web_view_button_width: 320
|
property int web_view_button_width: 320
|
||||||
property int web_view_corner_radius: 10
|
property int web_view_corner_radius: 10
|
||||||
property int web_view_overlay_button_vertical_margin: 10
|
property int web_view_overlay_button_vertical_margin: 10
|
||||||
property int web_view_overlay_horizontal_margin: 10
|
property int web_view_overlay_horizontal_padding: 10
|
||||||
property int web_view_overlay_margin: 50
|
property int web_view_overlay_horizontal_margin: 250
|
||||||
|
property int web_view_overlay_vertical_margin: 50
|
||||||
property real web_view_overlay_opacity: 0.6
|
property real web_view_overlay_opacity: 0.6
|
||||||
property int web_view_overlay_vertical_margin: web_view_corner_radius
|
property int web_view_overlay_vertical_padding: web_view_corner_radius
|
||||||
property int web_view_overley_border_width: 1
|
property int web_view_overley_border_width: 1
|
||||||
|
|
||||||
property int window_default_height: 780
|
property int window_default_height: 780
|
||||||
|
|||||||
@ -27,15 +27,8 @@ Item {
|
|||||||
function showBlankPage() {
|
function showBlankPage() {
|
||||||
webView.loadHtml("<!doctype html><meta charset=utf-8><title>blank</title>", "");
|
webView.loadHtml("<!doctype html><meta charset=utf-8><title>blank</title>", "");
|
||||||
}
|
}
|
||||||
|
function loadHTML(html) {
|
||||||
function showUnderConstruction() {
|
webView.loadHtml(html)
|
||||||
webView.loadHtml(`
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head><meta charset="UTF-8"><title>Coming soon</title><style> body { background-color: #888; font-family: sans-serif; } p { font-weight: bold; margin-top: 100px; text-align: center; }</style></head>
|
|
||||||
<body><p>The content of this page is under construction.</p></body></html>
|
|
||||||
`, "")
|
|
||||||
root.visible = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@ -46,16 +39,19 @@ Item {
|
|||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: overlay ? ProtonStyle.web_view_overlay_margin : 0
|
anchors.bottomMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0
|
||||||
|
anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0
|
||||||
|
anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0
|
||||||
|
anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0
|
||||||
color: root.colorScheme.background_norm
|
color: root.colorScheme.background_norm
|
||||||
radius: ProtonStyle.web_view_corner_radius
|
radius: ProtonStyle.web_view_corner_radius
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.bottomMargin: 0
|
anchors.bottomMargin: 0
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0
|
anchors.leftMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_padding : 0
|
||||||
anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_margin : 0
|
anchors.rightMargin: overlay ? ProtonStyle.web_view_overlay_horizontal_padding : 0
|
||||||
anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_margin : 0
|
anchors.topMargin: overlay ? ProtonStyle.web_view_overlay_vertical_padding : 0
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title></title>
|
||||||
|
<style>
|
||||||
|
body {font-family: sans-serif}
|
||||||
|
h1 { font-size: 1.5em; text-align: center; margin-bottom: 2em;}
|
||||||
|
p {text-align: justify; margin-bottom: 2em; }
|
||||||
|
p.standfirst { font-weight: bold;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
%1
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<h1>Why do I need bridge?</h1>
|
||||||
|
<p class="standfirst">
|
||||||
|
Proton does not have access to the content of your messages, so it cannot share your unencrypted messages with your email client from the
|
||||||
|
Proton servers.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Email clients such as Microsoft Outlook, Mozilla Thunderbird and Apple Mail use standard protocols named IMAP and SMTP to receive and send emails.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Even though the IMAP and SMTP protocols can use secure channels (using SSL/TLS), they do not offer support for encrypted messages.
|
||||||
|
Because Proton does not have access to the content of your messages, it is not possible to configure your email client to connect directly to
|
||||||
|
Proton servers.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The key to solving this problem is Bridge. Once installed on your computer and connected to your Proton account, Bridge can access your
|
||||||
|
encrypted messages stored on the Proton servers. Bridge integrates an IMAP and a SMTP server that run on your computer and are accessible only
|
||||||
|
to applications executing on your machine. Your email client connects to these local servers and Bridge is responsible for seamlessly encrypting
|
||||||
|
and decrypting the messages that you send and receive.
|
||||||
|
</p>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<h1>Why do I need to install a certificate when configuring Apple Mail with Bridge?</h1>
|
||||||
|
<p class="standfirst">
|
||||||
|
Apple Mail requires a secure channel for communications with email servers, and the server needs to be acknowledged as trusted.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In order to communicate with Bridge, Apple Mail requires secure connections using SSL/TLS. This cryptographic protocol includes an identity
|
||||||
|
verification system using certificates. For publicly available servers, certificates are normally issued and digitally signed by a certificate
|
||||||
|
authority, such as Let's Encrypt. This is not possible for Bridge, as the IMAP and SMTP servers are running on your own computer, and are not
|
||||||
|
accessible from any network (local or internet).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The solution is to use a self-signed certificate. When setting up an email account where the server provides a self-signed certificate, most
|
||||||
|
email clients will issue a warning asking you whether you trust the server or not, because the certificate was not issued by a certificate
|
||||||
|
authority.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Apple Mail requires an extra step. It will simply refuse to connect if the certificate is not set as trusted. Bridge solves this by storing this
|
||||||
|
certificate in the macOS keychain. This operation requires that you provide your macOS account password.
|
||||||
|
</p>
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
<h1>Why is there a warning sign when installing the Bridge profile on macOS?</h1>
|
||||||
|
<p class="standfirst">
|
||||||
|
This warning indicates that the certificate used to secure the communication channel between Bridge and your email client is not signed by a
|
||||||
|
trusted third party.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In order to communicate with Bridge, Apple Mail requires secure connections using SSL/TLS. This cryptographic protocol includes an identity
|
||||||
|
verification system using certificates. For publicly available servers, certificates are normally issued and digitally signed by a certificate
|
||||||
|
authority, such as Let's Encrypt. This is not possible for Bridge, as the IMAP and SMTP servers are running on your own computer, and are not
|
||||||
|
accessible from any network (local or internet).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The solution is to use a self-signed certificate. When setting up an email account where the server provides a self-signed certificate, most
|
||||||
|
email clients will issue a warning asking you whether you trust the server or not, because the certificate was not issued by a certificate
|
||||||
|
authority. The client has no way of verifying that the server is who it pretends to be.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You can safely ignore this warning. The check concerns only the communication between your email client and Bridge, which occurs within your
|
||||||
|
computer. On the other end, the communication between Bridge and the Proton servers uses the HTTPS protocol, and the identity of the remote
|
||||||
|
server is verified by Bridge.
|
||||||
|
</p>
|
||||||
@ -28,7 +28,6 @@ Item {
|
|||||||
signal appleMailAutoconfigProfileInstallPageShow
|
signal appleMailAutoconfigProfileInstallPageShow
|
||||||
|
|
||||||
function showAutoconfig() {
|
function showAutoconfig() {
|
||||||
certificateInstall.waitingForCert = false;
|
|
||||||
if (Backend.isTLSCertificateInstalled()) {
|
if (Backend.isTLSCertificateInstalled()) {
|
||||||
showProfileInstall();
|
showProfileInstall();
|
||||||
} else {
|
} else {
|
||||||
@ -41,6 +40,7 @@ Item {
|
|||||||
appleMailAutoconfigCertificateInstallPageShown();
|
appleMailAutoconfigCertificateInstallPageShown();
|
||||||
}
|
}
|
||||||
function showProfileInstall() {
|
function showProfileInstall() {
|
||||||
|
profileInstall.reset();
|
||||||
stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall;
|
stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall;
|
||||||
appleMailAutoconfigProfileInstallPageShow();
|
appleMailAutoconfigProfileInstallPageShow();
|
||||||
}
|
}
|
||||||
@ -193,6 +193,13 @@ Item {
|
|||||||
// stack index 1
|
// stack index 1
|
||||||
Item {
|
Item {
|
||||||
id: profileInstall
|
id: profileInstall
|
||||||
|
|
||||||
|
property bool profilePaneLaunched: false
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
profilePaneLaunched = false;
|
||||||
|
}
|
||||||
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
@ -239,11 +246,15 @@ Item {
|
|||||||
Button {
|
Button {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
colorScheme: wizard.colorScheme
|
colorScheme: wizard.colorScheme
|
||||||
text: qsTr("Install the profile")
|
text: profileInstall.profilePaneLaunched ? qsTr("I have installed the profile") : qsTr("Install the profile")
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
wizard.user.configureAppleMail(wizard.address);
|
if (profileInstall.profilePaneLaunched) {
|
||||||
wizard.showClientConfigEnd();
|
wizard.showClientConfigEnd();
|
||||||
|
} else {
|
||||||
|
wizard.user.configureAppleMail(wizard.address);
|
||||||
|
profileInstall.profilePaneLaunched = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ Item {
|
|||||||
function showAppleMailAutoconfigCertificateInstall() {
|
function showAppleMailAutoconfigCertificateInstall() {
|
||||||
showAppleMailAutoconfigCommon();
|
showAppleMailAutoconfigCommon();
|
||||||
descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain.");
|
descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain.");
|
||||||
linkLabel1.setCallback(showUnderConstruction, qsTr("Why is this certificate needed?"), false);
|
linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyCertificate.html"); }, qsTr("Why is this certificate needed?"), false);
|
||||||
}
|
}
|
||||||
function showAppleMailAutoconfigCommon() {
|
function showAppleMailAutoconfigCommon() {
|
||||||
titleLabel.text = "";
|
titleLabel.text = "";
|
||||||
@ -39,7 +39,7 @@ Item {
|
|||||||
function showAppleMailAutoconfigProfileInstall() {
|
function showAppleMailAutoconfigProfileInstall() {
|
||||||
showAppleMailAutoconfigCommon();
|
showAppleMailAutoconfigCommon();
|
||||||
descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails.");
|
descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails.");
|
||||||
linkLabel1.setCallback(showUnderConstruction, qsTr("Why is there a yellow warning sign?"), false);
|
linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyProfileWarning.html"); }, qsTr("Why is there a yellow warning sign?"), false);
|
||||||
linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false);
|
linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false);
|
||||||
}
|
}
|
||||||
function showClientSelector(newAccount = true) {
|
function showClientSelector(newAccount = true) {
|
||||||
@ -63,15 +63,12 @@ Item {
|
|||||||
function showOnboarding() {
|
function showOnboarding() {
|
||||||
titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account");
|
titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account");
|
||||||
descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. ");
|
descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. ");
|
||||||
linkLabel1.setCallback(showUnderConstruction, qsTr("Why do I need Bridge?"), false);
|
linkLabel1.setCallback(function() { Backend.showHelpOverlay("WhyBridge.html"); }, qsTr("Why do I need Bridge?"), false);
|
||||||
linkLabel2.clear();
|
linkLabel2.clear();
|
||||||
root.iconSource = "/qml/icons/img-welcome.svg";
|
root.iconSource = "/qml/icons/img-welcome.svg";
|
||||||
root.iconHeight = 148;
|
root.iconHeight = 148;
|
||||||
root.iconWidth = 265;
|
root.iconWidth = 265;
|
||||||
}
|
}
|
||||||
function showUnderConstruction() {
|
|
||||||
wizard.showUnderConstruction();
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onLogin2FARequested() {
|
function onLogin2FARequested() {
|
||||||
|
|||||||
@ -43,7 +43,6 @@ Item {
|
|||||||
|
|
||||||
signal bugReportRequested
|
signal bugReportRequested
|
||||||
signal wizardEnded
|
signal wizardEnded
|
||||||
signal showUnderConstruction
|
|
||||||
|
|
||||||
function _showClientConfig() {
|
function _showClientConfig() {
|
||||||
showClientConfig(root.user, root.address, false);
|
showClientConfig(root.user, root.address, false);
|
||||||
|
|||||||
Reference in New Issue
Block a user