Files
orbithub/src/profile_dialog.cpp

326 lines
13 KiB
C++

#include "profile_dialog.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QFileInfo>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSignalBlocker>
#include <QSpinBox>
#include <QVBoxLayout>
namespace {
int standardPortForProtocol(const QString& protocol)
{
if (protocol == QStringLiteral("RDP")) {
return 3389;
}
if (protocol == QStringLiteral("VNC")) {
return 5900;
}
return 22; // SSH default
}
QString normalizedProtocol(const QString& protocol)
{
if (protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) {
return QStringLiteral("RDP");
}
if (protocol.compare(QStringLiteral("VNC"), Qt::CaseInsensitive) == 0) {
return QStringLiteral("VNC");
}
return QStringLiteral("SSH");
}
QString normalizedAuthMode(const QString& protocol, const QString& authMode)
{
if (protocol != QStringLiteral("SSH")) {
return QStringLiteral("Password");
}
if (authMode.compare(QStringLiteral("Private Key"), Qt::CaseInsensitive) == 0) {
return QStringLiteral("Private Key");
}
return QStringLiteral("Password");
}
}
ProfileDialog::ProfileDialog(QWidget* parent)
: QDialog(parent),
m_nameInput(new QLineEdit(this)),
m_hostInput(new QLineEdit(this)),
m_portInput(new QSpinBox(this)),
m_usernameInput(new QLineEdit(this)),
m_domainInput(new QLineEdit(this)),
m_tagsInput(new QLineEdit(this)),
m_protocolInput(new QComboBox(this)),
m_authModeInput(new QComboBox(this)),
m_privateKeyPathInput(new QLineEdit(this)),
m_browsePrivateKeyButton(new QPushButton(QStringLiteral("Browse"), this)),
m_knownHostsPolicyInput(new QComboBox(this)),
m_rdpSecurityModeInput(new QComboBox(this)),
m_rdpPerformanceProfileInput(new QComboBox(this)),
m_protocolHint(new QLabel(this)),
m_folderHint(new QLabel(this))
{
resize(560, 360);
auto* layout = new QVBoxLayout(this);
auto* form = new QFormLayout();
m_nameInput->setPlaceholderText(QStringLiteral("Production Bastion"));
m_hostInput->setPlaceholderText(QStringLiteral("example.internal"));
m_portInput->setRange(1, 65535);
m_portInput->setValue(22);
m_usernameInput->setPlaceholderText(QStringLiteral("deploy"));
m_domainInput->setPlaceholderText(QStringLiteral("CONTOSO"));
m_tagsInput->setPlaceholderText(QStringLiteral("prod, linux, db"));
m_protocolInput->addItems({QStringLiteral("SSH"), QStringLiteral("RDP"), QStringLiteral("VNC")});
m_authModeInput->addItems({QStringLiteral("Password"), QStringLiteral("Private Key")});
m_knownHostsPolicyInput->addItems(
{QStringLiteral("Ask"), QStringLiteral("Strict"), QStringLiteral("Accept New"), QStringLiteral("Ignore")});
m_rdpSecurityModeInput->addItems(
{QStringLiteral("Negotiate"), QStringLiteral("NLA"), QStringLiteral("TLS"), QStringLiteral("RDP")});
m_rdpPerformanceProfileInput->addItems({QStringLiteral("Balanced"),
QStringLiteral("Best Quality"),
QStringLiteral("Best Performance"),
QStringLiteral("Auto Detect")});
m_privateKeyPathInput->setPlaceholderText(QStringLiteral("/home/user/.ssh/id_ed25519"));
auto* privateKeyRow = new QWidget(this);
auto* privateKeyLayout = new QHBoxLayout(privateKeyRow);
privateKeyLayout->setContentsMargins(0, 0, 0, 0);
privateKeyLayout->addWidget(m_privateKeyPathInput, 1);
privateKeyLayout->addWidget(m_browsePrivateKeyButton);
connect(m_browsePrivateKeyButton,
&QPushButton::clicked,
this,
[this]() {
const QString selected = QFileDialog::getOpenFileName(this,
QStringLiteral("Select Private Key"),
QString(),
QStringLiteral("All Files (*)"));
if (!selected.isEmpty()) {
m_privateKeyPathInput->setText(selected);
}
});
connect(m_protocolInput,
&QComboBox::currentTextChanged,
this,
[this](const QString& protocol) {
m_portInput->setValue(standardPortForProtocol(protocol));
if (protocol != QStringLiteral("SSH")) {
const QSignalBlocker blocker(m_authModeInput);
m_authModeInput->setCurrentText(QStringLiteral("Password"));
}
refreshAuthFields();
});
connect(m_authModeInput,
&QComboBox::currentTextChanged,
this,
[this](const QString&) { refreshAuthFields(); });
form->addRow(QStringLiteral("Name"), m_nameInput);
form->addRow(QStringLiteral("Host"), m_hostInput);
form->addRow(QStringLiteral("Port"), m_portInput);
form->addRow(QStringLiteral("Username"), m_usernameInput);
form->addRow(QStringLiteral("Domain"), m_domainInput);
form->addRow(QStringLiteral("Tags"), m_tagsInput);
form->addRow(QStringLiteral("Protocol"), m_protocolInput);
form->addRow(QStringLiteral("Auth Mode"), m_authModeInput);
form->addRow(QStringLiteral("Private Key"), privateKeyRow);
form->addRow(QStringLiteral("Known Hosts"), m_knownHostsPolicyInput);
form->addRow(QStringLiteral("RDP Security"), m_rdpSecurityModeInput);
form->addRow(QStringLiteral("RDP Performance"), m_rdpPerformanceProfileInput);
auto* note = new QLabel(
QStringLiteral("Passwords are requested at connect time and are not stored."),
this);
note->setWordWrap(true);
m_protocolHint->setWordWrap(true);
m_folderHint->setWordWrap(true);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addLayout(form);
layout->addWidget(m_protocolHint);
layout->addWidget(m_folderHint);
layout->addWidget(note);
layout->addWidget(buttons);
refreshAuthFields();
}
void ProfileDialog::setDialogTitle(const QString& title)
{
setWindowTitle(title);
}
void ProfileDialog::setDefaultFolderPath(const QString& folderPath)
{
m_defaultFolderPath = folderPath.trimmed();
refreshAuthFields();
}
void ProfileDialog::setProfile(const Profile& profile)
{
m_nameInput->setText(profile.name);
m_hostInput->setText(profile.host);
m_portInput->setValue(profile.port > 0 ? profile.port : 22);
m_usernameInput->setText(profile.username);
m_domainInput->setText(profile.domain);
m_defaultFolderPath = profile.folderPath.trimmed();
m_tagsInput->setText(profile.tags);
m_privateKeyPathInput->setText(profile.privateKeyPath);
const int protocolIndex = m_protocolInput->findText(profile.protocol);
{
// Keep stored custom port when loading an existing profile.
const QSignalBlocker blocker(m_protocolInput);
m_protocolInput->setCurrentIndex(protocolIndex >= 0 ? protocolIndex : 0);
}
const int authModeIndex = m_authModeInput->findText(profile.authMode);
m_authModeInput->setCurrentIndex(authModeIndex >= 0 ? authModeIndex : 0);
const int knownHostsIndex = m_knownHostsPolicyInput->findText(profile.knownHostsPolicy);
m_knownHostsPolicyInput->setCurrentIndex(knownHostsIndex >= 0 ? knownHostsIndex : 0);
const int securityModeIndex = m_rdpSecurityModeInput->findText(profile.rdpSecurityMode);
m_rdpSecurityModeInput->setCurrentIndex(securityModeIndex >= 0 ? securityModeIndex : 0);
const int performanceProfileIndex =
m_rdpPerformanceProfileInput->findText(profile.rdpPerformanceProfile);
m_rdpPerformanceProfileInput->setCurrentIndex(performanceProfileIndex >= 0 ? performanceProfileIndex
: 0);
refreshAuthFields();
}
Profile ProfileDialog::profile() const
{
Profile profile;
const QString protocol = normalizedProtocol(m_protocolInput->currentText());
const QString authMode = normalizedAuthMode(protocol, m_authModeInput->currentText());
profile.id = -1;
profile.name = m_nameInput->text().trimmed();
profile.host = m_hostInput->text().trimmed();
profile.port = m_portInput->value();
profile.username = m_usernameInput->text().trimmed();
profile.domain = protocol == QStringLiteral("RDP") ? m_domainInput->text().trimmed() : QString();
profile.folderPath = m_defaultFolderPath.trimmed();
profile.tags = m_tagsInput->text().trimmed();
profile.protocol = protocol;
profile.authMode = authMode;
profile.privateKeyPath = (protocol == QStringLiteral("SSH")
&& authMode == QStringLiteral("Private Key"))
? m_privateKeyPathInput->text().trimmed()
: QString();
profile.knownHostsPolicy = protocol == QStringLiteral("SSH") ? m_knownHostsPolicyInput->currentText()
: QStringLiteral("Ask");
profile.rdpSecurityMode = protocol == QStringLiteral("RDP")
? m_rdpSecurityModeInput->currentText()
: QStringLiteral("Negotiate");
profile.rdpPerformanceProfile = protocol == QStringLiteral("RDP")
? m_rdpPerformanceProfileInput->currentText()
: QStringLiteral("Balanced");
return profile;
}
void ProfileDialog::accept()
{
if (m_nameInput->text().trimmed().isEmpty()) {
QMessageBox::warning(this,
QStringLiteral("Validation Error"),
QStringLiteral("Profile name is required."));
return;
}
if (m_hostInput->text().trimmed().isEmpty()) {
QMessageBox::warning(this,
QStringLiteral("Validation Error"),
QStringLiteral("Host is required."));
return;
}
const QString protocol = m_protocolInput->currentText();
if ((protocol == QStringLiteral("SSH") || protocol == QStringLiteral("RDP"))
&& m_usernameInput->text().trimmed().isEmpty()) {
QMessageBox::warning(this,
QStringLiteral("Validation Error"),
QStringLiteral("Username is required for %1 profiles.").arg(protocol));
return;
}
if (protocol == QStringLiteral("SSH")
&& m_authModeInput->currentText() == QStringLiteral("Private Key")) {
const QString privateKeyPath = m_privateKeyPathInput->text().trimmed();
if (privateKeyPath.isEmpty()) {
QMessageBox::warning(this,
QStringLiteral("Validation Error"),
QStringLiteral("Private key path is required for SSH private key authentication."));
return;
}
if (!QFileInfo::exists(privateKeyPath)) {
QMessageBox::warning(this,
QStringLiteral("Validation Error"),
QStringLiteral("Private key file does not exist: %1").arg(privateKeyPath));
return;
}
}
QDialog::accept();
}
void ProfileDialog::refreshAuthFields()
{
const QString protocol = normalizedProtocol(m_protocolInput->currentText());
const bool isSsh = protocol == QStringLiteral("SSH");
const bool isRdp = protocol == QStringLiteral("RDP");
const bool isVnc = protocol == QStringLiteral("VNC");
const QString normalizedMode = normalizedAuthMode(protocol, m_authModeInput->currentText());
if (normalizedMode != m_authModeInput->currentText()) {
const QSignalBlocker blocker(m_authModeInput);
m_authModeInput->setCurrentText(normalizedMode);
}
const bool isPrivateKey = normalizedMode == QStringLiteral("Private Key");
m_authModeInput->setEnabled(isSsh);
m_privateKeyPathInput->setEnabled(isSsh && isPrivateKey);
m_browsePrivateKeyButton->setEnabled(isSsh && isPrivateKey);
m_knownHostsPolicyInput->setEnabled(isSsh);
m_domainInput->setEnabled(isRdp);
m_rdpSecurityModeInput->setEnabled(isRdp);
m_rdpPerformanceProfileInput->setEnabled(isRdp);
if (isSsh) {
m_usernameInput->setPlaceholderText(QStringLiteral("deploy"));
m_protocolHint->setText(
QStringLiteral("SSH: username is required. Choose Password or Private Key auth."));
} else if (isRdp) {
m_usernameInput->setPlaceholderText(QStringLiteral("Administrator"));
m_protocolHint->setText(
QStringLiteral("RDP: username and password are required. Domain is optional."));
} else if (isVnc) {
m_usernameInput->setPlaceholderText(QStringLiteral("optional"));
m_protocolHint->setText(
QStringLiteral("VNC: host and port are required. Username/domain are optional and ignored by most servers."));
}
m_folderHint->setText(m_defaultFolderPath.isEmpty()
? QStringLiteral("Target folder: root")
: QStringLiteral("Target folder: %1").arg(m_defaultFolderPath));
}