Start Milestone 4 interactive SSH terminal and host-key flow
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QTextCursor>
|
||||
#include <QThread>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
@@ -28,11 +29,15 @@ SessionTab::SessionTab(const Profile& profile, QWidget* parent)
|
||||
m_state(SessionState::Disconnected),
|
||||
m_statusLabel(nullptr),
|
||||
m_errorLabel(nullptr),
|
||||
m_terminalOutput(nullptr),
|
||||
m_terminalInput(nullptr),
|
||||
m_eventLog(nullptr),
|
||||
m_connectButton(nullptr),
|
||||
m_disconnectButton(nullptr),
|
||||
m_reconnectButton(nullptr),
|
||||
m_copyErrorButton(nullptr)
|
||||
m_copyErrorButton(nullptr),
|
||||
m_sendInputButton(nullptr),
|
||||
m_clearTerminalButton(nullptr)
|
||||
{
|
||||
qRegisterMetaType<SessionConnectOptions>("SessionConnectOptions");
|
||||
qRegisterMetaType<SessionState>("SessionState");
|
||||
@@ -59,6 +64,16 @@ SessionTab::SessionTab(const Profile& profile, QWidget* parent)
|
||||
m_backend,
|
||||
&SessionBackend::reconnectSession,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestInput,
|
||||
m_backend,
|
||||
&SessionBackend::sendInput,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestHostKeyConfirmation,
|
||||
m_backend,
|
||||
&SessionBackend::confirmHostKey,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(m_backend,
|
||||
&SessionBackend::stateChanged,
|
||||
@@ -75,6 +90,16 @@ SessionTab::SessionTab(const Profile& profile, QWidget* parent)
|
||||
this,
|
||||
&SessionTab::onBackendConnectionError,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::outputReceived,
|
||||
this,
|
||||
&SessionTab::onBackendOutputReceived,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::hostKeyConfirmationRequested,
|
||||
this,
|
||||
&SessionTab::onBackendHostKeyConfirmationRequested,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_backendThread->start();
|
||||
|
||||
@@ -139,6 +164,22 @@ void SessionTab::onCopyErrorClicked()
|
||||
appendEvent(QStringLiteral("Copied last error to clipboard."));
|
||||
}
|
||||
|
||||
void SessionTab::onSendInputClicked()
|
||||
{
|
||||
const QString input = m_terminalInput->text();
|
||||
if (input.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit requestInput(input + QStringLiteral("\n"));
|
||||
m_terminalInput->clear();
|
||||
}
|
||||
|
||||
void SessionTab::onClearTerminalClicked()
|
||||
{
|
||||
m_terminalOutput->clear();
|
||||
}
|
||||
|
||||
void SessionTab::onBackendStateChanged(SessionState state, const QString& message)
|
||||
{
|
||||
setState(state, message);
|
||||
@@ -156,6 +197,35 @@ void SessionTab::onBackendConnectionError(const QString& displayMessage, const Q
|
||||
m_copyErrorButton->setEnabled(true);
|
||||
}
|
||||
|
||||
void SessionTab::onBackendOutputReceived(const QString& text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_terminalOutput->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.insertText(text);
|
||||
m_terminalOutput->setTextCursor(cursor);
|
||||
m_terminalOutput->ensureCursorVisible();
|
||||
}
|
||||
|
||||
void SessionTab::onBackendHostKeyConfirmationRequested(const QString& prompt)
|
||||
{
|
||||
const QString question = prompt.isEmpty()
|
||||
? QStringLiteral("Unknown SSH host key. Do you trust this host?")
|
||||
: prompt;
|
||||
|
||||
const QMessageBox::StandardButton reply = QMessageBox::question(
|
||||
this,
|
||||
QStringLiteral("SSH Host Key Confirmation"),
|
||||
QStringLiteral("%1\n\nTrust and continue?").arg(question),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No);
|
||||
|
||||
emit requestHostKeyConfirmation(reply == QMessageBox::Yes);
|
||||
}
|
||||
|
||||
void SessionTab::setupUi()
|
||||
{
|
||||
auto* rootLayout = new QVBoxLayout(this);
|
||||
@@ -186,20 +256,33 @@ void SessionTab::setupUi()
|
||||
actionRow->addWidget(m_copyErrorButton);
|
||||
actionRow->addStretch();
|
||||
|
||||
auto* surfaceLabel = new QLabel(QStringLiteral("OrbitHub Native Surface"), this);
|
||||
QFont surfaceFont = surfaceLabel->font();
|
||||
surfaceFont.setPointSize(surfaceFont.pointSize() + 6);
|
||||
surfaceFont.setBold(true);
|
||||
surfaceLabel->setFont(surfaceFont);
|
||||
surfaceLabel->setAlignment(Qt::AlignCenter);
|
||||
surfaceLabel->setMinimumHeight(180);
|
||||
surfaceLabel->setStyleSheet(
|
||||
QStringLiteral("border: 1px solid #8a8a8a; background-color: #f5f5f5;"));
|
||||
auto* terminalHeader = new QHBoxLayout();
|
||||
auto* terminalLabel = new QLabel(QStringLiteral("SSH Terminal"), this);
|
||||
m_clearTerminalButton = new QPushButton(QStringLiteral("Clear"), this);
|
||||
terminalHeader->addWidget(terminalLabel);
|
||||
terminalHeader->addStretch();
|
||||
terminalHeader->addWidget(m_clearTerminalButton);
|
||||
|
||||
m_terminalOutput = new QPlainTextEdit(this);
|
||||
m_terminalOutput->setReadOnly(true);
|
||||
m_terminalOutput->setMaximumBlockCount(4000);
|
||||
QFont terminalFont(QStringLiteral("Monospace"));
|
||||
terminalFont.setStyleHint(QFont::TypeWriter);
|
||||
m_terminalOutput->setFont(terminalFont);
|
||||
m_terminalOutput->setMinimumHeight(220);
|
||||
|
||||
auto* terminalInputRow = new QHBoxLayout();
|
||||
m_terminalInput = new QLineEdit(this);
|
||||
m_terminalInput->setPlaceholderText(QStringLiteral("Type command and press Enter..."));
|
||||
m_sendInputButton = new QPushButton(QStringLiteral("Send"), this);
|
||||
terminalInputRow->addWidget(m_terminalInput, 1);
|
||||
terminalInputRow->addWidget(m_sendInputButton);
|
||||
|
||||
auto* eventHeader = new QLabel(QStringLiteral("Session Events"), this);
|
||||
m_eventLog = new QPlainTextEdit(this);
|
||||
m_eventLog->setReadOnly(true);
|
||||
m_eventLog->setPlaceholderText(QStringLiteral("Session event log..."));
|
||||
m_eventLog->setMinimumHeight(180);
|
||||
m_eventLog->setMinimumHeight(140);
|
||||
|
||||
rootLayout->addWidget(profileLabel);
|
||||
rootLayout->addWidget(endpointLabel);
|
||||
@@ -207,13 +290,22 @@ void SessionTab::setupUi()
|
||||
rootLayout->addWidget(m_statusLabel);
|
||||
rootLayout->addWidget(m_errorLabel);
|
||||
rootLayout->addLayout(actionRow);
|
||||
rootLayout->addWidget(surfaceLabel);
|
||||
rootLayout->addLayout(terminalHeader);
|
||||
rootLayout->addWidget(m_terminalOutput, 1);
|
||||
rootLayout->addLayout(terminalInputRow);
|
||||
rootLayout->addWidget(eventHeader);
|
||||
rootLayout->addWidget(m_eventLog, 1);
|
||||
|
||||
connect(m_connectButton, &QPushButton::clicked, this, &SessionTab::onConnectClicked);
|
||||
connect(m_disconnectButton, &QPushButton::clicked, this, &SessionTab::onDisconnectClicked);
|
||||
connect(m_reconnectButton, &QPushButton::clicked, this, &SessionTab::onReconnectClicked);
|
||||
connect(m_copyErrorButton, &QPushButton::clicked, this, &SessionTab::onCopyErrorClicked);
|
||||
connect(m_sendInputButton, &QPushButton::clicked, this, &SessionTab::onSendInputClicked);
|
||||
connect(m_terminalInput, &QLineEdit::returnPressed, this, &SessionTab::onSendInputClicked);
|
||||
connect(m_clearTerminalButton,
|
||||
&QPushButton::clicked,
|
||||
this,
|
||||
&SessionTab::onClearTerminalClicked);
|
||||
}
|
||||
|
||||
std::optional<SessionConnectOptions> SessionTab::buildConnectOptions()
|
||||
@@ -352,12 +444,17 @@ QString SessionTab::stateSuffix() const
|
||||
|
||||
void SessionTab::refreshActionButtons()
|
||||
{
|
||||
m_connectButton->setEnabled(m_state == SessionState::Disconnected
|
||||
|| m_state == SessionState::Failed);
|
||||
const bool isConnected = m_state == SessionState::Connected;
|
||||
const bool canConnect = m_state == SessionState::Disconnected || m_state == SessionState::Failed;
|
||||
|
||||
m_connectButton->setEnabled(canConnect);
|
||||
m_disconnectButton->setEnabled(m_state == SessionState::Connected
|
||||
|| m_state == SessionState::Connecting);
|
||||
m_reconnectButton->setEnabled(m_state == SessionState::Connected
|
||||
|| m_state == SessionState::Failed
|
||||
|| m_state == SessionState::Disconnected);
|
||||
m_copyErrorButton->setEnabled(!m_lastError.isEmpty());
|
||||
|
||||
m_terminalInput->setEnabled(isConnected);
|
||||
m_sendInputButton->setEnabled(isConnected);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user