diff --git a/CMakeLists.txt b/CMakeLists.txt index a91d2ed1..c7c66754 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,7 +244,8 @@ set(COMMON_SOURCES ${APP_SRC_DIR}/messageparser.cpp ${APP_SRC_DIR}/previewengine.cpp ${APP_SRC_DIR}/imagedownloader.cpp - ${APP_SRC_DIR}/pluginversionmanager.cpp) + ${APP_SRC_DIR}/pluginversionmanager.cpp + ${APP_SRC_DIR}/connectioninfolistmodel.cpp) set(COMMON_HEADERS ${APP_SRC_DIR}/avatarimageprovider.h @@ -310,8 +311,8 @@ set(COMMON_HEADERS ${APP_SRC_DIR}/messageparser.h ${APP_SRC_DIR}/htmlparser.h ${APP_SRC_DIR}/imagedownloader.h - ${APP_SRC_DIR}/pluginversionmanager.h) - + ${APP_SRC_DIR}/pluginversionmanager.h + ${APP_SRC_DIR}/connectioninfolistmodel.h) # For libavutil/avframe. set(LIBJAMI_CONTRIB_DIR "${DAEMON_DIR}/contrib") diff --git a/resources/icons/Connected_Black_24dp.svg b/resources/icons/Connected_Black_24dp.svg new file mode 100644 index 00000000..70325ffa --- /dev/null +++ b/resources/icons/Connected_Black_24dp.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/resources/icons/Connecting_Black_24dp.svg b/resources/icons/Connecting_Black_24dp.svg new file mode 100644 index 00000000..b35b6e7b --- /dev/null +++ b/resources/icons/Connecting_Black_24dp.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/src/app/connectioninfolistmodel.cpp b/src/app/connectioninfolistmodel.cpp new file mode 100644 index 00000000..99f1a808 --- /dev/null +++ b/src/app/connectioninfolistmodel.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "connectioninfolistmodel.h" + +ConnectionInfoListModel::ConnectionInfoListModel(LRCInstance* instance, QObject* parent) + : AbstractListModelBase(parent) +{ + lrcInstance_ = instance; + connect(lrcInstance_, + &LRCInstance::currentAccountIdChanged, + this, + &ConnectionInfoListModel::resetData); +} + +int +ConnectionInfoListModel::rowCount(const QModelIndex& parent) const +{ + return peerIds_.size(); +} + +QVariant +ConnectionInfoListModel::data(const QModelIndex& index, int role) const +{ + const auto accountId = lrcInstance_->get_currentAccountId(); + + if (accountId.isEmpty()) { + qWarning() << "ConnectionInfoListModel::data: accountId or peerID is empty"; + return {}; + } + const auto peerId = peerIds_[index.row()]; + const auto peerData = peerData_[peerId]; + + switch (role) { + case ConnectionInfoList::ChannelsMap: { + QVariantMap channelsMapMap; + int i = 0; + for (const auto& device : peerData.keys()) { + QString channelsId = peerData[device]["id"].toString(); + QVariantMap channelsMap; + const auto channelInfoList = lrcInstance_->getChannelList(accountId, channelsId); + for (const auto& channelInfo : channelInfoList) { + channelsMap.insert(channelInfo["id"], channelInfo["name"]); + } + channelsMapMap.insert(QString::number(i++), channelsMap); + } + return QVariant(channelsMapMap); + } + case ConnectionInfoList::ConnectionDatas: { + QString peerString; + peerString += "Peer:" + peerId; + for (const auto& device : peerData.keys()) { + peerString += "{"; + peerString += "Device:" + device + ","; + peerString += "Status:" + peerData[device]["status"].toString() + ","; + peerString += "Channels:" + peerData[device]["channels"].toString() + ","; + peerString += "Remote Address" + peerData[device]["remoteAddress"].toString(); + peerString += "}"; + } + return peerString; + } + case ConnectionInfoList::PeerId: + return peerId; + case ConnectionInfoList::RemoteAddress: { + QVariantMap remoteAddressMap; + int i = 0; + for (const auto& device : peerData.keys()) { + remoteAddressMap.insert(QString::number(i++), peerData[device]["remoteAddress"]); + } + return QVariant(remoteAddressMap); + } + case ConnectionInfoList::DeviceId: { + QVariantMap deviceMap; + int i = 0; + for (const auto& device : peerData.keys()) { + deviceMap.insert(QString::number(i++), device); + } + return QVariant(deviceMap); + } + case ConnectionInfoList::Status: { + QVariantMap statusMap; + int i = 0; + for (const auto& device : peerData.keys()) { + statusMap.insert(QString::number(i++), peerData[device]["status"]); + } + return QVariantMap(statusMap); + } + case ConnectionInfoList::Channels: { + QVariantMap channelsMap; + int i = 0; + for (const auto& device : peerData.keys()) { + channelsMap.insert(QString::number(i++), peerData[device]["channels"]); + } + return QVariant(channelsMap); + } + case ConnectionInfoList::Count: + return peerData.size(); + } + return {}; +} + +QHash +ConnectionInfoListModel::roleNames() const +{ + using namespace ConnectionInfoList; + QHash roles; +#define X(role) roles[role] = #role; + CONNECTONINFO_ROLES +#undef X + return roles; +} + +void +ConnectionInfoListModel::update() +{ + const auto accountId = lrcInstance_->get_currentAccountId(); + if (accountId.isEmpty()) { + return; + } + aggregateData(); +} + +template +std::tuple, QVector> +getSetDiff(QVector u, QVector v) +{ + using namespace std; + QVector a, r; + set_difference(v.begin(), v.end(), u.begin(), u.end(), inserter(a, a.begin())); + set_difference(u.begin(), u.end(), v.begin(), v.end(), inserter(r, r.begin())); + return {a, r}; +} + +void +ConnectionInfoListModel::aggregateData() +{ + const auto accountId = lrcInstance_->get_currentAccountId(); + if (accountId.isEmpty()) { + return; + } + + connectionInfoList_ = lrcInstance_->getConnectionList(accountId); + + peerData_ = {}; + + QSet newPeerIds; + + for (const auto& connectionInfo : connectionInfoList_) { + if (!connectionInfo["peer"].isEmpty()) { + newPeerIds.insert(connectionInfo["peer"]); + } + const auto channelInfoList = lrcInstance_->getChannelList(accountId, connectionInfo["id"]); + peerData_[connectionInfo["peer"]][connectionInfo["device"]] = {}; + peerData_[connectionInfo["peer"]][connectionInfo["device"]]["status"] + = connectionInfo["status"]; + peerData_[connectionInfo["peer"]][connectionInfo["device"]]["channels"] = channelInfoList + .size(); + peerData_[connectionInfo["peer"]][connectionInfo["device"]]["id"] = connectionInfo["id"]; + peerData_[connectionInfo["peer"]][connectionInfo["device"]]["remoteAddress"] + = connectionInfo["remoteAddress"]; + } + + QVector oldVector; + for (const auto& peerId : peerIds_) { + oldVector << peerId; + } + QVector newVector; + for (const auto& peerId : newPeerIds) { + newVector << peerId; + } + + std::sort(oldVector.begin(), oldVector.end()); + std::sort(newVector.begin(), newVector.end()); + + QVector removed, added; + std::tie(added, removed) = getSetDiff(oldVector, newVector); + Q_FOREACH (const auto& key, added) { + auto index = std::distance(newVector.begin(), + std::find(newVector.begin(), newVector.end(), key)); + beginInsertRows(QModelIndex(), index, index); + peerIds_.insert(index, key); + endInsertRows(); + } + Q_FOREACH (const auto& key, removed) { + auto index = std::distance(oldVector.begin(), + std::find(oldVector.begin(), oldVector.end(), key)); + beginRemoveRows(QModelIndex(), index, index); + if (peerIds_.size() > index) { + peerIds_.remove(index); + } else { + qWarning() << "ConnectionInfoListModel::aggregateData: index out of range"; + qWarning() << "index: " << index; + qWarning() << "key: " << key; + } + endRemoveRows(); + } + + // HACK: loop through all the peerIds_ and update the data for each one. + // This is not efficient, but it works. + Q_FOREACH (const auto& peerId, peerIds_) { + auto index = std::distance(peerIds_.begin(), + std::find(peerIds_.begin(), peerIds_.end(), peerId)); + Q_EMIT dataChanged(this->index(index), this->index(index)); + } +} + +void +ConnectionInfoListModel::resetData() +{ + beginResetModel(); + peerIds_.clear(); + peerData_.clear(); + endResetModel(); +} \ No newline at end of file diff --git a/src/app/connectioninfolistmodel.h b/src/app/connectioninfolistmodel.h new file mode 100644 index 00000000..74daa7a2 --- /dev/null +++ b/src/app/connectioninfolistmodel.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "abstractlistmodelbase.h" + +#define CONNECTONINFO_ROLES \ + X(ConnectionDatas) \ + X(ChannelsMap) \ + X(PeerName) \ + X(PeerId) \ + X(DeviceId) \ + X(Status) \ + X(Channels) \ + X(RemoteAddress) \ + X(Count) // this is the number of connections (convenience) + +namespace ConnectionInfoList { +Q_NAMESPACE +enum Role { + DummyRole = Qt::UserRole + 1, +#define X(role) role, + CONNECTONINFO_ROLES +#undef X +}; +Q_ENUM_NS(Role) +} // namespace ConnectionInfoList + +class ConnectionInfoListModel : public AbstractListModelBase +{ +public: + explicit ConnectionInfoListModel(LRCInstance* instance, QObject* parent = nullptr); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + Q_INVOKABLE void update(); + +private: + using Role = ConnectionInfoList::Role; + + VectorMapStringString connectionInfoList_; + + QVector peerIds_; + QMap>> peerData_; + void aggregateData(); + void resetData(); +}; diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml index 6bec14fb..f3c9d145 100644 --- a/src/app/constant/JamiStrings.qml +++ b/src/app/constant/JamiStrings.qml @@ -850,4 +850,16 @@ Item { property string shiftEnter: qsTr("SHIFT+ENTER") property string textFormattingDescription: qsTr("ENTER or SHIFT+ENTER to insert a new line") property string textFormatting: qsTr("Text formatting") + + //Connection monitoring + property string connected: qsTr("Connected") + property string connectingTLS: qsTr("Connecting TLS") + property string connectingICE: qsTr("Connecting ICE") + property string connecting: qsTr("Connecting") + property string waiting: qsTr("Waiting") + property string contact: qsTr("Contact") + property string connection: qsTr("Connection") + property string channels: qsTr("Channels") + property string copyAllData: qsTr("Copy all data") + property string remote: qsTr("Remote: ") } diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml index e29815fd..ed15e162 100644 --- a/src/app/constant/JamiTheme.qml +++ b/src/app/constant/JamiTheme.qml @@ -664,6 +664,11 @@ Item { property color donationBackgroundColor: "#D5E4EF" property string donationUrl: "https://jami.net/donate/" + //Connection monitoring + property color connectionMonitoringTableColor1: "#f0efef" + property color connectionMonitoringTableColor2: "#f6f5f5" + property color connectionMonitoringHeaderColor: "#d1d1d1" + function setTheme(dark) { darkTheme = dark; } diff --git a/src/app/contactadapter.cpp b/src/app/contactadapter.cpp index fb49f41d..7585f346 100644 --- a/src/app/contactadapter.cpp +++ b/src/app/contactadapter.cpp @@ -21,10 +21,16 @@ #include "contactadapter.h" #include "lrcinstance.h" +#include "qmlregister.h" ContactAdapter::ContactAdapter(LRCInstance* instance, QObject* parent) : QmlAdapterBase(instance, parent) + , connectionInfoListModel_(new ConnectionInfoListModel(lrcInstance_, this)) { + QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, + connectionInfoListModel_.get(), + "ConnectionInfoListModel"); + selectableProxyModel_.reset(new SelectableProxyModel(this)); if (lrcInstance_) { connectSignals(); @@ -246,6 +252,12 @@ ContactAdapter::removeContact(const QString& peerUri, bool banContact) accInfo.contactModel->removeContact(peerUri, banContact); } +void +ContactAdapter::updateConnectionInfo() +{ + connectionInfoListModel_->update(); +} + void ContactAdapter::connectSignals() { diff --git a/src/app/contactadapter.h b/src/app/contactadapter.h index c3666eec..ca92a669 100644 --- a/src/app/contactadapter.h +++ b/src/app/contactadapter.h @@ -21,6 +21,7 @@ #include "qmladapterbase.h" #include "smartlistmodel.h" #include "conversationlistmodel.h" +#include "connectioninfolistmodel.h" #include #include @@ -90,6 +91,7 @@ public: Q_INVOKABLE void setSearchFilter(const QString& filter); Q_INVOKABLE void contactSelected(int index); Q_INVOKABLE void removeContact(const QString& peerUri, bool banContact); + Q_INVOKABLE void updateConnectionInfo(); void connectSignals(); @@ -104,6 +106,7 @@ private: SmartListModel::Type listModeltype_; QScopedPointer smartListModel_; QScopedPointer selectableProxyModel_; + QScopedPointer connectionInfoListModel_; QStringList defaultModerators_; diff --git a/src/app/lrcinstance.cpp b/src/app/lrcinstance.cpp index 02bafe4f..0cd74c32 100644 --- a/src/app/lrcinstance.cpp +++ b/src/app/lrcinstance.cpp @@ -458,3 +458,15 @@ LRCInstance::set_selectedConvUid(QString selectedConvUid) Q_EMIT selectedConvUidChanged(); } } + +VectorMapStringString +LRCInstance::getConnectionList(const QString& accountId, const QString& uid) +{ + return Lrc::getConnectionList(accountId, uid); +} + +VectorMapStringString +LRCInstance::getChannelList(const QString& accountId, const QString& uid) +{ + return Lrc::getChannelList(accountId, uid); +} \ No newline at end of file diff --git a/src/app/lrcinstance.h b/src/app/lrcinstance.h index 08075930..502165c3 100644 --- a/src/app/lrcinstance.h +++ b/src/app/lrcinstance.h @@ -135,6 +135,10 @@ public: return debugMode_; } + VectorMapStringString getConnectionList(const QString& accountId, const QString& uid = {}); + + VectorMapStringString getChannelList(const QString& accountId, const QString& uid = {}); + Q_SIGNALS: void accountListChanged(); void selectedConvUidChanged(); diff --git a/src/app/settingsview/components/ChannelsPopup.qml b/src/app/settingsview/components/ChannelsPopup.qml new file mode 100644 index 00000000..4ef7ffa3 --- /dev/null +++ b/src/app/settingsview/components/ChannelsPopup.qml @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import Qt5Compat.GraphicalEffects +import QtQuick.Layouts +import net.jami.Models 1.1 +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 + +Popup { + id: popup + width: textComponent.contentWidth + 40 < popup.maxWidth - 20 ? textComponent.contentWidth + 40 : popup.maxWidth - 20 + height: textComponent.contentHeight + 40 < 350 ? textComponent.contentHeight + 40 : 350 + property string text: "" + property int maxWidth: 0 + x: -1 * (popup.width - 20) + + Rectangle { + anchors.fill: parent + color: JamiTheme.transparentColor + + Flickable { + anchors.fill: parent + contentHeight: textComponent.contentHeight + 10 + contentWidth: textComponent.contentWidth + 20 + clip: true + ScrollBar.vertical: ScrollBar { + active: contentHeight > height + } + ScrollBar.horizontal: ScrollBar { + active: contentWidth > width + } + contentX: 10 + contentY: 10 + + Text { + id: textComponent + width: popup.maxWidth - 20 + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + text: popup.text + } + } + } +} diff --git a/src/app/settingsview/components/ConnectionMonitoringTable.qml b/src/app/settingsview/components/ConnectionMonitoringTable.qml new file mode 100644 index 00000000..c770495f --- /dev/null +++ b/src/app/settingsview/components/ConnectionMonitoringTable.qml @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 +import net.jami.Enums 1.1 +import net.jami.Models 1.1 +import "../../commoncomponents" +import "../js/logviewwindowcreation.js" as LogViewWindowCreation + +ListView { + id: listview + height: contentItem.childrenRect.height + anchors.top: parent.top + anchors.topMargin: 10 + + spacing: 5 + cacheBuffer: 10 + + property int rota: 0 + + header: Rectangle { + height: 55 + width: connectionMonitoringTable.width + Rectangle { + color: JamiTheme.connectionMonitoringHeaderColor + anchors.top: parent.top + height: 50 + width: connectionMonitoringTable.width + + RowLayout { + anchors.fill: parent + Rectangle { + id: profile + height: 50 + Layout.leftMargin: 5 + Layout.preferredWidth: 210 + color: JamiTheme.transparentColor + Text { + id: textImage + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + text: JamiStrings.contact + } + } + + Rectangle { + id: device + Layout.fillWidth: true + height: 50 + color: JamiTheme.transparentColor + Text { + id: deviceText + anchors.verticalCenter: parent.verticalCenter + text: JamiStrings.device + } + } + + Rectangle { + id: connection + width: 130 + height: 50 + radius: 5 + color: JamiTheme.transparentColor + Text { + id: connectionText + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 10 + text: JamiStrings.connection + } + } + + Rectangle { + id: channel + height: 50 + width: 70 + color: JamiTheme.transparentColor + Text { + anchors.verticalCenter: parent.verticalCenter + text: JamiStrings.channels + } + } + } + } + } + + model: ConnectionInfoListModel + Timer { + interval: 1000 + running: root.visible + repeat: true + onTriggered: { + ContactAdapter.updateConnectionInfo(); + listview.rota = listview.rota + 5; + } + } + + delegate: Rectangle { + id: delegate + height: Count == 0 ? 0 : 10 + 40 * Count + width: connectionMonitoringTable.width + color: index % 2 === 0 ? JamiTheme.connectionMonitoringTableColor1 : JamiTheme.connectionMonitoringTableColor2 + + ListView { + id: listView2 + height: 40 * Count + + anchors.top: delegate.top + + spacing: 0 + + model: Count + + delegate: RowLayout { + id: rowLayoutDelegate + height: 40 + width: connectionMonitoringTable.width + + Rectangle { + id: profile + height: 50 + Layout.leftMargin: 5 + Layout.preferredWidth: 210 + color: JamiTheme.transparentColor + Avatar { + id: avatar + visible: index == 0 + anchors.left: parent.left + height: 40 + width: 40 + anchors.verticalCenter: parent.verticalCenter + imageId: PeerId + mode: Avatar.Mode.Contact + } + Rectangle { + id: usernameRect + anchors.left: avatar.right + anchors.verticalCenter: parent.verticalCenter + width: profile.width - 50 + height: 40 + color: JamiTheme.transparentColor + + Rectangle { + id: usernameRect2 + visible: index == 0 + width: profile.width - 50 + height: 20 + anchors.leftMargin: 10 + anchors.top: parent.top + anchors.left: parent.left + color: JamiTheme.transparentColor + + Text { + id: usernameText + text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, PeerId) + elide: Text.ElideRight + } + } + + Rectangle { + width: profile.width - 50 + height: 20 + anchors.leftMargin: 10 + anchors.top: usernameRect2.bottom + anchors.left: parent.left + visible: usernameRect2.visible && (UtilsAdapter.getBestIdForUri(CurrentAccount.id, PeerId) != UtilsAdapter.getBestNameForUri(CurrentAccount.id, PeerId)) + color: JamiTheme.transparentColor + + Text { + id: idText + anchors.fill: parent + text: UtilsAdapter.getBestIdForUri(CurrentAccount.id, PeerId) + font.pixelSize: 12 + font.underline: usernameText.font.underline + elide: Text.ElideRight + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + usernameText.font.underline = true; + tooltipContact.text = JamiStrings.copyAllData; + } + onExited: { + usernameText.font.underline = false; + tooltipContact.text = JamiStrings.copyAllData; + } + + ToolTip { + id: tooltipContact + visible: usernameText.font.underline + text: JamiStrings.copyAllData + } + onClicked: { + tooltipContact.text = JamiStrings.logsViewCopied; + UtilsAdapter.setClipboardText(ConnectionDatas); + } + } + } + } + + Rectangle { + height: 40 + Layout.fillWidth: true + color: delegate.color + Text { + id: delegateDeviceText + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + text: { + if (DeviceId[index] != undefined) { + return DeviceId[index]; + } else { + return ""; + } + } + elide: Text.ElideMiddle + width: parent.width - 10 + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + delegateDeviceText.font.underline = true; + } + onExited: { + delegateDeviceText.font.underline = false; + tooltipDevice.text = delegateDeviceText.text; + } + + ToolTip { + id: tooltipDevice + visible: delegateDeviceText.font.underline + text: delegateDeviceText.text + } + onClicked: { + tooltipDevice.text = delegateDeviceText.text + " (" + JamiStrings.logsViewCopied + ")"; + UtilsAdapter.setClipboardText(delegateDeviceText.text); + } + } + } + } + + Rectangle { + id: connectionRectangle + color: delegate.color + height: 40 + Layout.preferredWidth: 130 + property var status: Status[index] + ResponsiveImage { + id: connectionImage + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + rotation: connectionRectangle.status == 0 ? 0 : listview.rota + source: { + if (connectionRectangle.status == 0) { + return JamiResources.connected_black_24dp_svg; + } else { + return JamiResources.connecting_black_24dp_svg; + } + } + color: { + if (connectionRectangle.status == 0) { + return "#009c7f"; + } else { + if (connectionRectangle.status == 4) { + return "red"; + } else { + return "#ff8100"; + } + } + } + } + Text { + id: connectionText + anchors.left: connectionImage.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: if (connectionRectangle.status == 0) { + return JamiStrings.connected; + } else { + if (connectionRectangle.status == 1) { + return JamiStrings.connectingTLS; + } else { + if (connectionRectangle.status == 2) { + return JamiStrings.connectingICE; + } else { + if (connectionRectangle.status == 3) { + return JamiStrings.connecting; + } else { + return JamiStrings.waiting; + } + } + } + } + color: connectionImage.color + property var tooltipText: JamiStrings.remote + RemoteAddress[index] + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + connectionText.font.underline = true; + } + onExited: { + connectionText.font.underline = false; + } + + ToolTip { + visible: connectionText.font.underline + text: connectionText.tooltipText + } + } + } + } + + Rectangle { + id: channelDelegateRectangle + height: 40 + Layout.preferredWidth: 70 + color: delegate.color + Text { + id: channelText + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 10 + anchors.left: parent.left + text: { + if (Channels[index] != undefined) { + return Channels[index]; + } else { + return ""; + } + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onExited: { + channelText.font.underline = false; + } + + onEntered: { + channelText.font.underline = true; + } + + onClicked: { + var output = ""; + var channelMap = ChannelsMap[index]; + for (var key in channelMap) { + var value = channelMap[key]; + var keyHexa = parseInt(key, 16).toString(); + output += keyHexa + " : " + value + "\n"; + } + viewCoordinator.presentDialog(parent, "settingsview/components/ChannelsPopup.qml", { + "text": output, + "maxWidth": connectionMonitoringTable.width + }); + } + } + } + } + } + } + } +} diff --git a/src/app/settingsview/components/SettingsPageBase.qml b/src/app/settingsview/components/SettingsPageBase.qml index 69cab4ac..cc982f38 100644 --- a/src/app/settingsview/components/SettingsPageBase.qml +++ b/src/app/settingsview/components/SettingsPageBase.qml @@ -27,6 +27,7 @@ JamiSplitView { id: root required property Item flickableContent property real contentFlickableWidth: Math.min(JamiTheme.maximumWidthSettingsView, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize) + property real tableWidth: Math.min(JamiTheme.maximumWidthSettingsView * 2, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize) property alias title: settingsPage.title property color backgroundColor: JamiTheme.secondaryBackgroundColor property alias pageContainer: settingsPage diff --git a/src/app/settingsview/components/TroubleshootSettingsPage.qml b/src/app/settingsview/components/TroubleshootSettingsPage.qml index 63f5aded..6e1be6d4 100644 --- a/src/app/settingsview/components/TroubleshootSettingsPage.qml +++ b/src/app/settingsview/components/TroubleshootSettingsPage.qml @@ -29,11 +29,18 @@ import "../js/logviewwindowcreation.js" as LogViewWindowCreation SettingsPageBase { id: root + Layout.fillWidth: true + + readonly property string baseProviderPrefix: 'image://avatarImage' + + property string typePrefix: 'contact' + property string divider: '_' + property int itemWidth title: JamiStrings.troubleshootTitle - flickableContent: ColumnLayout { + flickableContent: Column { id: troubleshootSettingsColumnLayout width: contentFlickableWidth @@ -42,7 +49,7 @@ SettingsPageBase { anchors.leftMargin: JamiTheme.preferredSettingsMarginSize RowLayout { - + id: rawLayout Text { Layout.fillWidth: true Layout.preferredHeight: 30 @@ -85,5 +92,15 @@ SettingsPageBase { } } } + + Rectangle { + id: connectionMonitoringTable + height: listview.childrenRect.height + 60 + width: tableWidth + + ConnectionMonitoringTable { + id: listview + } + } } } diff --git a/src/libclient/api/lrc.h b/src/libclient/api/lrc.h index 1151c0be..03c61727 100644 --- a/src/libclient/api/lrc.h +++ b/src/libclient/api/lrc.h @@ -109,6 +109,16 @@ public: */ static VectorString getConferences(const QString& accountId = ""); + /** + * Get connection list from daemon + */ + static VectorMapStringString getConnectionList(const QString& accountId, const QString& uid); + + /** + * Get channel list from daemon + */ + static VectorMapStringString getChannelList(const QString& accountId, const QString& uid); + /** * Preference */ diff --git a/src/libclient/lrc.cpp b/src/libclient/lrc.cpp index c2fb60af..330b9475 100644 --- a/src/libclient/lrc.cpp +++ b/src/libclient/lrc.cpp @@ -211,6 +211,18 @@ Lrc::getConferences(const QString& accountId) return result; } +VectorMapStringString +Lrc::getConnectionList(const QString& accountId, const QString& uid) +{ + return ConfigurationManager::instance().getConnectionList(accountId, uid); +} + +VectorMapStringString +Lrc::getChannelList(const QString& accountId, const QString& uid) +{ + return ConfigurationManager::instance().getChannelList(accountId, uid); +} + bool isFinished(const QString& callState) { diff --git a/src/libclient/qtwrapper/configurationmanager_wrap.h b/src/libclient/qtwrapper/configurationmanager_wrap.h index 2eb19ef9..2ecafd2a 100644 --- a/src/libclient/qtwrapper/configurationmanager_wrap.h +++ b/src/libclient/qtwrapper/configurationmanager_wrap.h @@ -1016,6 +1016,24 @@ public Q_SLOTS: // METHODS libjami::setAllModerators(accountID.toStdString(), allModerators); } + VectorMapStringString getConnectionList(const QString& accountId, const QString& uid) + { + VectorMapStringString temp; + for (auto x : libjami::getConnectionList(accountId.toStdString(), uid.toStdString())) { + temp.push_back(convertMap(x)); + } + return temp; + } + + VectorMapStringString getChannelList(const QString& accountId, const QString& uid) + { + VectorMapStringString temp; + for (auto x : libjami::getChannelList(accountId.toStdString(), uid.toStdString())) { + temp.push_back(convertMap(x)); + } + return temp; + } + bool isAllModerators(const QString& accountID) { return libjami::isAllModerators(accountID.toStdString());