Compare commits

..

25 Commits

Author SHA1 Message Date
5c69af4418 chore: Xikou Bridge 3.8.1 changelog. 2023-12-11 11:49:01 +01:00
416f696863 feat(GODT-3121): added options to kb-tester CLI tool. 2023-12-08 11:04:48 +01:00
789c1cc816 feat(GODT-3121): kb suggestion first version of complete list. 2023-12-08 10:01:38 +01:00
58736dd254 chore: keep nighlty-job log as artifact. 2023-12-08 09:31:57 +01:00
a057138880 feat(GODT-3121): KB suggestion test tool now support multi-line input. 2023-12-07 10:48:33 +01:00
76087f1749 feat(GODT-3121): minimalist CLI tool to test KB suggestions. 2023-12-07 09:36:47 +01:00
83935f3a03 feat(GODT-3121): refactored retrieval kb article index lookup. 2023-12-07 09:35:05 +01:00
b93c10ad47 feat(GODT-3121): adds KB suggestion scoring. 2023-12-07 09:35:05 +01:00
3309137b80 feat(GODT-3121): forward user input to bridge. 2023-12-07 09:35:05 +01:00
88c4737ba4 feat(GODT-3121): reuse InfoTooltip. 2023-12-07 09:35:05 +01:00
e5db9b1ccc feat(GODT-3121): added display of bug report user input in bridge-gui-tester. 2023-12-07 09:35:05 +01:00
6e2e622a2f feat(GODT-3121): added tooltip for KB suggestions. 2023-12-07 09:35:05 +01:00
3a66063938 feat(GODT-3121): change log level of click on external link. 2023-12-07 09:35:05 +01:00
120ddbbcbb feat(GODT-3121): finalize UI for KB suggestions. 2023-12-07 09:35:05 +01:00
39b31abef8 feat(GODT-3121): fix issues reported by the resharper C++ engine. 2023-12-07 09:35:05 +01:00
ebeca394c7 feat(GODT-3121): implement suggestion list in bridge-gui. 2023-12-07 09:35:05 +01:00
2206cb3f12 feat(GODT-3121): suggestions links are in the final bug report page. 2023-12-07 09:35:05 +01:00
cfd07cf893 feat(GODT-3121): suggestions are transferred to QML. 2023-12-07 09:35:05 +01:00
2e2648fcd5 feat(GODT-3121): QML request suggestions. 2023-12-07 09:35:05 +01:00
3070912416 feat(GODT-3121): added gRPC call and event for KB suggestions. 2023-12-07 09:35:05 +01:00
51722eb1a4 feat(GODT-3121): introduced knowledgebase package. 2023-12-07 09:35:05 +01:00
5950eff083 chore(GODT-3160): silence vuln 2023-12-07 08:15:10 +01:00
5c67cc2e76 fix(GODT-3153): Do not take into account full address when hasing messages. 2023-12-06 16:14:38 +00:00
01db488caa feat(GODT-2001): add govulncheck to scan for vulnerabilities. 2023-12-06 15:29:21 +01:00
6cbef1d786 test: Improve TestMetadata_JobCorrectlyFinishesAfterCancel 2023-12-04 13:48:44 +00:00
78 changed files with 3717 additions and 2050 deletions

View File

