Internal
Public Access
QFormLayout without an explicit field growth policy falls back to the active Qt style's default, which differs per platform: Linux's Fusion/Breeze styles expand fields to fill the row, but macOS's native style defaults to FieldsStayAtSizeHint, leaving fields small with empty space beside them. Set the policy explicitly so the dialog renders consistently everywhere. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
329 lines
13 KiB
C++
329 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();
|
|
form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
|
|
form->setFormAlignment(Qt::AlignLeft | Qt::AlignTop);
|
|
form->setLabelAlignment(Qt::AlignRight);
|
|
|
|
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));
|
|
}
|