feat(profile): selezione del profilo di cui vedere le sessioni (v0.5.0)

- Impostazioni > Sessions: selettore "Profile" (elenco da /api/profiles;
  "(server active)" come prima voce = nessun cookie di profilo)
- ApiClient: fetchProfiles() + switchProfile(); lo switch usa
  /api/profile/switch (process_wide=False lato server: il cookie firmato
  hermes_profile scopa le richieste dell'app per-client; il profilo attivo
  globale del server non viene toccato); refresh automatico delle sessioni
- CookieJar::removeCookiesByName per l'opzione "(server active)"
- Il profilo scelto viene riapplicato automaticamente dopo il login
- i18n: nuove stringhe tradotte it/fr/de
This commit is contained in:
2026-09-12 22:46:27 +02:00
parent 22ccb49424
commit 7b8e5e1102
15 changed files with 421 additions and 104 deletions
+78 -1
View File
@@ -1,6 +1,7 @@
#include "apiclient.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
@@ -29,6 +30,9 @@ ApiClient::ApiClient(Settings *settings, QObject *parent)
m_jar->setFilePath(cfg + QStringLiteral("/cookies.dat"));
m_jar->load();
m_nam.setCookieJar(m_jar);
// Il profilo selezionato (impostazione app) e' esposto anche da ApiClient.
connect(m_settings, &Settings::profileChanged, this, &ApiClient::profileChanged);
}
// ---------------------------------------------------------------- helpers --
@@ -170,7 +174,12 @@ void ApiClient::login(const QString &password)
return;
}
setLoggedIn(true);
refreshSessions();
// Riapplica il profilo scelto dall'utente: il nuovo cookie di sessione
// va rilegato al profilo (switchProfile aggiorna anche le sessioni).
if (!m_settings->profile().isEmpty())
switchProfile(m_settings->profile());
else
refreshSessions();
});
}
@@ -221,6 +230,74 @@ int ApiClient::sessionsCount() const
return m_sessionsModel->rowCount();
}
QString ApiClient::profile() const
{
return m_settings->profile();
}
void ApiClient::fetchProfiles()
{
QNetworkReply *reply = m_nam.get(jsonRequest(QStringLiteral("/api/profiles")));
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
const QByteArray data = reply->readAll();
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status < 200 || status >= 300) {
qDebug() << "[api] fetchProfiles HTTP" << status;
return;
}
const QJsonArray arr = QJsonDocument::fromJson(data).object()
.value(QStringLiteral("profiles")).toArray();
QStringList names;
for (int i = 0; i < arr.size(); ++i) {
const QString n = arr.at(i).toObject().value(QStringLiteral("name")).toString();
if (!n.isEmpty())
names << n;
}
qDebug() << "[api] profili disponibili:" << names;
if (names != m_profiles) {
m_profiles = names;
emit profilesChanged();
}
});
}
void ApiClient::switchProfile(const QString &name)
{
const QString target = name.trimmed();
if (target.isEmpty()) {
// "(attivo sul server)": rimuove il cookie di profilo e torna a
// seguire il profilo attivo del server.
m_jar->removeCookiesByName(QStringLiteral("hermes_profile"));
m_settings->setProfile(QString());
emit profileSwitched(QString());
refreshSessions();
return;
}
QJsonObject body;
body.insert(QStringLiteral("name"), target);
QNetworkReply *reply = m_nam.post(jsonRequest(QStringLiteral("/api/profile/switch")),
QJsonDocument(body).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [this, reply, target]() {
reply->deleteLater();
const QByteArray data = reply->readAll();
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status >= 200 && status < 300) {
// Il Set-Cookie firmato (hermes_profile) viene salvato dal jar:
// da qui in poi le richieste dell'app sono profilate lato server.
m_settings->setProfile(target);
qDebug() << "[api] profilo selezionato:" << target;
emit profileSwitched(target);
refreshSessions();
} else {
const QString err = errorFromReply(reply, data);
qDebug() << "[api] switch profilo fallito:" << err;
emit profileSwitchFailed(err);
}
});
}
void ApiClient::openSession(const QString &id)
{
setLastError(QString());
+12
View File
@@ -40,6 +40,8 @@ class ApiClient : public QObject
Q_PROPERTY(SessionsModel *sessions READ sessions NOTIFY sessionsChanged)
Q_PROPERTY(int sessionsCount READ sessionsCount NOTIFY sessionsChanged)
Q_PROPERTY(QVariantMap session READ session NOTIFY sessionChanged)
Q_PROPERTY(QStringList profiles READ profiles NOTIFY profilesChanged)
Q_PROPERTY(QString profile READ profile NOTIFY profileChanged)
public:
explicit ApiClient(Settings *settings, QObject *parent = nullptr);
@@ -52,6 +54,8 @@ public:
SessionsModel *sessions() const { return m_sessionsModel; }
int sessionsCount() const;
QVariantMap session() const { return m_session; }
QStringList profiles() const { return m_profiles; }
QString profile() const;
// Avvio: verifica lo stato auth (cookie persistito o auth disabilitata).
Q_INVOKABLE void checkAuth();
@@ -59,6 +63,9 @@ public:
Q_INVOKABLE void logout();
Q_INVOKABLE void refreshSessions();
// Profilo server: elenco e selezione (per-client, cookie firmato WebUI).
Q_INVOKABLE void fetchProfiles();
Q_INVOKABLE void switchProfile(const QString &name);
Q_INVOKABLE void openSession(const QString &id);
Q_INVOKABLE void newSession();
@@ -80,6 +87,10 @@ signals:
void lastErrorChanged();
void sessionsChanged();
void sessionChanged();
void profilesChanged();
void profileChanged();
void profileSwitched(const QString &name); // ok (vuoto = attivo server)
void profileSwitchFailed(const QString &error);
void authChecked(bool loggedIn);
void sessionOpened(); // openSession/newSession completata
@@ -125,6 +136,7 @@ private:
SessionsModel *m_sessionsModel;
QVariantMap m_session;
QStringList m_profiles;
QString m_streamId;
QNetworkReply *m_streamReply;
SseParser m_parser;
+15
View File
@@ -63,6 +63,21 @@ void CookieJar::clear()
QFile::remove(m_path);
}
void CookieJar::removeCookiesByName(const QString &name)
{
const QByteArray raw = name.toUtf8();
const QList<QNetworkCookie> cookies = allCookies();
bool removed = false;
for (int i = 0; i < cookies.size(); ++i) {
if (cookies.at(i).name() == raw) {
deleteCookie(cookies.at(i)); // protected: accessibile dal subclass
removed = true;
}
}
if (removed)
save();
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
const bool changed = QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
+3
View File
@@ -21,6 +21,9 @@ public:
bool save() const;
void clear();
// Rimuove tutti i cookie con questo nome (es. hermes_profile) e salva.
void removeCookiesByName(const QString &name);
protected:
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url) override;
+2 -2
View File
@@ -21,9 +21,9 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
// AppConfigLocation = ~/.config/harbour/hermes (zona persistente della sandbox).
app->setOrganizationName(QStringLiteral("harbour"));
app->setApplicationName(QStringLiteral("hermes"));
app->setApplicationVersion(QStringLiteral("0.4.0"));
app->setApplicationVersion(QStringLiteral("0.5.0"));
qDebug() << "harbour-hermes v0.4.0 build" << __DATE__ << __TIME__;
qDebug() << "harbour-hermes v0.5.0 build" << __DATE__ << __TIME__;
// Traduzioni: it/fr/de da /usr/share/harbour-hermes/translations, in base
// alla lingua di sistema (l'inglese e' la lingua sorgente: nessun .qm).
+16
View File
@@ -123,6 +123,22 @@ void Settings::setVadMaxMs(int ms)
emit vadMaxMsChanged();
}
// Profilo server di cui mostrare le sessioni (vuoto = profilo attivo del
// server). L'app lo applica per-client tramite il cookie firmato del WebUI.
QString Settings::profile() const
{
return m_settings.value(QStringLiteral("profile"), QString()).toString();
}
void Settings::setProfile(const QString &name)
{
const QString clean = name.trimmed();
if (clean == profile())
return;
m_settings.setValue(QStringLiteral("profile"), clean);
emit profileChanged();
}
QStringList Settings::ttsVoices() const
{
return m_settings.value(QStringLiteral("ttsVoices")).toStringList();
+7
View File
@@ -28,6 +28,8 @@ class Settings : public QObject
NOTIFY vadThresholdChanged)
Q_PROPERTY(int vadMaxMs READ vadMaxMs WRITE setVadMaxMs
NOTIFY vadMaxMsChanged)
Q_PROPERTY(QString profile READ profile WRITE setProfile
NOTIFY profileChanged)
Q_PROPERTY(QStringList ttsVoices READ ttsVoices WRITE setTtsVoices
NOTIFY ttsVoicesChanged)
Q_PROPERTY(QStringList ttsEngines READ ttsEngines WRITE setTtsEngines
@@ -62,6 +64,10 @@ public:
int vadMaxMs() const;
void setVadMaxMs(int ms);
// Profilo server di cui mostrare le sessioni (vuoto = attivo del server).
QString profile() const;
void setProfile(const QString &name);
// Preset per i selettori voce/engine (liste configurabili, default vuote).
QStringList ttsVoices() const;
void setTtsVoices(const QStringList &list);
@@ -81,6 +87,7 @@ signals:
void vadSilenceMsChanged();
void vadThresholdChanged();
void vadMaxMsChanged();
void profileChanged();
void ttsVoicesChanged();
void ttsEnginesChanged();
void defaultWorkspaceChanged();