File indexing completed on 2024-05-05 17:55:36
0001 /******************************************************************************* 0002 * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com> * 0003 * * 0004 * This program is free software: you can redistribute it and/or modify it * 0005 * under the terms of the GNU General Public License as published by the Free * 0006 * Software Foundation, either version 2 of the License, or (at your option) * 0007 * any later version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT * 0010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 0011 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * 0012 * more details. * 0013 * * 0014 * You should have received a copy of the GNU General Public License along * 0015 * with this program. If not, see <http://www.gnu.org/licenses/>. * 0016 *******************************************************************************/ 0017 0018 #include "unitmodel.h" 0019 0020 #include <KLocalizedString> 0021 #include <KColorScheme> 0022 0023 #include <QColor> 0024 #include <QIcon> 0025 #include <QtDBus/QtDBus> 0026 0027 #include <systemd/sd-journal.h> 0028 0029 UnitModel::UnitModel(QObject *parent) 0030 : QAbstractTableModel(parent) 0031 { 0032 } 0033 0034 UnitModel::UnitModel(QObject *parent, const QVector<SystemdUnit> *list, QString userBusPath) 0035 : QAbstractTableModel(parent) 0036 { 0037 m_unitList = list; 0038 m_userBus = userBusPath; 0039 } 0040 0041 int UnitModel::rowCount(const QModelIndex &) const 0042 { 0043 return m_unitList->size(); 0044 } 0045 0046 int UnitModel::columnCount(const QModelIndex &) const 0047 { 0048 return 4; 0049 } 0050 0051 QVariant UnitModel::headerData(int section, Qt::Orientation orientation, int role) const 0052 { 0053 if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) { 0054 return i18n("Unit"); 0055 } else if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 1) { 0056 return i18n("Load State"); 0057 } else if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 2) { 0058 return i18n("Active State"); 0059 } else if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 3) { 0060 return i18n("Unit State"); 0061 } 0062 return QVariant(); 0063 } 0064 0065 QVariant UnitModel::data(const QModelIndex & index, int role) const 0066 { 0067 0068 if (!index.isValid()) { 0069 return QVariant(); 0070 } 0071 0072 if (role == Qt::DisplayRole) 0073 { 0074 if (index.column() == 0) 0075 return m_unitList->at(index.row()).id; 0076 else if (index.column() == 1) 0077 return m_unitList->at(index.row()).load_state; 0078 else if (index.column() == 2) 0079 return m_unitList->at(index.row()).active_state; 0080 else if (index.column() == 3) 0081 return m_unitList->at(index.row()).sub_state; 0082 } 0083 0084 else if (role == Qt::ForegroundRole) 0085 { 0086 const KColorScheme scheme(QPalette::Normal); 0087 if (m_unitList->at(index.row()).active_state == QLatin1String("active")) 0088 return scheme.foreground(KColorScheme::PositiveText); 0089 else if (m_unitList->at(index.row()).active_state == QLatin1String("failed")) 0090 return scheme.foreground(KColorScheme::NegativeText); 0091 else if (m_unitList->at(index.row()).active_state == QLatin1String("-")) 0092 return scheme.foreground(KColorScheme::InactiveText); 0093 else 0094 return QVariant(); 0095 } 0096 0097 else if (role == Qt::DecorationRole) 0098 { 0099 if (index.column() == 0) { 0100 if (m_unitList->at(index.row()).active_state == QLatin1String("active")) { 0101 return QIcon::fromTheme(QStringLiteral("emblem-success")); 0102 } else if (m_unitList->at(index.row()).active_state == QLatin1String("inactive")) { 0103 return QIcon::fromTheme(QStringLiteral("emblem-pause")); 0104 } else if (m_unitList->at(index.row()).active_state == QLatin1String("failed")) { 0105 return QIcon::fromTheme(QStringLiteral("emblem-error")); 0106 } else if (m_unitList->at(index.row()).active_state == QLatin1String("-")) { 0107 return QIcon::fromTheme(QStringLiteral("emblem-unavailable")); 0108 } else { 0109 return QVariant(); 0110 } 0111 } 0112 } 0113 0114 else if (role == Qt::ToolTipRole) 0115 { 0116 const QString selUnit = m_unitList->at(index.row()).id; 0117 const QString selUnitPath = m_unitList->at(index.row()).unit_path.path(); 0118 const QString selUnitFile = m_unitList->at(index.row()).unit_file; 0119 0120 QString toolTipText; 0121 toolTipText.append(QStringLiteral("<b>%1</b><hr>").arg(selUnit)); 0122 0123 // Create a DBus interface 0124 QDBusConnection bus(QDBusConnection::systemBus()); 0125 if (!m_userBus.isEmpty()) { 0126 bus = QDBusConnection::connectToBus(m_userBus, QStringLiteral("org.freedesktop.systemd1")); 0127 } 0128 QDBusInterface *iface; 0129 0130 // Use the DBus interface to get unit properties 0131 if (!selUnitPath.isEmpty()) 0132 { 0133 // Unit has a valid path 0134 0135 iface = new QDBusInterface (QStringLiteral("org.freedesktop.systemd1"), 0136 selUnitPath, 0137 QStringLiteral("org.freedesktop.systemd1.Unit"), 0138 bus); 0139 if (iface->isValid()) 0140 { 0141 // Unit has a valid unit DBus object 0142 toolTipText.append(i18n("<b>Description: </b>")); 0143 toolTipText.append(iface->property("Description").toString()); 0144 0145 const QString unitFilePath = iface->property("FragmentPath").toString(); 0146 if (!unitFilePath.isEmpty()) { 0147 toolTipText.append(i18n("<br><b>Unit file: </b>%1", unitFilePath)); 0148 toolTipText.append(i18n("<br><b>Unit file state: </b>")); 0149 toolTipText.append(iface->property("UnitFileState").toString()); 0150 } 0151 0152 const QString sourcePath = iface->property("SourcePath").toString(); 0153 if (!sourcePath.isEmpty()) { 0154 toolTipText.append(i18n("<br><b>Source path: </b>%1", sourcePath)); 0155 } 0156 0157 qulonglong ActiveEnterTimestamp = iface->property("ActiveEnterTimestamp").toULongLong(); 0158 toolTipText.append(i18n("<br><b>Activated: </b>")); 0159 if (ActiveEnterTimestamp == 0) 0160 toolTipText.append(QStringLiteral("n/a")); 0161 else 0162 { 0163 QDateTime timeActivated; 0164 timeActivated.setMSecsSinceEpoch(ActiveEnterTimestamp/1000); 0165 toolTipText.append(timeActivated.toString()); 0166 } 0167 0168 qulonglong InactiveEnterTimestamp = iface->property("InactiveEnterTimestamp").toULongLong(); 0169 toolTipText.append(i18n("<br><b>Deactivated: </b>")); 0170 if (InactiveEnterTimestamp == 0) 0171 toolTipText.append(QStringLiteral("n/a")); 0172 else 0173 { 0174 QDateTime timeDeactivated; 0175 timeDeactivated.setMSecsSinceEpoch(InactiveEnterTimestamp/1000); 0176 toolTipText.append(timeDeactivated.toString()); 0177 } 0178 } 0179 delete iface; 0180 0181 } else { 0182 0183 // Unit does not have a valid unit DBus object (typically unloaded units). 0184 // Retrieve UnitFileState from Manager object. 0185 0186 iface = new QDBusInterface (QStringLiteral("org.freedesktop.systemd1"), 0187 QStringLiteral("/org/freedesktop/systemd1"), 0188 QStringLiteral("org.freedesktop.systemd1.Manager"), 0189 bus); 0190 QList<QVariant> args; 0191 args << selUnit; 0192 0193 toolTipText.append(i18n("<b>Unit file: </b>")); 0194 if (!selUnitFile.isEmpty()) { 0195 toolTipText.append(selUnitFile); 0196 } 0197 0198 toolTipText.append(i18n("<br><b>Unit file state: </b>")); 0199 if (!selUnitFile.isEmpty()) { 0200 toolTipText.append(iface->callWithArgumentList(QDBus::AutoDetect, QStringLiteral("GetUnitFileState"), args).arguments().at(0).toString()); 0201 } 0202 0203 delete iface; 0204 } 0205 0206 // Journal entries for units 0207 toolTipText.append(i18n("<hr><b>Last log entries:</b>")); 0208 QStringList log = getLastJrnlEntries(selUnit); 0209 if (log.isEmpty()) { 0210 toolTipText.append(i18n("<br><i>No log entries found for this unit.</i>")); 0211 } else { 0212 for(int i = log.count()-1; i >= 0; --i) 0213 { 0214 if (!log.at(i).isEmpty()) 0215 toolTipText.append(QStringLiteral("<br>%1").arg(log.at(i))); 0216 } 0217 } 0218 0219 toolTipText.append(QStringLiteral("</FONT")); 0220 0221 return toolTipText; 0222 } 0223 0224 return QVariant(); 0225 } 0226 0227 QStringList UnitModel::getLastJrnlEntries(QString unit) const 0228 { 0229 QString match1, match2; 0230 int r, jflags; 0231 QStringList reply; 0232 const void *data; 0233 size_t length; 0234 uint64_t time; 0235 sd_journal *journal; 0236 0237 if (!m_userBus.isEmpty()) 0238 { 0239 match1 = QStringLiteral("USER_UNIT=%1").arg(unit); 0240 jflags = (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_CURRENT_USER); 0241 } 0242 else 0243 { 0244 match1 = QStringLiteral("_SYSTEMD_UNIT=%1").arg(unit); 0245 match2 = QStringLiteral("UNIT=%1").arg(unit); 0246 jflags = (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM); 0247 } 0248 0249 r = sd_journal_open(&journal, jflags); 0250 if (r != 0) 0251 { 0252 qDebug() << "Failed to open journal"; 0253 return reply; 0254 } 0255 0256 sd_journal_flush_matches(journal); 0257 0258 r = sd_journal_add_match(journal, match1.toUtf8(), 0); 0259 if (r != 0) 0260 return reply; 0261 0262 if (!match2.isEmpty()) 0263 { 0264 sd_journal_add_disjunction(journal); 0265 r = sd_journal_add_match(journal, match2.toUtf8(), 0); 0266 if (r != 0) 0267 return reply; 0268 } 0269 0270 0271 r = sd_journal_seek_tail(journal); 0272 if (r != 0) 0273 return reply; 0274 0275 // Fetch the last 5 entries 0276 for (int i = 0; i < 5; ++i) 0277 { 0278 r = sd_journal_previous(journal); 0279 if (r == 1) 0280 { 0281 QString line; 0282 0283 // Get the date and time 0284 r = sd_journal_get_realtime_usec(journal, &time); 0285 if (r == 0) 0286 { 0287 QDateTime date; 0288 date.setMSecsSinceEpoch(time/1000); 0289 line.append(date.toString(QStringLiteral("yyyy.MM.dd hh:mm"))); 0290 } 0291 0292 // Color messages according to priority 0293 r = sd_journal_get_data(journal, "PRIORITY", &data, &length); 0294 if (r == 0) 0295 { 0296 int prio = QString::fromUtf8((const char *)data, length).section(QLatin1Char('='),1).toInt(); 0297 if (prio <= 3) 0298 line.append(QStringLiteral("<span style='color:tomato;'>")); 0299 else if (prio == 4) 0300 line.append(QStringLiteral("<span style='color:khaki;'>")); 0301 else 0302 line.append(QStringLiteral("<span style='color:palegreen;'>")); 0303 } 0304 0305 // Get the message itself 0306 r = sd_journal_get_data(journal, "MESSAGE", &data, &length); 0307 if (r == 0) 0308 { 0309 line.append(QStringLiteral(": %1</span>").arg(QString::fromUtf8((const char *)data, length).section(QLatin1Char('='),1))); 0310 if (line.length() > 195) 0311 line = QStringLiteral("%1...</span>").arg(line.left(195)); 0312 reply << line; 0313 } 0314 } 0315 else // previous failed, no more entries 0316 return reply; 0317 } 0318 0319 sd_journal_close(journal); 0320 0321 return reply; 0322 }