@ -3,6 +3,20 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/) Changelog [format](http://keepachangelog.com/en/1.0.0/)
## Xikou Bridge 3.8.1
### Added
* GODT-3121: Suggest relevant KB articles in the in-app bug report form.
* GODT-2001: Add govulncheck to scan for vulnerabilities.
### Changed
* Keep nighlty-job log as artifact.
* Test: Improve TestMetadata_JobCorrectlyFinishesAfterCancel.
### Fixed
* GODT-3153: Do not take into account full address when hasing messages.
## Xikou Bridge 3.8.0 ## Xikou Bridge 3.8.0
### Added ### Added

View File

@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
.PHONY: build build-gui build-nogui build-launcher versioner hasher .PHONY: build build-gui build-nogui build-launcher versioner hasher
# Keep version hardcoded so app build works also without Git repository. # Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=3.8.0+git BRIDGE_APP_VERSION?=3.8.1+git
APP_VERSION:=${BRIDGE_APP_VERSION} APP_VERSION:=${BRIDGE_APP_VERSION}
APP_FULL_NAME:=Proton Mail Bridge APP_FULL_NAME:=Proton Mail Bridge
APP_VENDOR:=Proton AG APP_VENDOR:=Proton AG
@ -328,13 +328,6 @@ lint-bug-report:
lint-bug-report-preview: lint-bug-report-preview:
python3 utils/validate_bug_report_file.py --file "internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json" --preview python3 utils/validate_bug_report_file.py --file "internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json" --preview
gobinsec: gobinsec-cache.yml build
gobinsec -wait -cache -config utils/gobinsec_conf.yml ${EXE_TARGET} ${DEPLOY_DIR}/${TARGET_OS}/${LAUNCHER_EXE}
gobinsec-cache.yml:
./utils/gobinsec_update.sh
cp ./utils/gobinsec_update/gobinsec-cache-valid.yml ./gobinsec-cache.yml
updates: install-go-mod-outdated updates: install-go-mod-outdated
# Uncomment the "-ci" to fail the job if something can be updated. # Uncomment the "-ci" to fail the job if something can be updated.
go list -u -m -json all | go-mod-outdated -update -direct #-ci go list -u -m -json all | go-mod-outdated -update -direct #-ci

View File

@ -71,7 +71,11 @@ test-integration-nightly:
needs: needs:
- test-integration - test-integration
script: script:
- make test-integration-nightly - make test-integration-nightly | tee -a nightly-job.log
artifacts:
when: always
paths:
- nightly-job.log
test-windows: test-windows:
extends: extends:
@ -107,3 +111,18 @@ test-coverage:
coverage_report: coverage_report:
coverage_format: cobertura coverage_format: cobertura
path: coverage.xml path: coverage.xml
go-vuln-check:
extends:
- .rules-branch-manual-MR-and-devel-always
stage: test
tags:
- shared-medium
script:
- apt-get -y install jq
- ./utils/govulncheck.sh
artifacts:
when: always
paths:
- vulns*

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.20
require ( require (
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
github.com/Masterminds/semver/v3 v3.2.0 github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.17.1-0.20231114153341-2ecbdd2739f7 github.com/ProtonMail/gluon v0.17.1-0.20231206152152-caaf10897f9e
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366 github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton

4
go.sum
View File

@ -28,8 +28,8 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo= github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk= github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
github.com/ProtonMail/gluon v0.17.1-0.20231114153341-2ecbdd2739f7 h1:w+VoSAq9FQvKMm3DlH1MIEZ1KGe7LJ+81EJFVwSV4VU= github.com/ProtonMail/gluon v0.17.1-0.20231206152152-caaf10897f9e h1:kHmSOTxynSip1WJvwZTFOGJPVfI42e/I8bDzDjLK7aM=
github.com/ProtonMail/gluon v0.17.1-0.20231114153341-2ecbdd2739f7/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= github.com/ProtonMail/gluon v0.17.1-0.20231206152152-caaf10897f9e/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=

View File

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
"github.com/ProtonMail/proton-bridge/v3/internal/safe" "github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents" "github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
"github.com/ProtonMail/proton-bridge/v3/internal/updater" "github.com/ProtonMail/proton-bridge/v3/internal/updater"
@ -310,6 +311,10 @@ func (bridge *Bridge) SetColorScheme(colorScheme string) error {
return bridge.vault.SetColorScheme(colorScheme) return bridge.vault.SetColorScheme(colorScheme)
} }
func (bridge *Bridge) GetKnowledgeBaseSuggestions(userInput string) (kb.ArticleList, error) {
return kb.GetSuggestions(userInput)
}
// FactoryReset deletes all users, wipes the vault, and deletes all files. // FactoryReset deletes all users, wipes the vault, and deletes all files.
// Note: it does not clear the keychain. The only entry in the keychain is the vault password, // Note: it does not clear the keychain. The only entry in the keychain is the vault password,
// which we need at next startup to decrypt the vault. // which we need at next startup to decrypt the vault.

View File

@ -135,7 +135,7 @@ func (status *ConfigurationStatus) ApplyProgress() error {
return status.Save() return status.Save()
} }
func (status *ConfigurationStatus) RecordLinkClicked(link uint) error { func (status *ConfigurationStatus) RecordLinkClicked(link uint64) error {
status.DataLock.Lock() status.DataLock.Lock()
defer status.DataLock.Unlock() defer status.DataLock.Unlock()
@ -198,11 +198,11 @@ func (data *ConfigurationStatusData) init() {
data.DataV1.FailureDetails = "" data.DataV1.FailureDetails = ""
} }
func (data *ConfigurationStatusData) setClickedLink(pos uint) { func (data *ConfigurationStatusData) setClickedLink(pos uint64) {
data.DataV1.ClickedLink |= 1 << pos data.DataV1.ClickedLink |= 1 << pos
} }
func (data *ConfigurationStatusData) hasLinkClicked(pos uint) bool { func (data *ConfigurationStatusData) hasLinkClicked(pos uint64) bool {
val := data.DataV1.ClickedLink & (1 << pos) val := data.DataV1.ClickedLink & (1 << pos)
return val > 0 return val > 0
} }
@ -211,7 +211,7 @@ func (data *ConfigurationStatusData) clickedLinkToString() string {
var str = "" var str = ""
var first = true var first = true
for i := 0; i < 64; i++ { for i := 0; i < 64; i++ {
if data.hasLinkClicked(uint(i)) { if data.hasLinkClicked(uint64(i)) {
if !first { if !first {
str += "," str += ","
} else { } else {

View File

@ -18,7 +18,6 @@
#include "AppController.h" #include "AppController.h"
#include "GRPCService.h" #include "GRPCService.h"
#include <bridgepp/GRPC/GRPCUtils.h>
#include <bridgepp/Exception/Exception.h> #include <bridgepp/Exception/Exception.h>
#include "MainWindow.h" #include "MainWindow.h"
#include <bridgepp/Log/Log.h> #include <bridgepp/Log/Log.h>
@ -68,7 +67,7 @@ void AppController::setMainWindow(MainWindow *mainWindow) {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return The main window. /// \return The main window.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
MainWindow &AppController::mainWindow() { MainWindow &AppController::mainWindow() const {
if (!mainWindow_) { if (!mainWindow_) {
throw Exception("mainWindow has not yet been registered."); throw Exception("mainWindow has not yet been registered.");
} }
@ -79,7 +78,7 @@ MainWindow &AppController::mainWindow() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return A reference to the log. /// \return A reference to the log.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
bridgepp::Log &AppController::log() { bridgepp::Log &AppController::log() const {
return *log_; return *log_;
} }
@ -87,7 +86,7 @@ bridgepp::Log &AppController::log() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return A reference to the bridge-gui log. /// \return A reference to the bridge-gui log.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
bridgepp::Log &AppController::bridgeGUILog() { bridgepp::Log &AppController::bridgeGUILog() const {
return *bridgeGUILog_; return *bridgeGUILog_;
} }
@ -95,6 +94,6 @@ bridgepp::Log &AppController::bridgeGUILog() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return A reference to the gRPC service. /// \return A reference to the gRPC service.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
GRPCService &AppController::grpc() { GRPCService &AppController::grpc() const {
return *grpc_; return *grpc_;
} }

View File

@ -42,10 +42,10 @@ public: // member functions.
AppController &operator=(AppController const &) = delete; ///< Disabled assignment operator. AppController &operator=(AppController const &) = delete; ///< Disabled assignment operator.
AppController &operator=(AppController &&) = delete; ///< Disabled move assignment operator. AppController &operator=(AppController &&) = delete; ///< Disabled move assignment operator.
void setMainWindow(MainWindow *mainWindow); ///< Set the main window. void setMainWindow(MainWindow *mainWindow); ///< Set the main window.
MainWindow &mainWindow(); ///< Return the main window. MainWindow &mainWindow() const; ///< Return the main window.
bridgepp::Log &log(); ///< Return a reference to the log. bridgepp::Log &log() const; ///< Return a reference to the log.
bridgepp::Log &bridgeGUILog(); ///< Return a reference to the bridge-gui log. bridgepp::Log &bridgeGUILog() const; ///< Return a reference to the bridge-gui log.
GRPCService &grpc(); ///< Return a reference to the gRPC service. GRPCService &grpc() const; ///< Return a reference to the gRPC service.
private: // member functions. private: // member functions.
AppController(); ///< Default constructor. AppController(); ///< Default constructor.

View File

@ -66,11 +66,13 @@ add_executable(bridge-gui-tester
GRPCQtProxy.cpp GRPCQtProxy.h GRPCQtProxy.cpp GRPCQtProxy.h
GRPCService.cpp GRPCService.h GRPCService.cpp GRPCService.h
GRPCServerWorker.cpp GRPCServerWorker.h GRPCServerWorker.cpp GRPCServerWorker.h
Tabs/EventsTab.cpp Tabs/EventsTab.h
Tabs/KnowledgeBaseTab.cpp Tabs/KnowledgeBaseTab.h
Tabs/SettingsTab.cpp Tabs/SettingsTab.h Tabs/SettingsTab.cpp Tabs/SettingsTab.h
Tabs/UsersTab.cpp Tabs/UsersTab.h Tabs/UsersTab.cpp Tabs/UsersTab.h
UserDialog.cpp UserDialog.h UserDialog.cpp UserDialog.h
UserTable.cpp UserTable.h UserTable.cpp UserTable.h
) )
target_precompile_headers(bridge-gui-tester PRIVATE Pch.h) target_precompile_headers(bridge-gui-tester PRIVATE Pch.h)
target_include_directories(bridge-gui-tester PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(bridge-gui-tester PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -26,7 +26,7 @@ using namespace grpc;
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] The server token expected from gRPC calls /// \param[in] serverToken The server token expected from gRPC calls
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
GRPCMetadataProcessor::GRPCMetadataProcessor(QString const &serverToken) GRPCMetadataProcessor::GRPCMetadataProcessor(QString const &serverToken)
: serverToken_(serverToken.toStdString()) { : serverToken_(serverToken.toStdString()) {
@ -43,16 +43,16 @@ bool GRPCMetadataProcessor::IsBlocking() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] inputMeta /// \param authMetadata The authentication metadata.
/// \return the result of the metadata processing. /// \return the result of the metadata processing.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCMetadataProcessor::Process(AuthMetadataProcessor::InputMetadata const &auth_metadata, AuthContext *, Status GRPCMetadataProcessor::Process(InputMetadata const &authMetadata, AuthContext *,
AuthMetadataProcessor::OutputMetadata *, AuthMetadataProcessor::OutputMetadata *) { OutputMetadata *, OutputMetadata *) {
try { try {
AuthMetadataProcessor::InputMetadata::const_iterator pathIt = auth_metadata.find(":path"); const InputMetadata::const_iterator pathIt = authMetadata.find(":path");
QString const callName = (pathIt == auth_metadata.end()) ? ("unkown gRPC call") : QString::fromLocal8Bit(pathIt->second); QString const callName = (pathIt == authMetadata.end()) ? ("unkown gRPC call") : QString::fromLocal8Bit(pathIt->second);
AuthMetadataProcessor::InputMetadata::size_type const count = auth_metadata.count(grpcMetadataServerTokenKey); AuthMetadataProcessor::InputMetadata::size_type const count = authMetadata.count(grpcMetadataServerTokenKey);
if (count == 0) { if (count == 0) {
throw Exception(QString("Missing server token in gRPC client call '%1'.").arg(callName)); throw Exception(QString("Missing server token in gRPC client call '%1'.").arg(callName));
} }
@ -61,7 +61,7 @@ Status GRPCMetadataProcessor::Process(AuthMetadataProcessor::InputMetadata const
throw Exception(QString("Several server tokens were provided in gRPC client call '%1'.").arg(callName)); throw Exception(QString("Several server tokens were provided in gRPC client call '%1'.").arg(callName));
} }
if (auth_metadata.find(grpcMetadataServerTokenKey)->second != serverToken_) { if (authMetadata.find(grpcMetadataServerTokenKey)->second != serverToken_) {
throw Exception(QString("Invalid server token provided by gRPC client call '%1'.").arg(callName)); throw Exception(QString("Invalid server token provided by gRPC client call '%1'.").arg(callName));
} }
@ -70,7 +70,7 @@ Status GRPCMetadataProcessor::Process(AuthMetadataProcessor::InputMetadata const
} }
catch (Exception const &e) { catch (Exception const &e) {
app().log().error(e.qwhat()); app().log().error(e.qwhat());
return Status(StatusCode::UNAUTHENTICATED, e.qwhat().toStdString()); return Status(UNAUTHENTICATED, e.qwhat().toStdString());
} }
} }

View File

@ -35,7 +35,7 @@ public: // member functions.
GRPCMetadataProcessor &operator=(GRPCMetadataProcessor const &) = delete; ///< Disabled assignment operator. GRPCMetadataProcessor &operator=(GRPCMetadataProcessor const &) = delete; ///< Disabled assignment operator.
GRPCMetadataProcessor &operator=(GRPCMetadataProcessor &&) = delete; ///< Disabled move assignment operator. GRPCMetadataProcessor &operator=(GRPCMetadataProcessor &&) = delete; ///< Disabled move assignment operator.
bool IsBlocking() const override; ///< Is the processor blocking? bool IsBlocking() const override; ///< Is the processor blocking?
grpc::Status Process(InputMetadata const &auth_metadata, grpc::AuthContext *context, OutputMetadata *consumed_auth_metadata, grpc::Status Process(InputMetadata const &authMetadata, grpc::AuthContext *context, OutputMetadata *consumed_auth_metadata,
OutputMetadata *response_metadata) override; ///< Process the metadata OutputMetadata *response_metadata) override; ///< Process the metadata
private: private:

View File

@ -31,10 +31,11 @@ GRPCQtProxy::GRPCQtProxy()
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void GRPCQtProxy::connectSignals() { void GRPCQtProxy::connectSignals() const {
MainWindow &mainWindow = app().mainWindow(); MainWindow &mainWindow = app().mainWindow();
SettingsTab &settingsTab = mainWindow.settingsTab(); SettingsTab const &settingsTab = mainWindow.settingsTab();
UsersTab &usersTab = mainWindow.usersTab(); UsersTab const &usersTab = mainWindow.usersTab();
KnowledgeBaseTab const &kbTab = mainWindow.knowledgeBaseTab();
connect(this, &GRPCQtProxy::delayedEventRequested, &mainWindow, &MainWindow::sendDelayedEvent); connect(this, &GRPCQtProxy::delayedEventRequested, &mainWindow, &MainWindow::sendDelayedEvent);
connect(this, &GRPCQtProxy::setIsAutostartOnReceived, &settingsTab, &SettingsTab::setIsAutostartOn); connect(this, &GRPCQtProxy::setIsAutostartOnReceived, &settingsTab, &SettingsTab::setIsAutostartOn);
connect(this, &GRPCQtProxy::setIsBetaEnabledReceived, &settingsTab, &SettingsTab::setIsBetaEnabled); connect(this, &GRPCQtProxy::setIsBetaEnabledReceived, &settingsTab, &SettingsTab::setIsBetaEnabled);
@ -56,6 +57,7 @@ void GRPCQtProxy::connectSignals() {
connect(this, &GRPCQtProxy::setUserSplitModeReceived, &usersTab, &UsersTab::setUserSplitMode); connect(this, &GRPCQtProxy::setUserSplitModeReceived, &usersTab, &UsersTab::setUserSplitMode);
connect(this, &GRPCQtProxy::configureUserAppleMailReceived, &usersTab, &UsersTab::configureUserAppleMail); connect(this, &GRPCQtProxy::configureUserAppleMailReceived, &usersTab, &UsersTab::configureUserAppleMail);
connect(this, &GRPCQtProxy::sendBadEventUserFeedbackReceived, &usersTab, &UsersTab::processBadEventUserFeedback); connect(this, &GRPCQtProxy::sendBadEventUserFeedbackReceived, &usersTab, &UsersTab::processBadEventUserFeedback);
connect(this, &GRPCQtProxy::requestKnowledgeBaseSuggestionsReceived, &kbTab, &KnowledgeBaseTab::requestKnowledgeBaseSuggestions);
} }
@ -119,6 +121,13 @@ void GRPCQtProxy::reportBug(QString const &osType, QString const &osVersion, QSt
emit reportBugReceived(osType, osVersion, emailClient, address, description, includeLogs); emit reportBugReceived(osType, osVersion, emailClient, address, description, includeLogs);
} }
//****************************************************************************************************************************************************
/// \param[in] userInput The user input.
//****************************************************************************************************************************************************
void GRPCQtProxy::requestKnowledgeBaseSuggestions(QString const& userInput) {
emit requestKnowledgeBaseSuggestionsReceived(userInput);
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
// //
@ -157,8 +166,8 @@ void GRPCQtProxy::setClientPlatform(QString const &clientPlatform) {
/// \param[in] useSSLForIMAP The IMAP connexion mode. /// \param[in] useSSLForIMAP The IMAP connexion mode.
/// \param[in] useSSLForSMTP The IMAP connexion mode. /// \param[in] useSSLForSMTP The IMAP connexion mode.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void GRPCQtProxy::setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool userSSLForSMTP) { void GRPCQtProxy::setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) {
emit setMailServerSettingsReceived(imapPort, smtpPort, useSSLForIMAP, userSSLForSMTP); emit setMailServerSettingsReceived(imapPort, smtpPort, useSSLForIMAP, useSSLForSMTP);
} }

View File

@ -36,7 +36,7 @@ public: // member functions.
GRPCQtProxy &operator=(GRPCQtProxy const &) = delete; ///< Disabled assignment operator. GRPCQtProxy &operator=(GRPCQtProxy const &) = delete; ///< Disabled assignment operator.
GRPCQtProxy &operator=(GRPCQtProxy &&) = delete; ///< Disabled move assignment operator. GRPCQtProxy &operator=(GRPCQtProxy &&) = delete; ///< Disabled move assignment operator.
void connectSignals(); // connect the signals to the main window. void connectSignals() const; // connect the signals to the main window.
void sendDelayedEvent(bridgepp::SPStreamEvent const &event); ///< Sends a delayed stream event. void sendDelayedEvent(bridgepp::SPStreamEvent const &event); ///< Sends a delayed stream event.
void setIsAutostartOn(bool on); ///< Forwards a SetIsAutostartOn call via a Qt signal. void setIsAutostartOn(bool on); ///< Forwards a SetIsAutostartOn call via a Qt signal.
void setIsBetaEnabled(bool enabled); ///< Forwards a SetIsBetaEnabled call via a Qt signal. void setIsBetaEnabled(bool enabled); ///< Forwards a SetIsBetaEnabled call via a Qt signal.
@ -45,11 +45,12 @@ public: // member functions.
void setColorSchemeName(QString const &name); ///< Forward a SetColorSchemeName call via a Qt Signal void setColorSchemeName(QString const &name); ///< Forward a SetColorSchemeName call via a Qt Signal
void reportBug(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, void reportBug(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address,
QString const &description, bool includeLogs); ///< Forwards a ReportBug call via a Qt signal. QString const &description, bool includeLogs); ///< Forwards a ReportBug call via a Qt signal.
void requestKnowledgeBaseSuggestions(QString const &userInput); ///< Forwards a RequestKnowledgeBaseSuggestions call via a Qt signal.
void installTLSCertificate(); ///< Forwards a InstallTLScertificate call via a Qt signal. void installTLSCertificate(); ///< Forwards a InstallTLScertificate call via a Qt signal.
void exportTLSCertificates(QString const &folderPath); //< Forward an 'ExportTLSCertificates' call via a Qt signal. void exportTLSCertificates(QString const &folderPath); //< Forward an 'ExportTLSCertificates' call via a Qt signal.
void setIsStreaming(bool isStreaming); ///< Forward a isStreaming internal messages via a Qt signal. void setIsStreaming(bool isStreaming); ///< Forward a isStreaming internal messages via a Qt signal.
void setClientPlatform(QString const &clientPlatform); ///< Forward a setClientPlatform call via a Qt signal. void setClientPlatform(QString const &clientPlatform); ///< Forward a setClientPlatform call via a Qt signal.
void setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool userSSLForSMTP); ///< Forwards a setMailServerSettings' call via a Qt signal. void setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP); ///< Forwards a setMailServerSettings' call via a Qt signal.
void setIsDoHEnabled(bool enabled); ///< Forwards a setIsDoHEnabled call via a Qt signal. void setIsDoHEnabled(bool enabled); ///< Forwards a setIsDoHEnabled call via a Qt signal.
void setDiskCachePath(QString const &path); ///< Forwards a setDiskCachePath call via a Qt signal. void setDiskCachePath(QString const &path); ///< Forwards a setDiskCachePath call via a Qt signal.
void setIsAutomaticUpdateOn(bool on); ///< Forwards a SetIsAutomaticUpdateOn call via a Qt signal. void setIsAutomaticUpdateOn(bool on); ///< Forwards a SetIsAutomaticUpdateOn call via a Qt signal.
@ -68,6 +69,7 @@ signals:
void setColorSchemeNameReceived(QString const &name); ///< Forward a SetColorScheme call via a Qt Signal void setColorSchemeNameReceived(QString const &name); ///< Forward a SetColorScheme call via a Qt Signal
void reportBugReceived(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, void reportBugReceived(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address,
QString const &description, bool includeLogs); ///< Signal for the ReportBug gRPC call QString const &description, bool includeLogs); ///< Signal for the ReportBug gRPC call
void requestKnowledgeBaseSuggestionsReceived(QString const &userInput); ///< Signal for the RequestKnowledgeBaseSuggestions gRPC call.
void installTLSCertificateReceived(); ///< Signal for the InstallTLSCertificate gRPC call. void installTLSCertificateReceived(); ///< Signal for the InstallTLSCertificate gRPC call.
void exportTLSCertificatesReceived(QString const &folderPath); ///< Signal for the ExportTLSCertificates gRPC call. void exportTLSCertificatesReceived(QString const &folderPath); ///< Signal for the ExportTLSCertificates gRPC call.
void setIsStreamingReceived(bool isStreaming); ///< Signal for the IsStreaming internal message. void setIsStreamingReceived(bool isStreaming); ///< Signal for the IsStreaming internal message.

View File

@ -50,7 +50,7 @@ void GRPCServerWorker::run() {
SslServerCredentialsOptions ssl_opts; SslServerCredentialsOptions ssl_opts;
ssl_opts.pem_root_certs = ""; ssl_opts.pem_root_certs = "";
ssl_opts.pem_key_cert_pairs.push_back(pair); ssl_opts.pem_key_cert_pairs.push_back(pair);
std::shared_ptr<ServerCredentials> credentials = grpc::SslServerCredentials(ssl_opts); std::shared_ptr<ServerCredentials> const credentials = SslServerCredentials(ssl_opts);
GRPCConfig config; GRPCConfig config;
config.cert = testTLSCert; config.cert = testTLSCert;
@ -59,8 +59,7 @@ void GRPCServerWorker::run() {
credentials->SetAuthMetadataProcessor(processor_); // gRPC interceptors are still experimental in C++, so we use AuthMetadataProcessor credentials->SetAuthMetadataProcessor(processor_); // gRPC interceptors are still experimental in C++, so we use AuthMetadataProcessor
ServerBuilder builder; ServerBuilder builder;
int port = 0; // Port will not be known until ServerBuilder::BuildAndStart() is called int port = 0; // Port will not be known until ServerBuilder::BuildAndStart() is called
bool const useFileSocket = useFileSocketForGRPC(); if (useFileSocketForGRPC()) {
if (useFileSocket) {
QString const fileSocketPath = getAvailableFileSocketPath(); QString const fileSocketPath = getAvailableFileSocketPath();
if (fileSocketPath.isEmpty()) { if (fileSocketPath.isEmpty()) {
throw Exception("Could not get an available file socket."); throw Exception("Could not get an available file socket.");
@ -102,7 +101,7 @@ void GRPCServerWorker::run() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void GRPCServerWorker::stop() { void GRPCServerWorker::stop() const {
if (server_) { if (server_) {
server_->Shutdown(); server_->Shutdown();
} }

View File

@ -40,7 +40,7 @@ public: // member functions.
GRPCServerWorker &operator=(GRPCServerWorker &&) = delete; ///< Disabled move assignment operator. GRPCServerWorker &operator=(GRPCServerWorker &&) = delete; ///< Disabled move assignment operator.
void run() override; ///< Run the worker. void run() override; ///< Run the worker.
void stop(); ///< Stop the gRPC service. void stop() const; ///< Stop the gRPC service.
private: // data members private: // data members
std::unique_ptr<grpc::Server> server_ { nullptr }; ///< The gRPC server. std::unique_ptr<grpc::Server> server_ { nullptr }; ///< The gRPC server.

View File

@ -39,7 +39,7 @@ QString const defaultKeychain = "defaultKeychain"; ///< The default keychain.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void GRPCService::connectProxySignals() { void GRPCService::connectProxySignals() const {
qtProxy_.connectSignals(); qtProxy_.connectSignals();
} }
@ -185,7 +185,7 @@ Status GRPCService::SetIsAllMailVisible(ServerContext *, BoolValue const *reques
/// \param[out] response The response. /// \param[out] response The response.
/// \return The status for the call. /// \return The status for the call.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::IsAllMailVisible(ServerContext *, Empty const *request, BoolValue *response) { Status GRPCService::IsAllMailVisible(ServerContext *, Empty const *, BoolValue *response) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
response->set_value(app().mainWindow().settingsTab().isAllMailVisible()); response->set_value(app().mainWindow().settingsTab().isAllMailVisible());
return Status::OK; return Status::OK;
@ -354,25 +354,45 @@ Status GRPCService::SetMainExecutable(ServerContext *, StringValue const *reques
return Status::OK; return Status::OK;
} }
//****************************************************************************************************************************************************
/// \param[in] request The request.
/// \return The status for the call.
//****************************************************************************************************************************************************
grpc::Status GRPCService::RequestKnowledgeBaseSuggestions(ServerContext*, StringValue const* request, Empty*) {
QString const userInput = QString::fromUtf8(request->value());
app().log().info(QString("RequestKnowledgeBaseSuggestions: %1").arg(userInput.left(10) + "..."));
qtProxy_.requestKnowledgeBaseSuggestionsReceived(userInput);
QList<bridgepp::KnowledgeBaseSuggestion> suggestions;
for (qsizetype i = 1; i <= 3; ++i) {
suggestions.push_back( {
.title = QString("Suggested link %1").arg(i),
.url = QString("https://proton.me/support/bridge#%1").arg(i),
});
}
qtProxy_.sendDelayedEvent(newKnowledgeBaseSuggestionsEvent(app().mainWindow().knowledgeBaseTab().getSuggestions()));
return Status::OK;
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] request The request /// \param[in] request The request
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::ReportBug(ServerContext *, ReportBugRequest const *request, Empty *) { Status GRPCService::ReportBug(ServerContext *, ReportBugRequest const *request, Empty *) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
SettingsTab &tab = app().mainWindow().settingsTab(); EventsTab const&eventsTab = app().mainWindow().eventsTab();
qtProxy_.reportBug(QString::fromStdString(request->ostype()), QString::fromStdString(request->osversion()), qtProxy_.reportBug(QString::fromStdString(request->ostype()), QString::fromStdString(request->osversion()),
QString::fromStdString(request->emailclient()), QString::fromStdString(request->address()), QString::fromStdString(request->description()), QString::fromStdString(request->emailclient()), QString::fromStdString(request->address()), QString::fromStdString(request->description()),
request->includelogs()); request->includelogs());
SPStreamEvent event; SPStreamEvent event;
switch (tab.nextBugReportResult()) { switch (eventsTab.nextBugReportResult()) {
case SettingsTab::BugReportResult::Success: case EventsTab::BugReportResult::Success:
event = newReportBugSuccessEvent(); event = newReportBugSuccessEvent();
break; break;
case SettingsTab::BugReportResult::Error: case EventsTab::BugReportResult::Error:
event = newReportBugErrorEvent(); event = newReportBugErrorEvent();
break; break;
case SettingsTab::BugReportResult::DataSharingError: case EventsTab::BugReportResult::DataSharingError:
event = newReportBugFallbackEvent(); event = newReportBugFallbackEvent();
break; break;
} }
@ -392,7 +412,7 @@ Status GRPCService::Login(ServerContext *, LoginRequest const *request, Empty *)
UsersTab &usersTab = app().mainWindow().usersTab(); UsersTab &usersTab = app().mainWindow().usersTab();
loginUsername_ = QString::fromStdString(request->username()); loginUsername_ = QString::fromStdString(request->username());
SPUser const& user = usersTab.userTable().userWithUsernameOrEmail(QString::fromStdString(request->username())); SPUser const &user = usersTab.userTable().userWithUsernameOrEmail(QString::fromStdString(request->username()));
if (user) { if (user) {
qtProxy_.sendDelayedEvent(newLoginAlreadyLoggedInEvent(user->id())); qtProxy_.sendDelayedEvent(newLoginAlreadyLoggedInEvent(user->id()));
return Status::OK; return Status::OK;
@ -427,7 +447,7 @@ Status GRPCService::Login(ServerContext *, LoginRequest const *request, Empty *)
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::Login2FA(ServerContext *, LoginRequest const *request, Empty *) { Status GRPCService::Login2FA(ServerContext *, LoginRequest const *request, Empty *) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
UsersTab &usersTab = app().mainWindow().usersTab(); UsersTab const &usersTab = app().mainWindow().usersTab();
if (usersTab.nextUserTFAError()) { if (usersTab.nextUserTFAError()) {
qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::TFA_ERROR, "2FA Error.")); qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::TFA_ERROR, "2FA Error."));
return Status::OK; return Status::OK;
@ -452,7 +472,7 @@ Status GRPCService::Login2FA(ServerContext *, LoginRequest const *request, Empty
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::Login2Passwords(ServerContext *, LoginRequest const *request, Empty *) { Status GRPCService::Login2Passwords(ServerContext *, LoginRequest const *request, Empty *) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
UsersTab &usersTab = app().mainWindow().usersTab(); UsersTab const &usersTab = app().mainWindow().usersTab();
if (usersTab.nextUserTwoPasswordsError()) { if (usersTab.nextUserTwoPasswordsError()) {
qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::TWO_PASSWORDS_ERROR, "Two Passwords error.")); qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::TWO_PASSWORDS_ERROR, "Two Passwords error."));
@ -542,12 +562,12 @@ Status GRPCService::DiskCachePath(ServerContext *, Empty const *, StringValue *r
Status GRPCService::SetDiskCachePath(ServerContext *, StringValue const *path, Empty *) { Status GRPCService::SetDiskCachePath(ServerContext *, StringValue const *path, Empty *) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
SettingsTab &tab = app().mainWindow().settingsTab(); EventsTab const &eventsTab = app().mainWindow().eventsTab();
QString const qPath = QString::fromStdString(path->value()); QString const qPath = QString::fromStdString(path->value());
// we mimic the behaviour of Bridge // we mimic the behaviour of Bridge
if (!tab.nextCacheChangeWillSucceed()) { if (!eventsTab.nextCacheChangeWillSucceed()) {
qtProxy_.sendDelayedEvent(newDiskCacheErrorEvent(grpc::DiskCacheErrorType(CANT_MOVE_DISK_CACHE_ERROR))); qtProxy_.sendDelayedEvent(newDiskCacheErrorEvent(static_cast<DiskCacheErrorType>(CANT_MOVE_DISK_CACHE_ERROR)));
} else { } else {
qtProxy_.setDiskCachePath(qPath); qtProxy_.setDiskCachePath(qPath);
qtProxy_.sendDelayedEvent(newDiskCachePathChangedEvent(qPath)); qtProxy_.sendDelayedEvent(newDiskCachePathChangedEvent(qPath));
@ -584,7 +604,7 @@ Status GRPCService::IsDoHEnabled(ServerContext *, Empty const *, BoolValue *resp
/// \param[in] settings The IMAP/SMTP settings. /// \param[in] settings The IMAP/SMTP settings.
/// \return The status for the call. /// \return The status for the call.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::SetMailServerSettings(::grpc::ServerContext *context, ImapSmtpSettings const *settings, Empty *) { Status GRPCService::SetMailServerSettings(ServerContext *, ImapSmtpSettings const *settings, Empty *) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
qtProxy_.setMailServerSettings(settings->imapport(), settings->smtpport(), settings->usesslforimap(), settings->usesslforsmtp()); qtProxy_.setMailServerSettings(settings->imapport(), settings->smtpport(), settings->usesslforimap(), settings->usesslforsmtp());
qtProxy_.sendDelayedEvent(newMailServerSettingsChanged(*settings)); qtProxy_.sendDelayedEvent(newMailServerSettingsChanged(*settings));
@ -597,9 +617,9 @@ Status GRPCService::SetMailServerSettings(::grpc::ServerContext *context, ImapSm
/// \param[out] outSettings The settings /// \param[out] outSettings The settings
/// \return The status for the call. /// \return The status for the call.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::MailServerSettings(::grpc::ServerContext *, Empty const *, ImapSmtpSettings *outSettings) { Status GRPCService::MailServerSettings(ServerContext *, Empty const *, ImapSmtpSettings *outSettings) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
SettingsTab &tab = app().mainWindow().settingsTab(); SettingsTab const &tab = app().mainWindow().settingsTab();
outSettings->set_imapport(tab.imapPort()); outSettings->set_imapport(tab.imapPort());
outSettings->set_smtpport(tab.smtpPort()); outSettings->set_smtpport(tab.smtpPort());
outSettings->set_usesslforimap(tab.useSSLForIMAP()); outSettings->set_usesslforimap(tab.useSSLForIMAP());
@ -626,7 +646,7 @@ Status GRPCService::Hostname(ServerContext *, Empty const *, StringValue *respon
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::IsPortFree(ServerContext *, Int32Value const *request, BoolValue *response) { Status GRPCService::IsPortFree(ServerContext *, Int32Value const *request, BoolValue *response) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
response->set_value(app().mainWindow().settingsTab().isPortFree()); response->set_value(app().mainWindow().eventsTab().isPortFree());
return Status::OK; return Status::OK;
} }
@ -697,8 +717,8 @@ Status GRPCService::GetUserList(ServerContext *, Empty const *, UserListResponse
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::GetUser(ServerContext *, StringValue const *request, grpc::User *response) { Status GRPCService::GetUser(ServerContext *, StringValue const *request, grpc::User *response) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
QString userID = QString::fromStdString(request->value()); QString const userID = QString::fromStdString(request->value());
SPUser user = app().mainWindow().usersTab().userWithID(userID); SPUser const user = app().mainWindow().usersTab().userWithID(userID);
if (!user) { if (!user) {
return Status(NOT_FOUND, QString("user not found %1").arg(userID).toStdString()); return Status(NOT_FOUND, QString("user not found %1").arg(userID).toStdString());
} }
@ -766,14 +786,14 @@ Status GRPCService::ConfigureUserAppleMail(ServerContext *, ConfigureAppleMailRe
/// \param[in] request The request /// \param[in] request The request
/// \return The status for the call. /// \return The status for the call.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
Status GRPCService::ExportTLSCertificates(ServerContext *, StringValue const *request, Empty *response) { Status GRPCService::ExportTLSCertificates(ServerContext *, StringValue const *request, Empty *) {
app().log().debug(__FUNCTION__); app().log().debug(__FUNCTION__);
SettingsTab &tab = app().mainWindow().settingsTab(); SettingsTab const &tab = app().mainWindow().settingsTab();
if (!tab.nextTLSCertExportWillSucceed()) { if (!tab.nextTLSCertExportWillSucceed()) {
qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_CERT_EXPORT_ERROR)); qtProxy_.sendDelayedEvent(newGenericErrorEvent(TLS_CERT_EXPORT_ERROR));
} }
if (!tab.nextTLSKeyExportWillSucceed()) { if (!tab.nextTLSKeyExportWillSucceed()) {
qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_KEY_EXPORT_ERROR)); qtProxy_.sendDelayedEvent(newGenericErrorEvent(TLS_KEY_EXPORT_ERROR));
} }
qtProxy_.exportTLSCertificates(QString::fromStdString(request->value())); qtProxy_.exportTLSCertificates(QString::fromStdString(request->value()));
return Status::OK; return Status::OK;

View File

@ -37,7 +37,7 @@ public: // member functions.
~GRPCService() override = default; ///< Destructor. ~GRPCService() override = default; ///< Destructor.
GRPCService &operator=(GRPCService const &) = delete; ///< Disabled assignment operator. GRPCService &operator=(GRPCService const &) = delete; ///< Disabled assignment operator.
GRPCService &operator=(GRPCService &&) = delete; ///< Disabled move assignment operator. GRPCService &operator=(GRPCService &&) = delete; ///< Disabled move assignment operator.
void connectProxySignals(); ///< Connect the signals of the Qt Proxy to the GUI components void connectProxySignals() const; ///< Connect the signals of the Qt Proxy to the GUI components
bool isStreaming() const; ///< Check if the service is currently streaming events. bool isStreaming() const; ///< Check if the service is currently streaming events.
grpc::Status CheckTokens(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::StringValue *response) override; grpc::Status CheckTokens(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::StringValue *response) override;
grpc::Status AddLogEntry(::grpc::ServerContext *, ::grpc::AddLogEntryRequest const *request, ::google::protobuf::Empty *) override; grpc::Status AddLogEntry(::grpc::ServerContext *, ::grpc::AddLogEntryRequest const *request, ::google::protobuf::Empty *) override;
@ -67,6 +67,7 @@ public: // member functions.
grpc::Status ReportBug(::grpc::ServerContext *, ::grpc::ReportBugRequest const *request, ::google::protobuf::Empty *) override; grpc::Status ReportBug(::grpc::ServerContext *, ::grpc::ReportBugRequest const *request, ::google::protobuf::Empty *) override;
grpc::Status ForceLauncher(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status ForceLauncher(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status SetMainExecutable(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status SetMainExecutable(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status RequestKnowledgeBaseSuggestions(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override;
grpc::Status Login(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override; grpc::Status Login(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override;
grpc::Status Login2FA(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override; grpc::Status Login2FA(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override;
grpc::Status Login2Passwords(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override; grpc::Status Login2Passwords(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override;

View File

@ -63,7 +63,7 @@ MainWindow::MainWindow(QWidget *parent)
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return A reference to the 'General' tab. /// \return A reference to the 'General' tab.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
SettingsTab &MainWindow::settingsTab() { SettingsTab &MainWindow::settingsTab() const {
return *ui_.settingsTab; return *ui_.settingsTab;
} }
@ -71,16 +71,32 @@ SettingsTab &MainWindow::settingsTab() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return A reference to the users tab. /// \return A reference to the users tab.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
UsersTab &MainWindow::usersTab() { UsersTab &MainWindow::usersTab() const {
return *ui_.usersTab; return *ui_.usersTab;
} }
//****************************************************************************************************************************************************
/// \return A reference to the events tab.
//****************************************************************************************************************************************************
EventsTab& MainWindow::eventsTab() const {
return *ui_.eventsTab;
}
//****************************************************************************************************************************************************
/// \return A reference to the knowledge base tab.
//****************************************************************************************************************************************************
KnowledgeBaseTab& MainWindow::knowledgeBaseTab() const {
return *ui_.knowledgeBaseTab;
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] level The log level. /// \param[in] level The log level.
/// \param[in] message The log message /// \param[in] message The log message
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void MainWindow::addLogEntry(bridgepp::Log::Level level, const QString &message) { void MainWindow::addLogEntry(bridgepp::Log::Level level, const QString &message) const {
addEntryToLogEdit(level, message, *ui_.editLog); addEntryToLogEdit(level, message, *ui_.editLog);
} }
@ -89,7 +105,7 @@ void MainWindow::addLogEntry(bridgepp::Log::Level level, const QString &message)
/// \param[in] level The log level. /// \param[in] level The log level.
/// \param[in] message The log message /// \param[in] message The log message
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void MainWindow::addBridgeGUILogEntry(bridgepp::Log::Level level, const QString &message) { void MainWindow::addBridgeGUILogEntry(bridgepp::Log::Level level, const QString &message) const {
addEntryToLogEdit(level, message, *ui_.editBridgeGUILog); addEntryToLogEdit(level, message, *ui_.editBridgeGUILog);
} }
@ -97,8 +113,8 @@ void MainWindow::addBridgeGUILogEntry(bridgepp::Log::Level level, const QString
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] event The event. /// \param[in] event The event.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void MainWindow::sendDelayedEvent(SPStreamEvent const &event) { void MainWindow::sendDelayedEvent(SPStreamEvent const &event) const {
QTimer::singleShot(this->settingsTab().eventDelayMs(), [event] { app().grpc().sendEvent(event); }); QTimer::singleShot(this->eventsTab().eventDelayMs(), [event] { app().grpc().sendEvent(event); });
} }

View File

@ -38,15 +38,17 @@ public: // member functions.
MainWindow &operator=(MainWindow const &) = delete; ///< Disabled assignment operator. MainWindow &operator=(MainWindow const &) = delete; ///< Disabled assignment operator.
MainWindow &operator=(MainWindow &&) = delete; ///< Disabled move assignment operator. MainWindow &operator=(MainWindow &&) = delete; ///< Disabled move assignment operator.
SettingsTab &settingsTab(); ///< Returns a reference the 'Settings' tab. SettingsTab &settingsTab() const; ///< Returns a reference the 'Settings' tab.
UsersTab &usersTab(); ///< Returns a reference to the 'Users' tab. UsersTab &usersTab() const; ///< Returns a reference to the 'Users' tab.
EventsTab &eventsTab() const; ///< Returns a reference to the 'Events' tab.
KnowledgeBaseTab &knowledgeBaseTab() const; ///< Returns a reference to the 'Knowledge Base' tab.
public slots: public slots:
void sendDelayedEvent(bridgepp::SPStreamEvent const &event); ///< Sends a gRPC event after the delay specified in the UI. The call is non blocking. void sendDelayedEvent(bridgepp::SPStreamEvent const &event) const; ///< Sends a gRPC event after the delay specified in the UI. The call is non blocking.
private slots: private slots:
void addLogEntry(bridgepp::Log::Level level, QString const &message); ///< Add an entry to the log. void addLogEntry(bridgepp::Log::Level level, QString const &message) const; ///< Add an entry to the log.
void addBridgeGUILogEntry(bridgepp::Log::Level level, const QString &message); ///< Add an entry to the log. void addBridgeGUILogEntry(bridgepp::Log::Level level, const QString &message) const; ///< Add an entry to the log.
private: private:
Ui::MainWindow ui_ {}; ///< The GUI for the window. Ui::MainWindow ui_ {}; ///< The GUI for the window.

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1226</width> <width>1096</width>
<height>1086</height> <height>876</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -22,7 +22,7 @@
</property> </property>
<widget class="QTabWidget" name="tabTop"> <widget class="QTabWidget" name="tabTop">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>3</number>
</property> </property>
<widget class="SettingsTab" name="settingsTab"> <widget class="SettingsTab" name="settingsTab">
<attribute name="title"> <attribute name="title">
@ -34,6 +34,16 @@
<string>Users</string> <string>Users</string>
</attribute> </attribute>
</widget> </widget>
<widget class="EventsTab" name="eventsTab">
<attribute name="title">
<string>Events &amp;&amp; Errors</string>
</attribute>
</widget>
<widget class="KnowledgeBaseTab" name="knowledgeBaseTab">
<attribute name="title">
<string>Knowledge Base</string>
</attribute>
</widget>
</widget> </widget>
<widget class="QTabWidget" name="tabBottom"> <widget class="QTabWidget" name="tabBottom">
<property name="currentIndex"> <property name="currentIndex">
@ -96,6 +106,18 @@
<header>Tabs/UsersTab.h</header> <header>Tabs/UsersTab.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>EventsTab</class>
<extends>QWidget</extends>
<header>Tabs/EventsTab.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KnowledgeBaseTab</class>
<extends>QWidget</extends>
<header>Tabs/KnowledgeBaseTab.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -0,0 +1,109 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#include "EventsTab.h"
#include "GRPCService.h"
#include <bridgepp/GRPC/EventFactory.h>
using namespace bridgepp;
//****************************************************************************************************************************************************
/// \brief Connect an address error button to the generation of an address error event.
///
/// \param[in] button The error button.
/// \param[in] edit The edit containing the address.
/// \param[in] eventGenerator The factory function creating the event.
//****************************************************************************************************************************************************
void connectAddressError(QPushButton const* button, QLineEdit* edit, SPStreamEvent (*eventGenerator)(QString const&)) {
QObject::connect(button, &QPushButton::clicked, [edit, eventGenerator]() { app().grpc().sendEvent(eventGenerator(edit->text())); });
}
//****************************************************************************************************************************************************
/// \param[in] parent The parent widget.
//****************************************************************************************************************************************************
EventsTab::EventsTab(QWidget* parent)
: QWidget(parent) {
ui_.setupUi(this);
this->resetUI();
connect(ui_.buttonInternetOn, &QPushButton::clicked, []() { app().grpc().sendEvent(newInternetStatusEvent(true)); });
connect(ui_.buttonInternetOff, &QPushButton::clicked, []() { app().grpc().sendEvent(newInternetStatusEvent(false)); });
connect(ui_.buttonShowMainWindow, &QPushButton::clicked, []() { app().grpc().sendEvent(newShowMainWindowEvent()); });
connect(ui_.buttonNoKeychain, &QPushButton::clicked, []() { app().grpc().sendEvent(newHasNoKeychainEvent()); });
connect(ui_.buttonAPICertIssue, &QPushButton::clicked, []() { app().grpc().sendEvent(newApiCertIssueEvent()); });
connectAddressError(ui_.buttonAddressChanged, ui_.editAddressErrors, newAddressChangedEvent);
connectAddressError(ui_.buttonAddressChangedLogout, ui_.editAddressErrors, newAddressChangedLogoutEvent);
//connect(ui_.checkNextCacheChangeWillSucceed, &QCheckBox::toggled, this, &SettingsTab::updateGUIState);
connect(ui_.buttonUpdateError, &QPushButton::clicked, [&]() {
app().grpc().sendEvent(newUpdateErrorEvent(static_cast<grpc::UpdateErrorType>(ui_.comboUpdateError->currentIndex())));
});
connect(ui_.buttonUpdateManualReady, &QPushButton::clicked, [&] {
app().grpc().sendEvent(newUpdateManualReadyEvent(ui_.editUpdateVersion->text()));
});
connect(ui_.buttonUpdateForce, &QPushButton::clicked, [&] {
app().grpc().sendEvent(newUpdateForceEvent(ui_.editUpdateVersion->text()));
});
connect(ui_.buttonUpdateManualRestart, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateManualRestartNeededEvent()); });
connect(ui_.buttonUpdateSilentRestart, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateSilentRestartNeededEvent()); });
connect(ui_.buttonUpdateIsLatest, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateIsLatestVersionEvent()); });
connect(ui_.buttonUpdateCheckFinished, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateCheckFinishedEvent()); });
connect(ui_.buttonUpdateVersionChanged, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateVersionChangedEvent()); });
}
//****************************************************************************************************************************************************
/// \return The delay to apply before sending automatically generated events.
//****************************************************************************************************************************************************
qint32 EventsTab::eventDelayMs() const {
return ui_.spinEventDelay->value();
}
//****************************************************************************************************************************************************
/// \return The bug report results
//****************************************************************************************************************************************************
EventsTab::BugReportResult EventsTab::nextBugReportResult() const {
return static_cast<BugReportResult>(ui_.comboBugReportResult->currentIndex());
}
//****************************************************************************************************************************************************
/// \return The reply for the next IsPortFree gRPC call.
//****************************************************************************************************************************************************
bool EventsTab::isPortFree() const {
return ui_.checkIsPortFree->isChecked();
}
//****************************************************************************************************************************************************
/// \return The value for the 'Next Cache Change Will Succeed' check box.
//****************************************************************************************************************************************************
bool EventsTab::nextCacheChangeWillSucceed() const {
return ui_.checkNextCacheChangeWillSucceed->isChecked();
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void EventsTab::resetUI() const {
ui_.comboBugReportResult->setCurrentIndex(0);
ui_.checkIsPortFree->setChecked(true);
ui_.checkNextCacheChangeWillSucceed->setChecked(true);
}

View File

@ -0,0 +1,59 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#ifndef BRIDGE_GUI_TESTER_EVENTS_TAB_H
#define BRIDGE_GUI_TESTER_EVENTS_TAB_H
#include "ui_EventsTab.h"
//****************************************************************************************************************************************************
/// \brief Events tabs
//****************************************************************************************************************************************************
class EventsTab: public QWidget {
Q_OBJECT
public: // data types
enum class BugReportResult {
Success = 0,
Error = 1,
DataSharingError = 2,
}; ///< Enumeration for the result of bug report sending
public: // member functions.
explicit EventsTab(QWidget *parent = nullptr); ///< Default constructor.
EventsTab(EventsTab const&) = delete; ///< Disabled copy-constructor.
EventsTab(EventsTab&&) = delete; ///< Disabled assignment copy-constructor.
~EventsTab() override = default; ///< Destructor.
EventsTab& operator=(EventsTab const&) = delete; ///< Disabled assignment operator.
EventsTab& operator=(EventsTab&&) = delete; ///< Disabled move assignment operator.
qint32 eventDelayMs() const; ///< Get the delay for sending automatically generated events.
BugReportResult nextBugReportResult() const; ///< Get the value of the 'Next bug report result' combo box.
bool isPortFree() const; ///< Get the value for the "Is Port Free" check box.
bool nextCacheChangeWillSucceed() const; ///< Get the value for the 'Next Cache Change will succeed' edit.
void resetUI() const; ///< Resets the UI.
private: // data members
Ui::EventsTab ui_ {}; ///< The UI for the widget.
};
#endif

View File

@ -0,0 +1,359 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EventsTab</class>
<widget class="QWidget" name="EventsTab">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>563</width>
<height>571</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QLabel" name="labelEventDelay">
<property name="toolTip">
<string>Delay applied before sending automatically generated events</string>
</property>
<property name="text">
<string>Delay for asynchronous events</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinEventDelay">
<property name="suffix">
<string> ms</string>
</property>
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>3600000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Address related errors</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Address</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="editAddressErrors">
<property name="text">
<string>dummy.user@proton.me</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_15">
<item>
<widget class="QPushButton" name="buttonAddressChanged">
<property name="text">
<string>Address Changed</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonAddressChangedLogout">
<property name="text">
<string>Address Changed Logout</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QCheckBox" name="checkNextCacheChangeWillSucceed">
<property name="text">
<string>Next Cache Change will succeed</string>
</property>
</widget>
</item>
<item row="0" column="3">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="checkIsPortFree">
<property name="text">
<string>Reply true to the next 'Is Port Free' request.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QLabel" name="labelNextBugReportResult">
<property name="text">
<string>Next bug report result</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBugReportResult">
<item>
<property name="text">
<string>Success</string>
</property>
</item>
<item>
<property name="text">
<string>Error</string>
</property>
</item>
<item>
<property name="text">
<string>Data sharing error</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_6">
<property name="verticalSpacing">
<number>8</number>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="buttonUpdateManualReady">
<property name="text">
<string>Update Manual Ready</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="editUpdateVersion">
<property name="text">
<string>4.0</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="buttonUpdateVersionChanged">
<property name="text">
<string>Update version changed</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="buttonUpdateForce">
<property name="text">
<string>Update Force</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="buttonUpdateManualRestart">
<property name="text">
<string>Update manual restart</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="buttonUpdateCheckFinished">
<property name="text">
<string>Update check finished</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="buttonUpdateSilentRestart">
<property name="text">
<string>Update silent restart</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="comboUpdateError">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<item>
<property name="text">
<string>Update manual error</string>
</property>
</item>
<item>
<property name="text">
<string>Update force error</string>
</property>
</item>
<item>
<property name="text">
<string>Update silent error</string>
</property>
</item>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="buttonUpdateError">
<property name="text">
<string>Update error</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="buttonUpdateIsLatest">
<property name="text">
<string>Update is latest</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_5">
<property name="horizontalSpacing">
<number>-1</number>
</property>
<property name="verticalSpacing">
<number>8</number>
</property>
<item row="0" column="1">
<widget class="QPushButton" name="buttonInternetOn">
<property name="text">
<string>Internet On</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="buttonShowMainWindow">
<property name="text">
<string>Show Main Window</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="buttonInternetOff">
<property name="text">
<string>Internet Off</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="buttonAPICertIssue">
<property name="text">
<string>API Certficate Issue</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="buttonNoKeychain">
<property name="text">
<string>No Keychain</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>200</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,96 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#include "KnowledgeBaseTab.h"
#include "GRPCService.h"
#include "bridgepp/GRPC/EventFactory.h"
using namespace bridgepp;
//****************************************************************************************************************************************************
/// \param[in] parent The parent widget of the tab.
//****************************************************************************************************************************************************
KnowledgeBaseTab::KnowledgeBaseTab(QWidget* parent)
: QWidget(parent) {
ui_.setupUi(this);
connect(ui_.checkSuggestion1, &QCheckBox::stateChanged, this, &KnowledgeBaseTab::updateGuiState);
connect(ui_.checkSuggestion2, &QCheckBox::stateChanged, this, &KnowledgeBaseTab::updateGuiState);
connect(ui_.checkSuggestion3, &QCheckBox::stateChanged, this, &KnowledgeBaseTab::updateGuiState);
connect(ui_.buttonSend, &QCheckBox::clicked, this, &KnowledgeBaseTab::sendKnowledgeBaseSuggestions);
}
//****************************************************************************************************************************************************
/// \param[in] checkbox The check box.
/// \param[in] widgets The widgets to conditionally enable.
//****************************************************************************************************************************************************
void enableWidgetsIfChecked(QCheckBox const* checkbox, QWidgetList const& widgets) {
bool const checked = checkbox->isChecked();
for (QWidget *const widget: widgets) {
widget->setEnabled(checked);
}
}
//****************************************************************************************************************************************************
/// \return The suggestions.
//****************************************************************************************************************************************************
QList<KnowledgeBaseSuggestion> KnowledgeBaseTab::getSuggestions() const {
QList<KnowledgeBaseSuggestion> result;
if (ui_.checkSuggestion1->isChecked()) {
result.push_back({ .url = ui_.editUrl1->text(), .title = ui_.editTitle1->text() });
}
if (ui_.checkSuggestion2->isChecked()) {
result.push_back({ .url = ui_.editUrl2->text(), .title = ui_.editTitle2->text() });
}
if (ui_.checkSuggestion3->isChecked()) {
result.push_back({ .url = ui_.editUrl3->text(), .title = ui_.editTitle3->text() });
}
return result;
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void KnowledgeBaseTab::sendKnowledgeBaseSuggestions() const {
app().grpc().sendEvent(newKnowledgeBaseSuggestionsEvent(this->getSuggestions()));
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void KnowledgeBaseTab::updateGuiState() {
enableWidgetsIfChecked(ui_.checkSuggestion1, { ui_.labelTitle1, ui_.editTitle1, ui_.labelUrl1, ui_.editUrl1});
enableWidgetsIfChecked(ui_.checkSuggestion2, { ui_.labelTitle2, ui_.editTitle2, ui_.labelUrl2, ui_.editUrl2});
enableWidgetsIfChecked(ui_.checkSuggestion3, { ui_.labelTitle3, ui_.editTitle3, ui_.labelUrl3, ui_.editUrl3});
}
//****************************************************************************************************************************************************
/// \param[in] userInput The user input.
//****************************************************************************************************************************************************
void KnowledgeBaseTab::requestKnowledgeBaseSuggestions(QString const& userInput) const {
ui_.editUserInput->setPlainText(userInput);
ui_.labelLastReceived->setText(tr("Last received: %1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz")));
}

View File

@ -0,0 +1,53 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#ifndef BRIDGE_GUI_TESTER_KNOWLEDGE_BASE_TAB_H
#define BRIDGE_GUI_TESTER_KNOWLEDGE_BASE_TAB_H
#include "ui_KnowledgeBaseTab.h"
#include <bridgepp/GRPC/GRPCClient.h>
//****************************************************************************************************************************************************
/// \brief Knowledge base table.
//****************************************************************************************************************************************************
class KnowledgeBaseTab: public QWidget {
public: // member functions.
explicit KnowledgeBaseTab(QWidget *parent = nullptr); ///< Default constructor.
KnowledgeBaseTab(KnowledgeBaseTab const&) = delete; ///< Disabled copy-constructor.
KnowledgeBaseTab(KnowledgeBaseTab&&) = delete; ///< Disabled assignment copy-constructor.
~KnowledgeBaseTab() override = default; ///< Destructor.
KnowledgeBaseTab& operator=(KnowledgeBaseTab const&) = delete; ///< Disabled assignment operator.
KnowledgeBaseTab& operator=(KnowledgeBaseTab&&) = delete; ///< Disabled move assignment operator.
QList<bridgepp::KnowledgeBaseSuggestion> getSuggestions() const; ///< Returns the suggestions.
public slots:
void requestKnowledgeBaseSuggestions(QString const &userInput) const; ///< Slot for the 'RequestKnowledgeBaseSuggestions' gRPC call.
private slots:
void sendKnowledgeBaseSuggestions() const; ///< Send a KnowledgeBaseSuggestions event.
void updateGuiState(); ///< Update the GUI state.
private: // data members
Ui::KnowledgeBaseTab ui_ {}; ///< The UI for the widget.
};
#endif //BRIDGE_GUI_TESTER_KNOWLEDGE_BASE_TAB_H

View File

@ -0,0 +1,239 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KnowledgeBaseTab</class>
<widget class="QWidget" name="KnowledgeBaseTab">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>857</width>
<height>905</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="groupUserInput">
<property name="title">
<string>User Input</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPlainTextEdit" name="editUserInput">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelLastReceived">
<property name="text">
<string>Last received: never</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBoxSuggestion1">
<property name="title">
<string>Suggestion 1</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="checkSuggestion1">
<property name="text">
<string>Enabled</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelTitle1">
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="editTitle1">
<property name="text">
<string>Automatically start Bridge</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelUrl1">
<property name="text">
<string>URL</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="editUrl1">
<property name="text">
<string>https://proton.me/support/automatically-start-bridge</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBoxSuggestion2">
<property name="title">
<string>Suggestion 2</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="checkSuggestion2">
<property name="text">
<string>Enabled</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelTitle2">
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="editTitle2">
<property name="text">
<string>Proton Mail Bridge connection issues with Thunderbird, Outlook, and Apple Mail</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelUrl2">
<property name="text">
<string>URL</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="editUrl2">
<property name="text">
<string>https://proton.me/support/bridge-ssl-connection-issue</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBoxSuggestion3">
<property name="title">
<string>Suggestion 3</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="checkSuggestion3">
<property name="text">
<string>Enabled</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelTitle3">
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="editTitle3">
<property name="text">
<string>Difference between combined addresses mode and split addresses mode</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelUrl3">
<property name="text">
<string>URL</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="editUrl3">
<property name="text">
<string>https://proton.me/support/difference-combined-addresses-mode-split-addresses-mode</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonSend">
<property name="text">
<string>Send Knowledge Base Suggestions</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>208</width>
<height>289</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -18,7 +18,6 @@
#include "SettingsTab.h" #include "SettingsTab.h"
#include "GRPCService.h" #include "GRPCService.h"
#include <bridgepp/GRPC/EventFactory.h>
#include <bridgepp/BridgeUtils.h> #include <bridgepp/BridgeUtils.h>
@ -31,18 +30,6 @@ QString const colorSchemeLight = "light"; ///< THe light color scheme name.
} }
//****************************************************************************************************************************************************
/// \brief Connect an address error button to the generation of an address error event.
///
/// \param[in] button The error button.
/// \param[in] edit The edit containing the address.
/// \param[in] eventGenerator The factory function creating the event.
//****************************************************************************************************************************************************
void connectAddressError(QPushButton *button, QLineEdit* edit, bridgepp::SPStreamEvent (*eventGenerator)(QString const &)) {
QObject::connect(button, &QPushButton::clicked, [edit, eventGenerator]() { app().grpc().sendEvent(eventGenerator(edit->text())); });
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] parent The parent widget of the tab. /// \param[in] parent The parent widget of the tab.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
@ -50,29 +37,6 @@ SettingsTab::SettingsTab(QWidget *parent)
: QWidget(parent) { : QWidget(parent) {
ui_.setupUi(this); ui_.setupUi(this);
connect(ui_.buttonInternetOn, &QPushButton::clicked, []() { app().grpc().sendEvent(newInternetStatusEvent(true)); });
connect(ui_.buttonInternetOff, &QPushButton::clicked, []() { app().grpc().sendEvent(newInternetStatusEvent(false)); });
connect(ui_.buttonShowMainWindow, &QPushButton::clicked, []() { app().grpc().sendEvent(newShowMainWindowEvent()); });
connect(ui_.buttonNoKeychain, &QPushButton::clicked, []() { app().grpc().sendEvent(newHasNoKeychainEvent()); });
connect(ui_.buttonAPICertIssue, &QPushButton::clicked, []() { app().grpc().sendEvent(newApiCertIssueEvent()); });
connectAddressError(ui_.buttonAddressChanged, ui_.editAddressErrors, newAddressChangedEvent);
connectAddressError(ui_.buttonAddressChangedLogout, ui_.editAddressErrors, newAddressChangedLogoutEvent);
connect(ui_.checkNextCacheChangeWillSucceed, &QCheckBox::toggled, this, &SettingsTab::updateGUIState);
connect(ui_.buttonUpdateError, &QPushButton::clicked, [&]() {
app().grpc().sendEvent(newUpdateErrorEvent(static_cast<grpc::UpdateErrorType>(ui_.comboUpdateError->currentIndex())));
});
connect(ui_.buttonUpdateManualReady, &QPushButton::clicked, [&] {
app().grpc().sendEvent(newUpdateManualReadyEvent(ui_.editUpdateVersion->text()));
});
connect(ui_.buttonUpdateForce, &QPushButton::clicked, [&] {
app().grpc().sendEvent(newUpdateForceEvent(ui_.editUpdateVersion->text()));
});
connect(ui_.buttonUpdateManualRestart, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateManualRestartNeededEvent()); });
connect(ui_.buttonUpdateSilentRestart, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateSilentRestartNeededEvent()); });
connect(ui_.buttonUpdateIsLatest, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateIsLatestVersionEvent()); });
connect(ui_.buttonUpdateCheckFinished, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateCheckFinishedEvent()); });
connect(ui_.buttonUpdateVersionChanged, &QPushButton::clicked, []() { app().grpc().sendEvent(newUpdateVersionChangedEvent()); });
this->resetUI(); this->resetUI();
this->updateGUIState(); this->updateGUIState();
} }
@ -82,7 +46,7 @@ SettingsTab::SettingsTab(QWidget *parent)
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::updateGUIState() { void SettingsTab::updateGUIState() {
bool connected = app().grpc().isStreaming(); bool const connected = app().grpc().isStreaming();
for (QWidget *widget: { ui_.groupVersion, ui_.groupGeneral, ui_.groupMail, ui_.groupPaths, ui_.groupCache }) { for (QWidget *widget: { ui_.groupVersion, ui_.groupGeneral, ui_.groupMail, ui_.groupPaths, ui_.groupCache }) {
widget->setEnabled(!connected); widget->setEnabled(!connected);
} }
@ -101,7 +65,7 @@ void SettingsTab::setIsStreaming(bool isStreaming) {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] clientPlatform The client platform. /// \param[in] clientPlatform The client platform.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setClientPlatform(QString const &clientPlatform) { void SettingsTab::setClientPlatform(QString const &clientPlatform) const {
ui_.labelClientPlatformValue->setText(clientPlatform); ui_.labelClientPlatformValue->setText(clientPlatform);
} }
@ -166,7 +130,7 @@ bool SettingsTab::isAutostartOn() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] on Should autostart be turned on? /// \param[in] on Should autostart be turned on?
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setIsAutostartOn(bool on) { void SettingsTab::setIsAutostartOn(bool on) const {
ui_.checkAutostart->setChecked(on); ui_.checkAutostart->setChecked(on);
} }
@ -182,7 +146,7 @@ QString SettingsTab::colorSchemeName() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] name True if the 'Use Dark Theme' check box should be checked. /// \param[in] name True if the 'Use Dark Theme' check box should be checked.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setColorSchemeName(QString const &name) { void SettingsTab::setColorSchemeName(QString const &name) const {
ui_.checkDarkTheme->setChecked(name == colorSchemeDark); ui_.checkDarkTheme->setChecked(name == colorSchemeDark);
} }
@ -198,7 +162,7 @@ bool SettingsTab::isBetaEnabled() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] enabled The new state for the 'Beta Enabled' check box. /// \param[in] enabled The new state for the 'Beta Enabled' check box.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setIsBetaEnabled(bool enabled) { void SettingsTab::setIsBetaEnabled(bool enabled) const {
ui_.checkBetaEnabled->setChecked(enabled); ui_.checkBetaEnabled->setChecked(enabled);
} }
@ -214,7 +178,7 @@ bool SettingsTab::isAllMailVisible() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] visible The new value for the 'All Mail Visible' check box. /// \param[in] visible The new value for the 'All Mail Visible' check box.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setIsAllMailVisible(bool visible) { void SettingsTab::setIsAllMailVisible(bool visible) const {
ui_.checkAllMailVisible->setChecked(visible); ui_.checkAllMailVisible->setChecked(visible);
} }
@ -230,19 +194,11 @@ bool SettingsTab::isTelemetryDisabled() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] isDisabled The new value for the 'Disable Telemetry' check box. /// \param[in] isDisabled The new value for the 'Disable Telemetry' check box.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setIsTelemetryDisabled(bool isDisabled) { void SettingsTab::setIsTelemetryDisabled(bool isDisabled) const {
ui_.checkIsTelemetryDisabled->setChecked(isDisabled); ui_.checkIsTelemetryDisabled->setChecked(isDisabled);
} }
//****************************************************************************************************************************************************
/// \return The delay to apply before sending automatically generated events.
//****************************************************************************************************************************************************
qint32 SettingsTab::eventDelayMs() const {
return ui_.spinEventDelay->value();
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return The path /// \return The path
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
@ -292,7 +248,7 @@ QString SettingsTab::landingPageLink() const {
/// \param[in] includeLogs Are the log included. /// \param[in] includeLogs Are the log included.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, void SettingsTab::setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address,
QString const &description, bool includeLogs) { QString const &description, bool includeLogs) const {
ui_.editOSType->setText(osType); ui_.editOSType->setText(osType);
ui_.editOSVersion->setText(osVersion); ui_.editOSVersion->setText(osVersion);
ui_.editEmailClient->setText(emailClient); ui_.editEmailClient->setText(emailClient);
@ -305,7 +261,7 @@ void SettingsTab::setBugReport(QString const &osType, QString const &osVersion,
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::installTLSCertificate() { void SettingsTab::installTLSCertificate() const {
ui_.labelLastTLSCertInstall->setText(QString("Last install: %1").arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs))); ui_.labelLastTLSCertInstall->setText(QString("Last install: %1").arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs)));
ui_.checkTLSCertIsInstalled->setChecked(this->nextTLSCertInstallResult() == TLSCertInstallResult::Success); ui_.checkTLSCertIsInstalled->setChecked(this->nextTLSCertInstallResult() == TLSCertInstallResult::Success);
} }
@ -314,19 +270,11 @@ void SettingsTab::installTLSCertificate() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] folderPath The folder path. /// \param[in] folderPath The folder path.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::exportTLSCertificates(QString const &folderPath) { void SettingsTab::exportTLSCertificates(QString const &folderPath) const {
ui_.labeLastTLSCertExport->setText(QString("%1 Export to %2").arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs),folderPath)); ui_.labeLastTLSCertExport->setText(QString("%1 Export to %2").arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs),folderPath));
} }
//****************************************************************************************************************************************************
/// \return The state of the check box.
//****************************************************************************************************************************************************
SettingsTab::BugReportResult SettingsTab::nextBugReportResult() const {
return BugReportResult(ui_.comboBugReportResult->currentIndex());
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return the state of the 'TLS Certificate is installed' check box. /// \return the state of the 'TLS Certificate is installed' check box.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
@ -339,7 +287,7 @@ bool SettingsTab::isTLSCertificateInstalled() const {
/// \return The value for the 'Next TLS cert install result'. /// \return The value for the 'Next TLS cert install result'.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
SettingsTab::TLSCertInstallResult SettingsTab::nextTLSCertInstallResult() const { SettingsTab::TLSCertInstallResult SettingsTab::nextTLSCertInstallResult() const {
return TLSCertInstallResult(ui_.comboNextTLSCertInstallResult->currentIndex()); return static_cast<TLSCertInstallResult>(ui_.comboNextTLSCertInstallResult->currentIndex());
} }
@ -370,7 +318,7 @@ QString SettingsTab::hostname() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return The value of the IMAP port spin box. /// \return The value of the IMAP port spin box.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
qint32 SettingsTab::imapPort() { qint32 SettingsTab::imapPort() const {
return ui_.spinPortIMAP->value(); return ui_.spinPortIMAP->value();
} }
@ -378,7 +326,7 @@ qint32 SettingsTab::imapPort() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return The value of the SMTP port spin box. /// \return The value of the SMTP port spin box.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
qint32 SettingsTab::smtpPort() { qint32 SettingsTab::smtpPort() const {
return ui_.spinPortSMTP->value(); return ui_.spinPortSMTP->value();
} }
@ -389,7 +337,7 @@ qint32 SettingsTab::smtpPort() {
/// \param[in] useSSLForIMAP The IMAP connexion mode. /// \param[in] useSSLForIMAP The IMAP connexion mode.
/// \param[in] useSSLForSMTP The IMAP connexion mode. /// \param[in] useSSLForSMTP The IMAP connexion mode.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) { void SettingsTab::setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) const {
ui_.spinPortIMAP->setValue(imapPort); ui_.spinPortIMAP->setValue(imapPort);
ui_.spinPortSMTP->setValue(smtpPort); ui_.spinPortSMTP->setValue(smtpPort);
ui_.checkUseSSLForIMAP->setChecked(useSSLForIMAP); ui_.checkUseSSLForIMAP->setChecked(useSSLForIMAP);
@ -424,23 +372,15 @@ bool SettingsTab::isDoHEnabled() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] enabled The state of the 'DoH enabled' check box. /// \param[in] enabled The state of the 'DoH enabled' check box.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setIsDoHEnabled(bool enabled) { void SettingsTab::setIsDoHEnabled(bool enabled) const {
ui_.checkDoHEnabled->setChecked(enabled); ui_.checkDoHEnabled->setChecked(enabled);
} }
//****************************************************************************************************************************************************
/// \return The reply for the next IsPortFree gRPC call.
//****************************************************************************************************************************************************
bool SettingsTab::isPortFree() const {
return ui_.checkIsPortFree->isChecked();
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] path The path of the local cache. /// \param[in] path The path of the local cache.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setDiskCachePath(const QString &path) { void SettingsTab::setDiskCachePath(const QString &path) const {
ui_.editDiskCachePath->setText(path); ui_.editDiskCachePath->setText(path);
} }
@ -453,14 +393,6 @@ QString SettingsTab::diskCachePath() const {
} }
//****************************************************************************************************************************************************
/// \return The value for the 'Next Cache Change Will Succeed' check box.
//****************************************************************************************************************************************************
bool SettingsTab::nextCacheChangeWillSucceed() const {
return ui_.checkNextCacheChangeWillSucceed->isChecked();
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return the value for the 'Automatic Update' check. /// \return the value for the 'Automatic Update' check.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
@ -472,7 +404,7 @@ bool SettingsTab::isAutomaticUpdateOn() const {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] on The value for the 'Automatic Update' check. /// \param[in] on The value for the 'Automatic Update' check.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void SettingsTab::setIsAutomaticUpdateOn(bool on) { void SettingsTab::setIsAutomaticUpdateOn(bool on) const {
ui_.checkAutomaticUpdate->setChecked(on); ui_.checkAutomaticUpdate->setChecked(on);
} }
@ -521,19 +453,16 @@ void SettingsTab::resetUI() {
ui_.editAddress->setText(QString()); ui_.editAddress->setText(QString());
ui_.editDescription->setPlainText(QString()); ui_.editDescription->setPlainText(QString());
ui_.labelIncludeLogsValue->setText(QString()); ui_.labelIncludeLogsValue->setText(QString());
ui_.comboBugReportResult->setCurrentIndex(0);
ui_.editHostname->setText("localhost"); ui_.editHostname->setText("localhost");
ui_.spinPortIMAP->setValue(1143); ui_.spinPortIMAP->setValue(1143);
ui_.spinPortSMTP->setValue(1025); ui_.spinPortSMTP->setValue(1025);
ui_.checkUseSSLForSMTP->setChecked(false); ui_.checkUseSSLForSMTP->setChecked(false);
ui_.checkDoHEnabled->setChecked(true); ui_.checkDoHEnabled->setChecked(true);
ui_.checkIsPortFree->setChecked(true);
QString const cacheDir = QDir(tmpDir).absoluteFilePath("cache"); QString const cacheDir = QDir(tmpDir).absoluteFilePath("cache");
QDir().mkpath(cacheDir); QDir().mkpath(cacheDir);
ui_.editDiskCachePath->setText(QDir::toNativeSeparators(cacheDir)); ui_.editDiskCachePath->setText(QDir::toNativeSeparators(cacheDir));
ui_.checkNextCacheChangeWillSucceed->setChecked(true);
ui_.checkAutomaticUpdate->setChecked(true); ui_.checkAutomaticUpdate->setChecked(true);

View File

@ -35,12 +35,6 @@ public: // data types.
Failure = 2 Failure = 2
}; ///< Enumeration for the result of a TLS certificate installation. }; ///< Enumeration for the result of a TLS certificate installation.
enum class BugReportResult {
Success = 0,
Error = 1,
DataSharingError = 2,
}; ///< Enumeration for the result of bug report sending
public: // member functions. public: // member functions.
explicit SettingsTab(QWidget *parent = nullptr); ///< Default constructor. explicit SettingsTab(QWidget *parent = nullptr); ///< Default constructor.
SettingsTab(SettingsTab const &) = delete; ///< Disabled copy-constructor. SettingsTab(SettingsTab const &) = delete; ///< Disabled copy-constructor.
@ -60,45 +54,41 @@ public: // member functions.
bool isAllMailVisible() const; ///< Get the value for the 'All Mail Visible' check. bool isAllMailVisible() const; ///< Get the value for the 'All Mail Visible' check.
bool isTelemetryDisabled() const; ///< Get the value for the 'Disable Telemetry' check box. bool isTelemetryDisabled() const; ///< Get the value for the 'Disable Telemetry' check box.
QString colorSchemeName() const; ///< Get the value of the 'Use Dark Theme' checkbox. QString colorSchemeName() const; ///< Get the value of the 'Use Dark Theme' checkbox.
qint32 eventDelayMs() const; ///< Get the delay for sending automatically generated events.
QString logsPath() const; ///< Get the content of the 'Logs Path' edit. QString logsPath() const; ///< Get the content of the 'Logs Path' edit.
QString licensePath() const; ///< Get the content of the 'License Path' edit. QString licensePath() const; ///< Get the content of the 'License Path' edit.
QString releaseNotesPageLink() const; ///< Get the content of the 'Release Notes Page Link' edit. QString releaseNotesPageLink() const; ///< Get the content of the 'Release Notes Page Link' edit.
QString dependencyLicenseLink() const; ///< Get the content of the 'Dependency License Link' edit. QString dependencyLicenseLink() const; ///< Get the content of the 'Dependency License Link' edit.
QString landingPageLink() const; ///< Get the content of the 'Landing Page Link' edit. QString landingPageLink() const; ///< Get the content of the 'Landing Page Link' edit.
BugReportResult nextBugReportResult() const; ///< Get the value of the 'Next bug report result' combo box.
bool isTLSCertificateInstalled() const; ///< Get the status of the 'TLS Certificate is installed' check box. bool isTLSCertificateInstalled() const; ///< Get the status of the 'TLS Certificate is installed' check box.
TLSCertInstallResult nextTLSCertInstallResult() const; ///< Get the value of the 'Next TLS Certificate install result' combo box. TLSCertInstallResult nextTLSCertInstallResult() const; ///< Get the value of the 'Next TLS Certificate install result' combo box.
bool nextTLSCertExportWillSucceed() const; ///< Get the status of the 'Next TLS Cert export will succeed' check box. bool nextTLSCertExportWillSucceed() const; ///< Get the status of the 'Next TLS Cert export will succeed' check box.
bool nextTLSKeyExportWillSucceed() const; ///< Get the status of the 'Next TLS Key export will succeed' check box. bool nextTLSKeyExportWillSucceed() const; ///< Get the status of the 'Next TLS Key export will succeed' check box.
QString hostname() const; ///< Get the value of the 'Hostname' edit. QString hostname() const; ///< Get the value of the 'Hostname' edit.
qint32 imapPort(); ///< Get the value of the IMAP port spin. qint32 imapPort() const; ///< Get the value of the IMAP port spin.
qint32 smtpPort(); ///< Get the value of the SMTP port spin. qint32 smtpPort() const; ///< Get the value of the SMTP port spin.
bool useSSLForSMTP() const; ///< Get the value for the 'Use SSL for SMTP' check box. bool useSSLForSMTP() const; ///< Get the value for the 'Use SSL for SMTP' check box.
bool useSSLForIMAP() const; ///< Get the value for the 'Use SSL for IMAP' check box. bool useSSLForIMAP() const; ///< Get the value for the 'Use SSL for IMAP' check box.
bool isDoHEnabled() const; ///< Get the value for the 'DoH Enabled' check box. bool isDoHEnabled() const; ///< Get the value for the 'DoH Enabled' check box.
bool isPortFree() const; ///< Get the value for the "Is Port Free" check box.
QString diskCachePath() const; ///< Get the value for the 'Disk Cache Path' edit. QString diskCachePath() const; ///< Get the value for the 'Disk Cache Path' edit.
bool nextCacheChangeWillSucceed() const; ///< Get the value for the 'Next Cache Change will succeed' edit.
bool isAutomaticUpdateOn() const; ///<Get the value for the 'Automatic Update' check box. bool isAutomaticUpdateOn() const; ///<Get the value for the 'Automatic Update' check box.
public slots: public slots:
void updateGUIState(); ///< Update the GUI state. void updateGUIState(); ///< Update the GUI state.
void setIsStreaming(bool isStreaming); ///< Set the isStreamingEvents value. void setIsStreaming(bool isStreaming); ///< Set the isStreamingEvents value.
void setClientPlatform(QString const &clientPlatform); ///< Set the client platform. void setClientPlatform(QString const &clientPlatform) const; ///< Set the client platform.
void setIsAutostartOn(bool on); ///< Set the value for the 'Autostart' check box. void setIsAutostartOn(bool on) const; ///< Set the value for the 'Autostart' check box.
void setIsBetaEnabled(bool enabled); ///< Set the value for the 'Beta Enabled' check box. void setIsBetaEnabled(bool enabled) const; ///< Set the value for the 'Beta Enabled' check box.
void setIsAllMailVisible(bool visible); ///< Set the value for the 'All Mail Visible' check box. void setIsAllMailVisible(bool visible) const; ///< Set the value for the 'All Mail Visible' check box.
void setIsTelemetryDisabled(bool isDisabled); ///< Set the value for the 'Disable Telemetry' check box. void setIsTelemetryDisabled(bool isDisabled) const; ///< Set the value for the 'Disable Telemetry' check box.
void setColorSchemeName(QString const &name); ///< Set the value for the 'Use Dark Theme' check box. void setColorSchemeName(QString const &name) const; ///< Set the value for the 'Use Dark Theme' check box.
void setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description, void setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description,
bool includeLogs); ///< Set the content of the bug report box. bool includeLogs) const; ///< Set the content of the bug report box.
void installTLSCertificate(); ///< Install the TLS certificate. void installTLSCertificate() const; ///< Install the TLS certificate.
void exportTLSCertificates(QString const &folderPath); ///< Export the TLS certificates. void exportTLSCertificates(QString const &folderPath) const; ///< Export the TLS certificates.
void setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP); ///< Change the mail server settings. void setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP) const; ///< Change the mail server settings.
void setIsDoHEnabled(bool enabled); ///< Set the value for the 'DoH Enabled' check box. void setIsDoHEnabled(bool enabled) const; ///< Set the value for the 'DoH Enabled' check box.
void setDiskCachePath(QString const &path); ///< Set the value for the 'Cache On Disk Enabled' check box. void setDiskCachePath(QString const &path) const; ///< Set the value for the 'Cache On Disk Enabled' check box.
void setIsAutomaticUpdateOn(bool on); ///< Set the value for the 'Automatic Update' check box. void setIsAutomaticUpdateOn(bool on) const; ///< Set the value for the 'Automatic Update' check box.
private: // member functions. private: // member functions.
void resetUI(); ///< Reset the widget. void resetUI(); ///< Reset the widget.

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1160</width> <width>1146</width>
<height>777</height> <height>716</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -208,7 +208,7 @@
<widget class="QLineEdit" name="editHostname"> <widget class="QLineEdit" name="editHostname">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>200</width> <width>0</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -749,328 +749,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupApp">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Events &amp;&amp; Errors</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<property name="spacing">
<number>4</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QLabel" name="labelEventDelay">
<property name="toolTip">
<string>Delay applied before sending automatically generated events</string>
</property>
<property name="text">
<string>Delay for asynchronous events</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinEventDelay">
<property name="suffix">
<string> ms</string>
</property>
<property name="minimum">
<number>-1</number>
</property>
<property name="maximum">
<number>3600000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_5">
<property name="horizontalSpacing">
<number>-1</number>
</property>
<property name="verticalSpacing">
<number>8</number>
</property>
<item row="0" column="1">
<widget class="QPushButton" name="buttonInternetOn">
<property name="text">
<string>Internet On</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="buttonShowMainWindow">
<property name="text">
<string>Show Main Window</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="buttonInternetOff">
<property name="text">
<string>Internet Off</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="buttonAPICertIssue">
<property name="text">
<string>API Certficate Issue</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="buttonNoKeychain">
<property name="text">
<string>No Keychain</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Address related errors</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Address</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="editAddressErrors">
<property name="text">
<string>dummy.user@proton.me</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_15">
<item>
<widget class="QPushButton" name="buttonAddressChanged">
<property name="text">
<string>Address Changed</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonAddressChangedLogout">
<property name="text">
<string>Address Changed Logout</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkIsPortFree">
<property name="text">
<string>Reply true to the next 'Is Port Free' request.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkNextCacheChangeWillSucceed">
<property name="text">
<string>Next Cache Change will succeed</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QLabel" name="labelNextBugReportResult">
<property name="text">
<string>Next bug report result</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBugReportResult">
<item>
<property name="text">
<string>Success</string>
</property>
</item>
<item>
<property name="text">
<string>Error</string>
</property>
</item>
<item>
<property name="text">
<string>Data sharing error</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_6">
<property name="verticalSpacing">
<number>8</number>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="buttonUpdateManualReady">
<property name="text">
<string>Update Manual Ready</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="editUpdateVersion">
<property name="text">
<string>4.0</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="buttonUpdateVersionChanged">
<property name="text">
<string>Update version changed</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="buttonUpdateForce">
<property name="text">
<string>Update Force</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="buttonUpdateManualRestart">
<property name="text">
<string>Update manual restart</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="buttonUpdateCheckFinished">
<property name="text">
<string>Update check finished</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="buttonUpdateSilentRestart">
<property name="text">
<string>Update silent restart</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="comboUpdateError">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<item>
<property name="text">
<string>Update manual error</string>
</property>
</item>
<item>
<property name="text">
<string>Update force error</string>
</property>
</item>
<item>
<property name="text">
<string>Update silent error</string>
</property>
</item>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="buttonUpdateError">
<property name="text">
<string>Update error</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="buttonUpdateIsLatest">
<property name="text">
<string>Update is latest</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>
@ -1096,8 +774,6 @@
<tabstop>editDiskCachePath</tabstop> <tabstop>editDiskCachePath</tabstop>
<tabstop>editOSVersion</tabstop> <tabstop>editOSVersion</tabstop>
<tabstop>editEmailClient</tabstop> <tabstop>editEmailClient</tabstop>
<tabstop>spinEventDelay</tabstop>
<tabstop>checkIsPortFree</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -85,7 +85,7 @@ void UsersTab::onAddUserButton() {
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void UsersTab::onEditUserButton() { void UsersTab::onEditUserButton() {
int index = selectedIndex(); const int index = selectedIndex();
if ((index < 0) || (index >= users_.userCount())) { if ((index < 0) || (index >= users_.userCount())) {
return; return;
} }
@ -110,7 +110,7 @@ void UsersTab::onEditUserButton() {
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void UsersTab::onRemoveUserButton() { void UsersTab::onRemoveUserButton() {
int index = selectedIndex(); const int index = selectedIndex();
if ((index < 0) || (index >= users_.userCount())) { if ((index < 0) || (index >= users_.userCount())) {
return; return;
} }
@ -127,7 +127,7 @@ void UsersTab::onRemoveUserButton() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void UsersTab::onSelectionChanged(QItemSelection, QItemSelection) { void UsersTab::onSelectionChanged(QItemSelection const&, QItemSelection const&) {
this->updateGUIState(); this->updateGUIState();
} }
@ -137,7 +137,6 @@ void UsersTab::onSelectionChanged(QItemSelection, QItemSelection) {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void UsersTab::onSendUserBadEvent() { void UsersTab::onSendUserBadEvent() {
SPUser const user = selectedUser(); SPUser const user = selectedUser();
int const index = this->selectedIndex();
if (!user) { if (!user) {
app().log().error(QString("%1 failed. Unkown user.").arg(__FUNCTION__)); app().log().error(QString("%1 failed. Unkown user.").arg(__FUNCTION__));
@ -175,8 +174,8 @@ void UsersTab::onSendUsedBytesChangedEvent() {
app().log().error(QString("%1 failed. User is not connected").arg(__FUNCTION__)); app().log().error(QString("%1 failed. User is not connected").arg(__FUNCTION__));
} }
qint64 const usedBytes = qint64(ui_.spinUsedBytes->value()); auto const usedBytes = static_cast<qint64>(ui_.spinUsedBytes->value());
user->setUsedBytes(usedBytes); user->setUsedBytes(static_cast<float>(usedBytes));
users_.touch(index); users_.touch(index);
GRPCService &grpc = app().grpc(); GRPCService &grpc = app().grpc();
@ -224,9 +223,10 @@ void UsersTab::updateGUIState() {
QSignalBlocker b(ui_.checkSync); QSignalBlocker b(ui_.checkSync);
bool const syncing = user && user->isSyncing(); bool const syncing = user && user->isSyncing();
ui_.checkSync->setChecked(syncing); ui_.checkSync->setChecked(syncing);
// ReSharper disable once CppDFAUnusedValue
b = QSignalBlocker(ui_.sliderSync); b = QSignalBlocker(ui_.sliderSync);
ui_.sliderSync->setEnabled(syncing); ui_.sliderSync->setEnabled(syncing);
qint32 const progressPercent = syncing ? qint32(user->syncProgress() * 100.0f) : 0; qint32 const progressPercent = syncing ? static_cast<qint32>(user->syncProgress() * 100.0f) : 0;
ui_.sliderSync->setValue(progressPercent); ui_.sliderSync->setValue(progressPercent);
ui_.labelSync->setText(syncing ? QString("%1%").arg(progressPercent) : "" ); ui_.labelSync->setText(syncing ? QString("%1%").arg(progressPercent) : "" );
} }
@ -418,7 +418,7 @@ void UsersTab::processBadEventUserFeedback(QString const &userID, bool doResync)
return; // we do not do any form of emulation for resync. return; // we do not do any form of emulation for resync.
} }
SPUser user = users_.userWithID(userID); SPUser const user = users_.userWithID(userID);
if (!user) { if (!user) {
app().log().error(QString("%1(): could not find user with id %1.").arg(__func__, userID)); app().log().error(QString("%1(): could not find user with id %1.").arg(__func__, userID));
} }
@ -464,12 +464,12 @@ void UsersTab::onCheckSyncToggled(bool checked) {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void UsersTab::onSliderSyncValueChanged(int value) { void UsersTab::onSliderSyncValueChanged(int value) {
SPUser const user = this->selectedUser(); SPUser const user = this->selectedUser();
if ((!user) || (!user->isSyncing()) || user->syncProgress() == value) { if ((!user) || (!user->isSyncing()) || user->syncProgress() == static_cast<float>(value)) {
return; return;
} }
double const progress = value / 100.0; double const progress = value / 100.0;
user->setSyncProgress(progress); user->setSyncProgress(static_cast<float>(progress));
app().grpc().sendEvent(newSyncProgressEvent(user->id(), progress, 1, 1)); // we do not simulate elapsed & remaining. app().grpc().sendEvent(newSyncProgressEvent(user->id(), progress, 1, 1)); // we do not simulate elapsed & remaining.
this->updateGUIState(); this->updateGUIState();
} }

View File

@ -53,14 +53,14 @@ public slots:
void setUserSplitMode(QString const &userID, bool makeItActive); ///< Slot for the split mode. void setUserSplitMode(QString const &userID, bool makeItActive); ///< Slot for the split mode.
void logoutUser(QString const &userID); ///< slot for the logging out of a user. void logoutUser(QString const &userID); ///< slot for the logging out of a user.
void removeUser(QString const &userID); ///< Slot for the removal of a user. void removeUser(QString const &userID); ///< Slot for the removal of a user.
void configureUserAppleMail(QString const &userID, QString const &address); ///< Slot for the configuration of Apple mail. static void configureUserAppleMail(QString const &userID, QString const &address); ///< Slot for the configuration of Apple mail.
void processBadEventUserFeedback(QString const& userID, bool doResync); ///< Slot for the reception of a bad event user feedback. void processBadEventUserFeedback(QString const& userID, bool doResync); ///< Slot for the reception of a bad event user feedback.
private slots: private slots:
void onAddUserButton(); ///< Add a user to the user list. void onAddUserButton(); ///< Add a user to the user list.
void onEditUserButton(); ///< Edit the currently selected user. void onEditUserButton(); ///< Edit the currently selected user.
void onRemoveUserButton(); ///< Remove the currently selected user. void onRemoveUserButton(); ///< Remove the currently selected user.
void onSelectionChanged(QItemSelection, QItemSelection); ///< Slot for the change of the selection. void onSelectionChanged(QItemSelection const&, QItemSelection const&); ///< Slot for the change of the selection.
void onSendUserBadEvent(); ///< Slot for the 'Send Bad Event Error' button. void onSendUserBadEvent(); ///< Slot for the 'Send Bad Event Error' button.
void onSendUsedBytesChangedEvent(); ///< Slot for the 'Send Used Bytes Changed Event' button. void onSendUsedBytesChangedEvent(); ///< Slot for the 'Send Used Bytes Changed Event' button.
void onSendIMAPLoginFailedEvent(); ///< Slot for the 'Send IMAP Login failure Event' button. void onSendIMAPLoginFailedEvent(); ///< Slot for the 'Send IMAP Login failure Event' button.

View File

@ -26,7 +26,7 @@ using namespace bridgepp;
/// \param[in] user The user. /// \param[in] user The user.
/// \param[in] parent The parent widget of the dialog. /// \param[in] parent The parent widget of the dialog.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
UserDialog::UserDialog(bridgepp::SPUser &user, QWidget *parent) UserDialog::UserDialog(const bridgepp::SPUser &user, QWidget *parent)
: QDialog(parent) : QDialog(parent)
, user_(user) { , user_(user) {
ui_.setupUi(this); ui_.setupUi(this);
@ -57,8 +57,8 @@ void UserDialog::onOK() {
user_->setAvatarText(ui_.editAvatarText->text()); user_->setAvatarText(ui_.editAvatarText->text());
user_->setState(this->state()); user_->setState(this->state());
user_->setSplitMode(ui_.checkSplitMode->isChecked()); user_->setSplitMode(ui_.checkSplitMode->isChecked());
user_->setUsedBytes(float(ui_.spinUsedBytes->value())); user_->setUsedBytes(static_cast<float>(ui_.spinUsedBytes->value()));
user_->setTotalBytes(float(ui_.spinTotalBytes->value())); user_->setTotalBytes(static_cast<float>(ui_.spinTotalBytes->value()));
this->accept(); this->accept();
} }
@ -67,14 +67,14 @@ void UserDialog::onOK() {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return The user state that is currently selected in the dialog. /// \return The user state that is currently selected in the dialog.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
UserState UserDialog::state() { UserState UserDialog::state() const {
return UserState(ui_.comboState->currentIndex()); return static_cast<UserState>(ui_.comboState->currentIndex());
} }
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] state The user state to select in the dialog. /// \param[in] state The user state to select in the dialog.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void UserDialog::setState(UserState state) { void UserDialog::setState(UserState state) const {
ui_.comboState->setCurrentIndex(qint32(state)); ui_.comboState->setCurrentIndex(static_cast<qint32>(state));
} }

View File

@ -30,7 +30,7 @@
class UserDialog : public QDialog { class UserDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: // member functions. public: // member functions.
UserDialog(bridgepp::SPUser &user, QWidget *parent); ///< Default constructor. UserDialog(const bridgepp::SPUser &user, QWidget *parent); ///< Default constructor.
UserDialog(UserDialog const &) = delete; ///< Disabled copy-constructor. UserDialog(UserDialog const &) = delete; ///< Disabled copy-constructor.
UserDialog(UserDialog &&) = delete; ///< Disabled assignment copy-constructor. UserDialog(UserDialog &&) = delete; ///< Disabled assignment copy-constructor.
~UserDialog() override = default; ///< Destructor. ~UserDialog() override = default; ///< Destructor.
@ -38,8 +38,8 @@ public: // member functions.
UserDialog &operator=(UserDialog &&) = delete; ///< Disabled move assignment operator. UserDialog &operator=(UserDialog &&) = delete; ///< Disabled move assignment operator.
private: // member functions private: // member functions
bridgepp::UserState state(); ///< Get the user state selected in the dialog. bridgepp::UserState state() const; ///< Get the user state selected in the dialog.
void setState(bridgepp::UserState state); ///< Set the user state selected in the dialog void setState(bridgepp::UserState state) const; ///< Set the user state selected in the dialog
private slots: private slots:
void onOK(); ///< Slot for the OK button. void onOK(); ///< Slot for the OK button.

View File

@ -35,7 +35,7 @@ UserTable::UserTable(QObject *parent)
/// \return The number of rows in the table. /// \return The number of rows in the table.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
int UserTable::rowCount(QModelIndex const &) const { int UserTable::rowCount(QModelIndex const &) const {
return users_.size(); return static_cast<int>(users_.size());
} }
@ -111,7 +111,7 @@ QVariant UserTable::headerData(int section, Qt::Orientation orientation, int rol
/// \param[in] user The user to add. /// \param[in] user The user to add.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
void UserTable::append(SPUser const &user) { void UserTable::append(SPUser const &user) {
qint32 const count = users_.size(); qint32 const count = static_cast<int>(users_.size());
this->beginInsertRows(QModelIndex(), count, count); this->beginInsertRows(QModelIndex(), count, count);
users_.append(user); users_.append(user);
this->endInsertRows(); this->endInsertRows();
@ -122,7 +122,7 @@ void UserTable::append(SPUser const &user) {
/// \return The number of users in the table. /// \return The number of users in the table.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
qint32 UserTable::userCount() const { qint32 UserTable::userCount() const {
return users_.count(); return static_cast<qint32>(users_.count());
} }
@ -141,7 +141,7 @@ bridgepp::SPUser UserTable::userAtIndex(qint32 index) {
/// \return A null pointer if the user is not in the list. /// \return A null pointer if the user is not in the list.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
bridgepp::SPUser UserTable::userWithID(QString const &userID) { bridgepp::SPUser UserTable::userWithID(QString const &userID) {
QList<SPUser>::const_iterator it = std::find_if(users_.constBegin(), users_.constEnd(), [&userID](SPUser const &user) -> bool { QList<SPUser>::const_iterator const it = std::find_if(users_.constBegin(), users_.constEnd(), [&userID](SPUser const &user) -> bool {
return user->id() == userID; return user->id() == userID;
}); });
@ -155,7 +155,7 @@ bridgepp::SPUser UserTable::userWithID(QString const &userID) {
/// \return A null pointer if the user is not in the list. /// \return A null pointer if the user is not in the list.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
bridgepp::SPUser UserTable::userWithUsernameOrEmail(QString const &username) { bridgepp::SPUser UserTable::userWithUsernameOrEmail(QString const &username) {
QList<SPUser>::const_iterator it = std::find_if(users_.constBegin(), users_.constEnd(), [&username](SPUser const &user) -> bool { QList<SPUser>::const_iterator const it = std::find_if(users_.constBegin(), users_.constEnd(), [&username](SPUser const &user) -> bool {
if (user->username().compare(username, Qt::CaseInsensitive) == 0) { if (user->username().compare(username, Qt::CaseInsensitive) == 0) {
return true; return true;
} }
@ -172,7 +172,7 @@ bridgepp::SPUser UserTable::userWithUsernameOrEmail(QString const &username) {
/// \return -1 if the user could not be found. /// \return -1 if the user could not be found.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
qint32 UserTable::indexOfUser(QString const &userID) { qint32 UserTable::indexOfUser(QString const &userID) {
QList<SPUser>::const_iterator it = std::find_if(users_.constBegin(), users_.constEnd(), [&userID](SPUser const &user) -> bool { QList<SPUser>::const_iterator const it = std::find_if(users_.constBegin(), users_.constEnd(), [&userID](SPUser const &user) -> bool {
return user->id() == userID; return user->id() == userID;
}); });

View File

@ -33,7 +33,7 @@ public: // member functions.
explicit UserTable(QObject *parent); ///< Default constructor. explicit UserTable(QObject *parent); ///< Default constructor.
UserTable(UserTable const &) = delete; ///< Disabled copy-constructor. UserTable(UserTable const &) = delete; ///< Disabled copy-constructor.
UserTable(UserTable &&) = delete; ///< Disabled assignment copy-constructor. UserTable(UserTable &&) = delete; ///< Disabled assignment copy-constructor.
~UserTable() = default; ///< Destructor. ~UserTable() override = default; ///< Destructor.
UserTable &operator=(UserTable const &) = delete; ///< Disabled assignment operator. UserTable &operator=(UserTable const &) = delete; ///< Disabled assignment operator.
UserTable &operator=(UserTable &&) = delete; ///< Disabled move assignment operator. UserTable &operator=(UserTable &&) = delete; ///< Disabled move assignment operator.
qint32 userCount() const; ///< Return the number of users in the table. qint32 userCount() const; ///< Return the number of users in the table.

View File

@ -71,7 +71,7 @@ int main(int argc, char **argv) {
app().log().error(message); app().log().error(message);
qApp->exit(EXIT_FAILURE); qApp->exit(EXIT_FAILURE);
}); });
UPOverseer overseer = std::make_unique<Overseer>(serverWorker, nullptr); UPOverseer const overseer = std::make_unique<Overseer>(serverWorker, nullptr);
overseer->startWorker(true); overseer->startWorker(true);
qint32 const exitCode = QApplication::exec(); qint32 const exitCode = QApplication::exec();

View File

@ -303,6 +303,16 @@ void QMLBackend::openExternalLink(QString const &url) {
} }
//****************************************************************************************************************************************************
/// \param[in] categoryID The ID of the bug report category.
//****************************************************************************************************************************************************
void QMLBackend::requestKnowledgeBaseSuggestions(qint8 categoryID) const {
HANDLE_EXCEPTION(
app().grpc().requestKnowledgeBaseSuggestions(reportFlow_.collectUserInput(categoryID));
)
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return The value for the 'showOnStartup' property. /// \return The value for the 'showOnStartup' property.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
@ -1305,6 +1315,7 @@ void QMLBackend::connectGrpcEvents() {
connect(client, &GRPCClient::certificateInstallCanceled, this, &QMLBackend::certificateInstallCanceled); connect(client, &GRPCClient::certificateInstallCanceled, this, &QMLBackend::certificateInstallCanceled);
connect(client, &GRPCClient::certificateInstallFailed, this, &QMLBackend::certificateInstallFailed); connect(client, &GRPCClient::certificateInstallFailed, this, &QMLBackend::certificateInstallFailed);
connect(client, &GRPCClient::showMainWindow, [&]() { this->showMainWindow("gRPC showMainWindow event"); }); connect(client, &GRPCClient::showMainWindow, [&]() { this->showMainWindow("gRPC showMainWindow event"); });
connect(client, &GRPCClient::knowledgeBasSuggestionsReceived, this, &QMLBackend::receivedKnowledgeBaseSuggestions);
// cache events // cache events
connect(client, &GRPCClient::cantMoveDiskCache, this, &QMLBackend::cantMoveDiskCache); connect(client, &GRPCClient::cantMoveDiskCache, this, &QMLBackend::cantMoveDiskCache);

View File

@ -66,6 +66,7 @@ public: // member functions.
Q_INVOKABLE void clearAnswers(); ///< Clear all collected answers. Q_INVOKABLE void clearAnswers(); ///< Clear all collected answers.
Q_INVOKABLE bool isTLSCertificateInstalled(); ///< Check if the bridge certificate is installed in the OS keychain. Q_INVOKABLE bool isTLSCertificateInstalled(); ///< Check if the bridge certificate is installed in the OS keychain.
Q_INVOKABLE void openExternalLink(QString const & url = QString()); ///< Open a knowledge base article. Q_INVOKABLE void openExternalLink(QString const & url = QString()); ///< Open a knowledge base article.
Q_INVOKABLE void requestKnowledgeBaseSuggestions(qint8 categoryID) const; ///< Request knowledgebase article suggestions.
public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise) public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise)
Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged) Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged)
@ -278,6 +279,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML
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.
void receivedKnowledgeBaseSuggestions(QList<bridgepp::KnowledgeBaseSuggestion> const& suggestions); ///< Signal for the reception of knowledgebase article suggestions.
// This signal is emitted when an exception is intercepted is calls triggered by QML. QML engine would intercept the exception otherwise. // This signal is emitted when an exception is intercepted is calls triggered by QML. QML engine would intercept the exception otherwise.
void fatalError(bridgepp::Exception const& e) const; ///< Signal emitted when an fatal error occurs. void fatalError(bridgepp::Exception const& e) const; ///< Signal emitted when an fatal error occurs.

View File

@ -5,11 +5,6 @@
<file>qml/AccountView.qml</file> <file>qml/AccountView.qml</file>
<file>qml/Banner.qml</file> <file>qml/Banner.qml</file>
<file>qml/Bridge.qml</file> <file>qml/Bridge.qml</file>
<file>qml/BugCategoryView.qml</file>
<file>qml/BugQuestionView.qml</file>
<file>qml/BugReportFlow.qml</file>
<file>qml/BugReportView.qml</file>
<file>qml/CategoryItem.qml</file>
<file>qml/Configuration.qml</file> <file>qml/Configuration.qml</file>
<file>qml/ConfigurationItem.qml</file> <file>qml/ConfigurationItem.qml</file>
<file>qml/ContentWrapper.qml</file> <file>qml/ContentWrapper.qml</file>
@ -89,6 +84,12 @@
<file>qml/Notifications/Notifications.qml</file> <file>qml/Notifications/Notifications.qml</file>
<file>qml/Notifications/qmldir</file> <file>qml/Notifications/qmldir</file>
<file>qml/PortSettings.qml</file> <file>qml/PortSettings.qml</file>
<file>qml/BugReport/BugCategoryView.qml</file>
<file>qml/BugReport/BugQuestionView.qml</file>
<file>qml/BugReport/BugReportFlow.qml</file>
<file>qml/BugReport/BugReportView.qml</file>
<file>qml/BugReport/CategoryItem.qml</file>
<file>qml/BugReport/QuestionItem.qml</file>
<file>qml/Proton/Action.qml</file> <file>qml/Proton/Action.qml</file>
<file>qml/Proton/ApplicationWindow.qml</file> <file>qml/Proton/ApplicationWindow.qml</file>
<file>qml/Proton/Button.qml</file> <file>qml/Proton/Button.qml</file>
@ -96,6 +97,7 @@
<file>qml/Proton/ColorScheme.qml</file> <file>qml/Proton/ColorScheme.qml</file>
<file>qml/Proton/ComboBox.qml</file> <file>qml/Proton/ComboBox.qml</file>
<file>qml/Proton/Dialog.qml</file> <file>qml/Proton/Dialog.qml</file>
<file>qml/Proton/InfoTooltip.qml</file>
<file>qml/Proton/Label.qml</file> <file>qml/Proton/Label.qml</file>
<file>qml/Proton/LinkLabel.qml</file> <file>qml/Proton/LinkLabel.qml</file>
<file>qml/Proton/Menu.qml</file> <file>qml/Proton/Menu.qml</file>
@ -108,7 +110,6 @@
<file>qml/Proton/TextArea.qml</file> <file>qml/Proton/TextArea.qml</file>
<file>qml/Proton/TextField.qml</file> <file>qml/Proton/TextField.qml</file>
<file>qml/Proton/Toggle.qml</file> <file>qml/Proton/Toggle.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/Template.html</file>
<file>qml/Resources/Help/WhyBridge.html</file> <file>qml/Resources/Help/WhyBridge.html</file>

View File

@ -14,6 +14,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import ".."
SettingsView { SettingsView {
id: root id: root

View File

@ -14,6 +14,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import ".."
SettingsView { SettingsView {
id: root id: root

View File

@ -15,6 +15,7 @@ import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import Notifications import Notifications
import ".."
Item { Item {
id: root id: root
@ -35,7 +36,7 @@ Item {
} }
function showBugQuestion() { function showBugQuestion() {
bugQuestion.setCategoryId(root.categoryId); bugQuestion.setCategoryId(root.categoryId);
bugQuestion.positionViewAtBegining(); bugQuestion.positionViewAtBeginning();
bugReportFlow.currentIndex = 1; bugReportFlow.currentIndex = 1;
} }
function showBugReport() { function showBugReport() {
@ -77,6 +78,7 @@ Item {
root.showBugCategory(); root.showBugCategory();
} }
onQuestionAnswered: { onQuestionAnswered: {
Backend.requestKnowledgeBaseSuggestions(categoryId);
root.showBugReport(); root.showBugReport();
} }
} }

View File

@ -0,0 +1,201 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Proton
import ".."
SettingsView {
id: root
property var selectedAddress
property var categoryId: -1
property string category: Backend.getBugCategory(root.categoryId)
property var suggestions: null
signal bugReportWasSent
function isValidEmail(text) {
const reEmail = /^[^@]+@[^@]+\.[A-Za-z]+\s*$/;
return reEmail.test(text);
}
function setCategoryId(catId) {
root.categoryId = catId;
}
function setDefaultValue() {
description.text = Backend.collectAnswers(root.categoryId);
address.text = root.selectedAddress;
emailClient.text = Backend.currentEmailClient;
includeLogs.checked = true;
}
function submit() {
sendButton.loading = true;
Backend.reportBug(root.category, description.text, address.text, emailClient.text, includeLogs.checked);
}
fillHeight: true
onVisibleChanged: {
root.setDefaultValue();
}
ColumnLayout {
spacing: 32
Label {
colorScheme: root.colorScheme
text: qsTr("Send report")
type: Label.Heading
}
TextArea {
id: description
KeyNavigation.priority: KeyNavigation.BeforeItem
KeyNavigation.tab: address
Layout.fillHeight: true
Layout.fillWidth: true
Layout.minimumHeight: heightForLinesVisible(4)
colorScheme: root.colorScheme
textFormat: Text.MarkdownText
// set implicitHeight to explicit height because se don't
// want TextArea implicitHeight (which is height of all text)
// to be considered in SettingsView internal scroll view
implicitHeight: height
label: "Your answers to: " + qsTr(root.category);
readOnly: true
}
ColumnLayout {
id: suggestionBox
visible: suggestions && suggestions.length > 0
spacing: 8
RowLayout {
Label {
colorScheme: root.colorScheme
text: qsTr("We believe these links may help you solve your problem")
type: Label.Body_semibold
}
InfoTooltip {
colorScheme: root.colorScheme
text: qsTr("The links will open in an external browser. If you cancel the report, your input will be preserved until you restart Bridge.")
Layout.bottomMargin: -4
}
}
Repeater {
model: suggestions
LinkLabel {
required property var modelData
colorScheme: root.colorScheme
text: modelData.title
link: modelData.url
external: true
}
}
}
RowLayout {
spacing: 12
TextField {
id: address
Layout.preferredWidth: 1
Layout.fillWidth: true
colorScheme: root.colorScheme
label: qsTr("Your contact email")
placeholderText: qsTr("e.g. jane.doe@protonmail.com")
validator: function (str) {
if (!isValidEmail(str)) {
return qsTr("Enter valid email address");
}
}
}
TextField {
id: emailClient
Layout.preferredWidth: 1
Layout.fillWidth: true
colorScheme: root.colorScheme
label: qsTr("Your email client (including version)")
placeholderText: qsTr("e.g. Apple Mail 14.0")
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter an email client name and version");
}
}
}
}
RowLayout {
spacing: 12
CheckBox {
id: includeLogs
checked: true
colorScheme: root.colorScheme
text: qsTr("Include my recent logs")
}
Button {
colorScheme: root.colorScheme
secondary: true
text: qsTr("View logs")
onClicked: Backend.openExternalLink(Backend.logsPath)
}
Label {
Layout.fillWidth: true
verticalAlignment: Qt.AlignVCenter
colorScheme: root.colorScheme
type: Label.Caption
color: root.colorScheme.text_weak
text: qsTr("Reports are not end-to-end encrypted, please do not send any sensitive information.")
wrapMode: Text.WordWrap
}
}
Button {
id: sendButton
colorScheme: root.colorScheme
enabled: !loading
text: qsTr("Send")
onClicked: {
description.validate();
address.validate();
emailClient.validate();
if (description.error || address.error || emailClient.error) {
return;
}
submit();
}
Connections {
function onBugReportSendSuccess() {
root.bugReportWasSent();
}
function onReportBugFinished() {
sendButton.loading = false;
}
function onReceivedKnowledgeBaseSuggestions(suggestions) {
root.suggestions = suggestions
}
target: Backend
}
}
}
}

View File

@ -14,6 +14,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import ".."
Item { Item {
id: root id: root
@ -32,7 +33,7 @@ Item {
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
spacing: 16 spacing: 12
Label { Label {
id: mainLabel id: mainLabel
@ -44,42 +45,13 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
ColorImage { InfoTooltip {
id: infoImage
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.topMargin: 4
Layout.bottomMargin: root._bottomMargin Layout.bottomMargin: root._bottomMargin
color: root.colorScheme.interaction_norm colorScheme: root.colorScheme
height: 21 text: root.hint
width: 21 size: 16
source: "/qml/icons/ic-info-circle.svg"
sourceSize.height: 21
sourceSize.width: 21
visible: root.hint !== ""
MouseArea {
id: imageArea
anchors.fill: infoImage
hoverEnabled: true
}
ToolTip {
id: toolTipinfo
text: root.hint
visible: imageArea.containsMouse
implicitWidth: Math.min(400, tooltipText.implicitWidth)
background: Rectangle {
radius: 4
border.color: root.colorScheme.border_weak
color: root.colorScheme.background_weak
}
contentItem: Text {
id: tooltipText
color: root.colorScheme.text_hint
text: toolTipinfo.text
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
} }
// fill height so the footer label will always be attached to the bottom // fill height so the footer label will always be attached to the bottom

View File

@ -1,161 +0,0 @@
// Copyright (c) 2023 Proton AG
// This file is part of Proton Mail Bridge.
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Proton
SettingsView {
id: root
property var selectedAddress
property var categoryId:-1
property string category: Backend.getBugCategory(root.categoryId)
signal bugReportWasSent
function isValidEmail(text) {
const reEmail = /^[^@]+@[^@]+\.[A-Za-z]+\s*$/;
return reEmail.test(text);
}
function setCategoryId(catId) {
root.categoryId = catId;
}
function setDefaultValue() {
description.text = Backend.collectAnswers(root.categoryId);
address.text = root.selectedAddress;
emailClient.text = Backend.currentEmailClient;
includeLogs.checked = true;
}
function submit() {
sendButton.loading = true;
Backend.reportBug(root.category, description.text, address.text, emailClient.text, includeLogs.checked);
}
fillHeight: true
onVisibleChanged: {
root.setDefaultValue();
}
Label {
colorScheme: root.colorScheme
text: qsTr("Send report")
type: Label.Heading
}
TextArea {
id: description
KeyNavigation.priority: KeyNavigation.BeforeItem
KeyNavigation.tab: address
Layout.fillHeight: true
Layout.fillWidth: true
Layout.minimumHeight: heightForLinesVisible(4)
colorScheme: root.colorScheme
textFormat: Text.MarkdownText
// set implicitHeight to explicit height because se don't
// want TextArea implicitHeight (which is height of all text)
// to be considered in SettingsView internal scroll view
implicitHeight: height
label: "Your answers to: " + qsTr(root.category);
readOnly : true
}
TextField {
id: address
Layout.fillWidth: true
colorScheme: root.colorScheme
label: qsTr("Your contact email")
placeholderText: qsTr("e.g. jane.doe@protonmail.com")
validator: function (str) {
if (!isValidEmail(str)) {
return qsTr("Enter valid email address");
}
return;
}
}
TextField {
id: emailClient
Layout.fillWidth: true
colorScheme: root.colorScheme
label: qsTr("Your email client (including version)")
placeholderText: qsTr("e.g. Apple Mail 14.0")
validator: function (str) {
if (str.length === 0) {
return qsTr("Enter an email client name and version");
}
return;
}
}
RowLayout {
CheckBox {
id: includeLogs
checked: true
colorScheme: root.colorScheme
text: qsTr("Include my recent logs")
}
Button {
Layout.leftMargin: 12
colorScheme: root.colorScheme
secondary: true
text: qsTr("View logs")
onClicked: Backend.openExternalLink(Backend.logsPath)
}
}
TextEdit {
Layout.fillWidth: true
color: root.colorScheme.text_weak
font.family: ProtonStyle.font_family
font.letterSpacing: ProtonStyle.caption_letter_spacing
font.pixelSize: ProtonStyle.caption_font_size
font.weight: ProtonStyle.fontWeight_400
readOnly: true
selectByMouse: true
selectedTextColor: root.colorScheme.text_invert
// No way to set lineHeight: ProtonStyle.caption_line_height
selectionColor: root.colorScheme.interaction_norm
text: qsTr("Reports are not end-to-end encrypted, please do not send any sensitive information.")
wrapMode: Text.WordWrap
}
Button {
id: sendButton
colorScheme: root.colorScheme
enabled: !loading
text: qsTr("Send")
onClicked: {
description.validate();
address.validate();
emailClient.validate();
if (description.error || address.error || emailClient.error) {
return;
}
submit();
}
Connections {
function onBugReportSendSuccess() {
root.bugReportWasSent();
}
function onReportBugFinished() {
sendButton.loading = false;
}
target: Backend
}
}
}

View File

@ -15,6 +15,7 @@ import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Proton import Proton
import Notifications import Notifications
import "BugReport"
Item { Item {
id: root id: root

View File

@ -18,6 +18,7 @@ import QtQuick.Controls
import Proton import Proton
import Notifications import Notifications
import "SetupWizard" import "SetupWizard"
import "BugReport"
ApplicationWindow { ApplicationWindow {
id: root id: root

View File

@ -0,0 +1,60 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ColorImage {
id: root
property var colorScheme
property string text
property int size: 16
color: root.colorScheme.interaction_norm
height: size
width: size
source: "/qml/icons/ic-info-circle.svg"
sourceSize.height: size
sourceSize.width: size
visible: root.hint !== ""
MouseArea {
id: imageArea
anchors.fill: parent
hoverEnabled: true
}
ToolTip {
id: toolTipinfo
text: root.text
visible: imageArea.containsMouse
implicitWidth: Math.min(400, tooltipText.implicitWidth)
background: Rectangle {
radius: 4
border.color: root.colorScheme.border_weak
color: root.colorScheme.background_weak
}
contentItem: Text {
id: tooltipText
color: root.colorScheme.text_norm
text: root.text
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}

View File

@ -64,11 +64,12 @@ RowLayout {
} }
ColorImage { ColorImage {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.bottomMargin: -6
color: label.linkColor color: label.linkColor
height: sourceSize.height height: sourceSize.height
source: "/qml/icons/ic-external-link.svg" source: "/qml/icons/ic-external-link.svg"
sourceSize.height: 16 sourceSize.height: 14
sourceSize.width: 16 sourceSize.width: 14
visible: external visible: external
width: sourceSize.width width: sourceSize.width

View File

@ -27,6 +27,7 @@ Button 4.0 Button.qml
CheckBox 4.0 CheckBox.qml CheckBox 4.0 CheckBox.qml
ComboBox 4.0 ComboBox.qml ComboBox 4.0 ComboBox.qml
Dialog 4.0 Dialog.qml Dialog 4.0 Dialog.qml
InfoTooltip 4.0 InfoTooltip.qml
Label 4.0 Label.qml Label 4.0 Label.qml
LinkLabel 4.0 LinkLabel.qml LinkLabel 4.0 LinkLabel.qml
Menu 4.0 Menu.qml Menu 4.0 Menu.qml

View File

@ -30,7 +30,7 @@ Item {
property bool fillHeight: false property bool fillHeight: false
default property alias items: content.children default property alias items: content.children
function positionViewAtBegining() { function positionViewAtBeginning() {
scrollView.ScrollBar.vertical.position = 0 scrollView.ScrollBar.vertical.position = 0
} }
signal back signal back

View File

@ -30,13 +30,6 @@ namespace {
namespace bridgepp { namespace bridgepp {
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
BugReportFlow::BugReportFlow() {
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] filepath The path of the file to parse. /// \param[in] filepath The path of the file to parse.
/// \return True iff the file can be properly parsed. /// \return True iff the file can be properly parsed.
@ -92,7 +85,7 @@ bool BugReportFlow::setAnswer(quint8 questionId, QString const &answer) {
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] questionId The id of the question. /// \param[in] categoryId The id of the question.
/// \return answer the given question. /// \return answer the given question.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
QString BugReportFlow::getCategory(quint8 categoryId) const { QString BugReportFlow::getCategory(quint8 categoryId) const {
@ -128,7 +121,7 @@ QString BugReportFlow::collectAnswers(quint8 categoryId) const {
QVariantList sets = this->questionSet(categoryId); QVariantList sets = this->questionSet(categoryId);
for (QVariant const &var: sets) { for (QVariant const &var: sets) {
const QString& answer = getAnswer(var.toInt()); const QString answer = getAnswer(var.toInt());
if (answer.isEmpty()) if (answer.isEmpty())
continue; continue;
answers += "#### " + questions_[var.toInt()].toMap()["text"].toString() + "\n\r"; answers += "#### " + questions_[var.toInt()].toMap()["text"].toString() + "\n\r";
@ -139,6 +132,24 @@ QString BugReportFlow::collectAnswers(quint8 categoryId) const {
} }
//****************************************************************************************************************************************************
/// \param[in] categoryId The id of the question set.
//****************************************************************************************************************************************************
QString BugReportFlow::collectUserInput(quint8 categoryId) const {
if (categoryId > categories_.count() - 1)
return {};
QString input = this->getCategory(categoryId);
for (QVariant const &var: this->questionSet(categoryId)) {
QString const answer = getAnswer(var.toInt());
if (!answer.isEmpty())
input += " " + answer;
}
return input;
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
// //
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************

View File

@ -28,7 +28,7 @@ namespace bridgepp {
class BugReportFlow { class BugReportFlow {
public: // member functions. public: // member functions.
BugReportFlow(); ///< Default constructor. BugReportFlow() = default; ///< Default constructor.
BugReportFlow(BugReportFlow const &) = delete; ///< Disabled copy-constructor. BugReportFlow(BugReportFlow const &) = delete; ///< Disabled copy-constructor.
BugReportFlow(BugReportFlow &&) = delete; ///< Disabled assignment copy-constructor. BugReportFlow(BugReportFlow &&) = delete; ///< Disabled assignment copy-constructor.
~BugReportFlow() = default; ///< Destructor. ~BugReportFlow() = default; ///< Destructor.
@ -42,6 +42,7 @@ public: // member functions.
[[nodiscard]] QString getCategory(quint8 categoryId) const; ///< Get category name. [[nodiscard]] QString getCategory(quint8 categoryId) const; ///< Get category name.
[[nodiscard]] QString getAnswer(quint8 questionId) const; ///< Get answer for a given question. [[nodiscard]] QString getAnswer(quint8 questionId) const; ///< Get answer for a given question.
[[nodiscard]] QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions. [[nodiscard]] QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions.
[[nodiscard]] QString collectUserInput(quint8 categoryId) const; ///< Collect the user input (user answers without quesitons) for a given set of questions.
void clearAnswers(); ///< Clear all collected answers. void clearAnswers(); ///< Clear all collected answers.

View File

@ -247,6 +247,22 @@ SPStreamEvent newCertificateInstallFailedEvent() {
} }
//****************************************************************************************************************************************************
/// \param[in] suggestions the suggestions
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newKnowledgeBaseSuggestionsEvent(QList<KnowledgeBaseSuggestion> const& suggestions) {
auto event = new grpc::KnowledgeBaseSuggestionsEvent;
for (KnowledgeBaseSuggestion const &suggestion: suggestions) {
grpc::KnowledgeBaseSuggestion *s = event->add_suggestions();
s->set_url(suggestion.url.toStdString());
s->set_title(suggestion.title.toStdString());
}
auto appEvent = new grpc::AppEvent;
appEvent->set_allocated_knowledgebasesuggestions(event);
return wrapAppEvent(appEvent);
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \return The event. /// \return The event.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************

View File

@ -22,6 +22,7 @@
#include "bridge.grpc.pb.h" #include "bridge.grpc.pb.h"
#include "GRPCUtils.h" #include "GRPCUtils.h"
#include <bridgepp/GRPC/GRPCClient.h>
namespace bridgepp { namespace bridgepp {
@ -39,6 +40,7 @@ SPStreamEvent newCertificateInstallSuccessEvent(); ///< Create a new Certificate
SPStreamEvent newCertificateInstallCanceledEvent(); ///< Create a new CertificateInstallCanceledEvent event. SPStreamEvent newCertificateInstallCanceledEvent(); ///< Create a new CertificateInstallCanceledEvent event.
SPStreamEvent newCertificateInstallFailedEvent(); ///< Create anew CertificateInstallFailedEvent event. SPStreamEvent newCertificateInstallFailedEvent(); ///< Create anew CertificateInstallFailedEvent event.
SPStreamEvent newShowMainWindowEvent(); ///< Create a new ShowMainWindowEvent event. SPStreamEvent newShowMainWindowEvent(); ///< Create a new ShowMainWindowEvent event.
SPStreamEvent newKnowledgeBaseSuggestionsEvent(QList<KnowledgeBaseSuggestion> const& suggestions); ///< Create a new KnowledgeBaseSuggestions event.
// Login events // Login events
SPStreamEvent newLoginError(grpc::LoginErrorType error, QString const &message); ///< Create a new LoginError event. SPStreamEvent newLoginError(grpc::LoginErrorType error, QString const &message); ///< Create a new LoginError event.

View File

@ -568,6 +568,14 @@ grpc::Status GRPCClient::hostname(QString &outHostname) {
} }
//****************************************************************************************************************************************************
/// \param[in] input The user input to analyze.
//****************************************************************************************************************************************************
grpc::Status GRPCClient::requestKnowledgeBaseSuggestions(QString const &input) {
return this->logGRPCCallStatus(this->setString(&Bridge::Stub::RequestKnowledgeBaseSuggestions, input), __FUNCTION__);
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[out] outPath The value for the property. /// \param[out] outPath The value for the property.
/// \return The status for the gRPC call. /// \return The status for the gRPC call.
@ -1164,6 +1172,19 @@ void GRPCClient::processAppEvent(AppEvent const &event) {
this->logTrace("App event received: CertificateInstallFailed."); this->logTrace("App event received: CertificateInstallFailed.");
emit certificateInstallFailed(); emit certificateInstallFailed();
break; break;
case AppEvent::kKnowledgeBaseSuggestions:
{
this->logTrace("App event received: KnowledgeBaseSuggestions.");
QList<KnowledgeBaseSuggestion> suggestions;
for (grpc::KnowledgeBaseSuggestion const &suggestion: event.knowledgebasesuggestions().suggestions()) {
suggestions.push_back(KnowledgeBaseSuggestion{
.url = QString::fromUtf8(suggestion.url()),
.title = QString::fromUtf8(suggestion.title())
});
}
emit knowledgeBasSuggestionsReceived(suggestions);
break;
}
default: default:
this->logError("Unknown App event received."); this->logError("Unknown App event received.");
} }

View File

@ -42,6 +42,20 @@ typedef grpc::Status (grpc::Bridge::Stub::*StringParamMethod)(grpc::ClientContex
typedef std::unique_ptr<grpc::ClientContext> UPClientContext; typedef std::unique_ptr<grpc::ClientContext> UPClientContext;
//****************************************************************************************************************************************************
/// \brief A struct for knowledge base suggestion.
//****************************************************************************************************************************************************
struct KnowledgeBaseSuggestion {
// The following lines make the type transmissible to QML (but not instanciable there)
Q_GADGET
Q_PROPERTY(QString url MEMBER url)
Q_PROPERTY(QString title MEMBER title)
public:
QString url; ///< The URL of the knowledge base article
QString title; ///< The title of the knowledge base article.
};
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \brief gRPC client class. This class encapsulate the gRPC service, abstracting all data type conversions. /// \brief gRPC client class. This class encapsulate the gRPC service, abstracting all data type conversions.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
@ -93,6 +107,7 @@ public: // member functions.
grpc::Status releaseNotesPageLink(QUrl &outUrl); ///< Performs the 'releaseNotesPageLink' call. grpc::Status releaseNotesPageLink(QUrl &outUrl); ///< Performs the 'releaseNotesPageLink' call.
grpc::Status landingPageLink(QUrl &outUrl); ///< Performs the 'landingPageLink' call. grpc::Status landingPageLink(QUrl &outUrl); ///< Performs the 'landingPageLink' call.
grpc::Status hostname(QString &outHostname); ///< Performs the 'Hostname' call. grpc::Status hostname(QString &outHostname); ///< Performs the 'Hostname' call.
grpc::Status requestKnowledgeBaseSuggestions(QString const &input); ///< Performs the 'RequestKnowledgeBaseSuggestions' call.
signals: // app related signals signals: // app related signals
void internetStatus(bool isOn); void internetStatus(bool isOn);
@ -106,9 +121,10 @@ signals: // app related signals
void certificateInstallCanceled(); void certificateInstallCanceled();
void certificateInstallFailed(); void certificateInstallFailed();
void showMainWindow(); void showMainWindow();
void knowledgeBasSuggestionsReceived(QList<KnowledgeBaseSuggestion> const& suggestions);
// cache related calls
public: public: // cache related calls
grpc::Status diskCachePath(QUrl &outPath); ///< Performs the 'diskCachePath' call. grpc::Status diskCachePath(QUrl &outPath); ///< Performs the 'diskCachePath' call.
grpc::Status setDiskCachePath(QUrl const &path); ///< Performs the 'setDiskCachePath' call grpc::Status setDiskCachePath(QUrl const &path); ///< Performs the 'setDiskCachePath' call
@ -117,8 +133,8 @@ signals:
void diskCachePathChanged(QUrl const &path); void diskCachePathChanged(QUrl const &path);
void diskCachePathChangeFinished(); void diskCachePathChangeFinished();
// mail settings related calls
public: public: // mail settings related calls
grpc::Status mailServerSettings(qint32 &outIMAPPort, qint32 &outSMTPPort, bool &outUseSSLForIMAP, bool &outUseSSLForSMTP); ///< Performs the 'MailServerSettings' gRPC call. grpc::Status mailServerSettings(qint32 &outIMAPPort, qint32 &outSMTPPort, bool &outUseSSLForIMAP, bool &outUseSSLForSMTP); ///< Performs the 'MailServerSettings' gRPC call.
grpc::Status setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP); ///< Performs the 'SetMailServerSettings' gRPC call. grpc::Status setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP); ///< Performs the 'SetMailServerSettings' gRPC call.
grpc::Status isDoHEnabled(bool &outEnabled); ///< Performs the 'isDoHEnabled' gRPC call. grpc::Status isDoHEnabled(bool &outEnabled); ///< Performs the 'isDoHEnabled' gRPC call.

File diff suppressed because it is too large Load Diff

View File

@ -58,6 +58,7 @@ service Bridge {
rpc ReportBug(ReportBugRequest) returns (google.protobuf.Empty); rpc ReportBug(ReportBugRequest) returns (google.protobuf.Empty);
rpc ForceLauncher(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc ForceLauncher(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc SetMainExecutable(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc SetMainExecutable(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc RequestKnowledgeBaseSuggestions(google.protobuf.StringValue) returns (google.protobuf.Empty);
// login // login
rpc Login(LoginRequest) returns (google.protobuf.Empty); rpc Login(LoginRequest) returns (google.protobuf.Empty);
@ -269,6 +270,7 @@ message AppEvent {
CertificateInstallSuccessEvent certificateInstallSuccess = 9; CertificateInstallSuccessEvent certificateInstallSuccess = 9;
CertificateInstallCanceledEvent certificateInstallCanceled = 10; CertificateInstallCanceledEvent certificateInstallCanceled = 10;
CertificateInstallFailedEvent certificateInstallFailed = 11; CertificateInstallFailedEvent certificateInstallFailed = 11;
KnowledgeBaseSuggestionsEvent knowledgeBaseSuggestions = 12;
} }
} }
@ -287,6 +289,15 @@ message CertificateInstallSuccessEvent {}
message CertificateInstallCanceledEvent {} message CertificateInstallCanceledEvent {}
message CertificateInstallFailedEvent {} message CertificateInstallFailedEvent {}
message KnowledgeBaseSuggestion {
string url = 1;
string title = 2;
}
message KnowledgeBaseSuggestionsEvent {
repeated KnowledgeBaseSuggestion suggestions = 1;
}
//********************************************************** //**********************************************************
// Login related events // Login related events
//********************************************************** //**********************************************************
@ -478,6 +489,7 @@ message UserEvent {
} }
} }
message ToggleSplitModeFinishedEvent { message ToggleSplitModeFinishedEvent {
string userID = 1; string userID = 1;
} }

View File

@ -38,68 +38,69 @@ import (
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion7
const ( const (
Bridge_CheckTokens_FullMethodName = "/grpc.Bridge/CheckTokens" Bridge_CheckTokens_FullMethodName = "/grpc.Bridge/CheckTokens"
Bridge_AddLogEntry_FullMethodName = "/grpc.Bridge/AddLogEntry" Bridge_AddLogEntry_FullMethodName = "/grpc.Bridge/AddLogEntry"
Bridge_GuiReady_FullMethodName = "/grpc.Bridge/GuiReady" Bridge_GuiReady_FullMethodName = "/grpc.Bridge/GuiReady"
Bridge_Quit_FullMethodName = "/grpc.Bridge/Quit" Bridge_Quit_FullMethodName = "/grpc.Bridge/Quit"
Bridge_Restart_FullMethodName = "/grpc.Bridge/Restart" Bridge_Restart_FullMethodName = "/grpc.Bridge/Restart"
Bridge_ShowOnStartup_FullMethodName = "/grpc.Bridge/ShowOnStartup" Bridge_ShowOnStartup_FullMethodName = "/grpc.Bridge/ShowOnStartup"
Bridge_SetIsAutostartOn_FullMethodName = "/grpc.Bridge/SetIsAutostartOn" Bridge_SetIsAutostartOn_FullMethodName = "/grpc.Bridge/SetIsAutostartOn"
Bridge_IsAutostartOn_FullMethodName = "/grpc.Bridge/IsAutostartOn" Bridge_IsAutostartOn_FullMethodName = "/grpc.Bridge/IsAutostartOn"
Bridge_SetIsBetaEnabled_FullMethodName = "/grpc.Bridge/SetIsBetaEnabled" Bridge_SetIsBetaEnabled_FullMethodName = "/grpc.Bridge/SetIsBetaEnabled"
Bridge_IsBetaEnabled_FullMethodName = "/grpc.Bridge/IsBetaEnabled" Bridge_IsBetaEnabled_FullMethodName = "/grpc.Bridge/IsBetaEnabled"
Bridge_SetIsAllMailVisible_FullMethodName = "/grpc.Bridge/SetIsAllMailVisible" Bridge_SetIsAllMailVisible_FullMethodName = "/grpc.Bridge/SetIsAllMailVisible"
Bridge_IsAllMailVisible_FullMethodName = "/grpc.Bridge/IsAllMailVisible" Bridge_IsAllMailVisible_FullMethodName = "/grpc.Bridge/IsAllMailVisible"
Bridge_SetIsTelemetryDisabled_FullMethodName = "/grpc.Bridge/SetIsTelemetryDisabled" Bridge_SetIsTelemetryDisabled_FullMethodName = "/grpc.Bridge/SetIsTelemetryDisabled"
Bridge_IsTelemetryDisabled_FullMethodName = "/grpc.Bridge/IsTelemetryDisabled" Bridge_IsTelemetryDisabled_FullMethodName = "/grpc.Bridge/IsTelemetryDisabled"
Bridge_GoOs_FullMethodName = "/grpc.Bridge/GoOs" Bridge_GoOs_FullMethodName = "/grpc.Bridge/GoOs"
Bridge_TriggerReset_FullMethodName = "/grpc.Bridge/TriggerReset" Bridge_TriggerReset_FullMethodName = "/grpc.Bridge/TriggerReset"
Bridge_Version_FullMethodName = "/grpc.Bridge/Version" Bridge_Version_FullMethodName = "/grpc.Bridge/Version"
Bridge_LogsPath_FullMethodName = "/grpc.Bridge/LogsPath" Bridge_LogsPath_FullMethodName = "/grpc.Bridge/LogsPath"
Bridge_LicensePath_FullMethodName = "/grpc.Bridge/LicensePath" Bridge_LicensePath_FullMethodName = "/grpc.Bridge/LicensePath"
Bridge_ReleaseNotesPageLink_FullMethodName = "/grpc.Bridge/ReleaseNotesPageLink" Bridge_ReleaseNotesPageLink_FullMethodName = "/grpc.Bridge/ReleaseNotesPageLink"
Bridge_DependencyLicensesLink_FullMethodName = "/grpc.Bridge/DependencyLicensesLink" Bridge_DependencyLicensesLink_FullMethodName = "/grpc.Bridge/DependencyLicensesLink"
Bridge_LandingPageLink_FullMethodName = "/grpc.Bridge/LandingPageLink" Bridge_LandingPageLink_FullMethodName = "/grpc.Bridge/LandingPageLink"
Bridge_SetColorSchemeName_FullMethodName = "/grpc.Bridge/SetColorSchemeName" Bridge_SetColorSchemeName_FullMethodName = "/grpc.Bridge/SetColorSchemeName"
Bridge_ColorSchemeName_FullMethodName = "/grpc.Bridge/ColorSchemeName" Bridge_ColorSchemeName_FullMethodName = "/grpc.Bridge/ColorSchemeName"
Bridge_CurrentEmailClient_FullMethodName = "/grpc.Bridge/CurrentEmailClient" Bridge_CurrentEmailClient_FullMethodName = "/grpc.Bridge/CurrentEmailClient"
Bridge_ReportBug_FullMethodName = "/grpc.Bridge/ReportBug" Bridge_ReportBug_FullMethodName = "/grpc.Bridge/ReportBug"
Bridge_ForceLauncher_FullMethodName = "/grpc.Bridge/ForceLauncher" Bridge_ForceLauncher_FullMethodName = "/grpc.Bridge/ForceLauncher"
Bridge_SetMainExecutable_FullMethodName = "/grpc.Bridge/SetMainExecutable" Bridge_SetMainExecutable_FullMethodName = "/grpc.Bridge/SetMainExecutable"
Bridge_Login_FullMethodName = "/grpc.Bridge/Login" Bridge_RequestKnowledgeBaseSuggestions_FullMethodName = "/grpc.Bridge/RequestKnowledgeBaseSuggestions"
Bridge_Login2FA_FullMethodName = "/grpc.Bridge/Login2FA" Bridge_Login_FullMethodName = "/grpc.Bridge/Login"
Bridge_Login2Passwords_FullMethodName = "/grpc.Bridge/Login2Passwords" Bridge_Login2FA_FullMethodName = "/grpc.Bridge/Login2FA"
Bridge_LoginAbort_FullMethodName = "/grpc.Bridge/LoginAbort" Bridge_Login2Passwords_FullMethodName = "/grpc.Bridge/Login2Passwords"
Bridge_CheckUpdate_FullMethodName = "/grpc.Bridge/CheckUpdate" Bridge_LoginAbort_FullMethodName = "/grpc.Bridge/LoginAbort"
Bridge_InstallUpdate_FullMethodName = "/grpc.Bridge/InstallUpdate" Bridge_CheckUpdate_FullMethodName = "/grpc.Bridge/CheckUpdate"
Bridge_SetIsAutomaticUpdateOn_FullMethodName = "/grpc.Bridge/SetIsAutomaticUpdateOn" Bridge_InstallUpdate_FullMethodName = "/grpc.Bridge/InstallUpdate"
Bridge_IsAutomaticUpdateOn_FullMethodName = "/grpc.Bridge/IsAutomaticUpdateOn" Bridge_SetIsAutomaticUpdateOn_FullMethodName = "/grpc.Bridge/SetIsAutomaticUpdateOn"
Bridge_DiskCachePath_FullMethodName = "/grpc.Bridge/DiskCachePath" Bridge_IsAutomaticUpdateOn_FullMethodName = "/grpc.Bridge/IsAutomaticUpdateOn"
Bridge_SetDiskCachePath_FullMethodName = "/grpc.Bridge/SetDiskCachePath" Bridge_DiskCachePath_FullMethodName = "/grpc.Bridge/DiskCachePath"
Bridge_SetIsDoHEnabled_FullMethodName = "/grpc.Bridge/SetIsDoHEnabled" Bridge_SetDiskCachePath_FullMethodName = "/grpc.Bridge/SetDiskCachePath"
Bridge_IsDoHEnabled_FullMethodName = "/grpc.Bridge/IsDoHEnabled" Bridge_SetIsDoHEnabled_FullMethodName = "/grpc.Bridge/SetIsDoHEnabled"
Bridge_MailServerSettings_FullMethodName = "/grpc.Bridge/MailServerSettings" Bridge_IsDoHEnabled_FullMethodName = "/grpc.Bridge/IsDoHEnabled"
Bridge_SetMailServerSettings_FullMethodName = "/grpc.Bridge/SetMailServerSettings" Bridge_MailServerSettings_FullMethodName = "/grpc.Bridge/MailServerSettings"
Bridge_Hostname_FullMethodName = "/grpc.Bridge/Hostname" Bridge_SetMailServerSettings_FullMethodName = "/grpc.Bridge/SetMailServerSettings"
Bridge_IsPortFree_FullMethodName = "/grpc.Bridge/IsPortFree" Bridge_Hostname_FullMethodName = "/grpc.Bridge/Hostname"
Bridge_AvailableKeychains_FullMethodName = "/grpc.Bridge/AvailableKeychains" Bridge_IsPortFree_FullMethodName = "/grpc.Bridge/IsPortFree"
Bridge_SetCurrentKeychain_FullMethodName = "/grpc.Bridge/SetCurrentKeychain" Bridge_AvailableKeychains_FullMethodName = "/grpc.Bridge/AvailableKeychains"
Bridge_CurrentKeychain_FullMethodName = "/grpc.Bridge/CurrentKeychain" Bridge_SetCurrentKeychain_FullMethodName = "/grpc.Bridge/SetCurrentKeychain"
Bridge_GetUserList_FullMethodName = "/grpc.Bridge/GetUserList" Bridge_CurrentKeychain_FullMethodName = "/grpc.Bridge/CurrentKeychain"
Bridge_GetUser_FullMethodName = "/grpc.Bridge/GetUser" Bridge_GetUserList_FullMethodName = "/grpc.Bridge/GetUserList"
Bridge_SetUserSplitMode_FullMethodName = "/grpc.Bridge/SetUserSplitMode" Bridge_GetUser_FullMethodName = "/grpc.Bridge/GetUser"
Bridge_SendBadEventUserFeedback_FullMethodName = "/grpc.Bridge/SendBadEventUserFeedback" Bridge_SetUserSplitMode_FullMethodName = "/grpc.Bridge/SetUserSplitMode"
Bridge_LogoutUser_FullMethodName = "/grpc.Bridge/LogoutUser" Bridge_SendBadEventUserFeedback_FullMethodName = "/grpc.Bridge/SendBadEventUserFeedback"
Bridge_RemoveUser_FullMethodName = "/grpc.Bridge/RemoveUser" Bridge_LogoutUser_FullMethodName = "/grpc.Bridge/LogoutUser"
Bridge_ConfigureUserAppleMail_FullMethodName = "/grpc.Bridge/ConfigureUserAppleMail" Bridge_RemoveUser_FullMethodName = "/grpc.Bridge/RemoveUser"
Bridge_ReportBugClicked_FullMethodName = "/grpc.Bridge/ReportBugClicked" Bridge_ConfigureUserAppleMail_FullMethodName = "/grpc.Bridge/ConfigureUserAppleMail"
Bridge_AutoconfigClicked_FullMethodName = "/grpc.Bridge/AutoconfigClicked" Bridge_ReportBugClicked_FullMethodName = "/grpc.Bridge/ReportBugClicked"
Bridge_ExternalLinkClicked_FullMethodName = "/grpc.Bridge/ExternalLinkClicked" Bridge_AutoconfigClicked_FullMethodName = "/grpc.Bridge/AutoconfigClicked"
Bridge_IsTLSCertificateInstalled_FullMethodName = "/grpc.Bridge/IsTLSCertificateInstalled" Bridge_ExternalLinkClicked_FullMethodName = "/grpc.Bridge/ExternalLinkClicked"
Bridge_InstallTLSCertificate_FullMethodName = "/grpc.Bridge/InstallTLSCertificate" Bridge_IsTLSCertificateInstalled_FullMethodName = "/grpc.Bridge/IsTLSCertificateInstalled"
Bridge_ExportTLSCertificates_FullMethodName = "/grpc.Bridge/ExportTLSCertificates" Bridge_InstallTLSCertificate_FullMethodName = "/grpc.Bridge/InstallTLSCertificate"
Bridge_RunEventStream_FullMethodName = "/grpc.Bridge/RunEventStream" Bridge_ExportTLSCertificates_FullMethodName = "/grpc.Bridge/ExportTLSCertificates"
Bridge_StopEventStream_FullMethodName = "/grpc.Bridge/StopEventStream" Bridge_RunEventStream_FullMethodName = "/grpc.Bridge/RunEventStream"
Bridge_StopEventStream_FullMethodName = "/grpc.Bridge/StopEventStream"
) )
// BridgeClient is the client API for Bridge service. // BridgeClient is the client API for Bridge service.
@ -135,6 +136,7 @@ type BridgeClient interface {
ReportBug(ctx context.Context, in *ReportBugRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) ReportBug(ctx context.Context, in *ReportBugRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
SetMainExecutable(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) SetMainExecutable(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
RequestKnowledgeBaseSuggestions(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
// login // login
Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
Login2FA(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) Login2FA(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
@ -440,6 +442,15 @@ func (c *bridgeClient) SetMainExecutable(ctx context.Context, in *wrapperspb.Str
return out, nil return out, nil
} }
func (c *bridgeClient) RequestKnowledgeBaseSuggestions(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_RequestKnowledgeBaseSuggestions_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bridgeClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { func (c *bridgeClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty) out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_Login_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, Bridge_Login_FullMethodName, in, out, opts...)
@ -802,6 +813,7 @@ type BridgeServer interface {
ReportBug(context.Context, *ReportBugRequest) (*emptypb.Empty, error) ReportBug(context.Context, *ReportBugRequest) (*emptypb.Empty, error)
ForceLauncher(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) ForceLauncher(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
SetMainExecutable(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) SetMainExecutable(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
RequestKnowledgeBaseSuggestions(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
// login // login
Login(context.Context, *LoginRequest) (*emptypb.Empty, error) Login(context.Context, *LoginRequest) (*emptypb.Empty, error)
Login2FA(context.Context, *LoginRequest) (*emptypb.Empty, error) Login2FA(context.Context, *LoginRequest) (*emptypb.Empty, error)
@ -936,6 +948,9 @@ func (UnimplementedBridgeServer) ForceLauncher(context.Context, *wrapperspb.Stri
func (UnimplementedBridgeServer) SetMainExecutable(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { func (UnimplementedBridgeServer) SetMainExecutable(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMainExecutable not implemented") return nil, status.Errorf(codes.Unimplemented, "method SetMainExecutable not implemented")
} }
func (UnimplementedBridgeServer) RequestKnowledgeBaseSuggestions(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method RequestKnowledgeBaseSuggestions not implemented")
}
func (UnimplementedBridgeServer) Login(context.Context, *LoginRequest) (*emptypb.Empty, error) { func (UnimplementedBridgeServer) Login(context.Context, *LoginRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
} }
@ -1555,6 +1570,24 @@ func _Bridge_SetMainExecutable_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _Bridge_RequestKnowledgeBaseSuggestions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(wrapperspb.StringValue)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BridgeServer).RequestKnowledgeBaseSuggestions(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Bridge_RequestKnowledgeBaseSuggestions_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BridgeServer).RequestKnowledgeBaseSuggestions(ctx, req.(*wrapperspb.StringValue))
}
return interceptor(ctx, in, info, handler)
}
func _Bridge_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _Bridge_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoginRequest) in := new(LoginRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -2289,6 +2322,10 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
MethodName: "SetMainExecutable", MethodName: "SetMainExecutable",
Handler: _Bridge_SetMainExecutable_Handler, Handler: _Bridge_SetMainExecutable_Handler,
}, },
{
MethodName: "RequestKnowledgeBaseSuggestions",
Handler: _Bridge_RequestKnowledgeBaseSuggestions_Handler,
},
{ {
MethodName: "Login", MethodName: "Login",
Handler: _Bridge_Login_Handler, Handler: _Bridge_Login_Handler,

View File

@ -17,6 +17,11 @@
package grpc package grpc
import (
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
"github.com/bradenaw/juniper/xslices"
)
func NewInternetStatusEvent(connected bool) *StreamEvent { func NewInternetStatusEvent(connected bool) *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_InternetStatus{InternetStatus: &InternetStatusEvent{Connected: connected}}}) return appEvent(&AppEvent{Event: &AppEvent_InternetStatus{InternetStatus: &InternetStatusEvent{Connected: connected}}})
} }
@ -61,6 +66,20 @@ func NewShowMainWindowEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ShowMainWindow{ShowMainWindow: &ShowMainWindowEvent{}}}) return appEvent(&AppEvent{Event: &AppEvent_ShowMainWindow{ShowMainWindow: &ShowMainWindowEvent{}}})
} }
func NewRequestKnowledgeBaseSuggestionsEvent(suggestions kb.ArticleList) *StreamEvent {
s := xslices.Map(
suggestions,
func(article *kb.Article) *KnowledgeBaseSuggestion {
return &KnowledgeBaseSuggestion{Url: article.URL, Title: article.Title}
},
)
return appEvent(&AppEvent{Event: &AppEvent_KnowledgeBaseSuggestions{
KnowledgeBaseSuggestions: &KnowledgeBaseSuggestionsEvent{
Suggestions: s,
},
}})
}
func NewLoginError(err LoginErrorType, message string) *StreamEvent { func NewLoginError(err LoginErrorType, message string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_Error{Error: &LoginErrorEvent{Type: err, Message: message}}}) return loginEvent(&LoginEvent{Event: &LoginEvent_Error{Error: &LoginErrorEvent{Type: err, Message: message}}})
} }

View File

@ -30,6 +30,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme" "github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme"
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
"github.com/ProtonMail/proton-bridge/v3/internal/safe" "github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/ProtonMail/proton-bridge/v3/internal/service"
"github.com/ProtonMail/proton-bridge/v3/internal/updater" "github.com/ProtonMail/proton-bridge/v3/internal/updater"
@ -375,6 +376,23 @@ func (s *Service) SetMainExecutable(_ context.Context, exe *wrapperspb.StringVal
return &emptypb.Empty{}, nil return &emptypb.Empty{}, nil
} }
func (s *Service) RequestKnowledgeBaseSuggestions(_ context.Context, userInput *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.Debug("RequestKnowledgeBaseSuggestions")
go func() {
defer async.HandlePanic(s.panicHandler)
articles, err := s.bridge.GetKnowledgeBaseSuggestions(userInput.Value)
if err != nil {
s.log.WithError(err).Error("Could not retrieve KB article suggestions")
articles = kb.ArticleList{}
}
_ = s.SendEvent(NewRequestKnowledgeBaseSuggestionsEvent(articles))
}()
return &emptypb.Empty{}, nil
}
func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) { func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Debug("Login") s.log.WithField("username", login.Username).Debug("Login")

View File

@ -21,6 +21,7 @@ import (
"context" "context"
"github.com/ProtonMail/gluon/async" "github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
@ -122,6 +123,7 @@ func (s *Service) StartEventTest() error {
NewReportBugSuccessEvent(), NewReportBugSuccessEvent(),
NewReportBugErrorEvent(), NewReportBugErrorEvent(),
NewShowMainWindowEvent(), NewShowMainWindowEvent(),
NewRequestKnowledgeBaseSuggestionsEvent(kb.ArticleList{}),
// login // login
NewLoginError(LoginErrorType_FREE_USER, "error"), NewLoginError(LoginErrorType_FREE_USER, "error"),

View File

@ -0,0 +1,425 @@
[
{
"index": 0,
"url": "https://proton.me/support/automatically-start-bridge",
"title": "Automatically start Bridge",
"keywords": [
"automatic",
"login",
"start",
"boot"
]
},
{
"index": 1,
"url": "https://proton.me/support/bridge-automatic-update",
"title": "Automatic Update and Bridge",
"keywords": [
"update",
"upgrade",
"restart",
"automatic",
"manual"
]
},
{
"index": 2,
"url": "https://proton.me/support/messages-encrypted-via-bridge",
"title": "Are my messages encrypted via Proton Mail Bridge?",
"keywords": [
"encrypted",
"privacy",
"message",
"security",
"gpg",
"pgp",
"crypto"
]
},
{
"index": 3,
"url": "https://proton.me/support/labels-in-bridge",
"title": "Labels in Bridge",
"keywords": [
"labels",
"folders",
"directories"
]
},
{
"index": 4,
"url": "https://proton.me/support/bridge-ssl-connection-issue",
"title": "Proton Mail Bridge connection issues with Thunderbird, Outlook, and Apple Mail",
"keywords": [
"connect",
"SSL",
"STARTTLS",
"client",
"program",
"Outlook",
"Apple Mail",
"Thunderbird"
]
},
{
"index": 5,
"url": "https://proton.me/support/sending-pgp-emails-bridge",
"title": "Sending PGP emails in Proton Mail Bridge",
"keywords": [
"pgp",
"gpg",
"encrypt",
"crypto"
]
},
{
"index": 6,
"url": "https://proton.me/support/difference-combined-addresses-mode-split-addresses-mode",
"title": "Difference between combined addresses mode and split addresses mode",
"keywords": [
"combined",
"split",
"address",
"mode"
]
},
{
"index": 7,
"url": "https://proton.me/support/thunderbird-connection-server-timed-error",
"title": "Thunderbird: 'Connection to server timed out' error",
"keywords": [
"Thunderbird",
"Connection to server timed out",
"Connection",
"Timeout"
]
},
{
"index": 8,
"url": "https://proton.me/support/update-required",
"title": "Update required",
"keywords": [
"update required",
"update",
"upgrade",
"restart",
"reboot"
]
},
{
"index": 9,
"url": "https://proton.me/support/port-already-occupied-error",
"title": "Port already occupied error",
"keywords": [
"Port",
"occupied",
"1143",
"1025",
"SMTP",
"IMAP",
"error"
]
},
{
"index": 10,
"url": "https://proton.me/support/clients-supported-bridge",
"title": "Email clients supported by Proton Mail Bridge",
"keywords": [
"client",
"Outlook",
"Thunderbird",
"Apple Mail",
"EM Client",
"The Bat",
"Eudora",
"Postbox",
"Canary",
"Spark"
]
},
{
"index": 11,
"url": "https://proton.me/support/imap-smtp-and-pop3-setup",
"title": "IMAP, SMTP, and POP3 setup",
"keywords": [
"IMAP",
"SMTP",
"setup",
"set up",
"configure",
"configuration",
"parameters"
]
},
{
"index": 12,
"url": "https://proton.me/support/protonmail-bridge-install",
"title": "How to install Proton Mail Bridge",
"keywords": [
"install",
"installer",
"setup",
"download",
"windows",
"mac",
"macos",
"linux"
]
},
{
"index": 13,
"url": "https://proton.me/support/bridge-for-linux",
"title": "Proton Mail Bridge for Linux",
"keywords": [
"Linux",
"Ubuntu",
"Fedora",
"Debian",
"Unix",
"deb",
"rpm",
"CentOS",
"Arch",
"Mint"
]
},
{
"index": 14,
"url": "https://proton.me/support/operating-systems-supported-bridge",
"title": "System requirements for Proton Mail Bridge",
"keywords": [
"requirement",
"cpu",
"memory",
"Windows",
"macOS",
"linux"
]
},
{
"index": 15,
"url": "https://proton.me/support/protonmail-bridge-configure-client",
"title": "How to configure your email client for Proton Mail Bridge",
"keywords": [
"Client",
"Outlook",
"configure",
"setup",
"application",
"setup",
"IMAP",
"SMTP"
]
},
{
"index": 16,
"url": "https://proton.me/support/invalid-password-error-setting-email-client",
"title": "Invalid password error while setting up email client",
"keywords": [
"password",
"invalid",
"error"
]
},
{
"index": 17,
"url": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019",
"title": "Proton Mail Bridge Microsoft Outlook for Windows 2019 setup guide",
"keywords": [
"Outlook",
"2019",
"setup",
"configuration"
]
},
{
"index": 18,
"url": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2016",
"title": "Proton Mail Bridge Microsoft Outlook 2016 for Windows setup guide",
"keywords": [
"Outlook",
"2019",
"setup",
"configuration"
]
},
{
"index": 19,
"url": "https://proton.me/support/protonmail-bridge-clients-apple-mail",
"title": "Proton Mail Bridge Apple Mail setup guide",
"keywords": [
"Apple Mail",
"setup",
"configuration"
]
},
{
"index": 20,
"url": "https://proton.me/support/protonmail-bridge-clients-macos-new-outlook",
"title": "Proton Mail Bridge new Outlook for macOS setup guide",
"keywords": [
"Outlook",
"setup",
"configuration"
]
},
{
"index": 21,
"url": "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird",
"title": "Proton Mail Bridge Thunderbird setup guide for Windows, macOS, and Linux",
"keywords": [
"Thunderbird",
"setup",
"configuration"
]
},
{
"index": 22,
"url": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2016",
"title": "Proton Mail Bridge Microsoft Outlook 2016 for macOS setup guide",
"keywords": [
"Outlook 2016",
"macOS"
]
},
{
"index": 23,
"url": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019",
"title": "Proton Mail Bridge Microsoft Outlook 2019 for macOS setup guide",
"keywords": [
"Outlook 2019",
"macOS"
]
},
{
"index": 24,
"url": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2013",
"title": "Proton Mail Bridge Microsoft Outlook 2013 for Windows setup guide",
"keywords": [
"Outlook 2013",
"macOS"
]
},
{
"index": 25,
"url": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2011",
"title": "Proton Mail Bridge Microsoft Outlook 2011 for macOS setup guide",
"keywords": [
"Outlook 2011",
"macOS"
]
},
{
"index": 26,
"url": "https://proton.me/support/install-bridge-linux-pkgbuild-file",
"title": "Installing Proton Mail Bridge for Linux using a PKGBUILD file",
"keywords": [
"Linux",
"pkgbuild"
]
},
{
"index": 27,
"url": "https://proton.me/support/installing-bridge-linux-deb-file",
"title": "Installing Proton Mail Bridge for Linux using a DEB file",
"keywords": [
"Linux",
"deb"
]
},
{
"index": 28,
"url": "https://proton.me/support/verifying-bridge-package",
"title": "Verifying the Proton Mail Bridge package for Linux",
"keywords": [
"Linux",
"Package",
"Verify"
]
},
{
"index": 29,
"url": "https://proton.me/support/bridge-cli-guide",
"title": "Bridge CLI (command line interface) guide",
"keywords": [
"CLI",
"Terminal",
"Command-line",
"Powershell"
]
},
{
"index": 30,
"url": "https://proton.me/support/install-bridge-linux-rpm-file",
"title": "Installing Proton Mail Bridge for Linux using an RPM file",
"keywords": [
"Linux",
"Install",
"RPM"
]
},
{
"index": 31,
"url": "https://proton.me/support/bridge-linux-login-error",
"title": "How to fix Proton Bridge login errors",
"keywords": [
"login",
"error",
"connection"
]
},
{
"index": 32,
"url": "https://proton.me/support/bridge-linux-tray-icon",
"title": "How to fix a missing system tray icon in Linux",
"keywords": [
"linux",
"tray",
"notification"
]
},
{
"index": 33,
"url": "https://proton.me/support/why-you-need-bridge",
"title": "Why you need Proton Mail Bridge",
"keywords": [
"Bridge",
"email",
"client"
]
},
{
"index": 34,
"url": "https://proton.me/support/protonmail-bridge-manual-update",
"title": "How to manually update Proton Mail Bridge",
"keywords": [
"update",
"upgrade",
"manual",
"download"
]
},
{
"index": 35,
"url": "https://proton.me/support/macos-certificate-warning",
"title": "Warning when installing Proton Mail Bridge on macOS",
"keywords": [
"install",
"macOS",
"warning",
"certificate"
]
},
{
"index": 36,
"url": "https://proton.me/support/apple-mail-certificate",
"title": "Why you need to install a certificate for Apple Mail with Proton Mail Bridge",
"keywords": [
"certificate",
"Apple Mail",
"macOS"
]
}
]

107
internal/kb/suggester.go Normal file
View File

@ -0,0 +1,107 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package kb
import (
_ "embed"
"encoding/json"
"errors"
"regexp"
"strings"
"github.com/bradenaw/juniper/xslices"
"golang.org/x/exp/slices"
)
var ErrArticleNotFound = errors.New("KB article not found")
//go:embed kbArticleList.json
var articleListString []byte
// Article is a struct that holds information about a knowledge-base article.
type Article struct {
Index uint64 `json:"index"`
URL string `json:"url"`
Title string `json:"title"`
Keywords []string `json:"keywords"`
Score int
}
type ArticleList []*Article
// GetArticleList returns the list of KB articles.
func GetArticleList() (ArticleList, error) {
var articles ArticleList
err := json.Unmarshal(articleListString, &articles)
return articles, err
}
// GetSuggestions returns a list of up to 3 suggestions for the built-in list of KB articles matching the given user input.
func GetSuggestions(userInput string) (ArticleList, error) {
articles, err := GetArticleList()
if err != nil {
return ArticleList{}, err
}
return GetSuggestionsFromArticleList(userInput, articles)
}
// GetSuggestionsFromArticleList returns a list of up to 3 suggestions for the given list of KB articles matching the given user input.
func GetSuggestionsFromArticleList(userInput string, articles ArticleList) (ArticleList, error) {
userInput = strings.ToUpper(userInput)
for _, article := range articles {
for _, keyword := range article.Keywords {
if strings.Contains(userInput, strings.ToUpper(keyword)) {
article.Score++
}
}
}
articles = xslices.Filter(articles, func(article *Article) bool { return article.Score > 0 })
slices.SortFunc(articles, func(lhs, rhs *Article) bool { return lhs.Score > rhs.Score })
if len(articles) > 3 {
return articles[:3], nil
}
return articles, nil
}
// GetArticleIndex retrieves the index of an article from its url. if the article is not found, ErrArticleNotFound is returned.
func GetArticleIndex(url string) (uint64, error) {
articles, err := GetArticleList()
if err != nil {
return 0, err
}
index := xslices.IndexFunc(articles, func(article *Article) bool { return strings.EqualFold(article.URL, url) })
if index == -1 {
return 0, ErrArticleNotFound
}
return uint64(index), nil
}
func simplifyUserInput(input string) string {
// replace any sequence not matching of the following with a single space:
// - letters in any language (accentuated or not)
// - numbers
// - the apostrophe character '
return strings.TrimSpace(regexp.MustCompile(`[^\p{L}\p{N}']+`).ReplaceAllString(input, " "))
}

View File

@ -0,0 +1,91 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package kb
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_ArticleList(t *testing.T) {
articles, err := GetArticleList()
require.NoError(t, err)
require.NotEmpty(t, articles)
var bits uint64
for _, article := range articles {
require.Truef(t, article.Index < 64, "Invalid KB article index %d, (must be < 64)", article.Index)
require.Zerof(t, bits&(1<<article.Index), "Duplicate index %d in knowledge base", article.Index)
bits |= bits | (1 << article.Index)
require.NotEmpty(t, article.URL, "KB article with index %d has no URL", article.Index)
require.NotEmpty(t, article.Title, "KB article with index %d has no title", article.Index)
require.NotEmpty(t, article.Keywords, "KB article with index %d has no keyword", article.Index)
}
}
func Test_GetSuggestions(t *testing.T) {
suggestions, err := GetSuggestions("Thunderbird is not working, error during password")
require.NoError(t, err)
count := len(suggestions)
require.True(t, (count > 0) && (count <= 3))
suggestions, err = GetSuggestions("Supercalifragilisticexpialidocious Sesquipedalian Worcestershire")
require.NoError(t, err)
require.Empty(t, suggestions)
}
func Test_GetSuggestionsFromArticleList(t *testing.T) {
articleList := ArticleList{}
suggestions, err := GetSuggestionsFromArticleList("Thunderbird", articleList)
require.NoError(t, err)
require.Empty(t, suggestions)
articleList = ArticleList{
&Article{
Index: 0,
URL: "https://proton.me",
Title: "Proton home page",
Keywords: []string{"proton"},
},
&Article{
Index: 1,
URL: "https://mozilla.org",
Title: "Mozilla home page",
Keywords: []string{"mozilla"},
},
}
suggestions, err = GetSuggestionsFromArticleList("PRoToN", articleList)
require.NoError(t, err)
require.Len(t, suggestions, 1)
require.Equal(t, suggestions[0].URL, "https://proton.me")
}
func Test_GetArticleIndex(t *testing.T) {
index1, err := GetArticleIndex("https://proton.me/support/bridge-for-linux")
require.NoError(t, err)
index2, err := GetArticleIndex("HTTPS://PROTON.ME/support/bridge-for-linux")
require.NoError(t, err)
require.Equal(t, index1, index2)
_, err = GetArticleIndex("https://proton.me")
require.ErrorIs(t, err, ErrArticleNotFound)
}
func Test_simplifyUserInput(t *testing.T) {
require.Equal(t, "word1 ñóÄ don't déjà 33 pizza", simplifyUserInput(" \nword1 \n\tñóÄ don't\n\n\ndéjà, 33 pizza=🍕\n,\n"))
}

View File

@ -102,9 +102,6 @@ func TestMetadataStage_JobCorrectlyFinishesAfterCancel(t *testing.T) {
// read one output then cancel // read one output then cancel
request, err := output.Consume(ctx) request, err := output.Consume(ctx)
require.NoError(t, err) require.NoError(t, err)
request.onFinished(ctx)
// cancel job context
jobCancel()
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(1) wg.Add(1)
@ -117,10 +114,13 @@ func TestMetadataStage_JobCorrectlyFinishesAfterCancel(t *testing.T) {
return return
} }
// cancel job context
jobCancel()
req.checkCancelled() req.checkCancelled()
} }
}() }()
wg.Wait() wg.Wait()
request.onFinished(ctx)
err = tj.job.waitAndClose(ctx) err = tj.job.waitAndClose(ctx)
require.Error(t, err) require.Error(t, err)
require.ErrorIs(t, err, context.Canceled) require.ErrorIs(t, err, context.Canceled)

View File

@ -20,9 +20,11 @@ package user
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"github.com/ProtonMail/gluon/reporter" "github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus" "github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
) )
func (user *User) SendConfigStatusSuccess(ctx context.Context) { func (user *User) SendConfigStatusSuccess(ctx context.Context) {
@ -193,39 +195,25 @@ func (user *User) AutoconfigUsed(client string) {
} }
} }
func (user *User) ExternalLinkClicked(article string) { func (user *User) ExternalLinkClicked(url string) {
if !user.configStatus.IsPending() { if !user.configStatus.IsPending() {
return return
} }
var trackedLinks = [...]string{ const externalLinkWasClicked = "External link was clicked."
"https://proton.me/support/bridge", index, err := kb.GetArticleIndex(url)
"https://proton.me/support/protonmail-bridge-clients-apple-mail", if err != nil {
"https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019", if errors.Is(err, kb.ErrArticleNotFound) {
"https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019", user.log.WithField("report", false).WithField("url", url).Debug(externalLinkWasClicked)
"https://proton.me/support/protonmail-bridge-clients-windows-thunderbird", } else {
"https://proton.me/support/protonmail-bridge-configure-client", user.log.WithError(err).Error("Failed to retrieve list of KB articles.")
"https://proton.me/support/bridge-address-list-has-changed", }
"https://proton.me/blog/tls-ssl-certificate#Extra-security-precautions-taken-by-ProtonMail", return
"https://proton.me/support/bridge-cant-move-cache",
"https://proton.me/support/difference-combined-addresses-mode-split-addresses-mode",
"https://proton.me/support/bridge-imap-login-failed",
"https://proton.me/support/port-already-occupied-error",
"https://proton.me/support/bridge-cannot-access-keychain",
"https://proton.me/support/protonmail-bridge-manual-update",
"https://proton.me/support/bridge-internal-error",
"https://proton.me/support/apple-mail-certificate",
"https://proton.me/support/macos-certificate-warning",
"https://proton.me/support/why-you-need-bridge",
} }
for id, url := range trackedLinks { if err := user.configStatus.RecordLinkClicked(index); err != nil {
if url == article { user.log.WithError(err).Error("Failed to log LinkClicked in config_status.")
if err := user.configStatus.RecordLinkClicked(uint(id)); err != nil { } else {
user.log.WithError(err).Error("Failed to log LinkClicked in config_status.") user.log.WithField("report", true).WithField("url", url).Debug(externalLinkWasClicked)
}
return
}
} }
user.log.WithField("article", article).Error("Failed to find KB article id.")
} }

View File

@ -1,21 +0,0 @@
---
wait: true
strict: true
file:
name: "./gobinsec-cache.yml"
expiration: "24h"
ignore:
# golang.org/x/net wrong match, we are using v0.1.0, fixed by 37e1c6af in v0.0.xxx
- "CVE-2021-33194"
# golang.org/x/crypto wrong match, we are using v0.1.0 all of this have been fixed in vO.O.xx
- "CVE-2019-11840"
- "CVE-2020-29652"
- "CVE-2021-43565"
- "CVE-2022-27191"
- "CVE-2020-9283"
- "CVE-2017-3204"
# golang.org/x/text wrong match, we are using v0.4.0, fixed in a previous version
- "CVE-2020-14040"

View File

@ -1,32 +0,0 @@
#!/bin/bash
# Copyright (c) 2023 Proton AG
#
# This file is part of Proton Mail Bridge.
#
# Proton Mail Bridge is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Proton Mail Bridge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
cd "$(\
cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd \
)"/gobinsec_update || exit 1
for i in $(seq 10); do
echo "Trying $i"
if make run; then
echo "Try $i succeeded"
break
fi
echo "Waiting to try again..."
sleep 30
done

View File

@ -1,6 +0,0 @@
run:
FILECACHE_FILE=gobinsec-cache-valid.yml \
FILECACHE_EXPIRATION=1h \
go run main.go

View File

@ -1,15 +0,0 @@
module gobinsec_update
go 1.18
require github.com/intercloud/gobinsec v0.10.2
require (
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/memcachier/gomemcache v0.0.0-20170425125614-d027381f7653 // indirect
golang.org/x/sys v0.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -1,33 +0,0 @@
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/intercloud/gobinsec v0.10.2 h1:4L2d4SaIqlHnUQ6Hlg1E51dqUg4jK+TpSILVTHaEvx4=
github.com/intercloud/gobinsec v0.10.2/go.mod h1:Y/AMKT0aQM40WDkTqlEe18W/IL6ZUuuJjdOXdayi+CI=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/memcachier/gomemcache v0.0.0-20170425125614-d027381f7653 h1:222emoxOt/bCmNHp8Xt0Pr5Am3gIbqRKFpb4CQ9O2SI=
github.com/memcachier/gomemcache v0.0.0-20170425125614-d027381f7653/go.mod h1:KoYVbOQexD45AOLfn+gsFB6c3o4ANzP1QKzjE6tZbK0=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,76 +0,0 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>
package main
import (
"fmt"
"io/ioutil"
"regexp"
"strings"
"github.com/intercloud/gobinsec/gobinsec"
)
type Depend struct {
Name string
Version string
}
func loadDependencies(file string) []Depend {
var dependencies []Depend
txt, err := ioutil.ReadFile(file)
if err != nil {
return dependencies
}
re := regexp.MustCompile(`\t[a-zA-Z0-9-\/\.]* v.*`)
matches := re.FindAllString(string(txt), -1)
for _, str := range matches {
withoutTab := strings.Split(str, "\t")
split := strings.Split(withoutTab[1], " ")
dependencies = append(dependencies, Depend{split[0], split[1]})
}
return dependencies
}
func main() {
dependencies := loadDependencies("../../go.mod")
if err := gobinsec.LoadConfig("", true, true, true, true); err != nil {
panic(err)
}
if err := gobinsec.BuildCache(); err != nil {
panic(err)
}
for _, dep := range dependencies {
fmt.Println("... Checking " + dep.Name + " " + dep.Version)
dep, err := gobinsec.NewDependency(dep.Name, dep.Version)
if err != nil {
panic(err)
}
if err := dep.LoadVulnerabilities(); err != nil {
panic(err)
}
if err := gobinsec.CacheInstance.Close(); err != nil {
panic(err)
}
}
}

71
utils/govulncheck.sh Executable file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Copyright (c) 2023 Proton AG
#
# This file is part of Proton Mail Bridge.
#
# Proton Mail Bridge is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Proton Mail Bridge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
set -eo pipefail
main(){
go install golang.org/x/vuln/cmd/govulncheck@latest
make gofiles
govulncheck -json ./... > vulns.json
jq -r '.osv.id | select( . != null )' < vulns.json > vulns_osv_ids.txt
ignore GO-2023-2102 "GODT-3160 update go to 1.21.4"
ignore GO-2023-2043 "GODT-3160 update go to 1.21.4"
ignore GO-2023-2041 "GODT-3160 update go to 1.21.4"
ignore GO-2023-1878 "GODT-3160 update go to 1.21.4"
ignore GO-2023-1987 "GODT-3160 update go to 1.21.4"
ignore GO-2023-1840 "GODT-3160 update go to 1.21.4"
ignore GO-2023-2185 "GODT-3160 update go to 1.21.4"
ignore GO-2023-2186 "GODT-3160 update go to 1.21.4"
ignore GO-2023-2382 "GODT-3160 update go to 1.21.4"
ignore GO-2023-2328 "GODT-3124 RESTY race condition"
has_vulns
echo
echo "No new vulnerabilities found."
}
ignore(){
echo "ignoring $1 fix: $2"
cp vulns_osv_ids.txt tmp
grep -v "$1" < tmp > vulns_osv_ids.txt || true
rm tmp
}
has_vulns(){
has=false
while read -r osv; do
jq \
--arg osvid "$osv" \
'.osv | select ( .id == $osvid) | {"id":.id, "ranges": .affected[0].ranges, "import": .affected[0].ecosystem_specific.imports[0].path}' \
< vulns.json
has=true
done < vulns_osv_ids.txt
if [ "$has" == true ]; then
echo
echo "Vulnerability found"
return 1
fi
}
main

View File

@ -0,0 +1,137 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
"github.com/urfave/cli/v2"
)
const flagArticles = "articles"
const flagInput = "input"
func main() {
app := &cli.App{
Name: "kb-suggester",
Usage: "test bridge KB article suggester",
HideHelpCommand: true,
ArgsUsage: "",
Flags: []cli.Flag{
&cli.StringFlag{
Name: flagArticles,
Aliases: []string{"a"},
Usage: "use `articles.json` as the JSON article list",
TakesFile: true,
},
&cli.StringFlag{
Name: flagInput,
Aliases: []string{"i"},
Usage: "read user input from the `userInput` file",
TakesFile: true,
},
},
Action: run,
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
func getUserInput(ctx *cli.Context) (string, error) {
inputFile := ctx.String(flagInput)
var bytes []byte
var err error
if len(inputFile) == 0 {
var fi os.FileInfo
if fi, err = os.Stdin.Stat(); err != nil {
return "", err
}
if (fi.Mode() & os.ModeNamedPipe) == 0 {
fmt.Println("Type your input, Ctrl+D to finish: ")
}
bytes, err = io.ReadAll(os.Stdin)
} else {
bytes, err = os.ReadFile(filepath.Clean(inputFile))
}
if err != nil {
return "", err
}
return string(bytes), nil
}
func getArticleList(ctx *cli.Context) (kb.ArticleList, error) {
articleFile := ctx.String(flagArticles)
if len(articleFile) == 0 {
return kb.GetArticleList()
}
bytes, err := os.ReadFile(filepath.Clean(articleFile))
if err != nil {
return nil, err
}
var result kb.ArticleList
err = json.Unmarshal(bytes, &result)
return result, err
}
func run(ctx *cli.Context) error {
if ctx.Args().Len() > 0 {
_ = cli.ShowAppHelp(ctx)
return errors.New("command accept no argument")
}
articles, err := getArticleList(ctx)
if err != nil {
return err
}
userInput, err := getUserInput(ctx)
if err != nil {
return err
}
suggestions, err := kb.GetSuggestionsFromArticleList(userInput, articles)
if err != nil {
return err
}
if len(suggestions) == 0 {
fmt.Println("No suggestions found")
return nil
}
for _, suggestion := range suggestions {
fmt.Printf("Score %v: %v (%v)\n", suggestion.Score, suggestion.Title, suggestion.URL)
}
return nil
}