diff --git a/.gitignore b/.gitignore index 6fb4235..0d36bb9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ # Created by https://www.gitignore.io/api/xcode,macos,cocoapods +### Pi-hole docker data ### +dnsmasq.d/ +pihole/ + ### CocoaPods ### ## CocoaPods GitIgnore Template diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..50eed14 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ + +MIT License + +Copyright (c) 2018 Lukas Wolfsteiner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Podfile b/Podfile deleted file mode 100644 index 9de9515..0000000 --- a/Podfile +++ /dev/null @@ -1,10 +0,0 @@ -# Uncomment the next line to define a global platform for your project -# platform :ios, '9.0' - -target 'Shortcuts for Pi-hole' do - # Comment the next line if you're not using Swift and don't want to use dynamic frameworks - use_frameworks! - - # Pods for Shortcuts for Pi-hole - pod 'Preferences' -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index 91f0867..0000000 --- a/Podfile.lock +++ /dev/null @@ -1,16 +0,0 @@ -PODS: - - Preferences (0.2.0) - -DEPENDENCIES: - - Preferences - -SPEC REPOS: - https://github.com/cocoapods/specs.git: - - Preferences - -SPEC CHECKSUMS: - Preferences: ebc036a176a298cde835155c73251173768a2f8d - -PODFILE CHECKSUM: 0dcb2629e69c41c47786f0497bcb8052df37c64d - -COCOAPODS: 1.5.3 diff --git a/README.md b/README.md index 962f61e..a3a223b 100644 --- a/README.md +++ b/README.md @@ -1 +1,46 @@ -# Shortcuts for Pi-hole \ No newline at end of file +# Shortcuts for Pi-hole + +Shortcuts for Pi-hole is a small menu bar application that lives in your status bar. It provides quick actions for managing and monitoring your Pi-hole® instance. + + + +General Preferences | Connection Preferences +:-------------------------:|:-------------------------: + | + +## Things to complete + +- [ ] Application icon +- [ ] Update mechanism (using the [Spark Framework](https://sparkle-project.org/)) +- [ ] Implement Unit-/Testing +- [ ] Create as cask + +## Copyright acknowledges and credits + +Big thanks to the whole Pi-hole userspace for developing and maintaining the Pi-hole project. Visit [pi-hole.net](https://pi-hole.net/) for more information. This application uses the Pi-hole API and it's logo as menu bar icon. + +**Pi-hole® is a registered trademark of Pi-hole LLC** + +## Source code license + +MIT License + +Copyright (c) 2018 Lukas Wolfsteiner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Screenshots/ConnectionPreferences.png b/Screenshots/ConnectionPreferences.png new file mode 100644 index 0000000..25e5807 Binary files /dev/null and b/Screenshots/ConnectionPreferences.png differ diff --git a/Screenshots/GeneralPreferences.png b/Screenshots/GeneralPreferences.png new file mode 100644 index 0000000..2667075 Binary files /dev/null and b/Screenshots/GeneralPreferences.png differ diff --git a/Screenshots/Overview.png b/Screenshots/Overview.png new file mode 100644 index 0000000..2e866bc Binary files /dev/null and b/Screenshots/Overview.png differ diff --git a/Shortcuts for Pi-hole.xcodeproj/project.pbxproj b/Shortcuts for Pi-hole.xcodeproj/project.pbxproj index eb2beb2..64b0e6b 100644 --- a/Shortcuts for Pi-hole.xcodeproj/project.pbxproj +++ b/Shortcuts for Pi-hole.xcodeproj/project.pbxproj @@ -7,32 +7,54 @@ objects = { /* Begin PBXBuildFile section */ - 803305682171427400132604 /* GeneralPreferenceViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 803305672171427400132604 /* GeneralPreferenceViewController.xib */; }; - 8033056A2171429100132604 /* GeneralPreferenceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 803305692171429100132604 /* GeneralPreferenceViewController.swift */; }; - 8033056C217152AE00132604 /* GeneralPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8033056B217152AE00132604 /* GeneralPreferences.swift */; }; + 800A2FFB217D3038002EC725 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 800A2FFA217D3038002EC725 /* README.md */; }; + 8033056A2171429100132604 /* GeneralPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 803305692171429100132604 /* GeneralPreferencesViewController.swift */; }; + 8033056C217152AE00132604 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8033056B217152AE00132604 /* Preferences.swift */; }; 8033056E2171649C00132604 /* ColoredStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8033056D2171649C00132604 /* ColoredStatusView.swift */; }; 803305702171796B00132604 /* PiHoleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8033056F2171796B00132604 /* PiHoleProxy.swift */; }; + 8046492121725E3A0028E457 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8046492021725E3A0028E457 /* Main.storyboard */; }; + 804C398E217C9A2600576BFD /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 804C398D217C9A2600576BFD /* Credits.rtf */; }; + 804C3991217CD24700576BFD /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804C3990217CD24700576BFD /* AboutWindowController.swift */; }; + 804C3993217CD6D600576BFD /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804C3992217CD6D600576BFD /* AboutViewController.swift */; }; 804E3D5021713D1400BD1DA0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804E3D4F21713D1400BD1DA0 /* AppDelegate.swift */; }; 804E3D5221713D1500BD1DA0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 804E3D5121713D1500BD1DA0 /* Assets.xcassets */; }; - 804E3D5521713D1500BD1DA0 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 804E3D5321713D1500BD1DA0 /* MainMenu.xib */; }; - 9FBE1CAC0A0BAF179ACE61FC /* Pods_Shortcuts_for_Pi_hole.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 710A42177EE15A4B24668808 /* Pods_Shortcuts_for_Pi_hole.framework */; }; + 804E7FFE21977F5000A22D35 /* PiHoleConnectionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804E7FFD21977F5000A22D35 /* PiHoleConnectionResult.swift */; }; + 804E800021977F7E00A22D35 /* PiHoleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804E7FFF21977F7E00A22D35 /* PiHoleAction.swift */; }; + 804E800321977FBA00A22D35 /* Overview.png in Resources */ = {isa = PBXBuildFile; fileRef = 804E800221977FBA00A22D35 /* Overview.png */; }; + 804E80062197810200A22D35 /* GeneralPreferences.png in Resources */ = {isa = PBXBuildFile; fileRef = 804E80042197810200A22D35 /* GeneralPreferences.png */; }; + 804E80072197810200A22D35 /* ConnectionPreferences.png in Resources */ = {isa = PBXBuildFile; fileRef = 804E80052197810200A22D35 /* ConnectionPreferences.png */; }; + 805F21F4217E1BAF00F12C6E /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 805F21F3217E1BAF00F12C6E /* LICENSE */; }; + 80CA0040217260DA00CC9FB6 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80CA003F217260DA00CC9FB6 /* PreferencesWindowController.swift */; }; + 80CA005321729EF800CC9FB6 /* ConnectionPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80CA005221729EF800CC9FB6 /* ConnectionPreferencesViewController.swift */; }; + 80ECF9AD217BC3E500879DA2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 80ECF9AC217BC3E500879DA2 /* MainMenu.xib */; }; + 80ECF9B0217BCE9400879DA2 /* MainMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80ECF9AF217BCE9400879DA2 /* MainMenuController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 639B4EFAD3D1CA453BFE7D8D /* Pods-Shortcuts for Pi-hole.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Shortcuts for Pi-hole.release.xcconfig"; path = "Pods/Target Support Files/Pods-Shortcuts for Pi-hole/Pods-Shortcuts for Pi-hole.release.xcconfig"; sourceTree = ""; }; - 710A42177EE15A4B24668808 /* Pods_Shortcuts_for_Pi_hole.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Shortcuts_for_Pi_hole.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 74562D576827A7D15A133C16 /* Pods-Shortcuts for Pi-hole.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Shortcuts for Pi-hole.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Shortcuts for Pi-hole/Pods-Shortcuts for Pi-hole.debug.xcconfig"; sourceTree = ""; }; - 803305672171427400132604 /* GeneralPreferenceViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GeneralPreferenceViewController.xib; sourceTree = ""; }; - 803305692171429100132604 /* GeneralPreferenceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferenceViewController.swift; sourceTree = ""; }; - 8033056B217152AE00132604 /* GeneralPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferences.swift; sourceTree = ""; }; + 800A2FFA217D3038002EC725 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 803305692171429100132604 /* GeneralPreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesViewController.swift; sourceTree = ""; }; + 8033056B217152AE00132604 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 8033056D2171649C00132604 /* ColoredStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredStatusView.swift; sourceTree = ""; }; - 8033056F2171796B00132604 /* PiHoleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PiHoleProxy.swift; path = "Shortcuts for Pi-hole/PiHoleProxy.swift"; sourceTree = SOURCE_ROOT; }; + 8033056F2171796B00132604 /* PiHoleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PiHoleProxy.swift; path = "Shortcuts for Pi-hole/Tools/PiHoleProxy.swift"; sourceTree = SOURCE_ROOT; }; 803305712171882E00132604 /* Shortcuts for Pi-hole.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Shortcuts for Pi-hole.entitlements"; sourceTree = ""; }; + 8046492021725E3A0028E457 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + 804C398D217C9A2600576BFD /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; + 804C3990217CD24700576BFD /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; + 804C3992217CD6D600576BFD /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 804E3D4C21713D1400BD1DA0 /* Shortcuts for Pi-hole.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Shortcuts for Pi-hole.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 804E3D4F21713D1400BD1DA0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 804E3D5121713D1500BD1DA0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 804E3D5421713D1500BD1DA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 804E3D5621713D1500BD1DA0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 804E7FFD21977F5000A22D35 /* PiHoleConnectionResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiHoleConnectionResult.swift; sourceTree = ""; }; + 804E7FFF21977F7E00A22D35 /* PiHoleAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiHoleAction.swift; sourceTree = ""; }; + 804E800221977FBA00A22D35 /* Overview.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Overview.png; sourceTree = ""; }; + 804E80042197810200A22D35 /* GeneralPreferences.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GeneralPreferences.png; sourceTree = ""; }; + 804E80052197810200A22D35 /* ConnectionPreferences.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ConnectionPreferences.png; sourceTree = ""; }; + 805F21F3217E1BAF00F12C6E /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 80CA003F217260DA00CC9FB6 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; + 80CA005221729EF800CC9FB6 /* ConnectionPreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionPreferencesViewController.swift; sourceTree = ""; }; + 80ECF9AC217BC3E500879DA2 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; + 80ECF9AF217BCE9400879DA2 /* MainMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenuController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -40,39 +62,51 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9FBE1CAC0A0BAF179ACE61FC /* Pods_Shortcuts_for_Pi_hole.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 7E615BC3416DBAA933758B78 /* Frameworks */ = { + 8046491B2172597A0028E457 /* Preferences */ = { isa = PBXGroup; children = ( - 710A42177EE15A4B24668808 /* Pods_Shortcuts_for_Pi_hole.framework */, + 8033056B217152AE00132604 /* Preferences.swift */, + 8033056D2171649C00132604 /* ColoredStatusView.swift */, + 80CA003F217260DA00CC9FB6 /* PreferencesWindowController.swift */, + 803305692171429100132604 /* GeneralPreferencesViewController.swift */, + 80CA005221729EF800CC9FB6 /* ConnectionPreferencesViewController.swift */, ); - name = Frameworks; + path = Preferences; sourceTree = ""; }; - 803305662171425000132604 /* PreferenceWindow */ = { + 804C398F217CC62700576BFD /* About */ = { + isa = PBXGroup; + children = ( + 804C3990217CD24700576BFD /* AboutWindowController.swift */, + 804C3992217CD6D600576BFD /* AboutViewController.swift */, + ); + path = About; + sourceTree = ""; + }; + 804C3997217D251100576BFD /* Tools */ = { isa = PBXGroup; children = ( - 803305672171427400132604 /* GeneralPreferenceViewController.xib */, - 803305692171429100132604 /* GeneralPreferenceViewController.swift */, - 8033056B217152AE00132604 /* GeneralPreferences.swift */, 8033056F2171796B00132604 /* PiHoleProxy.swift */, + 804E7FFD21977F5000A22D35 /* PiHoleConnectionResult.swift */, + 804E7FFF21977F7E00A22D35 /* PiHoleAction.swift */, ); - path = PreferenceWindow; + path = Tools; sourceTree = ""; }; 804E3D4321713D1400BD1DA0 = { isa = PBXGroup; children = ( + 804E800121977FBA00A22D35 /* Screenshots */, + 805F21F3217E1BAF00F12C6E /* LICENSE */, + 800A2FFA217D3038002EC725 /* README.md */, 804E3D4E21713D1400BD1DA0 /* Shortcuts for Pi-hole */, 804E3D4D21713D1400BD1DA0 /* Products */, - C0924F09F3A6ED1030708736 /* Pods */, - 7E615BC3416DBAA933758B78 /* Frameworks */, ); sourceTree = ""; }; @@ -87,24 +121,37 @@ 804E3D4E21713D1400BD1DA0 /* Shortcuts for Pi-hole */ = { isa = PBXGroup; children = ( + 804C3997217D251100576BFD /* Tools */, + 804C398F217CC62700576BFD /* About */, + 8046491B2172597A0028E457 /* Preferences */, + 80ECF9AE217BCD6700879DA2 /* Main */, 803305712171882E00132604 /* Shortcuts for Pi-hole.entitlements */, - 803305662171425000132604 /* PreferenceWindow */, 804E3D4F21713D1400BD1DA0 /* AppDelegate.swift */, 804E3D5121713D1500BD1DA0 /* Assets.xcassets */, - 804E3D5321713D1500BD1DA0 /* MainMenu.xib */, 804E3D5621713D1500BD1DA0 /* Info.plist */, - 8033056D2171649C00132604 /* ColoredStatusView.swift */, + 804C398D217C9A2600576BFD /* Credits.rtf */, ); path = "Shortcuts for Pi-hole"; sourceTree = ""; }; - C0924F09F3A6ED1030708736 /* Pods */ = { + 804E800121977FBA00A22D35 /* Screenshots */ = { isa = PBXGroup; children = ( - 74562D576827A7D15A133C16 /* Pods-Shortcuts for Pi-hole.debug.xcconfig */, - 639B4EFAD3D1CA453BFE7D8D /* Pods-Shortcuts for Pi-hole.release.xcconfig */, + 804E80052197810200A22D35 /* ConnectionPreferences.png */, + 804E80042197810200A22D35 /* GeneralPreferences.png */, + 804E800221977FBA00A22D35 /* Overview.png */, ); - name = Pods; + path = Screenshots; + sourceTree = ""; + }; + 80ECF9AE217BCD6700879DA2 /* Main */ = { + isa = PBXGroup; + children = ( + 8046492021725E3A0028E457 /* Main.storyboard */, + 80ECF9AC217BC3E500879DA2 /* MainMenu.xib */, + 80ECF9AF217BCE9400879DA2 /* MainMenuController.swift */, + ); + path = Main; sourceTree = ""; }; /* End PBXGroup section */ @@ -114,11 +161,9 @@ isa = PBXNativeTarget; buildConfigurationList = 804E3D5A21713D1500BD1DA0 /* Build configuration list for PBXNativeTarget "Shortcuts for Pi-hole" */; buildPhases = ( - 74A149B67BD62690FFB2CEB6 /* [CP] Check Pods Manifest.lock */, 804E3D4821713D1400BD1DA0 /* Sources */, 804E3D4921713D1400BD1DA0 /* Frameworks */, 804E3D4A21713D1400BD1DA0 /* Resources */, - 3D6C9348414E50D0014BF6D9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -172,79 +217,42 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 805F21F4217E1BAF00F12C6E /* LICENSE in Resources */, + 804E80062197810200A22D35 /* GeneralPreferences.png in Resources */, + 804E800321977FBA00A22D35 /* Overview.png in Resources */, + 80ECF9AD217BC3E500879DA2 /* MainMenu.xib in Resources */, 804E3D5221713D1500BD1DA0 /* Assets.xcassets in Resources */, - 804E3D5521713D1500BD1DA0 /* MainMenu.xib in Resources */, - 803305682171427400132604 /* GeneralPreferenceViewController.xib in Resources */, + 800A2FFB217D3038002EC725 /* README.md in Resources */, + 8046492121725E3A0028E457 /* Main.storyboard in Resources */, + 804C398E217C9A2600576BFD /* Credits.rtf in Resources */, + 804E80072197810200A22D35 /* ConnectionPreferences.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 3D6C9348414E50D0014BF6D9 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Shortcuts for Pi-hole/Pods-Shortcuts for Pi-hole-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Preferences/Preferences.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Preferences.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Shortcuts for Pi-hole/Pods-Shortcuts for Pi-hole-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 74A149B67BD62690FFB2CEB6 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Shortcuts for Pi-hole-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 804E3D4821713D1400BD1DA0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 803305702171796B00132604 /* PiHoleProxy.swift in Sources */, - 8033056C217152AE00132604 /* GeneralPreferences.swift in Sources */, + 8033056C217152AE00132604 /* Preferences.swift in Sources */, + 80ECF9B0217BCE9400879DA2 /* MainMenuController.swift in Sources */, + 80CA0040217260DA00CC9FB6 /* PreferencesWindowController.swift in Sources */, + 804E800021977F7E00A22D35 /* PiHoleAction.swift in Sources */, 8033056E2171649C00132604 /* ColoredStatusView.swift in Sources */, - 8033056A2171429100132604 /* GeneralPreferenceViewController.swift in Sources */, + 804E7FFE21977F5000A22D35 /* PiHoleConnectionResult.swift in Sources */, + 8033056A2171429100132604 /* GeneralPreferencesViewController.swift in Sources */, + 804C3991217CD24700576BFD /* AboutWindowController.swift in Sources */, + 80CA005321729EF800CC9FB6 /* ConnectionPreferencesViewController.swift in Sources */, + 804C3993217CD6D600576BFD /* AboutViewController.swift in Sources */, 804E3D5021713D1400BD1DA0 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXVariantGroup section */ - 804E3D5321713D1500BD1DA0 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 804E3D5421713D1500BD1DA0 /* Base */, - ); - name = MainMenu.xib; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ 804E3D5821713D1500BD1DA0 /* Debug */ = { isa = XCBuildConfiguration; @@ -363,7 +371,6 @@ }; 804E3D5B21713D1500BD1DA0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 74562D576827A7D15A133C16 /* Pods-Shortcuts for Pi-hole.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "Shortcuts for Pi-hole/Shortcuts for Pi-hole.entitlements"; @@ -385,11 +392,10 @@ }; 804E3D5C21713D1500BD1DA0 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 639B4EFAD3D1CA453BFE7D8D /* Pods-Shortcuts for Pi-hole.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "Shortcuts for Pi-hole/Shortcuts for Pi-hole.entitlements"; - CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 4EGEDRDDUV; diff --git a/Shortcuts for Pi-hole.xcodeproj/xcuserdata/lukas.xcuserdatad/xcschemes/xcschememanagement.plist b/Shortcuts for Pi-hole.xcodeproj/xcuserdata/lukas.xcuserdatad/xcschemes/xcschememanagement.plist index 4c18f7d..b7c5a7d 100644 --- a/Shortcuts for Pi-hole.xcodeproj/xcuserdata/lukas.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Shortcuts for Pi-hole.xcodeproj/xcuserdata/lukas.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Shortcuts for Pi-hole.xcscheme orderHint - 2 + 0 diff --git a/Shortcuts for Pi-hole.xcworkspace/contents.xcworkspacedata b/Shortcuts for Pi-hole.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index da731f8..0000000 --- a/Shortcuts for Pi-hole.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/Shortcuts for Pi-hole.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Shortcuts for Pi-hole.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/Shortcuts for Pi-hole.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Shortcuts for Pi-hole/About/AboutViewController.swift b/Shortcuts for Pi-hole/About/AboutViewController.swift new file mode 100644 index 0000000..96e528e --- /dev/null +++ b/Shortcuts for Pi-hole/About/AboutViewController.swift @@ -0,0 +1,34 @@ +// +// AboutViewController.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 21.10.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +class AboutViewController: NSViewController { + + @IBOutlet weak var versionTextField: NSTextField! + + @IBAction func viewSourceCodeActionHandler(_ sender: NSButton) { + NSWorkspace.shared.open(URL(string: "https://github.com/dotWee/macOS-PiholeShortcuts")!) + } + + @IBAction func visitPiHoleProjectActionHandler(_ sender: NSButton) { + NSWorkspace.shared.open(URL(string: "https://pi-hole.net")!) + } + + override func viewDidLoad() { + super.viewDidLoad() + + // Do view setup here. + print("AboutViewController: viewDidLoad()") + + let versionValue = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + let bundleValue = Bundle.main.infoDictionary?["CFBundleVersion"] as? String + versionTextField.stringValue = "Version: \(versionValue!) (Bundle No. #\(bundleValue!))"; + } + +} diff --git a/Shortcuts for Pi-hole/About/AboutWindowController.swift b/Shortcuts for Pi-hole/About/AboutWindowController.swift new file mode 100644 index 0000000..d907fcc --- /dev/null +++ b/Shortcuts for Pi-hole/About/AboutWindowController.swift @@ -0,0 +1,26 @@ +// +// AboutWindowController.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 21.10.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +class AboutWindowController: NSWindowController { + + @IBOutlet weak var aboutWindow: NSWindow! + + override func showWindow(_ sender: Any?) { + super.showWindow(sender) + + AppDelegate.bringToFront(window: self.window!) + } + + override func windowDidLoad() { + super.windowDidLoad() + + // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. + } +} diff --git a/Shortcuts for Pi-hole/AppDelegate.swift b/Shortcuts for Pi-hole/AppDelegate.swift index 8f93bc3..d862d76 100644 --- a/Shortcuts for Pi-hole/AppDelegate.swift +++ b/Shortcuts for Pi-hole/AppDelegate.swift @@ -7,76 +7,22 @@ // import Cocoa -import Preferences @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - - static let menuItems = [AppDelegate.menuItemStatus, AppKit.NSMenuItem.separator(), AppDelegate.menuItemEnable, AppDelegate.menuItemDisable, AppKit.NSMenuItem.separator(), AppDelegate.menuItemPreferences, AppKit.NSMenuItem.separator(), AppDelegate.menuItemQuit] - - static let menuItemStatus = NSMenuItem(title: "Status", action: nil, keyEquivalent: "", isEnabled: false) - @objc func menuItemStatusActionHandler(_ sender: Any?) { - - } - - static let menuItemEnable = NSMenuItem(title: "Enable", action: #selector(AppDelegate.menuItemEnableActionHandler(_:)), keyEquivalent: "E", isEnabled: true) - @objc func menuItemEnableActionHandler(_ sender: Any?) { - - } - - static let menuItemDisable = NSMenuItem(title: "Disable", action: #selector(AppDelegate.menuItemDisableActionHandler(_:)), keyEquivalent: "D", isEnabled: true) - @objc func menuItemDisableActionHandler(_ sender: Any?) { - - } - - static let menuItemPreferences = NSMenuItem(title: "Preferences", action: #selector(AppDelegate.menuItemPreferenceActionHandler(_:)), keyEquivalent: "P", isEnabled: true) - @objc func menuItemPreferenceActionHandler(_ sender: Any?) { - preferencesWindowController.showWindow() - } - - static let menuItemQuit = NSMenuItem(title: "Quit " + (Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? ""), action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q", isEnabled: true) - - @IBOutlet weak var window: NSWindow! - - let statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) - @objc func statusBarItemActionHandler(_ sender: NSStatusBarButton) { - - } - - let preferencesWindowController = PreferencesWindowController(viewControllers: [GeneralPreferenceViewController()]) func applicationDidFinishLaunching(_ aNotification: Notification) { // Insert code here to initialize your application - preferencesWindowController.showWindow() - if let button = statusBarItem.button { - button.image = NSImage(named:"StatusBarButtonImage") - button.action = #selector(self.statusBarItemActionHandler(_:)) - button.target = self - } - - let menu = NSMenu() - - AppDelegate.menuItems.forEach { menuItem in menu.addItem(menuItem) } - statusBarItem.menu = menu - } - - func applicationWillFinishLaunching(_ notification: Notification) { - window.orderOut(self) } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application - preferencesWindowController.close() - } - - @IBAction func preferencesMenuItemActionHandler(_ sender: Any) { - preferencesWindowController.showWindow() } - static func NSMenuItem(title: String, action selector: Selector?, keyEquivalent charCode: String, isEnabled: Bool) -> NSMenuItem { - let menuItem = AppKit.NSMenuItem(title: title, action: selector, keyEquivalent: charCode) - menuItem.isEnabled = isEnabled - return menuItem + public static func bringToFront(window: NSWindow) { + window.center() + window.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) } } diff --git a/Shortcuts for Pi-hole/Base.lproj/MainMenu.xib b/Shortcuts for Pi-hole/Base.lproj/MainMenu.xib deleted file mode 100644 index bcb5399..0000000 --- a/Shortcuts for Pi-hole/Base.lproj/MainMenu.xib +++ /dev/null @@ -1,708 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Shortcuts for Pi-hole/Credits.rtf b/Shortcuts for Pi-hole/Credits.rtf new file mode 100644 index 0000000..04aeafc --- /dev/null +++ b/Shortcuts for Pi-hole/Credits.rtf @@ -0,0 +1,30 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1671 +{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{upper-roman\}.}{\leveltext\leveltemplateid1\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid1} +{\list\listtemplateid2\listhybrid{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{upper-roman\}.}{\leveltext\leveltemplateid101\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid2}} +{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}} +\paperw11900\paperh16840\margl1440\margr1440\vieww15920\viewh14840\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 + +\f0\b\fs28 \cf0 References and links\ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 + +\f1\b0\fs20 \cf0 \ +\pard\tx220\tx720\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\li720\fi-720\sl288\slmult1\pardirnatural\partightenfactor0 +\ls1\ilvl0{\field{\*\fldinst{HYPERLINK "https://pi-hole.net/"}}{\fldrslt +\fs24 \cf0 The Pi-hole project}} +\fs24 \ +\ls1\ilvl0{\field{\*\fldinst{HYPERLINK "https://github.com/dotWee/macOS-PiholeShortcuts"}}{\fldrslt Source code}} +\fs28 \ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 +\cf0 \ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 + +\f0\b \cf0 Copyright acknowledgments\ + +\fs20 \ +\pard\tx220\tx720\tx796\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\li720\fi-720\sl288\slmult1\pardirnatural\partightenfactor0 +\ls2\ilvl0 +\f1\b0\fs24 \cf0 Pi-hole\'ae is a registered trademark of Pi-hole LLC} \ No newline at end of file diff --git a/Shortcuts for Pi-hole/Info.plist b/Shortcuts for Pi-hole/Info.plist index 3a6e93b..f7db763 100644 --- a/Shortcuts for Pi-hole/Info.plist +++ b/Shortcuts for Pi-hole/Info.plist @@ -2,42 +2,42 @@ - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Shortcuts for Pi-hole - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 LSApplicationCategoryType public.app-category.utilities - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - LSUIElement - + CFBundleVersion + 1 + CFBundleShortVersionString + 1.0.0 + CFBundleExecutable + $(EXECUTABLE_NAME) NSAppTransportSecurity NSAllowsArbitraryLoads - NSHumanReadableCopyright - Copyright © 2018 Lukas Wolfsteiner. All rights reserved. - NSMainNibFile - MainMenu + CFBundleDisplayName + Shortcuts for Pi-hole NSPrincipalClass NSApplication + CFBundlePackageType + APPL + CFBundleIconFile + + LSUIElement + + NSMainNibFile + MainMenu + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + NSHumanReadableCopyright + Copyright © 2018 Lukas Wolfsteiner. All rights reserved. + CFBundleName + $(PRODUCT_NAME) diff --git a/Shortcuts for Pi-hole/Main/Main.storyboard b/Shortcuts for Pi-hole/Main/Main.storyboard new file mode 100644 index 0000000..e501137 --- /dev/null +++ b/Shortcuts for Pi-hole/Main/Main.storyboard @@ -0,0 +1,676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Shortcuts for Pi-hole is a small menu bar application that lives in your status bar. + +It provides quick actions for managing and monitoring your Pi-hole® instance. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shortcuts for Pi-hole/Main/MainMenu.xib b/Shortcuts for Pi-hole/Main/MainMenu.xib new file mode 100644 index 0000000..411c324 --- /dev/null +++ b/Shortcuts for Pi-hole/Main/MainMenu.xib @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shortcuts for Pi-hole/Main/MainMenuController.swift b/Shortcuts for Pi-hole/Main/MainMenuController.swift new file mode 100644 index 0000000..648f389 --- /dev/null +++ b/Shortcuts for Pi-hole/Main/MainMenuController.swift @@ -0,0 +1,111 @@ +// +// StatusBarMenuController.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 20.10.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +class MainMenuController: NSObject, NSMenuDelegate { + + @IBOutlet weak var statusBarMenu: NSMenu! + let statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + + @IBOutlet weak var statusMenuItem: NSMenuItem! + @IBOutlet weak var aboutMenuItem: NSMenuItem! + @IBOutlet weak var preferencesMenuItem: NSMenuItem! + @IBOutlet weak var quitMenuItem: NSMenuItem! + + let mainWindowController = NSStoryboard(name: "Main", bundle: nil).instantiateInitialController() as! PreferencesWindowController + let aboutWindowController = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "AboutWindowControllerIdentifier") as! AboutWindowController + + override init() { + super.init() + } + + override func awakeFromNib() { + if let statusBarButton = statusBarItem.button { + statusBarButton.image = NSImage(named: "StatusBarButtonImage") + } + + statusBarItem.menu = statusBarMenu + statusBarMenu.delegate = self + + // refresh status on creation + performPiHoleAction(action: PiHoleAction.Status) + } + + @IBAction func refreshMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Status) + } + + @IBAction func enableFor30sMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Enable, seconds: 30) + } + + @IBAction func enableFor1mMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Enable, seconds: 60) + } + + @IBAction func enableFor1hMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Enable, seconds: 3600) + } + + @IBAction func enablePermanentlyMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Enable) + } + + @IBAction func disableFor30sMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Disable, seconds: 30) + } + + @IBAction func disableFor1mMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Disable, seconds: 60) + } + + @IBAction func disableFor1hMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Disable, seconds: 3600) + } + + @IBAction func disablePermanentlyMenuItemActionHandler(_ sender: NSMenuItem) { + performPiHoleAction(action: PiHoleAction.Disable) + } + + func performPiHoleAction(action: PiHoleAction, seconds: Int = 0) { + if PiHoleProxy.getConfigStatus().isPositive() { + self.dispatchStatusMenuItemUpdate(withTitle: "Pi-hole Status: Requesting...") + + PiHoleProxy.performActionRequest(action, seconds: seconds, onSuccess: { (status) in + self.dispatchStatusMenuItemUpdate(withTitle: "Pi-hole Status: " + status) + }) { (error) in self.dispatchStatusMenuItemUpdate(withTitle: "Error: No connection.") } + } else { + self.dispatchStatusMenuItemUpdate(withTitle: "Error: Configuration invalid.") + } + } + + func dispatchStatusMenuItemUpdate(withTitle: String) { + print("dispatchStatusMenuItemUpdate with title " + withTitle) + DispatchQueue.main.async { + self.statusMenuItem.title = withTitle + } + } + + @IBAction func aboutMenuItemActionHandler(_ sender: NSMenuItem) { + aboutWindowController.showWindow(self) + } + + @IBAction func dashboardMenuItemActionHandler(_ sender: NSMenuItem) { + let url = PiHoleProxy.getDashboardUrl() + NSWorkspace.shared.open(url!) + } + + @IBAction func preferencesMenuItemActionHandler(_ sender: NSMenuItem) { + mainWindowController.showWindow(self) + } + + @IBAction func quitMenuItemActionHandler(_ sender: NSMenuItem) { + NSApplication.shared.terminate(self) + } +} diff --git a/Shortcuts for Pi-hole/PiHoleProxy.swift b/Shortcuts for Pi-hole/PiHoleProxy.swift deleted file mode 100644 index f38f642..0000000 --- a/Shortcuts for Pi-hole/PiHoleProxy.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// PiHoleProxy.swift -// Shortcuts for Pi-hole -// -// Created by Lukas Wolfsteiner on 13.10.18. -// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. -// - -import Cocoa - -struct ConnectionStatus { - let message: String - let color: NSColor - - init(message: String, color: NSColor) { - self.message = message - self.color = color - } -} - -class PiHoleProxy: NSObject { - - public static func getBaseUrl() -> URL? { - let hostAddress = GeneralPreferences.getHostAddress() - let hostPort = GeneralPreferences.getHostPort() - let requestProtocol = GeneralPreferences.getRequestProtocol() - let apiKey = GeneralPreferences.getApiKey() - - let urlString = requestProtocol + "://" + hostAddress! + ":" + hostPort + "/admin/api.php?auth=" + apiKey - return URL(string: urlString) - } - - public static func getConfigStatus() -> ConnectionStatus { - // check if host address is valid - if (!GeneralPreferences.isHostAddressValid()) { - return ConnectionStatus(message: "Invalid Host Address", color: NSColor.red) - } - - // check if host port is valid - if (!GeneralPreferences.isHostPortValid()) { - return ConnectionStatus(message: "Invalid Host Port", color: NSColor.red) - } - - // check if request protocol is valid - if (!GeneralPreferences.isRequestProtocolValid()) { - return ConnectionStatus(message: "Invalid Protocol", color: NSColor.red) - } - - // check if api key is valid - if (!GeneralPreferences.isApiKeyValid()) { - return ConnectionStatus(message: "Invalid API Key", color: NSColor.red) - } - - // define url on possible host - if (getBaseUrl() == nil) { - return ConnectionStatus(message: "Invalid URL", color: NSColor.yellow) - } - - // config looks good! - return ConnectionStatus(message: "Configuration looks valid", color: NSColor.green) - } - - public static func getDefaultURLSession() -> URLSession { - let config = URLSessionConfiguration.default - - // timeout 2s - config.timeoutIntervalForRequest = 5 - config.timeoutIntervalForResource = 5 - - return URLSession(configuration: config) - } - - public static func performPiRequest(_ endpoint: String, onSuccess success: @escaping (_ status: String) -> Void, onFailure failure: @escaping (_ error: Error?, _ endpoint: String) -> Void) { - print("performPiRequest: endpoint=" + endpoint) - - do { - guard let url = URL(string: endpoint) else { - failure(NSError(domain: "Error invalid endpoint", code: 1, userInfo: nil), endpoint) - return - } - - let urlRequest = URLRequest(url: url) - - let config = URLSessionConfiguration.default - config.timeoutIntervalForRequest = 5 - config.timeoutIntervalForResource = 5 - - let session = URLSession(configuration: config) - - let task = session.dataTask(with: urlRequest) { - (data, response, error) in - - // Check for any errors - guard error == nil else { - failure(NSError(domain: "Error at executing request (timeout)", code: 2, userInfo: nil), endpoint) - return - } - - // Make sure we got data - guard let responseData = data else { - failure(NSError(domain: "Error at reading response", code: 3, userInfo: nil), endpoint) - return - } - - // Parse the result as JSON, since that's what the API provides - do { - guard let responseObj = try JSONSerialization.jsonObject(with: responseData, options: []) - as? [String: Any] else { - failure(NSError(domain: "Error converting response to JSON", code: 4, userInfo: nil), endpoint) - return - } - - // The response object is a dictionary so we just access the title using the "status" key - guard let status = responseObj["status"] as? String else { - failure(NSError(domain: "Error getting status from response", code: 5, userInfo: nil), endpoint) - return - } - - success(status) - } catch { - failure(NSError(domain: "Error at converting response into JSON", code: 6, userInfo: nil), endpoint) - return - } - } - task.resume() - } - } -} diff --git a/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferenceViewController.swift b/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferenceViewController.swift deleted file mode 100644 index 65a445e..0000000 --- a/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferenceViewController.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// GeneralPreferenceViewController.swift -// Shortcuts for Pi-hole -// -// Created by Lukas Wolfsteiner on 12.10.18. -// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. -// - -import Cocoa -import Preferences - -final class GeneralPreferenceViewController: NSViewController, Preferenceable { - let toolbarItemTitle: String = "General" - let toolbarItemIcon = NSImage(named: NSImage.preferencesGeneralName)! - - @IBOutlet weak var secureTextFieldApiKey: NSSecureTextField! - @IBOutlet weak var popUpButtonRequestProtocol: NSPopUpButton! - @IBOutlet weak var textFieldHostPort: NSTextField! - @IBOutlet weak var textFieldHostAddress: NSTextField! - @IBOutlet weak var viewCircleConnectionStatus: ColoredStatusView! - @IBOutlet weak var textFieldConnectionStatus: NSTextField! - - @IBAction func hostAddressActionHandler(_ sender: NSTextField) { - let hostAddress = sender.stringValue - print("hostAddressActionHandler", hostAddress) - GeneralPreferences.saveHostAddress(hostAddress: hostAddress) - - self.onPreferencesChange() - } - - - @IBAction func connectButtonActionHandler(_ sender: NSButton) { - self.onConnectionStatusChange(status: ConnectionStatus(message: "Requesting...", color: NSColor.gray)) - - guard let url = PiHoleProxy.getBaseUrl() else { - onConnectionStatusChange(status: ConnectionStatus(message: "Error constructing endpoint", color: NSColor.red)) - return; - } - - let task = PiHoleProxy.getDefaultURLSession().dataTask(with: url) { (data, response, error) in - print("dataTask on " + url.absoluteString) - - let connectionStatus: ConnectionStatus; - if (error != nil) { - connectionStatus = ConnectionStatus(message: error!.localizedDescription, color: NSColor.red) - } else if let httpResponse = response as? HTTPURLResponse { - let statusCode = httpResponse.statusCode - print("status code " + String(describing: statusCode) + " on host " + (url.absoluteString)) - - connectionStatus = ConnectionStatus( - message: (statusCode == 200) ? "Connection established" : "Host returned invalid response", - color: (statusCode == 200) ? NSColor.green : NSColor.red) - } else { - connectionStatus = ConnectionStatus(message: "Host returned invalid response", color: NSColor.red) - } - - DispatchQueue.main.async { - print(connectionStatus.message) - self.onConnectionStatusChange(status: connectionStatus) - } - } - task.resume() - } - - @IBAction func hostPortActionHandler(_ sender: NSTextField) { - let hostPort = sender.stringValue - print("hostPortActionHandler", hostPort) - GeneralPreferences.saveHostPort(hostPort: hostPort) - - self.onPreferencesChange() - } - - @IBAction func requestProtocolActionHandler(_ sender: NSPopUpButton) { - let requestProtocol = sender.titleOfSelectedItem! - print("requestProtocolActionHandler", requestProtocol) - GeneralPreferences.saveRequestProtocol(requestProtocol: requestProtocol) - - self.onPreferencesChange() - } - @IBAction func apiKeyActionHandler(_ sender: NSSecureTextField) { - let apiKey = sender.stringValue - print("apiKeyActionHandler", apiKey) - GeneralPreferences.saveApiKey(apiKey: apiKey) - - self.onPreferencesChange() - } - - func onConnectionStatusChange(status: ConnectionStatus) { - self.viewCircleConnectionStatus.updateFillingColor(color: status.color) - self.textFieldConnectionStatus.stringValue = status.message - } - - func onPreferencesChange() { - let connectionStatus = PiHoleProxy.getConfigStatus() - self.onConnectionStatusChange(status: connectionStatus) - } - - override var nibName: NSNib.Name? { - return "GeneralPreferenceViewController" - } - - override func viewDidLoad() { - super.viewDidLoad() - - // restore persisted preferences in view - if let hostAddress = GeneralPreferences.getHostAddress(), !hostAddress.isEmpty { - textFieldHostAddress.stringValue = hostAddress - } - - let hostPort = GeneralPreferences.getHostPort() - if !hostPort.isEmpty { - textFieldHostPort.stringValue = hostPort - } - - let requestProtocol = GeneralPreferences.getRequestProtocol() - popUpButtonRequestProtocol.selectItem(withTitle: requestProtocol) - - let apiKey = GeneralPreferences.getApiKey() - if !apiKey.isEmpty { - secureTextFieldApiKey.stringValue = apiKey - } - - // check status - self.onPreferencesChange() - } -} diff --git a/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferenceViewController.xib b/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferenceViewController.xib deleted file mode 100644 index efc3a51..0000000 --- a/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferenceViewController.xib +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferences.swift b/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferences.swift deleted file mode 100644 index 1d6fdb1..0000000 --- a/Shortcuts for Pi-hole/PreferenceWindow/GeneralPreferences.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// GeneralPreferences.swift -// Shortcuts for Pi-hole -// -// Created by Lukas Wolfsteiner on 13.10.18. -// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. -// - -import Cocoa - -struct GeneralPreferences { - static let (hostAddressKey, hostPortKey, requestProtocolKey, apiKey) = ("HOST_ADDRESS", "HOST_PORT", "REQUEST_PROTOCOL", "API_KEY") - static let userSessionKey = Bundle.main.bundleIdentifier! - - struct Model { - var hostAddress: String? - var hostPort: String? - var requestProtocol: String? - var apiKey: String? - - init(_ json: [String: String]) { - self.hostAddress = json[GeneralPreferences.hostAddressKey] - self.hostPort = json[GeneralPreferences.hostPortKey] - self.requestProtocol = json[GeneralPreferences.requestProtocolKey] - self.apiKey = json[GeneralPreferences.apiKey] - } - } - - static func isHostAddressValid() -> Bool { - if let hostAddress = UserDefaults.standard.string(forKey: GeneralPreferences.hostAddressKey), !hostAddress.isEmpty { - // it's not nil nor an empty string - return true - } else { - return false - } - } - - static func isRequestProtocolValid() -> Bool { - let requestProtocol = getRequestProtocol() - return requestProtocol == "http" || requestProtocol == "https" - } - - static func isApiKeyValid() -> Bool { - if let apiKey = UserDefaults.standard.string(forKey: GeneralPreferences.apiKey), !apiKey.isEmpty { - // it's not nil nor an empty string - return true - } else { - return false - } - } - - static func isHostPortValid() -> Bool { - let hostPort = getHostPort() - return Int(hostPort) != nil - } - - static func getHostAddress() -> String? { - return UserDefaults.standard.string(forKey: GeneralPreferences.hostAddressKey) - } - - static func getHostPort() -> String { - if let hostPort = UserDefaults.standard.string(forKey: GeneralPreferences.hostPortKey), !hostPort.isEmpty { - // it's not nil nor an empty string - return hostPort - } - - return "80" - } - - static func getRequestProtocol() -> String { - if let requestProtocol = UserDefaults.standard.string(forKey: GeneralPreferences.requestProtocolKey), !requestProtocol.isEmpty { - // it's not nil nor an empty string - return requestProtocol - } - - return "http" - } - - static func getApiKey() -> String { - return UserDefaults.standard.string(forKey: GeneralPreferences.apiKey) ?? "" - } - - static func saveHostAddress(hostAddress: String) { - UserDefaults.standard.set(hostAddress, forKey: GeneralPreferences.hostAddressKey) - } - - static func saveHostPort(hostPort: String) { - UserDefaults.standard.set(hostPort, forKey: GeneralPreferences.hostPortKey) - } - - static func saveRequestProtocol(requestProtocol: String) { - UserDefaults.standard.set(requestProtocol, forKey: GeneralPreferences.requestProtocolKey) - } - - static func saveApiKey(apiKey: String) { - UserDefaults.standard.set(apiKey, forKey: GeneralPreferences.apiKey) - } - - static var savePreferences = { (hostAddress: String, hostPort: String, requestProtocol: String, apiKey: String) in UserDefaults.standard.set([GeneralPreferences.hostAddressKey: hostAddress, GeneralPreferences.hostPortKey: hostPort, GeneralPreferences.requestProtocolKey: requestProtocol, GeneralPreferences.apiKey: apiKey], forKey: GeneralPreferences.userSessionKey) - } - - static var getPreferences = { _ -> Model in - return Model((UserDefaults.standard.value(forKey: GeneralPreferences.userSessionKey) as? [String: String]) ?? [:]) - }(()) - - static func clearPreferences(){ - UserDefaults.standard.removeObject(forKey: GeneralPreferences.userSessionKey) - } -} diff --git a/Shortcuts for Pi-hole/ColoredStatusView.swift b/Shortcuts for Pi-hole/Preferences/ColoredStatusView.swift similarity index 97% rename from Shortcuts for Pi-hole/ColoredStatusView.swift rename to Shortcuts for Pi-hole/Preferences/ColoredStatusView.swift index 9adb078..eafc1d4 100644 --- a/Shortcuts for Pi-hole/ColoredStatusView.swift +++ b/Shortcuts for Pi-hole/Preferences/ColoredStatusView.swift @@ -10,17 +10,17 @@ import Cocoa class ColoredStatusView: NSView { var fillingColor: NSColor = NSColor.red - + override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) - + // Drawing code here. let fillColor = self.fillingColor let path = NSBezierPath(ovalIn: dirtyRect) fillColor.setFill() path.fill() } - + public func updateFillingColor(color: NSColor) { self.fillingColor = color self.display() diff --git a/Shortcuts for Pi-hole/Preferences/ConnectionPreferencesViewController.swift b/Shortcuts for Pi-hole/Preferences/ConnectionPreferencesViewController.swift new file mode 100644 index 0000000..e20e4e9 --- /dev/null +++ b/Shortcuts for Pi-hole/Preferences/ConnectionPreferencesViewController.swift @@ -0,0 +1,86 @@ +// +// MainAboutViewController.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 13.10.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +class ConnectionPreferencesViewController: NSViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do view setup here. + _ = self.displayBuiltUrl() + } + + @IBOutlet weak var coloredStatusViewOutlet: ColoredStatusView! + + @IBOutlet weak var connectionStatusTextField: NSTextField! + + @IBOutlet weak var urlTextField: NSTextField! + + @IBOutlet var connectionLogTextView: NSTextView! + + private func displayStatus(status: String, color: NSColor, log: String? = nil) { + self.connectionStatusTextField.stringValue = status + self.coloredStatusViewOutlet.updateFillingColor(color: color) + + if (log != nil) { + self.connectionLogTextView.string = log! + } + } + + private func displayConfigurationErrorAlert() { + let alert = NSAlert() + alert.messageText = "Invalid configuration" + alert.informativeText = "Please check your configuration." + alert.addButton(withTitle: "Ok") + + if let mainWindow = self.view.window { + alert.beginSheetModal(for: mainWindow) { (returnCode: NSApplication.ModalResponse) -> Void in + print ("returnCode: ", returnCode) + } + } + } + + private func displayBuiltUrl() -> Bool { + let connectionStatus = PiHoleProxy.getConfigStatus() + + if connectionStatus.isPositive() { + + // display url + let url = PiHoleProxy.getApiUrl(action: PiHoleAction.Enable) + urlTextField.stringValue = url!.absoluteString + + self.displayStatus(status: "Press connect to verify", color: PiHoleConnectionResult.colorResultNeutral) + return true; + } else { + self.displayStatus(status: connectionStatus.message, color: connectionStatus.color) + + // show invalid config alert + self.displayConfigurationErrorAlert() + return false; + } + } + + @IBAction func connectionButtonActionHandler(_ sender: Any) { + + if self.displayBuiltUrl() { + displayStatus(status: "Requesting...", color: PiHoleConnectionResult.colorResultNeutral) + + PiHoleProxy.performActionRequest(PiHoleAction.Status, onSuccess: { (status) in + DispatchQueue.main.async { + self.displayStatus(status: "Connection established", color: PiHoleConnectionResult.colorResultPositive, log: "Pi-hole status: \(status)") + } + }) { (error) in + DispatchQueue.main.async { + self.displayStatus(status: "An error occurred", color: PiHoleConnectionResult.colorResultNegative, log: error.domain) + } + } + } + } +} diff --git a/Shortcuts for Pi-hole/Preferences/GeneralPreferencesViewController.swift b/Shortcuts for Pi-hole/Preferences/GeneralPreferencesViewController.swift new file mode 100644 index 0000000..1e5c2f0 --- /dev/null +++ b/Shortcuts for Pi-hole/Preferences/GeneralPreferencesViewController.swift @@ -0,0 +1,93 @@ +// +// GeneralPreferenceViewController.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 12.10.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +final class GeneralPreferencesViewController: NSViewController { + @IBOutlet weak var secureTextFieldApiKey: NSSecureTextField! + @IBOutlet weak var popUpButtonRequestProtocol: NSPopUpButton! + + @IBOutlet weak var textFieldHostPort: NSTextField! + @IBOutlet weak var textFieldHostAddress: NSTextField! + + @IBOutlet weak var viewCircleConnectionStatus: ColoredStatusView! + + @IBOutlet weak var labelConnectionStatus: NSTextField! + @IBOutlet weak var textFieldTimeout: NSTextField! + + @IBAction func hostAddressActionHandler(_ sender: NSTextField) { + let hostAddress = sender.stringValue + print("hostAddressActionHandler", hostAddress) + Preferences.saveHostAddress(hostAddress: hostAddress) + + self.onPreferencesChange() + } + + @IBAction func hostPortActionHandler(_ sender: NSTextField) { + let hostPort = sender.integerValue + print("hostPortActionHandler", hostPort) + Preferences.saveHostPort(hostPort: hostPort) + + self.onPreferencesChange() + } + + @IBAction func requestProtocolActionHandler(_ sender: NSPopUpButton) { + let requestProtocol = sender.titleOfSelectedItem! + print("requestProtocolActionHandler", requestProtocol) + Preferences.saveRequestProtocol(requestProtocol: requestProtocol) + + self.onPreferencesChange() + } + @IBAction func apiKeyActionHandler(_ sender: NSSecureTextField) { + let apiKey = sender.stringValue + print("apiKeyActionHandler", apiKey) + Preferences.saveApiKey(apiKey: apiKey) + + self.onPreferencesChange() + } + @IBAction func timeoutActionHandler(_ sender: NSTextField) { + let timeout = sender.integerValue + print("timeoutActionHandler", timeout) + Preferences.saveTimeout(timeout: timeout) + + self.onPreferencesChange() + } + + func onConnectionStatusChange(status: PiHoleConnectionResult) { + self.viewCircleConnectionStatus.updateFillingColor(color: status.color) + self.labelConnectionStatus.stringValue = status.message + } + + func onPreferencesChange() { + let connectionStatus = PiHoleProxy.getConfigStatus() + self.onConnectionStatusChange(status: connectionStatus) + } + + override func viewDidLoad() { + super.viewDidLoad() + + // restore persisted preferences in view + if let hostAddress = Preferences.getHostAddress(), !hostAddress.isEmpty { + textFieldHostAddress.stringValue = hostAddress + } + + let hostPort = Preferences.getHostPort() + textFieldHostPort.integerValue = hostPort + + let requestProtocol = Preferences.getRequestProtocol() + popUpButtonRequestProtocol.selectItem(withTitle: requestProtocol) + + let apiKey = Preferences.getApiKey() + if !apiKey.isEmpty { + secureTextFieldApiKey.stringValue = apiKey + } + + // check status + self.onPreferencesChange() + } +} diff --git a/Shortcuts for Pi-hole/Preferences/Preferences.swift b/Shortcuts for Pi-hole/Preferences/Preferences.swift new file mode 100644 index 0000000..c1ae213 --- /dev/null +++ b/Shortcuts for Pi-hole/Preferences/Preferences.swift @@ -0,0 +1,118 @@ +// +// Preferences.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 13.10.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +struct Preferences { + static let (hostAddressKey, hostPortKey, requestProtocolKey, apiKey, timeoutKey) = ("HOST_ADDRESS", "HOST_PORT", "REQUEST_PROTOCOL", "API_KEY", "REQUEST_TIMEOUT") + static let userSessionKey = Bundle.main.bundleIdentifier! + + struct Model { + var hostAddress: String? + var hostPort: String? + var requestProtocol: String? + var apiKey: String? + var timeout: String? + + init(_ json: [String: String]) { + self.hostAddress = json[Preferences.hostAddressKey] + self.hostPort = json[Preferences.hostPortKey] + self.requestProtocol = json[Preferences.requestProtocolKey] + self.apiKey = json[Preferences.apiKey] + self.timeout = json[Preferences.timeoutKey] + } + } + + static func isHostAddressValid() -> Bool { + if let hostAddress = UserDefaults.standard.string(forKey: Preferences.hostAddressKey), !hostAddress.isEmpty { + // it's not nil nor an empty string + return true + } else { + return false + } + } + + static func isRequestProtocolValid() -> Bool { + let requestProtocol = getRequestProtocol() + return requestProtocol == "http" || requestProtocol == "https" + } + + static func isApiKeyValid() -> Bool { + if let apiKey = UserDefaults.standard.string(forKey: Preferences.apiKey), !apiKey.isEmpty { + // it's not nil nor an empty string + return true + } else { + return false + } + } + + static func isHostPortValid() -> Bool { + return getHostPort() > 0 + } + + static func isTimeoutValid() -> Bool { + return getTimeout() > 0 + } + + static func getHostAddress() -> String? { + return UserDefaults.standard.string(forKey: Preferences.hostAddressKey) + } + + static func getHostPort() -> Int { + let hostPort = UserDefaults.standard.integer(forKey: Preferences.hostPortKey) + return (hostPort == 0) ? 80 : hostPort + } + + static func getRequestProtocol() -> String { + if let requestProtocol = UserDefaults.standard.string(forKey: Preferences.requestProtocolKey), !requestProtocol.isEmpty { + // it's not nil nor an empty string + return requestProtocol + } + + return "http" + } + + static func getApiKey() -> String { + return UserDefaults.standard.string(forKey: Preferences.apiKey) ?? "" + } + + static func getTimeout() -> Int { + return UserDefaults.standard.integer(forKey: Preferences.timeoutKey) + } + + static func saveHostAddress(hostAddress: String) { + UserDefaults.standard.set(hostAddress, forKey: Preferences.hostAddressKey) + } + + static func saveHostPort(hostPort: Int) { + UserDefaults.standard.set(hostPort, forKey: Preferences.hostPortKey) + } + + static func saveRequestProtocol(requestProtocol: String) { + UserDefaults.standard.set(requestProtocol, forKey: Preferences.requestProtocolKey) + } + + static func saveApiKey(apiKey: String) { + UserDefaults.standard.set(apiKey, forKey: Preferences.apiKey) + } + + static func saveTimeout(timeout: Int) { + UserDefaults.standard.set(timeout, forKey: Preferences.timeoutKey) + } + + static var savePreferences = { (hostAddress: String, hostPort: String, requestProtocol: String, apiKey: String, timeout: Int) in UserDefaults.standard.set([Preferences.hostAddressKey: hostAddress, Preferences.hostPortKey: hostPort, Preferences.requestProtocolKey: requestProtocol, Preferences.apiKey: apiKey, Preferences.timeoutKey: timeout], forKey: Preferences.userSessionKey) + } + + static var getPreferences = { _ -> Model in + return Model((UserDefaults.standard.value(forKey: Preferences.userSessionKey) as? [String: String]) ?? [:]) + }(()) + + static func clearPreferences() { + UserDefaults.standard.removeObject(forKey: Preferences.userSessionKey) + } +} diff --git a/Shortcuts for Pi-hole/Preferences/PreferencesWindowController.swift b/Shortcuts for Pi-hole/Preferences/PreferencesWindowController.swift new file mode 100644 index 0000000..1729b40 --- /dev/null +++ b/Shortcuts for Pi-hole/Preferences/PreferencesWindowController.swift @@ -0,0 +1,24 @@ +// +// MainWindowController.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 13.10.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +class PreferencesWindowController: NSWindowController { + + override func showWindow(_ sender: Any?) { + super.showWindow(sender) + + AppDelegate.bringToFront(window: self.window!) + } + + override func windowDidLoad() { + super.windowDidLoad() + + // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. + } +} diff --git a/Shortcuts for Pi-hole/Shortcuts for Pi-hole.entitlements b/Shortcuts for Pi-hole/Shortcuts for Pi-hole.entitlements index 625af03..ee95ab7 100644 --- a/Shortcuts for Pi-hole/Shortcuts for Pi-hole.entitlements +++ b/Shortcuts for Pi-hole/Shortcuts for Pi-hole.entitlements @@ -4,8 +4,6 @@ com.apple.security.app-sandbox - com.apple.security.files.user-selected.read-only - com.apple.security.network.client diff --git a/Shortcuts for Pi-hole/Tools/PiHoleAction.swift b/Shortcuts for Pi-hole/Tools/PiHoleAction.swift new file mode 100644 index 0000000..e56950d --- /dev/null +++ b/Shortcuts for Pi-hole/Tools/PiHoleAction.swift @@ -0,0 +1,15 @@ +// +// PiHoleAction.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 10.11.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +enum PiHoleAction { + case Status + case Enable + case Disable +} diff --git a/Shortcuts for Pi-hole/Tools/PiHoleConnectionResult.swift b/Shortcuts for Pi-hole/Tools/PiHoleConnectionResult.swift new file mode 100644 index 0000000..5da5708 --- /dev/null +++ b/Shortcuts for Pi-hole/Tools/PiHoleConnectionResult.swift @@ -0,0 +1,44 @@ +// +// PiHoleConnectionResult.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 10.11.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +class PiHoleConnectionResult: NSObject { + + let message: String + let color: NSColor + let error: NSError? + + init(message: String, color: NSColor, error: NSError? = nil) { + self.message = message + self.color = color + self.error = error + } + + public static func Positive(message: String) -> PiHoleConnectionResult { + return PiHoleConnectionResult(message: message, color: colorResultPositive) + } + + public static func Negative(message: String, error: NSError? = nil) -> PiHoleConnectionResult { + return PiHoleConnectionResult(message: message, color: colorResultNegative, error: error) + } + + public func isPositive() -> Bool { + return self.color == PiHoleConnectionResult.colorResultPositive + } + + static let lightRed = NSColor(red: 0.96, green: 0.05, blue: 0.10, alpha: 1.0) + static let lightGreen = NSColor(red: 0.16, green: 0.99, blue: 0.18, alpha: 1.0) + + static let darkRed = NSColor(red: 0.59, green: 0.02, blue: 0.05, alpha: 1.0) + static let darkGreen = NSColor(red: 0.13, green: 0.70, blue: 0.15, alpha: 1.0) + + static let colorResultPositive = lightGreen + static let colorResultNegative = lightRed + static let colorResultNeutral = NSColor.gray +} diff --git a/Shortcuts for Pi-hole/Tools/PiHoleProxy.swift b/Shortcuts for Pi-hole/Tools/PiHoleProxy.swift new file mode 100644 index 0000000..9544cc0 --- /dev/null +++ b/Shortcuts for Pi-hole/Tools/PiHoleProxy.swift @@ -0,0 +1,142 @@ +// +// PiHoleProxy.swift +// Shortcuts for Pi-hole +// +// Created by Lukas Wolfsteiner on 13.10.18. +// Copyright © 2018 Lukas Wolfsteiner. All rights reserved. +// + +import Cocoa + +class PiHoleProxy: NSObject { + + public static func getBaseUrlString() -> String { + let hostAddress = Preferences.getHostAddress() + let hostPort = Preferences.getHostPort() + let requestProtocol = Preferences.getRequestProtocol() + + return requestProtocol + "://" + hostAddress! + ":" + String(hostPort) + } + + public static func getDashboardUrl() -> URL? { + return URL(string: getBaseUrlString() + "/admin") + } + + public static func getApiUrl(action: PiHoleAction = PiHoleAction.Status, seconds: Int = 0) -> URL? { + let apiKey = Preferences.getApiKey() + + let base = getBaseUrlString() + let path: String = "/admin/api.php" + + var urlString: String = base + path + + if action == PiHoleAction.Enable || action == PiHoleAction.Disable { + urlString += "?auth=" + apiKey + urlString += (action == PiHoleAction.Enable ? "&enable" : "&disable") + + if seconds > 0 { + urlString += "=" + String(seconds) + } + } else { + // PiHoleAction.Status + } + + return URL(string: urlString) + } + + public static func getConfigStatus() -> PiHoleConnectionResult { + // check if host address is valid + if (!Preferences.isHostAddressValid()) { + return PiHoleConnectionResult.Negative(message: "Invalid Host Address") + } + + // check if host port is valid + if (!Preferences.isHostPortValid()) { + return PiHoleConnectionResult.Negative(message: "Invalid Host Port") + } + + // check if request protocol is valid + if (!Preferences.isRequestProtocolValid()) { + return PiHoleConnectionResult.Negative(message: "Invalid Protocol") + } + + // check if api key is valid + if (!Preferences.isApiKeyValid()) { + return PiHoleConnectionResult.Negative(message: "Invalid API Key") + } + + // check if timeout is valid + if (!Preferences.isTimeoutValid()) { + return PiHoleConnectionResult.Negative(message: "Invalid Timeout Value") + } + + // define url on possible host + if (getApiUrl() == nil) { + return PiHoleConnectionResult.Negative(message: "Invalid URL") + } + + // config looks good! + return PiHoleConnectionResult.Positive(message: "Configuration looks valid") + } + + public static func getDefaultURLSession() -> URLSession { + let config = URLSessionConfiguration.default + let timeout = Preferences.getTimeout() + + // timeout 2s + config.timeoutIntervalForRequest = TimeInterval(timeout) + config.timeoutIntervalForResource = TimeInterval(timeout) + + return URLSession(configuration: config) + } + + public static func performActionRequest(_ action: PiHoleAction, seconds: Int = 0, onSuccess success: @escaping (_ status: String) -> Void, onFailure failure: @escaping (_ error: NSError) -> Void) { + + do { + guard let url = getApiUrl(action: action, seconds: seconds) else { + failure(NSError(domain: "Error invalid endpoint", code: 1, userInfo: nil)) + return + } + + let urlRequest = URLRequest(url: url) + let session = getDefaultURLSession() + + let task = session.dataTask(with: urlRequest) { + (data, response, error) in + + // Check for any errors + guard error == nil else { + failure(NSError(domain: "Error at executing request (timeout)", code: 2)) + return + } + + // Make sure we got data + guard let responseData = data else { + failure(NSError(domain: "Error at reading response", code: 3, userInfo: nil)) + return + } + + // Parse the result as JSON, since that's what the API provides + do { + guard let responseObj = try JSONSerialization.jsonObject(with: responseData, options: []) + as? [String: Any] else { + failure(NSError(domain: "Error converting response to JSON", code: 4, userInfo: nil)) + return + } + + // The response object is a dictionary so we just access the title using the "status" key + guard let status = responseObj["status"] as? String else { + failure(NSError(domain: "Error getting status from response", code: 5, userInfo: nil)) + return + } + + success(status) + } catch { + failure(NSError(domain: "Error at converting response into JSON", code: 6, userInfo: nil)) + return + } + } + task.resume() + } + } +}