Compare commits
2 Commits
v0-m1-done
...
01762422e9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01762422e9 | ||
|
|
f8a81ebe36 |
@@ -16,6 +16,8 @@ qt_standard_project_setup()
|
|||||||
|
|
||||||
add_executable(orbithub
|
add_executable(orbithub
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/profile_dialog.cpp
|
||||||
|
src/profile_dialog.h
|
||||||
src/profile_repository.cpp
|
src/profile_repository.cpp
|
||||||
src/profile_repository.h
|
src/profile_repository.h
|
||||||
src/profiles_window.cpp
|
src/profiles_window.cpp
|
||||||
|
|||||||
@@ -54,3 +54,13 @@ OrbitHub uses a two-window model:
|
|||||||
- Profiles CRUD
|
- Profiles CRUD
|
||||||
- Connect loads profile name into tab
|
- Connect loads profile name into tab
|
||||||
- Tag: v0-m1-done
|
- Tag: v0-m1-done
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Milestone 2
|
||||||
|
|
||||||
|
- Extend profiles schema (`host`, `port`, `username`, `protocol`, `auth_mode`)
|
||||||
|
- Replace prompt-based create/edit with a structured profile dialog form
|
||||||
|
- Connect loads full profile details into session tab
|
||||||
|
- Session lifecycle states in UI (`Connecting`, `Connected`, `Failed`) with non-blocking updates
|
||||||
|
- Tag: v0-m2-done
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ Delivered:
|
|||||||
- Cross-platform build command guide in `docs/BUILDING.md`
|
- Cross-platform build command guide in `docs/BUILDING.md`
|
||||||
|
|
||||||
Git:
|
Git:
|
||||||
- Branch: `milestone-0-restart-cpp`
|
|
||||||
- Tag: `v0-m0-done`
|
- Tag: `v0-m0-done`
|
||||||
|
|
||||||
## Milestone 1 - Storage and CRUD
|
## Milestone 1 - Storage and CRUD
|
||||||
@@ -29,5 +28,18 @@ Delivered:
|
|||||||
- Double-click connect opens `SessionWindow` tab with selected profile name
|
- Double-click connect opens `SessionWindow` tab with selected profile name
|
||||||
|
|
||||||
Git:
|
Git:
|
||||||
- Branch: `milestone-1-storage`
|
|
||||||
- Tag: `v0-m1-done`
|
- Tag: `v0-m1-done`
|
||||||
|
|
||||||
|
## Milestone 2 - Profile Details and Connect Lifecycle
|
||||||
|
|
||||||
|
Status: Completed
|
||||||
|
|
||||||
|
Delivered:
|
||||||
|
- SQLite schema migration for profile details (`host`, `port`, `username`, `protocol`, `auth_mode`)
|
||||||
|
- New `ProfileDialog` form for New/Edit profile workflows
|
||||||
|
- Profiles list now shows endpoint metadata and supports search by name or host
|
||||||
|
- Connect now loads complete profile details into `SessionWindow`
|
||||||
|
- Session tab lifecycle status updates (`Connecting`, `Connected`, `Failed`) via non-blocking timer flow
|
||||||
|
|
||||||
|
Git:
|
||||||
|
- Tag: `v0-m2-done`
|
||||||
|
|||||||
99
src/profile_dialog.cpp
Normal file
99
src/profile_dialog.cpp
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#include "profile_dialog.h"
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
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_protocolInput(new QComboBox(this)),
|
||||||
|
m_authModeInput(new QComboBox(this))
|
||||||
|
{
|
||||||
|
resize(420, 260);
|
||||||
|
|
||||||
|
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_protocolInput->addItems({QStringLiteral("SSH"), QStringLiteral("RDP"), QStringLiteral("VNC")});
|
||||||
|
m_authModeInput->addItems({QStringLiteral("Password"), QStringLiteral("Private Key")});
|
||||||
|
|
||||||
|
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("Protocol"), m_protocolInput);
|
||||||
|
form->addRow(QStringLiteral("Auth Mode"), m_authModeInput);
|
||||||
|
|
||||||
|
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(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileDialog::setDialogTitle(const QString& title)
|
||||||
|
{
|
||||||
|
setWindowTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
const int protocolIndex = m_protocolInput->findText(profile.protocol);
|
||||||
|
m_protocolInput->setCurrentIndex(protocolIndex >= 0 ? protocolIndex : 0);
|
||||||
|
|
||||||
|
const int authModeIndex = m_authModeInput->findText(profile.authMode);
|
||||||
|
m_authModeInput->setCurrentIndex(authModeIndex >= 0 ? authModeIndex : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile ProfileDialog::profile() const
|
||||||
|
{
|
||||||
|
Profile profile;
|
||||||
|
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.protocol = m_protocolInput->currentText();
|
||||||
|
profile.authMode = m_authModeInput->currentText();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog::accept();
|
||||||
|
}
|
||||||
35
src/profile_dialog.h
Normal file
35
src/profile_dialog.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef ORBITHUB_PROFILE_DIALOG_H
|
||||||
|
#define ORBITHUB_PROFILE_DIALOG_H
|
||||||
|
|
||||||
|
#include "profile_repository.h"
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
class QComboBox;
|
||||||
|
class QLineEdit;
|
||||||
|
class QSpinBox;
|
||||||
|
|
||||||
|
class ProfileDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ProfileDialog(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
void setDialogTitle(const QString& title);
|
||||||
|
void setProfile(const Profile& profile);
|
||||||
|
Profile profile() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void accept() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLineEdit* m_nameInput;
|
||||||
|
QLineEdit* m_hostInput;
|
||||||
|
QSpinBox* m_portInput;
|
||||||
|
QLineEdit* m_usernameInput;
|
||||||
|
QComboBox* m_protocolInput;
|
||||||
|
QComboBox* m_authModeInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "profile_repository.h"
|
#include "profile_repository.h"
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QSet>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
@@ -20,6 +21,35 @@ QString buildDatabasePath()
|
|||||||
|
|
||||||
return dataDir.filePath(QStringLiteral("orbithub_profiles.sqlite"));
|
return dataDir.filePath(QStringLiteral("orbithub_profiles.sqlite"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bindProfileFields(QSqlQuery& query, const Profile& profile)
|
||||||
|
{
|
||||||
|
query.addBindValue(profile.name.trimmed());
|
||||||
|
query.addBindValue(profile.host.trimmed());
|
||||||
|
query.addBindValue(profile.port);
|
||||||
|
query.addBindValue(profile.username.trimmed());
|
||||||
|
query.addBindValue(profile.protocol.trimmed());
|
||||||
|
query.addBindValue(profile.authMode.trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile profileFromQuery(const QSqlQuery& query)
|
||||||
|
{
|
||||||
|
Profile profile;
|
||||||
|
profile.id = query.value(0).toLongLong();
|
||||||
|
profile.name = query.value(1).toString();
|
||||||
|
profile.host = query.value(2).toString();
|
||||||
|
profile.port = query.value(3).toInt();
|
||||||
|
profile.username = query.value(4).toString();
|
||||||
|
profile.protocol = query.value(5).toString();
|
||||||
|
profile.authMode = query.value(6).toString();
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isProfileValid(const Profile& profile)
|
||||||
|
{
|
||||||
|
return !profile.name.trimmed().isEmpty() && !profile.host.trimmed().isEmpty()
|
||||||
|
&& profile.port >= 1 && profile.port <= 65535;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileRepository::ProfileRepository() : m_connectionName(QStringLiteral("orbithub_main"))
|
ProfileRepository::ProfileRepository() : m_connectionName(QStringLiteral("orbithub_main"))
|
||||||
@@ -45,6 +75,11 @@ QString ProfileRepository::initError() const
|
|||||||
return m_initError;
|
return m_initError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ProfileRepository::lastError() const
|
||||||
|
{
|
||||||
|
return m_lastError;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Profile> ProfileRepository::listProfiles(const QString& searchQuery) const
|
std::vector<Profile> ProfileRepository::listProfiles(const QString& searchQuery) const
|
||||||
{
|
{
|
||||||
std::vector<Profile> result;
|
std::vector<Profile> result;
|
||||||
@@ -53,68 +88,115 @@ std::vector<Profile> ProfileRepository::listProfiles(const QString& searchQuery)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLastError(QString());
|
||||||
|
|
||||||
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
if (searchQuery.trimmed().isEmpty()) {
|
if (searchQuery.trimmed().isEmpty()) {
|
||||||
query.prepare(QStringLiteral(
|
query.prepare(QStringLiteral(
|
||||||
"SELECT id, name FROM profiles ORDER BY lower(name) ASC, id ASC"));
|
"SELECT id, name, host, port, username, protocol, auth_mode "
|
||||||
|
"FROM profiles "
|
||||||
|
"ORDER BY lower(name) ASC, id ASC"));
|
||||||
} else {
|
} else {
|
||||||
query.prepare(QStringLiteral(
|
query.prepare(QStringLiteral(
|
||||||
"SELECT id, name FROM profiles "
|
"SELECT id, name, host, port, username, protocol, auth_mode "
|
||||||
"WHERE lower(name) LIKE lower(?) "
|
"FROM profiles "
|
||||||
|
"WHERE lower(name) LIKE lower(?) OR lower(host) LIKE lower(?) "
|
||||||
"ORDER BY lower(name) ASC, id ASC"));
|
"ORDER BY lower(name) ASC, id ASC"));
|
||||||
query.addBindValue(QStringLiteral("%") + searchQuery.trimmed() + QStringLiteral("%"));
|
const QString search = QStringLiteral("%") + searchQuery.trimmed() + QStringLiteral("%");
|
||||||
|
query.addBindValue(search);
|
||||||
|
query.addBindValue(search);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
|
setLastError(query.lastError().text());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (query.next()) {
|
while (query.next()) {
|
||||||
result.push_back(Profile{query.value(0).toLongLong(), query.value(1).toString()});
|
result.push_back(profileFromQuery(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Profile> ProfileRepository::createProfile(const QString& name) const
|
std::optional<Profile> ProfileRepository::getProfile(qint64 id) const
|
||||||
{
|
{
|
||||||
if (!QSqlDatabase::contains(m_connectionName)) {
|
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString trimmedName = name.trimmed();
|
setLastError(QString());
|
||||||
if (trimmedName.isEmpty()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
query.prepare(QStringLiteral("INSERT INTO profiles(name) VALUES (?)"));
|
query.prepare(QStringLiteral(
|
||||||
query.addBindValue(trimmedName);
|
"SELECT id, name, host, port, username, protocol, auth_mode "
|
||||||
|
"FROM profiles WHERE id = ?"));
|
||||||
if (!query.exec()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Profile{query.lastInsertId().toLongLong(), trimmedName};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProfileRepository::updateProfile(qint64 id, const QString& name) const
|
|
||||||
{
|
|
||||||
if (!QSqlDatabase::contains(m_connectionName)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString trimmedName = name.trimmed();
|
|
||||||
if (trimmedName.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
|
||||||
query.prepare(QStringLiteral("UPDATE profiles SET name = ? WHERE id = ?"));
|
|
||||||
query.addBindValue(trimmedName);
|
|
||||||
query.addBindValue(id);
|
query.addBindValue(id);
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
|
setLastError(query.lastError().text());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.next()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return profileFromQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Profile> ProfileRepository::createProfile(const Profile& profile) const
|
||||||
|
{
|
||||||
|
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastError(QString());
|
||||||
|
|
||||||
|
if (!isProfileValid(profile)) {
|
||||||
|
setLastError(QStringLiteral("Name, host, and a valid port are required."));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
|
query.prepare(QStringLiteral(
|
||||||
|
"INSERT INTO profiles(name, host, port, username, protocol, auth_mode) "
|
||||||
|
"VALUES (?, ?, ?, ?, ?, ?)"));
|
||||||
|
bindProfileFields(query, profile);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
setLastError(query.lastError().text());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile created = profile;
|
||||||
|
created.id = query.lastInsertId().toLongLong();
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfileRepository::updateProfile(const Profile& profile) const
|
||||||
|
{
|
||||||
|
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastError(QString());
|
||||||
|
|
||||||
|
if (profile.id < 0 || !isProfileValid(profile)) {
|
||||||
|
setLastError(QStringLiteral("Invalid profile data."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
|
query.prepare(QStringLiteral(
|
||||||
|
"UPDATE profiles "
|
||||||
|
"SET name = ?, host = ?, port = ?, username = ?, protocol = ?, auth_mode = ? "
|
||||||
|
"WHERE id = ?"));
|
||||||
|
bindProfileFields(query, profile);
|
||||||
|
query.addBindValue(profile.id);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
setLastError(query.lastError().text());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,11 +209,14 @@ bool ProfileRepository::deleteProfile(qint64 id) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLastError(QString());
|
||||||
|
|
||||||
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
QSqlQuery query(QSqlDatabase::database(m_connectionName));
|
||||||
query.prepare(QStringLiteral("DELETE FROM profiles WHERE id = ?"));
|
query.prepare(QStringLiteral("DELETE FROM profiles WHERE id = ?"));
|
||||||
query.addBindValue(id);
|
query.addBindValue(id);
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
|
setLastError(query.lastError().text());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,12 +237,74 @@ bool ProfileRepository::initializeDatabase()
|
|||||||
const bool created = query.exec(QStringLiteral(
|
const bool created = query.exec(QStringLiteral(
|
||||||
"CREATE TABLE IF NOT EXISTS profiles ("
|
"CREATE TABLE IF NOT EXISTS profiles ("
|
||||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
"name TEXT NOT NULL UNIQUE"
|
"name TEXT NOT NULL UNIQUE,"
|
||||||
|
"host TEXT NOT NULL DEFAULT '',"
|
||||||
|
"port INTEGER NOT NULL DEFAULT 22,"
|
||||||
|
"username TEXT NOT NULL DEFAULT '',"
|
||||||
|
"protocol TEXT NOT NULL DEFAULT 'SSH',"
|
||||||
|
"auth_mode TEXT NOT NULL DEFAULT 'Password'"
|
||||||
")"));
|
")"));
|
||||||
|
|
||||||
if (!created) {
|
if (!created) {
|
||||||
m_initError = query.lastError().text();
|
m_initError = query.lastError().text();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return created;
|
if (!ensureProfileSchema()) {
|
||||||
|
m_initError = m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfileRepository::ensureProfileSchema() const
|
||||||
|
{
|
||||||
|
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
setLastError(QStringLiteral("Database connection missing."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery tableInfo(QSqlDatabase::database(m_connectionName));
|
||||||
|
if (!tableInfo.exec(QStringLiteral("PRAGMA table_info(profiles)"))) {
|
||||||
|
setLastError(tableInfo.lastError().text());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QString> columns;
|
||||||
|
while (tableInfo.next()) {
|
||||||
|
columns.insert(tableInfo.value(1).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ColumnDef {
|
||||||
|
QString name;
|
||||||
|
QString ddl;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<ColumnDef> required = {
|
||||||
|
{QStringLiteral("host"), QStringLiteral("ALTER TABLE profiles ADD COLUMN host TEXT NOT NULL DEFAULT ''")},
|
||||||
|
{QStringLiteral("port"), QStringLiteral("ALTER TABLE profiles ADD COLUMN port INTEGER NOT NULL DEFAULT 22")},
|
||||||
|
{QStringLiteral("username"), QStringLiteral("ALTER TABLE profiles ADD COLUMN username TEXT NOT NULL DEFAULT ''")},
|
||||||
|
{QStringLiteral("protocol"), QStringLiteral("ALTER TABLE profiles ADD COLUMN protocol TEXT NOT NULL DEFAULT 'SSH'")},
|
||||||
|
{QStringLiteral("auth_mode"), QStringLiteral("ALTER TABLE profiles ADD COLUMN auth_mode TEXT NOT NULL DEFAULT 'Password'")}};
|
||||||
|
|
||||||
|
for (const ColumnDef& column : required) {
|
||||||
|
if (columns.contains(column.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery alter(QSqlDatabase::database(m_connectionName));
|
||||||
|
if (!alter.exec(column.ddl)) {
|
||||||
|
setLastError(alter.lastError().text());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastError(QString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileRepository::setLastError(const QString& error) const
|
||||||
|
{
|
||||||
|
m_lastError = error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,20 @@
|
|||||||
#define ORBITHUB_PROFILE_REPOSITORY_H
|
#define ORBITHUB_PROFILE_REPOSITORY_H
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct Profile
|
struct Profile
|
||||||
{
|
{
|
||||||
qint64 id;
|
qint64 id = -1;
|
||||||
QString name;
|
QString name;
|
||||||
|
QString host;
|
||||||
|
int port = 22;
|
||||||
|
QString username;
|
||||||
|
QString protocol = QStringLiteral("SSH");
|
||||||
|
QString authMode = QStringLiteral("Password");
|
||||||
};
|
};
|
||||||
|
|
||||||
class ProfileRepository
|
class ProfileRepository
|
||||||
@@ -19,17 +25,22 @@ public:
|
|||||||
~ProfileRepository();
|
~ProfileRepository();
|
||||||
|
|
||||||
QString initError() const;
|
QString initError() const;
|
||||||
|
QString lastError() const;
|
||||||
|
|
||||||
std::vector<Profile> listProfiles(const QString& searchQuery = QString()) const;
|
std::vector<Profile> listProfiles(const QString& searchQuery = QString()) const;
|
||||||
std::optional<Profile> createProfile(const QString& name) const;
|
std::optional<Profile> getProfile(qint64 id) const;
|
||||||
bool updateProfile(qint64 id, const QString& name) const;
|
std::optional<Profile> createProfile(const Profile& profile) const;
|
||||||
|
bool updateProfile(const Profile& profile) const;
|
||||||
bool deleteProfile(qint64 id) const;
|
bool deleteProfile(qint64 id) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_connectionName;
|
QString m_connectionName;
|
||||||
QString m_initError;
|
QString m_initError;
|
||||||
|
mutable QString m_lastError;
|
||||||
|
|
||||||
bool initializeDatabase();
|
bool initializeDatabase();
|
||||||
|
bool ensureProfileSchema() const;
|
||||||
|
void setLastError(const QString& error) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "profiles_window.h"
|
#include "profiles_window.h"
|
||||||
|
|
||||||
|
#include "profile_dialog.h"
|
||||||
#include "profile_repository.h"
|
#include "profile_repository.h"
|
||||||
#include "session_window.h"
|
#include "session_window.h"
|
||||||
|
|
||||||
@@ -7,7 +8,6 @@
|
|||||||
|
|
||||||
#include <QAbstractItemView>
|
#include <QAbstractItemView>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QInputDialog>
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QListWidget>
|
#include <QListWidget>
|
||||||
@@ -18,6 +18,14 @@
|
|||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QString formatProfileListItem(const Profile& profile)
|
||||||
|
{
|
||||||
|
return QStringLiteral("%1 [%2 %3:%4]")
|
||||||
|
.arg(profile.name, profile.protocol, profile.host, QString::number(profile.port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ProfilesWindow::ProfilesWindow(QWidget* parent)
|
ProfilesWindow::ProfilesWindow(QWidget* parent)
|
||||||
: QMainWindow(parent),
|
: QMainWindow(parent),
|
||||||
m_searchBox(nullptr),
|
m_searchBox(nullptr),
|
||||||
@@ -28,7 +36,7 @@ ProfilesWindow::ProfilesWindow(QWidget* parent)
|
|||||||
m_repository(std::make_unique<ProfileRepository>())
|
m_repository(std::make_unique<ProfileRepository>())
|
||||||
{
|
{
|
||||||
setWindowTitle(QStringLiteral("OrbitHub Profiles"));
|
setWindowTitle(QStringLiteral("OrbitHub Profiles"));
|
||||||
resize(520, 620);
|
resize(640, 620);
|
||||||
|
|
||||||
setupUi();
|
setupUi();
|
||||||
|
|
||||||
@@ -57,7 +65,7 @@ void ProfilesWindow::setupUi()
|
|||||||
|
|
||||||
auto* searchLabel = new QLabel(QStringLiteral("Search"), central);
|
auto* searchLabel = new QLabel(QStringLiteral("Search"), central);
|
||||||
m_searchBox = new QLineEdit(central);
|
m_searchBox = new QLineEdit(central);
|
||||||
m_searchBox->setPlaceholderText(QStringLiteral("Filter profiles..."));
|
m_searchBox->setPlaceholderText(QStringLiteral("Filter by name or host..."));
|
||||||
|
|
||||||
m_profilesList = new QListWidget(central);
|
m_profilesList = new QListWidget(central);
|
||||||
m_profilesList->setSelectionMode(QAbstractItemView::SingleSelection);
|
m_profilesList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
@@ -97,15 +105,31 @@ void ProfilesWindow::setupUi()
|
|||||||
void ProfilesWindow::loadProfiles(const QString& query)
|
void ProfilesWindow::loadProfiles(const QString& query)
|
||||||
{
|
{
|
||||||
m_profilesList->clear();
|
m_profilesList->clear();
|
||||||
|
m_profileCache.clear();
|
||||||
|
|
||||||
const std::vector<Profile> profiles = m_repository->listProfiles(query);
|
const std::vector<Profile> profiles = m_repository->listProfiles(query);
|
||||||
|
if (!m_repository->lastError().isEmpty()) {
|
||||||
|
QMessageBox::warning(this,
|
||||||
|
QStringLiteral("Load Profiles"),
|
||||||
|
QStringLiteral("Failed to load profiles: %1")
|
||||||
|
.arg(m_repository->lastError()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const Profile& profile : profiles) {
|
for (const Profile& profile : profiles) {
|
||||||
auto* item = new QListWidgetItem(profile.name, m_profilesList);
|
auto* item = new QListWidgetItem(formatProfileListItem(profile), m_profilesList);
|
||||||
item->setData(Qt::UserRole, QVariant::fromValue(profile.id));
|
item->setData(Qt::UserRole, QVariant::fromValue(profile.id));
|
||||||
|
item->setToolTip(QStringLiteral("%1://%2@%3:%4\nAuth: %5")
|
||||||
|
.arg(profile.protocol,
|
||||||
|
profile.username.isEmpty() ? QStringLiteral("<none>") : profile.username,
|
||||||
|
profile.host,
|
||||||
|
QString::number(profile.port),
|
||||||
|
profile.authMode));
|
||||||
|
m_profileCache.insert_or_assign(profile.id, profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<qint64> ProfilesWindow::selectedProfileId() const
|
std::optional<Profile> ProfilesWindow::selectedProfile() const
|
||||||
{
|
{
|
||||||
QListWidgetItem* item = m_profilesList->currentItem();
|
QListWidgetItem* item = m_profilesList->currentItem();
|
||||||
if (item == nullptr) {
|
if (item == nullptr) {
|
||||||
@@ -117,27 +141,31 @@ std::optional<qint64> ProfilesWindow::selectedProfileId() const
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.toLongLong();
|
const qint64 id = value.toLongLong();
|
||||||
|
const auto cacheIt = m_profileCache.find(id);
|
||||||
|
if (cacheIt != m_profileCache.end()) {
|
||||||
|
return cacheIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_repository->getProfile(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfilesWindow::createProfile()
|
void ProfilesWindow::createProfile()
|
||||||
{
|
{
|
||||||
bool accepted = false;
|
ProfileDialog dialog(this);
|
||||||
const QString name = QInputDialog::getText(this,
|
dialog.setDialogTitle(QStringLiteral("New Profile"));
|
||||||
QStringLiteral("New Profile"),
|
|
||||||
QStringLiteral("Profile name:"),
|
|
||||||
QLineEdit::Normal,
|
|
||||||
QString(),
|
|
||||||
&accepted);
|
|
||||||
|
|
||||||
if (!accepted || name.trimmed().isEmpty()) {
|
if (dialog.exec() != QDialog::Accepted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_repository->createProfile(name).has_value()) {
|
if (!m_repository->createProfile(dialog.profile()).has_value()) {
|
||||||
QMessageBox::warning(this,
|
QMessageBox::warning(this,
|
||||||
QStringLiteral("Create Profile"),
|
QStringLiteral("Create Profile"),
|
||||||
QStringLiteral("Failed to create profile. Names must be unique."));
|
QStringLiteral("Failed to create profile: %1")
|
||||||
|
.arg(m_repository->lastError().isEmpty()
|
||||||
|
? QStringLiteral("unknown error")
|
||||||
|
: m_repository->lastError()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,31 +174,32 @@ void ProfilesWindow::createProfile()
|
|||||||
|
|
||||||
void ProfilesWindow::editSelectedProfile()
|
void ProfilesWindow::editSelectedProfile()
|
||||||
{
|
{
|
||||||
const std::optional<qint64> profileId = selectedProfileId();
|
const std::optional<Profile> selected = selectedProfile();
|
||||||
QListWidgetItem* currentItem = m_profilesList->currentItem();
|
if (!selected.has_value()) {
|
||||||
if (!profileId.has_value() || currentItem == nullptr) {
|
|
||||||
QMessageBox::information(this,
|
QMessageBox::information(this,
|
||||||
QStringLiteral("Edit Profile"),
|
QStringLiteral("Edit Profile"),
|
||||||
QStringLiteral("Select a profile first."));
|
QStringLiteral("Select a profile first."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool accepted = false;
|
ProfileDialog dialog(this);
|
||||||
const QString name = QInputDialog::getText(this,
|
dialog.setDialogTitle(QStringLiteral("Edit Profile"));
|
||||||
QStringLiteral("Edit Profile"),
|
dialog.setProfile(selected.value());
|
||||||
QStringLiteral("Profile name:"),
|
|
||||||
QLineEdit::Normal,
|
|
||||||
currentItem->text(),
|
|
||||||
&accepted);
|
|
||||||
|
|
||||||
if (!accepted || name.trimmed().isEmpty()) {
|
if (dialog.exec() != QDialog::Accepted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_repository->updateProfile(profileId.value(), name)) {
|
Profile updated = dialog.profile();
|
||||||
|
updated.id = selected->id;
|
||||||
|
|
||||||
|
if (!m_repository->updateProfile(updated)) {
|
||||||
QMessageBox::warning(this,
|
QMessageBox::warning(this,
|
||||||
QStringLiteral("Edit Profile"),
|
QStringLiteral("Edit Profile"),
|
||||||
QStringLiteral("Failed to update profile. Names must be unique."));
|
QStringLiteral("Failed to update profile: %1")
|
||||||
|
.arg(m_repository->lastError().isEmpty()
|
||||||
|
? QStringLiteral("unknown error")
|
||||||
|
: m_repository->lastError()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,9 +208,8 @@ void ProfilesWindow::editSelectedProfile()
|
|||||||
|
|
||||||
void ProfilesWindow::deleteSelectedProfile()
|
void ProfilesWindow::deleteSelectedProfile()
|
||||||
{
|
{
|
||||||
const std::optional<qint64> profileId = selectedProfileId();
|
const std::optional<Profile> selected = selectedProfile();
|
||||||
QListWidgetItem* currentItem = m_profilesList->currentItem();
|
if (!selected.has_value()) {
|
||||||
if (!profileId.has_value() || currentItem == nullptr) {
|
|
||||||
QMessageBox::information(this,
|
QMessageBox::information(this,
|
||||||
QStringLiteral("Delete Profile"),
|
QStringLiteral("Delete Profile"),
|
||||||
QStringLiteral("Select a profile first."));
|
QStringLiteral("Select a profile first."));
|
||||||
@@ -191,7 +219,7 @@ void ProfilesWindow::deleteSelectedProfile()
|
|||||||
const QMessageBox::StandardButton confirm = QMessageBox::question(
|
const QMessageBox::StandardButton confirm = QMessageBox::question(
|
||||||
this,
|
this,
|
||||||
QStringLiteral("Delete Profile"),
|
QStringLiteral("Delete Profile"),
|
||||||
QStringLiteral("Delete profile '%1'?").arg(currentItem->text()),
|
QStringLiteral("Delete profile '%1'?").arg(selected->name),
|
||||||
QMessageBox::Yes | QMessageBox::No,
|
QMessageBox::Yes | QMessageBox::No,
|
||||||
QMessageBox::No);
|
QMessageBox::No);
|
||||||
|
|
||||||
@@ -199,10 +227,13 @@ void ProfilesWindow::deleteSelectedProfile()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_repository->deleteProfile(profileId.value())) {
|
if (!m_repository->deleteProfile(selected->id)) {
|
||||||
QMessageBox::warning(this,
|
QMessageBox::warning(this,
|
||||||
QStringLiteral("Delete Profile"),
|
QStringLiteral("Delete Profile"),
|
||||||
QStringLiteral("Failed to delete profile."));
|
QStringLiteral("Failed to delete profile: %1")
|
||||||
|
.arg(m_repository->lastError().isEmpty()
|
||||||
|
? QStringLiteral("unknown error")
|
||||||
|
: m_repository->lastError()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +246,24 @@ void ProfilesWindow::openSessionForItem(QListWidgetItem* item)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* session = new SessionWindow(item->text());
|
const QVariant value = item->data(Qt::UserRole);
|
||||||
|
if (!value.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qint64 id = value.toLongLong();
|
||||||
|
const std::optional<Profile> profile = m_repository->getProfile(id);
|
||||||
|
if (!profile.has_value()) {
|
||||||
|
QMessageBox::warning(this,
|
||||||
|
QStringLiteral("Connect"),
|
||||||
|
QStringLiteral("Failed to load profile for session: %1")
|
||||||
|
.arg(m_repository->lastError().isEmpty()
|
||||||
|
? QStringLiteral("profile not found")
|
||||||
|
: m_repository->lastError()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* session = new SessionWindow(profile.value());
|
||||||
session->setAttribute(Qt::WA_DeleteOnClose);
|
session->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
m_sessionWindows.emplace_back(session);
|
m_sessionWindows.emplace_back(session);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef ORBITHUB_PROFILES_WINDOW_H
|
#ifndef ORBITHUB_PROFILES_WINDOW_H
|
||||||
#define ORBITHUB_PROFILES_WINDOW_H
|
#define ORBITHUB_PROFILES_WINDOW_H
|
||||||
|
|
||||||
|
#include "profile_repository.h"
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
@@ -8,6 +10,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class QListWidget;
|
class QListWidget;
|
||||||
@@ -15,7 +18,6 @@ class QListWidgetItem;
|
|||||||
class QLineEdit;
|
class QLineEdit;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class SessionWindow;
|
class SessionWindow;
|
||||||
class ProfileRepository;
|
|
||||||
|
|
||||||
class ProfilesWindow : public QMainWindow
|
class ProfilesWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
@@ -33,10 +35,11 @@ private:
|
|||||||
QPushButton* m_deleteButton;
|
QPushButton* m_deleteButton;
|
||||||
std::vector<QPointer<SessionWindow>> m_sessionWindows;
|
std::vector<QPointer<SessionWindow>> m_sessionWindows;
|
||||||
std::unique_ptr<ProfileRepository> m_repository;
|
std::unique_ptr<ProfileRepository> m_repository;
|
||||||
|
std::unordered_map<qint64, Profile> m_profileCache;
|
||||||
|
|
||||||
void setupUi();
|
void setupUi();
|
||||||
void loadProfiles(const QString& query = QString());
|
void loadProfiles(const QString& query = QString());
|
||||||
std::optional<qint64> selectedProfileId() const;
|
std::optional<Profile> selectedProfile() const;
|
||||||
void createProfile();
|
void createProfile();
|
||||||
void editSelectedProfile();
|
void editSelectedProfile();
|
||||||
void deleteSelectedProfile();
|
void deleteSelectedProfile();
|
||||||
|
|||||||
@@ -3,43 +3,93 @@
|
|||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
|
#include <QTimer>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
SessionWindow::SessionWindow(const QString& profileName, QWidget* parent)
|
SessionWindow::SessionWindow(const Profile& profile, QWidget* parent)
|
||||||
: QMainWindow(parent), m_tabs(new QTabWidget(this))
|
: QMainWindow(parent), m_tabs(new QTabWidget(this))
|
||||||
{
|
{
|
||||||
setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profileName));
|
setWindowTitle(QStringLiteral("OrbitHub Session - %1").arg(profile.name));
|
||||||
resize(900, 600);
|
resize(980, 680);
|
||||||
|
|
||||||
|
m_tabs->setTabsClosable(true);
|
||||||
|
connect(m_tabs,
|
||||||
|
&QTabWidget::tabCloseRequested,
|
||||||
|
this,
|
||||||
|
[this](int index) {
|
||||||
|
QWidget* tab = m_tabs->widget(index);
|
||||||
|
m_tabs->removeTab(index);
|
||||||
|
delete tab;
|
||||||
|
if (m_tabs->count() == 0) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setCentralWidget(m_tabs);
|
setCentralWidget(m_tabs);
|
||||||
addPlaceholderTab(profileName);
|
addSessionTab(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionWindow::addPlaceholderTab(const QString& profileName)
|
void SessionWindow::addSessionTab(const Profile& profile)
|
||||||
{
|
{
|
||||||
auto* container = new QWidget(this);
|
auto* container = new QWidget(this);
|
||||||
auto* layout = new QVBoxLayout(container);
|
auto* layout = new QVBoxLayout(container);
|
||||||
|
|
||||||
auto* titleLabel = new QLabel(QStringLiteral("Profile: %1").arg(profileName), container);
|
auto* profileLabel = new QLabel(QStringLiteral("Profile: %1").arg(profile.name), container);
|
||||||
|
auto* endpointLabel = new QLabel(
|
||||||
|
QStringLiteral("Endpoint: %1://%2@%3:%4")
|
||||||
|
.arg(profile.protocol,
|
||||||
|
profile.username.isEmpty() ? QStringLiteral("<none>") : profile.username,
|
||||||
|
profile.host,
|
||||||
|
QString::number(profile.port)),
|
||||||
|
container);
|
||||||
|
auto* authModeLabel = new QLabel(QStringLiteral("Auth Mode: %1").arg(profile.authMode), container);
|
||||||
|
auto* statusLabel = new QLabel(QStringLiteral("Connection State: Connecting"), container);
|
||||||
auto* surfaceLabel = new QLabel(QStringLiteral("OrbitHub Native Surface"), container);
|
auto* surfaceLabel = new QLabel(QStringLiteral("OrbitHub Native Surface"), container);
|
||||||
|
|
||||||
QFont titleFont = titleLabel->font();
|
QFont profileFont = profileLabel->font();
|
||||||
titleFont.setBold(true);
|
profileFont.setBold(true);
|
||||||
titleLabel->setFont(titleFont);
|
profileLabel->setFont(profileFont);
|
||||||
|
|
||||||
QFont surfaceFont = surfaceLabel->font();
|
QFont surfaceFont = surfaceLabel->font();
|
||||||
surfaceFont.setPointSize(surfaceFont.pointSize() + 6);
|
surfaceFont.setPointSize(surfaceFont.pointSize() + 6);
|
||||||
surfaceFont.setBold(true);
|
surfaceFont.setBold(true);
|
||||||
surfaceLabel->setFont(surfaceFont);
|
surfaceLabel->setFont(surfaceFont);
|
||||||
|
|
||||||
|
statusLabel->setStyleSheet(
|
||||||
|
QStringLiteral("border: 1px solid #a5a5a5; background-color: #fff3cd; padding: 6px;"));
|
||||||
|
|
||||||
surfaceLabel->setAlignment(Qt::AlignCenter);
|
surfaceLabel->setAlignment(Qt::AlignCenter);
|
||||||
surfaceLabel->setMinimumHeight(200);
|
surfaceLabel->setMinimumHeight(220);
|
||||||
surfaceLabel->setStyleSheet(
|
surfaceLabel->setStyleSheet(
|
||||||
QStringLiteral("border: 1px solid #8a8a8a; background-color: #f5f5f5;"));
|
QStringLiteral("border: 1px solid #8a8a8a; background-color: #f5f5f5;"));
|
||||||
|
|
||||||
layout->addWidget(titleLabel);
|
layout->addWidget(profileLabel);
|
||||||
|
layout->addWidget(endpointLabel);
|
||||||
|
layout->addWidget(authModeLabel);
|
||||||
|
layout->addWidget(statusLabel);
|
||||||
layout->addWidget(surfaceLabel, 1);
|
layout->addWidget(surfaceLabel, 1);
|
||||||
|
|
||||||
m_tabs->addTab(container, profileName);
|
const int tabIndex = m_tabs->addTab(container, QStringLiteral("%1 (Connecting)").arg(profile.name));
|
||||||
|
|
||||||
|
QTimer::singleShot(900,
|
||||||
|
this,
|
||||||
|
[this, tabIndex, statusLabel, profile]() {
|
||||||
|
const bool shouldFail = profile.host.contains(QStringLiteral("fail"),
|
||||||
|
Qt::CaseInsensitive);
|
||||||
|
if (shouldFail) {
|
||||||
|
statusLabel->setText(QStringLiteral("Connection State: Failed"));
|
||||||
|
statusLabel->setStyleSheet(QStringLiteral(
|
||||||
|
"border: 1px solid #a94442; background-color: #f2dede; padding: 6px;"));
|
||||||
|
m_tabs->setTabText(tabIndex,
|
||||||
|
QStringLiteral("%1 (Failed)").arg(profile.name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusLabel->setText(QStringLiteral("Connection State: Connected"));
|
||||||
|
statusLabel->setStyleSheet(QStringLiteral(
|
||||||
|
"border: 1px solid #3c763d; background-color: #dff0d8; padding: 6px;"));
|
||||||
|
m_tabs->setTabText(tabIndex,
|
||||||
|
QStringLiteral("%1 (Connected)").arg(profile.name));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#ifndef ORBITHUB_SESSION_WINDOW_H
|
#ifndef ORBITHUB_SESSION_WINDOW_H
|
||||||
#define ORBITHUB_SESSION_WINDOW_H
|
#define ORBITHUB_SESSION_WINDOW_H
|
||||||
|
|
||||||
|
#include "profile_repository.h"
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
class QTabWidget;
|
class QTabWidget;
|
||||||
|
|
||||||
@@ -11,12 +12,12 @@ class SessionWindow : public QMainWindow
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SessionWindow(const QString& profileName, QWidget* parent = nullptr);
|
explicit SessionWindow(const Profile& profile, QWidget* parent = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTabWidget* m_tabs;
|
QTabWidget* m_tabs;
|
||||||
|
|
||||||
void addPlaceholderTab(const QString& profileName);
|
void addSessionTab(const Profile& profile);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user