// What the blocker is doing right now, and what it will do. // // The rules themselves are server-side (edit them in the web UI or below); this // page is about *enforcement on this machine* — the helper, the hosts file and // the process watcher. import QtQuick import QtQuick.Controls.Material import QtQuick.Layouts import "components" ScrollView { id: page contentWidth: availableWidth clip: true ColumnLayout { width: page.availableWidth spacing: 16 Banner { Layout.fillWidth: true visible: !controller.helperInstalled text: "The privileged helper is not installed, so websites cannot be blocked. " + "Run: sudo desktop/helper/install.sh" actionText: "" } Banner { Layout.fillWidth: true visible: controller.blockerError !== "" text: controller.blockerError } ColumnLayout { Layout.fillWidth: true Layout.margins: 20 spacing: 16 Card { title: controller.blockerActive ? "🛡 Blocking is ON" : "Blocking is off" subtitle: controller.blockerReason RowLayout { Layout.fillWidth: true spacing: 20 StatTile { label: "Domains" value: controller.blockedDomains.length caption: "redirected to 127.0.0.1" } StatTile { label: "Apps" value: controller.blockedApps.length caption: "closed on sight" } StatTile { label: "Attempts today" value: controller.stats.today ? controller.stats.today.blocked_attempts : 0 caption: "and counting" } } RowLayout { Layout.fillWidth: true Switch { text: "Enforce on this machine" checked: controller.config.enforce_blocking onToggled: controller.setConfig("enforce_blocking", checked) } Switch { text: "Websites" checked: controller.config.block_hosts_file onToggled: controller.setConfig("block_hosts_file", checked) } Switch { text: "Apps" checked: controller.config.block_apps onToggled: controller.setConfig("block_apps", checked) } } } Card { title: "Blocked websites" subtitle: "One hostname per line. `www.` and `m.` are added automatically." TextArea { id: domainsArea Layout.fillWidth: true Layout.minimumHeight: 140 wrapMode: TextArea.NoWrap font.family: "Roboto Mono, monospace" text: (controller.settings.blocker ? controller.settings.blocker.blocked_domains : []).join("\n") placeholderText: "youtube.com\nreddit.com\ntwitter.com" } RowLayout { PrimaryButton { text: "Save websites" onClicked: { var list = domainsArea.text.split("\n").map(function (s) { return s.trim() }) .filter(function (s) { return s.length > 0 }) controller.patchSettings({ blocker: { blocked_domains: list } }) } } Item { Layout.fillWidth: true } } } Card { title: "Blocked applications" subtitle: "Process names, matched loosely: `steam` also catches `steamwebhelper`." TextArea { id: appsArea Layout.fillWidth: true Layout.minimumHeight: 100 wrapMode: TextArea.NoWrap font.family: "Roboto Mono, monospace" text: (controller.settings.blocker ? controller.settings.blocker.blocked_apps : []).join("\n") placeholderText: "steam\ndiscord\ntelegram-desktop" } RowLayout { PrimaryButton { text: "Save applications" onClicked: { var list = appsArea.text.split("\n").map(function (s) { return s.trim() }) .filter(function (s) { return s.length > 0 }) controller.patchSettings({ blocker: { blocked_apps: list } }) } } Item { Layout.fillWidth: true } } } Card { title: "When to block" subtitle: "Server-side, so every device agrees." RowLayout { Layout.fillWidth: true Switch { text: "Blocking enabled" checked: controller.settings.blocker ? controller.settings.blocker.enabled : false onToggled: controller.patchSettings({ blocker: { enabled: checked } }) } ComboBox { Layout.preferredWidth: 220 model: ["During focus intervals", "Always", "On a schedule"] currentIndex: { var mode = controller.settings.blocker ? controller.settings.blocker.mode : "during_work" return mode === "always" ? 1 : (mode === "scheduled" ? 2 : 0) } onActivated: function (index) { var modes = ["during_work", "always", "scheduled"] controller.patchSettings({ blocker: { mode: modes[index] } }) } } Switch { text: "Keep blocking during breaks" checked: controller.settings.blocker ? controller.settings.blocker.block_during_breaks : false onToggled: controller.patchSettings({ blocker: { block_during_breaks: checked } }) } } } Card { title: "Today's temptations" subtitle: "Every attempt the blocker turned away — this is what the daily report jokes about." Repeater { model: controller.stats.today ? controller.stats.today.blocked_top : [] delegate: RowLayout { required property var modelData Layout.fillWidth: true Label { text: modelData.target; Layout.fillWidth: true } Label { text: "× " + modelData.count; font.weight: Font.DemiBold } } } Label { visible: !controller.stats.today || controller.stats.today.blocked_top.length === 0 text: "Nothing yet today. Either the blocker is off, or you are behaving." opacity: 0.6 font.pixelSize: 12 } } } } Component.onCompleted: controller.loadSettings() }