Warning, /plasma/plasma-firewall/kcm/ui/main.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0002 // SPDX-FileCopyrightText: 2018 Alexis Lopes Zubeta <contact@azubieta.net> 0003 // SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org> 0004 // SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk> 0005 0006 import QtQuick 0007 import QtQuick.Layouts 1.3 0008 import QtQuick.Controls as QQC2 0009 import Qt.labs.qmlmodels as Labs 0010 0011 import org.kde.kcmutils as KCMUtils 0012 0013 import org.kcm.firewall 1.0 0014 0015 import org.kde.kirigami 2.14 as Kirigami 0016 0017 KCMUtils.ScrollViewKCM { 0018 id: root 0019 0020 implicitHeight: Kirigami.Units.gridUnit * 25 0021 implicitWidth: Kirigami.Units.gridUnit * 44 0022 0023 property var policyChoices : [ 0024 {text: i18n("Allow"), data: "allow", tooltip: i18n("Allow all connections")}, 0025 {text: i18n("Ignore"), data: "deny", tooltip: i18n("Keeps the program waiting until the connection attempt times out, some short time later.")}, 0026 {text: i18n("Reject"), data: "reject", tooltip: i18n("Produces an immediate and very informative 'Connection refused' message")} 0027 ] 0028 0029 Kirigami.OverlaySheet { 0030 id: drawer 0031 0032 parent: root.QQC2.Overlay.overlay 0033 0034 onVisibleChanged: { 0035 if (visible) { 0036 ruleEdit.forceActiveFocus(); 0037 } else { 0038 // FIXME also reset rule 0039 ruleEditMessage.visible = false; 0040 } 0041 } 0042 0043 title: ruleEdit.newRule ? i18n("Create A New Firewall Rule") : i18n("Edit Firewall Rule") 0044 0045 ColumnLayout { 0046 spacing: Kirigami.Units.largeSpacing 0047 0048 Kirigami.InlineMessage { 0049 id: ruleEditMessage 0050 type: Kirigami.MessageType.Error 0051 Layout.fillWidth: true 0052 } 0053 0054 RuleEdit { 0055 id: ruleEdit 0056 client: kcm.client 0057 height: childrenRect.height 0058 implicitWidth: 30 * Kirigami.Units.gridUnit 0059 0060 Keys.onEnterPressed: event => accept() 0061 Keys.onReturnPressed: event => accept() 0062 0063 function accept() { 0064 var job = kcm.client[newRule ? "addRule" : "updateRule"](rule); 0065 if (!job) { 0066 ruleEditMessage.text = i18n("Please restart plasma firewall, the backend disconnected."); 0067 ruleEditMessage.visible = true; 0068 return; 0069 } 0070 0071 busy = true; 0072 kcm.needsSave = true; 0073 job.result.connect(() => { 0074 busy = false; 0075 0076 if (job.error) { 0077 // don't show an error when user canceled… 0078 if (job.error !== 4) { // FIXME magic number 0079 if (newRule) { 0080 ruleEditMessage.text = i18n("Error creating rule: %1", job.errorString); 0081 } else { 0082 ruleEditMessage.text = i18n("Error updating rule: %1", job.errorString); 0083 } 0084 ruleEditMessage.visible = true; 0085 0086 } 0087 // …but also don't close in this case! 0088 return; 0089 } 0090 0091 drawer.close(); 0092 }); 0093 } 0094 } 0095 0096 InlineBusyIndicator { 0097 Layout.alignment: Qt.AlignHCenter 0098 running: ruleEdit.busy 0099 visible: running 0100 } 0101 } 0102 0103 footer: QQC2.DialogButtonBox { 0104 enabled: ruleEdit.ready 0105 0106 QQC2.Button { 0107 text: ruleEdit.newRule ? i18n("Create") : i18n("Save") 0108 icon.name: ruleEdit.newRule ? "document-new" : "document-save" 0109 QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole 0110 } 0111 0112 onAccepted: { 0113 if (ruleEdit.simple.index > -1) { 0114 ruleEdit.rule.sourceApplication = ruleEdit.simple.service[ruleEdit.simple.index] 0115 } 0116 ruleEdit.accept() 0117 } 0118 } 0119 } 0120 0121 header: ColumnLayout { 0122 id: columnLayout 0123 0124 Kirigami.InlineMessage { 0125 id: firewallInlineErrorMessage 0126 Layout.fillWidth: true 0127 type: Kirigami.MessageType.Error 0128 } 0129 Kirigami.FormLayout { 0130 RowLayout { 0131 Kirigami.FormData.label: i18n("Firewall Status:") 0132 QQC2.CheckBox { 0133 id: enabledCheckBox 0134 property QtObject activeJob: null 0135 text: { 0136 if (kcm.client.enabled) { 0137 return activeJob ? i18n("Disabling…") : i18n("Enabled") 0138 } else { 0139 return activeJob ? i18n("Enabling…") : i18n("Disabled") 0140 } 0141 } 0142 enabled: !activeJob && !connectEnableTimer.running 0143 0144 function bindCurrent() { 0145 checked = Qt.binding(function() { 0146 return kcm.client.enabled; 0147 }); 0148 } 0149 Component.onCompleted: bindCurrent() 0150 0151 // FirewallD has a delay after the request to disable, to accept 0152 // enable actions, but the delay does not return with the job result 0153 // this is an ugly hack. 0154 Timer { 0155 id: connectEnableTimer 0156 interval: 4000 0157 repeat: false 0158 } 0159 0160 onToggled: { 0161 const enable = checked; // store the state on job begin, not when it finished 0162 0163 const job = kcm.client.setEnabled(checked); 0164 if (job === null) { 0165 firewallInlineErrorMessage.text = i18n("The firewall application, please install %1", kcm.client.name); 0166 firewallInlineErrorMessage.visible = true; 0167 return; 0168 } 0169 enabledCheckBox.activeJob = job; 0170 job.result.connect(function () { 0171 enabledCheckBox.activeJob = null; // need to explicitly unset since gc will clear it non-deterministic 0172 bindCurrent(); 0173 0174 if (job.error && job.error !== 4) { 0175 console.log(job.errorString); 0176 var errorString = job.errorString; 0177 // Firewalld is sending a typo to us. 0178 if (errorString.indexOf("Permission denied") !== -1) { 0179 errorString = i18n("Permission denied"); 0180 } 0181 0182 if (errorString.indexOf("unable to initialize table") !== -1) { 0183 firewallInlineErrorMessage.text = i18n("You recently updated your kernel. Iptables is failing to initialize, please reboot.") 0184 } else { 0185 firewallInlineErrorMessage.text = enabled 0186 ? i18n("Error enabling firewall: %1", errorString) 0187 : i18n("Error disabling firewall: %1", errorString) 0188 } 0189 firewallInlineErrorMessage.visible = true; 0190 } 0191 if (!enable && !job.error) { 0192 connectEnableTimer.start(); 0193 } 0194 }); 0195 job.start(); 0196 } 0197 } 0198 0199 InlineBusyIndicator { 0200 Layout.fillHeight: true 0201 running: enabledCheckBox.activeJob !== null || connectEnableTimer.running 0202 } 0203 } 0204 0205 Repeater { 0206 model: [ 0207 {label: i18n("Default Incoming Policy:"), key: "Incoming"}, 0208 {label: i18n("Default Outgoing Policy:"), key: "Outgoing"} 0209 ] 0210 0211 RowLayout { 0212 Kirigami.FormData.label: modelData.label 0213 0214 QQC2.ComboBox { 0215 id: policyCombo 0216 0217 property QtObject activeJob: null 0218 // TODO currentValue 0219 readonly property string currentPolicy: policyChoices[currentIndex].data 0220 0221 model: policyChoices 0222 textRole: "text" 0223 enabled: !activeJob && kcm.client.enabled 0224 QQC2.ToolTip.text: policyChoices[currentIndex].tooltip 0225 QQC2.ToolTip.delay: 1000 0226 QQC2.ToolTip.timeout: 5000 0227 QQC2.ToolTip.visible: hovered 0228 0229 Binding { // :( 0230 target: ruleEdit 0231 property: "default" + modelData.key + "PolicyRule" 0232 value: policyCombo.currentPolicy 0233 } 0234 0235 function bindCurrent() { 0236 currentIndex = Qt.binding(function() { 0237 return policyChoices.findIndex((choice) => choice.data === kcm.client["default" + modelData.key + "Policy"]); 0238 }); 0239 } 0240 Component.onCompleted: bindCurrent() 0241 0242 onActivated: { 0243 const job = kcm.client["setDefault" + modelData.key + "Policy"](currentPolicy) 0244 if (!job) { 0245 firewallInlineErrorMessage.text = i18n("Please restart plasma firewall, the backend disconnected."); 0246 firewallInlineErrorMessage.visible = true; 0247 return; 0248 } 0249 policyCombo.activeJob = job; 0250 job.result.connect(function () { 0251 policyCombo.activeJob = null; 0252 bindCurrent(); 0253 0254 if (job.error && job.error !== 4) { // TODO magic number 0255 firewallInlineErrorMessage.text = i18n("Error changing policy: %1", job.errorString) 0256 firewallInlineErrorMessage.visible = true; 0257 } 0258 }); 0259 } 0260 } 0261 0262 InlineBusyIndicator { 0263 Layout.fillHeight: true 0264 running: policyCombo.activeJob !== null 0265 } 0266 } 0267 } 0268 } 0269 } 0270 0271 QQC2.HorizontalHeaderView { 0272 id: horizontalHeader 0273 syncView: tableView 0274 visible: tableView.rows > 0 0275 selectionModel: ItemSelectionModel{} 0276 } 0277 0278 view: TableView { 0279 id: tableView 0280 anchors.fill: parent 0281 topMargin: horizontalHeader.height 0282 resizableColumns: true 0283 alternatingRows: true 0284 0285 property int currentHoveredRow: -1 0286 0287 selectionModel: ItemSelectionModel {} 0288 selectionMode: TreeView.SelectRows 0289 function selectRelative(delta) { 0290 var nextRow = selectionModel.currentIndex.row + delta 0291 if (nextRow < 0) { 0292 nextRow = 0 0293 } 0294 if (nextRow >= rows) { 0295 nextRow = rows - 1 0296 } 0297 var index = model.index(nextRow, selectionModel.currentIndex.column) 0298 selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows) 0299 } 0300 Keys.onUpPressed: selectRelative(-1) 0301 Keys.onDownPressed: selectRelative(1) 0302 0303 function editRule(row) { 0304 ruleEdit.rule = kcm.client.ruleAt(row); 0305 ruleEdit.newRule = false; 0306 drawer.open(); 0307 } 0308 Keys.onEnterPressed: Keys.returnPressed(event) 0309 Keys.onReturnPressed: { 0310 if (selectionModel.currentIndex) { 0311 editRule(selectionModel.currentIndex.row) 0312 } 0313 } 0314 0315 HoverHandler { 0316 onPointChanged: { 0317 view.currentHoveredRow = view.cellAtPosition(point.position).y 0318 } 0319 } 0320 0321 model: kcm.client.rulesModel 0322 columnWidthProvider: (column) => { 0323 let explicitWidth = explicitColumnWidth(column) 0324 if (explicitWidth > 0) { 0325 return explicitWidth 0326 } 0327 const columnWidths = []; 0328 columnWidths[RuleListModel.ActionColumn] = Kirigami.Units.gridUnit * 8 0329 columnWidths[RuleListModel.FromColumn] = Kirigami.Units.gridUnit * 10 0330 columnWidths[RuleListModel.ToColumn] = Kirigami.Units.gridUnit * 10 0331 columnWidths[RuleListModel.Ipv6Column] = Kirigami.Units.gridUnit * 4 0332 columnWidths[RuleListModel.LoggingColumn] = Kirigami.Units.gridUnit * 5 0333 columnWidths[RuleListModel.EditColumn] = Kirigami.Units.gridUnit * 6 0334 return columnWidths[column] 0335 } 0336 delegate: Labs.DelegateChooser { 0337 Labs.DelegateChoice { 0338 column: RuleListModel.EditColumn 0339 RowLayout { 0340 id: ruleActionsRow 0341 required property var model 0342 required property bool current 0343 required property bool selected 0344 property QtObject activeJob: null 0345 spacing: 0 0346 // TODO InlineBusyIndicator? 0347 enabled: !activeJob 0348 visible: tableView.currentHoveredRow === model.row || selected 0349 0350 Item { 0351 Layout.fillWidth: true 0352 } 0353 0354 QQC2.ToolButton { 0355 Layout.fillHeight: true 0356 icon.name: "edit-entry" 0357 visible: kcm.client.supportsRuleUpdate 0358 onClicked: tableView.editRule(model.row) 0359 QQC2.ToolTip { 0360 text: i18nc("@info:tooltip", "Edit Rule") 0361 } 0362 } 0363 QQC2.ToolButton { 0364 Layout.fillHeight: true 0365 icon.name: "edit-delete" 0366 onClicked: { 0367 const job = kcm.client.removeRule(model.row); 0368 if (!job) { 0369 firewallInlineErrorMessage.text = i18n("Please restart plasma firewall, the backend disconnected."); 0370 firewallInlineErrorMessage.visible = true; 0371 return; 0372 } 0373 0374 ruleActionsRow.activeJob = job; 0375 kcm.needsSave = true; 0376 job.result.connect(function () { 0377 ruleActionsRow.activeJob = null; 0378 0379 if (job.error && job.error !== 4) { // TODO magic number 0380 firewallInlineErrorMessage.text = i18n("Error removing rule: %1", job.errorString); 0381 firewallInlineErrorMessage.visible = true; 0382 } 0383 0384 }); 0385 } 0386 QQC2.ToolTip { 0387 text: i18nc("@info:tooltip", "Remove Rule") 0388 } 0389 } 0390 } 0391 } 0392 Labs.DelegateChoice { 0393 QQC2.ItemDelegate { 0394 required property var model 0395 required property bool current 0396 required property bool selected 0397 text: model.display 0398 highlighted: selected || current 0399 onClicked: { 0400 tableView.selectionModel.setCurrentIndex(tableView.model.index(model.row, model.column), ItemSelectionModel.Rows | ItemSelectionModel.ClearAndSelect) 0401 } 0402 } 0403 } 0404 } 0405 0406 Kirigami.PlaceholderMessage { 0407 parent: tableView.parent 0408 anchors.centerIn: parent 0409 width: tableView.width - (Kirigami.Units.largeSpacing * 12) 0410 visible: tableView.rows === 0 0411 text: !kcm.client.enabled ? i18n("Firewall is disabled") : i18n("No firewall rules have been added") 0412 explanation: kcm.client.enabled ? 0413 xi18nc("@info", "Click the <interface>Add Rule…</interface> button below to add one") : 0414 xi18nc("@info", "Enable the firewall with the <interface>Firewall Status</interface> checkbox above, and then click the <interface>Add Rule…</interface> button below to add one") 0415 } 0416 } 0417 0418 footer: RowLayout { 0419 QQC2.Button { 0420 text: i18nc("'view' is being used as a verb here", "View Connections") 0421 icon.name: "network-connect" 0422 onClicked: kcm.push("ConnectionsView.qml"); 0423 } 0424 QQC2.Button { 0425 text: i18nc("'view' is being used as a verb here", "View Logs") 0426 icon.name: "viewlog" 0427 onClicked: kcm.push("LogsView.qml"); 0428 } 0429 Item { 0430 Layout.fillWidth: true 0431 } 0432 0433 QQC2.Button { 0434 enabled: !kcm.client.busy && kcm.client.enabled 0435 icon.name: "list-add" 0436 text: i18n("Add Rule…") 0437 onClicked: { 0438 ruleEdit.newRule = true 0439 drawer.open() 0440 } 0441 } 0442 0443 QQC2.Button { 0444 icon.name: "help-about" 0445 text: i18n("About") 0446 onClicked: root.showAboutView() 0447 } 0448 0449 } 0450 Component.onCompleted: { 0451 if (kcm.client.name === "") { 0452 firewallInlineErrorMessage.text = i18n("Please install a firewall, such as ufw or firewalld"); 0453 firewallInlineErrorMessage.visible = true; 0454 enabledCheckBox.enabled = false; 0455 } else { 0456 // Initialize the client's status. 0457 kcm.client.refresh(); 0458 } 0459 } 0460 0461 function showAboutView() { 0462 const sheet = aboutComponent.createObject(root.QQC2.Overlay.overlay, {name: kcm.client.name, version: kcm.client.version()}); 0463 sheet.open(); 0464 } 0465 0466 Component { 0467 id: aboutComponent 0468 About { 0469 onClosed: destroy() 0470 } 0471 } 0472 }