From fe23344906fbd4f0db3142738071225ae7c64644 Mon Sep 17 00:00:00 2001 From: Adrian Serrano Date: Thu, 31 May 2018 11:57:23 +0700 Subject: [PATCH] Beats preference pane for macOS (#6923) * Beats preference pane for macOS This preference pane shows up in System Preferences and allows to manage installed beats. * Generate installable package for preference pane Added a make target to generate a package (.pkg) file that can be installed or bundled along another installer. It can be optionally signed for distribution. * Capitalize beat name in the UI * Update license headers Added new Elastic license boilerplate plus some missing headers. * Fix code-signing when no dev certificate present Makes build to work when no internal developer certificate has been created in XCode. --- .../platforms/darwin/preference-pane/Makefile | 37 ++ .../project.pbxproj | 523 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDETemplateMacros.plist | 24 + .../xcschemes/xcschememanagement.plist | 32 ++ .../beats-preference-pane/Authorization.h | 23 + .../Base.lproj/BeatView.xib | 129 +++++ .../Base.lproj/BeatsPrefPane.xib | 66 +++ .../Base.lproj/EditorWindow.xib | 147 +++++ .../beats-preference-pane/Base.lproj/pane.xib | 122 ++++ .../beats-preference-pane/BeatManager.m | 13 + .../BeatViewController.h | 48 ++ .../BeatViewController.m | 151 +++++ .../beats-preference-pane/Beats.icns | Bin 0 -> 77351 bytes .../beats-preference-pane/BeatsPrefPane.h | 46 ++ .../beats-preference-pane/BeatsPrefPane.m | 164 ++++++ .../beats-preference-pane/EditorWindow.h | 34 ++ .../beats-preference-pane/EditorWindow.m | 89 +++ .../beats-preference-pane/Info.plist | 32 ++ .../beats-preference-pane/TabViewDelegate.h | 43 ++ .../beats-preference-pane/TabViewDelegate.m | 99 ++++ .../beats-preference-pane/beats/Beats.h | 39 ++ .../beats/BeatsService.h | 29 + .../beats/BeatsService.m | 294 ++++++++++ .../beats-preference-pane/common/common.h | 29 + .../beats-preference-pane/common/common.m | 75 +++ .../beats-preference-pane/config.h | 31 ++ .../en.lproj/beats_preference_pane.strings | 3 + .../darwin/preference-pane/helper/main.m | 72 +++ .../darwin/preference-pane/helper/setboot.m | 91 +++ 30 files changed, 2492 insertions(+) create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/Makefile create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/project.pbxproj create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/xcshareddata/IDETemplateMacros.plist create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/xcuserdata/adrian.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Authorization.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/BeatView.xib create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/BeatsPrefPane.xib create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/EditorWindow.xib create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/pane.xib create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatManager.m create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatViewController.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatViewController.m create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Beats.icns create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatsPrefPane.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatsPrefPane.m create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/EditorWindow.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/EditorWindow.m create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Info.plist create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/TabViewDelegate.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/TabViewDelegate.m create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/Beats.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/BeatsService.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/BeatsService.m create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/common/common.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/common/common.m create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/config.h create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/en.lproj/beats_preference_pane.strings create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/helper/main.m create mode 100644 dev-tools/packer/platforms/darwin/preference-pane/helper/setboot.m diff --git a/dev-tools/packer/platforms/darwin/preference-pane/Makefile b/dev-tools/packer/platforms/darwin/preference-pane/Makefile new file mode 100644 index 00000000000..5ada05ae680 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/Makefile @@ -0,0 +1,37 @@ +PROJECT=beats-preference-pane.xcodeproj +CONFIGURATION?=Release +SIGN_IDENTITY_APP?= +SIGN_IDENTITY_INSTALLER?= +PACKAGE_NAME?=BeatsPrefPane.pkg +INSTALL_DIR=/Library/PreferencePanes +IDENTIFIER?=co.elastic.beats.preference-pane +VERSION?=1.0.0 + +default: build + +build: + xcodebuild build -project $(PROJECT) -alltargets -configuration $(CONFIGURATION) CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO + echo version="${VERSION}" + test -n '$(SIGN_IDENTITY_APP)' && \ + codesign -s '$(SIGN_IDENTITY_APP)' --force --keychain '$(KEYCHAIN)' --timestamp \ + build/$(CONFIGURATION)/*.prefPane/Contents/MacOS/helper && \ + codesign -s '$(SIGN_IDENTITY_APP)' --force --keychain '$(KEYCHAIN)' --timestamp \ + build/$(CONFIGURATION)/*.prefPane + +.PHONY: clean pkg + +pkg: $(PACKAGE_NAME) + +$(PACKAGE_NAME): build + mkdir -p build/root/$(INSTALL_DIR) + cp -a build/$(CONFIGURATION)/Beats.prefPane build/root/$(INSTALL_DIR)/ + SIGN_ARGS='' ;\ + test -z '$(SIGN_IDENTITY_INSTALLER)' || SIGN_ARGS="--sign $(SIGN_IDENTITY_INSTALLER)" ;\ + pkgbuild --root build/root/ \ + --identifier $(IDENTIFIER) \ + --version '$(VERSION)' \ + $$SIGN_ARGS --keychain "$(KEYCHAIN)" --timestamp \ + $(PACKAGE_NAME) + +clean: + rm -rf build/ $(PACKAGE_NAME) diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/project.pbxproj b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..0f46547b728 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/project.pbxproj @@ -0,0 +1,523 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 3A0F656C205D5AE9009DBAE7 /* setboot.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A0F656B205D5AE9009DBAE7 /* setboot.m */; }; + 3A34B24E203B1F480079E62C /* Beats.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A34B24C203B1F480079E62C /* Beats.h */; }; + 3A34B251203B239F0079E62C /* Beats.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3A34B250203B239F0079E62C /* Beats.icns */; }; + 3A6BA0642062CEAA00A05921 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A6BA0632062CEAA00A05921 /* config.h */; }; + 3A6F9AB6202C939D0046F40E /* BeatsPrefPane.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A6F9AB5202C939D0046F40E /* BeatsPrefPane.h */; }; + 3A6F9AB8202C939D0046F40E /* BeatsPrefPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A6F9AB7202C939D0046F40E /* BeatsPrefPane.m */; }; + 3A6F9ABD202C939D0046F40E /* BeatsPrefPane.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A6F9ABB202C939D0046F40E /* BeatsPrefPane.xib */; }; + 3A8FA06D20626DEF0091102D /* common.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AC0A94420626C7B00741C93 /* common.m */; }; + 3A915924205EB4F4007DE746 /* EditorWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A915921205EB4F4007DE746 /* EditorWindow.h */; }; + 3A915925205EB4F4007DE746 /* EditorWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A915922205EB4F4007DE746 /* EditorWindow.m */; }; + 3A915926205EB4F4007DE746 /* Base.lproj/EditorWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A915923205EB4F4007DE746 /* Base.lproj/EditorWindow.xib */; }; + 3AA415CE2051B206008915AA /* BeatsService.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AA415CC2051B206008915AA /* BeatsService.h */; }; + 3AA415CF2051B206008915AA /* BeatsService.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AA415CD2051B206008915AA /* BeatsService.m */; }; + 3AA415DD20582B34008915AA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AA415DC20582B34008915AA /* main.m */; }; + 3AA415E4205833A3008915AA /* helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3AA415DA20582B34008915AA /* helper */; }; + 3AC0A94620626C7C00741C93 /* common.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AC0A94420626C7B00741C93 /* common.m */; }; + 3AD0D414203CE40800338653 /* BeatViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AD0D411203CE40800338653 /* BeatViewController.h */; }; + 3AD0D415203CE40800338653 /* BeatViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AD0D412203CE40800338653 /* BeatViewController.m */; }; + 3AD0D419203CECDA00338653 /* TabViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AD0D417203CECDA00338653 /* TabViewDelegate.h */; }; + 3AD0D41A203CECDA00338653 /* TabViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AD0D418203CECDA00338653 /* TabViewDelegate.m */; }; + 3AD0D420203D791100338653 /* Base.lproj/BeatView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3AD0D41F203D791100338653 /* Base.lproj/BeatView.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 3A0F6569205BE29D009DBAE7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3A6F9AA9202C939D0046F40E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3AA415D920582B34008915AA; + remoteInfo = helper; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 3AA415D820582B34008915AA /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = ""; + dstSubfolderSpec = 6; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3AA415E32058339A008915AA /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 6; + files = ( + 3AA415E4205833A3008915AA /* helper in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 3A0F656B205D5AE9009DBAE7 /* setboot.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = setboot.m; sourceTree = ""; }; + 3A34B24C203B1F480079E62C /* Beats.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Beats.h; sourceTree = ""; }; + 3A34B250203B239F0079E62C /* Beats.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Beats.icns; sourceTree = ""; }; + 3A6BA0632062CEAA00A05921 /* config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; + 3A6F9AB2202C939D0046F40E /* Beats.prefPane */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Beats.prefPane; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A6F9AB5202C939D0046F40E /* BeatsPrefPane.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BeatsPrefPane.h; sourceTree = ""; }; + 3A6F9AB7202C939D0046F40E /* BeatsPrefPane.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BeatsPrefPane.m; sourceTree = ""; }; + 3A6F9ABC202C939D0046F40E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/BeatsPrefPane.xib; sourceTree = ""; }; + 3A6F9ABE202C939D0046F40E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3A915921205EB4F4007DE746 /* EditorWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EditorWindow.h; sourceTree = ""; }; + 3A915922205EB4F4007DE746 /* EditorWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EditorWindow.m; sourceTree = ""; }; + 3A915923205EB4F4007DE746 /* Base.lproj/EditorWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Base.lproj/EditorWindow.xib; sourceTree = ""; }; + 3AA415CC2051B206008915AA /* BeatsService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BeatsService.h; sourceTree = ""; }; + 3AA415CD2051B206008915AA /* BeatsService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BeatsService.m; sourceTree = ""; }; + 3AA415D520572B5A008915AA /* Authorization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Authorization.h; sourceTree = ""; }; + 3AA415DA20582B34008915AA /* helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = helper; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AA415DC20582B34008915AA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 3AC0A94420626C7B00741C93 /* common.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = common.m; sourceTree = ""; }; + 3AC0A94520626C7C00741C93 /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; }; + 3AD0D411203CE40800338653 /* BeatViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BeatViewController.h; sourceTree = ""; }; + 3AD0D412203CE40800338653 /* BeatViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BeatViewController.m; sourceTree = ""; }; + 3AD0D417203CECDA00338653 /* TabViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TabViewDelegate.h; sourceTree = ""; }; + 3AD0D418203CECDA00338653 /* TabViewDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TabViewDelegate.m; sourceTree = ""; }; + 3AD0D41F203D791100338653 /* Base.lproj/BeatView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Base.lproj/BeatView.xib; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3A6F9AAE202C939D0046F40E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3AA415D720582B34008915AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3A6F9AA8202C939D0046F40E = { + isa = PBXGroup; + children = ( + 3A6F9AB4202C939D0046F40E /* beats-preference-pane */, + 3AA415DB20582B34008915AA /* helper */, + 3A6F9AB3202C939D0046F40E /* Products */, + ); + sourceTree = ""; + }; + 3A6F9AB3202C939D0046F40E /* Products */ = { + isa = PBXGroup; + children = ( + 3A6F9AB2202C939D0046F40E /* Beats.prefPane */, + 3AA415DA20582B34008915AA /* helper */, + ); + name = Products; + sourceTree = ""; + }; + 3A6F9AB4202C939D0046F40E /* beats-preference-pane */ = { + isa = PBXGroup; + children = ( + 3AC0A94320626C6B00741C93 /* common */, + 3AA415C9205159E1008915AA /* beats */, + 3AD0D41F203D791100338653 /* Base.lproj/BeatView.xib */, + 3A34B250203B239F0079E62C /* Beats.icns */, + 3A6F9AB5202C939D0046F40E /* BeatsPrefPane.h */, + 3A6F9AB7202C939D0046F40E /* BeatsPrefPane.m */, + 3A6F9ABB202C939D0046F40E /* BeatsPrefPane.xib */, + 3A6F9ABE202C939D0046F40E /* Info.plist */, + 3AD0D411203CE40800338653 /* BeatViewController.h */, + 3AD0D412203CE40800338653 /* BeatViewController.m */, + 3AD0D417203CECDA00338653 /* TabViewDelegate.h */, + 3AD0D418203CECDA00338653 /* TabViewDelegate.m */, + 3AA415D520572B5A008915AA /* Authorization.h */, + 3A915921205EB4F4007DE746 /* EditorWindow.h */, + 3A915922205EB4F4007DE746 /* EditorWindow.m */, + 3A915923205EB4F4007DE746 /* Base.lproj/EditorWindow.xib */, + 3A6BA0632062CEAA00A05921 /* config.h */, + ); + path = "beats-preference-pane"; + sourceTree = ""; + }; + 3AA415C9205159E1008915AA /* beats */ = { + isa = PBXGroup; + children = ( + 3A34B24C203B1F480079E62C /* Beats.h */, + 3AA415CC2051B206008915AA /* BeatsService.h */, + 3AA415CD2051B206008915AA /* BeatsService.m */, + ); + path = beats; + sourceTree = ""; + }; + 3AA415DB20582B34008915AA /* helper */ = { + isa = PBXGroup; + children = ( + 3AA415DC20582B34008915AA /* main.m */, + 3A0F656B205D5AE9009DBAE7 /* setboot.m */, + ); + path = helper; + sourceTree = ""; + }; + 3AC0A94320626C6B00741C93 /* common */ = { + isa = PBXGroup; + children = ( + 3AC0A94520626C7C00741C93 /* common.h */, + 3AC0A94420626C7B00741C93 /* common.m */, + ); + path = common; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 3A6F9AAF202C939D0046F40E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AD0D414203CE40800338653 /* BeatViewController.h in Headers */, + 3A34B24E203B1F480079E62C /* Beats.h in Headers */, + 3AA415CE2051B206008915AA /* BeatsService.h in Headers */, + 3A6BA0642062CEAA00A05921 /* config.h in Headers */, + 3A915924205EB4F4007DE746 /* EditorWindow.h in Headers */, + 3A6F9AB6202C939D0046F40E /* BeatsPrefPane.h in Headers */, + 3AD0D419203CECDA00338653 /* TabViewDelegate.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 3A6F9AB1202C939D0046F40E /* Beats */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3A6F9AC1202C939D0046F40E /* Build configuration list for PBXNativeTarget "Beats" */; + buildPhases = ( + 3A6F9AAD202C939D0046F40E /* Sources */, + 3A6F9AAE202C939D0046F40E /* Frameworks */, + 3A6F9AAF202C939D0046F40E /* Headers */, + 3A6F9AB0202C939D0046F40E /* Resources */, + 3AA415E32058339A008915AA /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 3A0F656A205BE29D009DBAE7 /* PBXTargetDependency */, + ); + name = Beats; + productName = "beats-preference-pane"; + productReference = 3A6F9AB2202C939D0046F40E /* Beats.prefPane */; + productType = "com.apple.product-type.bundle"; + }; + 3AA415D920582B34008915AA /* helper */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3AA415DE20582B34008915AA /* Build configuration list for PBXNativeTarget "helper" */; + buildPhases = ( + 3AA415D620582B34008915AA /* Sources */, + 3AA415D720582B34008915AA /* Frameworks */, + 3AA415D820582B34008915AA /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = helper; + productName = "launchctl-helper"; + productReference = 3AA415DA20582B34008915AA /* helper */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3A6F9AA9202C939D0046F40E /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = Elastic; + TargetAttributes = { + 3A6F9AB1202C939D0046F40E = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + }; + 3AA415D920582B34008915AA = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 3A6F9AAC202C939D0046F40E /* Build configuration list for PBXProject "beats-preference-pane" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 3A6F9AA8202C939D0046F40E; + productRefGroup = 3A6F9AB3202C939D0046F40E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3AA415D920582B34008915AA /* helper */, + 3A6F9AB1202C939D0046F40E /* Beats */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3A6F9AB0202C939D0046F40E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A915926205EB4F4007DE746 /* Base.lproj/EditorWindow.xib in Resources */, + 3A34B251203B239F0079E62C /* Beats.icns in Resources */, + 3AD0D420203D791100338653 /* Base.lproj/BeatView.xib in Resources */, + 3A6F9ABD202C939D0046F40E /* BeatsPrefPane.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3A6F9AAD202C939D0046F40E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A8FA06D20626DEF0091102D /* common.m in Sources */, + 3A915925205EB4F4007DE746 /* EditorWindow.m in Sources */, + 3AD0D41A203CECDA00338653 /* TabViewDelegate.m in Sources */, + 3A6F9AB8202C939D0046F40E /* BeatsPrefPane.m in Sources */, + 3AA415CF2051B206008915AA /* BeatsService.m in Sources */, + 3AD0D415203CE40800338653 /* BeatViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3AA415D620582B34008915AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A0F656C205D5AE9009DBAE7 /* setboot.m in Sources */, + 3AA415DD20582B34008915AA /* main.m in Sources */, + 3AC0A94620626C7C00741C93 /* common.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3A0F656A205BE29D009DBAE7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3AA415D920582B34008915AA /* helper */; + targetProxy = 3A0F6569205BE29D009DBAE7 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 3A6F9ABB202C939D0046F40E /* BeatsPrefPane.xib */ = { + isa = PBXVariantGroup; + children = ( + 3A6F9ABC202C939D0046F40E /* Base */, + ); + name = BeatsPrefPane.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 3A6F9ABF202C939D0046F40E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 3A6F9AC0202C939D0046F40E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 3A6F9AC2202C939D0046F40E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = UZ3M9EEN74; + INFOPLIST_FILE = "beats-preference-pane/Info.plist"; + INSTALL_PATH = "$(HOME)/Library/PreferencePanes"; + PRODUCT_BUNDLE_IDENTIFIER = "co.elastic.beats-preference-pane"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = prefPane; + }; + name = Debug; + }; + 3A6F9AC3202C939D0046F40E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = UZ3M9EEN74; + INFOPLIST_FILE = "beats-preference-pane/Info.plist"; + INSTALL_PATH = "$(HOME)/Library/PreferencePanes"; + PRODUCT_BUNDLE_IDENTIFIER = "co.elastic.beats-preference-pane"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = prefPane; + }; + name = Release; + }; + 3AA415DF20582B34008915AA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = UZ3M9EEN74; + INSTALL_PATH = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + }; + name = Debug; + }; + 3AA415E020582B34008915AA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = UZ3M9EEN74; + INSTALL_PATH = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3A6F9AAC202C939D0046F40E /* Build configuration list for PBXProject "beats-preference-pane" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3A6F9ABF202C939D0046F40E /* Debug */, + 3A6F9AC0202C939D0046F40E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3A6F9AC1202C939D0046F40E /* Build configuration list for PBXNativeTarget "Beats" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3A6F9AC2202C939D0046F40E /* Debug */, + 3A6F9AC3202C939D0046F40E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3AA415DE20582B34008915AA /* Build configuration list for PBXNativeTarget "helper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3AA415DF20582B34008915AA /* Debug */, + 3AA415E020582B34008915AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3A6F9AA9202C939D0046F40E /* Project object */; +} diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..b7675b8ff50 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/xcshareddata/IDETemplateMacros.plist b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/xcshareddata/IDETemplateMacros.plist new file mode 100644 index 00000000000..ae4bfae6263 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/xcshareddata/IDETemplateMacros.plist @@ -0,0 +1,24 @@ + + + + + FILEHEADER + + + diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/xcuserdata/adrian.xcuserdatad/xcschemes/xcschememanagement.plist b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/xcuserdata/adrian.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000000..58581c18ad7 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane.xcodeproj/xcuserdata/adrian.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,32 @@ + + + + + SchemeUserState + + beats-preference-pane.xcscheme + + orderHint + 0 + + helper.xcscheme + + orderHint + 1 + + launchctl-helper.xcscheme + + orderHint + 1 + + + SuppressBuildableAutocreation + + 3A6F9AB1202C939D0046F40E + + primary + + + + + diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Authorization.h b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Authorization.h new file mode 100644 index 00000000000..fd25568f4e0 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Authorization.h @@ -0,0 +1,23 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +@protocol AuthorizationProvider +- (BOOL) isUnlocked; +- (BOOL) forceUnlock; +- (int) runAsRoot:(NSString*) program args:(NSArray*)args; +- (int) runHelperAsRootWithArgs:(NSArray*)args; +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/BeatView.xib b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/BeatView.xib new file mode 100644 index 00000000000..b097b91ba51 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/BeatView.xib @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/BeatsPrefPane.xib b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/BeatsPrefPane.xib new file mode 100644 index 00000000000..ede949b14a0 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/BeatsPrefPane.xib @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/EditorWindow.xib b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/EditorWindow.xib new file mode 100644 index 00000000000..e95b5c215ae --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/EditorWindow.xib @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/pane.xib b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/pane.xib new file mode 100644 index 00000000000..4f80f209bb9 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Base.lproj/pane.xib @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatManager.m b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatManager.m new file mode 100644 index 00000000000..b581e0df66e --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatManager.m @@ -0,0 +1,13 @@ +// +// BeatManager.m +// beats-preference-pane +// +// Created by Adrian Serrano on 19/02/2018. +// Copyright © 2018 Elastic. All rights reserved. +// + +#import "BeatManager.h" + +@implementation BeatManager + +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatViewController.h b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatViewController.h new file mode 100644 index 00000000000..bc15c830049 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatViewController.h @@ -0,0 +1,48 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import + +#import "beats/Beats.h" +#import "Authorization.h" + +// BeatViewController handles the individual UI view for a beat +@interface BeatViewController : NSViewController +{ + IBOutlet NSTextField *statusLabel; + IBOutlet NSTextField *bootLabel; + IBOutlet NSTextField *configField; + IBOutlet NSTextField *logsField; + IBOutlet NSButton *startStopButton; + IBOutlet NSButton *bootButton; + IBOutlet NSButton *editButton; + IBOutlet NSButton *logsButton; + + // The Beat being displayed by this view + id beat; + id beatsInterface; + id auth; +} + +- (id)initWithBeat:(id)_ auth:(id)_ bundle:(NSBundle*)_ beatsInterface:(id)_; +- (IBAction)startStopTapped:(id)sender; +- (IBAction)startAtBootTapped:(id)sender; +- (IBAction)editConfigTapped:(id)sender; +- (IBAction)viewLogsTapped:(id)sender; +- (void)update; + +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatViewController.m b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatViewController.m new file mode 100644 index 00000000000..2edfd6bbba8 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatViewController.m @@ -0,0 +1,151 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import "BeatViewController.h" +#import "EditorWindow.h" +#import "common/common.h" + +@implementation BeatViewController + +- (id) initWithBeat:(id)beat + auth:(id)auth + bundle:(NSBundle*)bundle + beatsInterface:(id)beatsInterface; +{ + if (self = [self initWithNibName:@"BeatView" bundle:bundle]) { + self->beat = beat; + self->auth = auth; + self->beatsInterface = beatsInterface; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + [self updateUI]; +} + +- (void)updateUI { + id beat = self->beat; + + if ([beat isRunning]) { + [statusLabel setStringValue:[NSString stringWithFormat:@"%@ is running with PID %d", [beat displayName], [beat pid]]]; + [startStopButton setTitle:@"Stop"]; + } else { + [statusLabel setStringValue:[NSString stringWithFormat:@"%@ is stopped", [beat displayName]]]; + [startStopButton setTitle:@"Start"]; + } + + if ([beat isBoot]) { + [bootLabel setStringValue:@"Automatic start at boot is enabled"]; + [bootButton setTitle:@"Disable"]; + } else { + [bootLabel setStringValue:@"Automatic start at boot is disabled"]; + [bootButton setTitle:@"Enable"]; + } + [configField setStringValue:strOrNil([beat configFile])]; + [logsField setStringValue:strOrNil([beat logsPath])]; + + BOOL unlocked = [auth isUnlocked]; + [startStopButton setEnabled:unlocked]; + [bootButton setEnabled:unlocked]; + [editButton setEnabled:unlocked]; + [logsButton setEnabled:unlocked]; +} + +- (void) update { + beat = [beatsInterface getBeat:[beat name]]; + [self updateUI]; +} + +- (IBAction)startStopTapped:(id)sender { + if (![auth isUnlocked]) { + return; + } + uint64_t took = getTimeMicroseconds(); + id beat = self->beat; + + if ([beat isRunning]) { + [beat stopWithAuth:auth]; + } else { + [beat startWithAuth:auth]; + } + took = getTimeMicroseconds() - took; + NSLog(@"start/stop took %lld us", took); + [self update]; +} + +- (IBAction)startAtBootTapped:(id)sender { + if (![auth isUnlocked]) { + return; + } + [beat toggleRunAtBootWithAuth:auth]; + [self update]; +} + +- (IBAction)editConfigTapped:(id)sender { + if (![auth isUnlocked]) { + return; + } + id beat = self->beat; + NSString *conf = [beat configFile]; + + // Create a temporal file with current user permissions + NSString *tmpFile = [NSString stringWithFormat:@"%@/beatconf-%@.yml",NSTemporaryDirectory(), [[NSUUID UUID] UUIDString]]; + [@"" writeToFile:tmpFile atomically:NO encoding:NSUTF8StringEncoding error:nil]; + + // Cat the config file contents into the temporal file + [auth runAsRoot:@"/bin/sh" args:@[@"-c", [NSString stringWithFormat:@"cat '%@' > '%@'", conf, tmpFile]]]; + + // Display editor on temp file + EditorWindow *editor = [[EditorWindow alloc] initWithBeat:[beat displayName] config:tmpFile]; + NSWindow *window = [editor window]; + [window setFrameOrigin:[[[self view] window] frame].origin]; + NSModalResponse resp = [NSApp runModalForWindow: window]; + + if (resp == NSModalResponseOK) { + // Cat temporal file contents into config file. + while ([auth runAsRoot:@"/bin/sh" args:@[@"-c", [NSString stringWithFormat:@"cat '%@' > '%@'", tmpFile, conf]]] != errAuthorizationSuccess) { + // Authorization expired because the user took a while to edit the config + // Ask to reauthorize + NSAlert *alert = [[NSAlert alloc] init]; + [alert addButtonWithTitle:@"Retry"]; + [alert addButtonWithTitle:@"Cancel"]; + [alert setMessageText:@"Retry authentication?"]; + [alert setInformativeText:@"Authentication expired. Configuration changes will be lost unless valid credentials are provided."]; + [alert setAlertStyle:NSAlertStyleWarning]; + if ([alert runModal] != NSAlertFirstButtonReturn) { + break; + } + [auth forceUnlock]; + } + } + + [[NSFileManager defaultManager] removeItemAtPath:tmpFile error:nil]; +} + +- (IBAction)viewLogsTapped:(id)sender +{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert addButtonWithTitle:@"OK"]; + [alert setMessageText:@"Can't display logs"]; + [alert setInformativeText:@"Due to strict permissions in Beats logs, they are only accessible using the command line as root."]; + [alert setAlertStyle:NSAlertStyleWarning]; + [alert runModal]; +} + +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Beats.icns b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Beats.icns new file mode 100644 index 0000000000000000000000000000000000000000..87108ae8ff720eae139e4ff7e2a94c1628c583f3 GIT binary patch literal 77351 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 Z-i8*2){P*OTYzk!$r~HjGpt(7003)kXl4Ka literal 0 HcmV?d00001 diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatsPrefPane.h b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatsPrefPane.h new file mode 100644 index 00000000000..bf592a20546 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatsPrefPane.h @@ -0,0 +1,46 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import +#import + +#import "TabViewDelegate.h" +#import "Authorization.h" + +/* BeatsPrefPane is the main class for handling the preference pane. + Implements so that it can provide authorization + obtained via the SFAuthorizationView to other components. + */ +@interface BeatsPrefPane : NSPreferencePane { + IBOutlet NSTabView *beatsTab; + IBOutlet TabViewDelegate *tabDelegate; + IBOutlet SFAuthorizationView *authView; + IBOutlet NSTextField *messageLabel; + NSTimer *updateTimer; + NSBundle *bundle; + NSArray *knownBeats; + NSString *helperPath; + id beatsInterface; +} + +- (id)initWithBundle:(NSBundle *)bundle; +- (void)mainViewDidLoad; +- (void)didSelect; +- (void)willSelect; +- (void)didUnselect; +- (BOOL)isUnlocked; +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatsPrefPane.m b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatsPrefPane.m new file mode 100644 index 00000000000..29fafed1244 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/BeatsPrefPane.m @@ -0,0 +1,164 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import "config.h" +#import "BeatsPrefPane.h" +#import "beats/BeatsService.h" + +@implementation BeatsPrefPane + +// Constructor +- (id)initWithBundle:(NSBundle *)bundle +{ + if ( ( self = [super initWithBundle:bundle] ) != nil ) { + self->beatsInterface = [[BeatsService alloc] initWithPrefix:BEATS_PREFIX]; + self->updateTimer = nil; + self->knownBeats = [beatsInterface listBeats]; + self->bundle = bundle; + self->helperPath = [bundle pathForAuxiliaryExecutable:HELPER_BINARY]; + NSLog(@"Using helper: `%@`", helperPath); + } + return self; +} + +// Called when UI file is loaded +- (void)mainViewDidLoad +{ + // Setup SFAuthorizationView + AuthorizationItem items = {kAuthorizationRightExecute, 0, NULL, 0}; + AuthorizationRights rights = {1, &items}; + [authView setAuthorizationRights:&rights]; + authView.delegate = self; + [authView updateStatus:nil]; + // Allocate tabview delegate + tabDelegate = [[TabViewDelegate alloc] initWithTabView:beatsTab bundle:bundle beats:beatsInterface]; +} + +// Called before the preference pane is shown +- (void)willSelect +{ + [self updateUI]; +} + +// Called when the preference pane is shown +- (void)didSelect +{ + updateTimer = [NSTimer scheduledTimerWithTimeInterval:UPDATE_INTERVAL_SECS target:self selector:@selector(onTimer) userInfo:nil repeats:YES]; +} + +// Called when the preference pane is closed +- (void)didUnselect +{ + [updateTimer invalidate]; + updateTimer = nil; +} + +// Custom code to update the UI elements +- (void)updateUI { + [tabDelegate populateTabs:knownBeats withAuth:self]; + [messageLabel setHidden:knownBeats.count > 0]; +} + +static BOOL beatArrayEquals(NSArray *a, NSArray *b) +{ + size_t n = a.count; + if (b.count != n) return NO; + for (size_t i = 0; i < n; i++) { + if (![(NSString*)a[i] isEqualToString:b[i]]) + return NO; + } + return YES; +} + +- (void)onTimer +{ + [authView updateStatus:nil]; + NSArray *beats = [beatsInterface listBeats]; + if (!beatArrayEquals(beats, knownBeats)) { + knownBeats = beats; + [self updateUI]; + } else { + [tabDelegate update]; + } +} + +// +// SFAuthorization delegates +// + +- (void)authorizationViewDidAuthorize:(SFAuthorizationView *)view { + // Update the tab delegate so that it can enable UI elements + [tabDelegate update]; +} + +- (void)authorizationViewDidDeauthorize:(SFAuthorizationView *)view { + // Update the tab delegate so that it can disable UI elements + [tabDelegate update]; +} + +// +// AuthorizationProvider protocol +// + +- (BOOL)isUnlocked { + return [authView authorizationState] == SFAuthorizationViewUnlockedState; +} + +- (int)runAsRoot:(NSString*)program args:(NSArray*)args { + size_t numArgs = args.count; + char **cArgs = alloca(sizeof(char*) * (1 + numArgs)); + for (int i=0; i + +/* EditorWindow manages the window to edit configuration files + */ +@interface EditorWindow : NSWindowController { + NSString *filePath, + *beatName; + IBOutlet NSView *verticalStackView; + IBOutlet NSScrollView *textEditor; + NSString *sourceText; +} + +- (id) initWithBeat:(NSString*) name config:(NSString*) path; +- (IBAction)saveAndCloseTapped:(id)sender; +- (IBAction)closeTapped:(id)sender; +- (BOOL)windowShouldClose:(id)sender; +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/EditorWindow.m b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/EditorWindow.m new file mode 100644 index 00000000000..915092d70a2 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/EditorWindow.m @@ -0,0 +1,89 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import "EditorWindow.h" + +@implementation EditorWindow + +- (id) initWithBeat:(NSString*)name config:(NSString*)path { + if (self = [super initWithWindowNibName:@"EditorWindow"]) { + self->beatName = name; + self->filePath = path; + } + return self; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + verticalStackView.translatesAutoresizingMaskIntoConstraints = YES; + [[self window] setTitle:[NSString stringWithFormat:@"%@ configuration", beatName]]; + + NSError *err = nil; + sourceText = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&err]; + if (sourceText == nil) { + sourceText = [err localizedDescription]; + } + NSTextStorage *storage = [(NSTextView*)[textEditor documentView] textStorage]; + [[storage mutableString] setString:sourceText]; + // Yaml needs a monospace font + [storage setFont:[NSFont userFixedPitchFontOfSize:-1]]; +} + +- (BOOL)onClose +{ + NSTextStorage *storage = [(NSTextView*)[textEditor documentView] textStorage]; + if (![[storage string] isEqualToString:sourceText]) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert addButtonWithTitle:@"Discard"]; + [alert addButtonWithTitle:@"Continue editing"]; + [alert setMessageText:@"Discard changes?"]; + [alert setInformativeText:@"Changes will be lost if the dialog is closed without saving."]; + [alert setAlertStyle:NSAlertStyleWarning]; + if ([alert runModal] != NSAlertFirstButtonReturn) { + return NO; + } + } + [NSApp stopModalWithCode:NSModalResponseStop]; + return YES; +} + +- (IBAction)saveAndCloseTapped:(id)sender +{ + NSError *err = nil; + NSTextStorage *storage = [(NSTextView*)[textEditor documentView] textStorage]; + if (![[storage string] writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&err]) { + NSAlert *alert = [NSAlert alertWithError:err]; + [alert runModal]; + return; + } + [NSApp stopModalWithCode:NSModalResponseOK]; + [self close]; +} + +- (IBAction)closeTapped:(id)sender +{ + if ([self onClose]) { + [NSApp stopModalWithCode:NSModalResponseStop]; + [self close]; + } +} + +- (BOOL)windowShouldClose:(id)sender { + return [self onClose]; +} + +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Info.plist b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Info.plist new file mode 100644 index 00000000000..f145834a69b --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSHumanReadableCopyright + Copyright © 2018 Elastic. All rights reserved. + NSMainNibFile + BeatsPrefPane + NSPrefPaneIconFile + Beats.icns + NSPrefPaneIconLabel + Beats + NSPrincipalClass + BeatsPrefPane + + diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/TabViewDelegate.h b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/TabViewDelegate.h new file mode 100644 index 00000000000..6cc20a3c2ca --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/TabViewDelegate.h @@ -0,0 +1,43 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import +#import + +#import "beats/Beats.h" +#import "Authorization.h" + +@class BeatViewController; + +/* TabViewDelegate takes care of the NSTabView that displays all the installed beats + */ +@interface TabViewDelegate : NSObject { + NSTabView *tabView; + NSBundle *bundle; + BeatViewController *selectedTab; + id beatsInterface; +} +- (id) initWithTabView:(NSTabView*)_ bundle:(NSBundle*)_ beats:(id)_; +- (void) update; +- (void) populateTabs:(NSArray*)_ withAuth:(id)_; + +// NSTabViewDelegate +- (void) tabViewDidChangeNumberOfTabViewItems:(NSTabView*)_; +- (BOOL) tabView:(NSTabView*)_ shouldSelectTabViewItem:(NSTabViewItem*)_; +- (void) tabView:(NSTabView*)_ willSelectTabViewItem:(NSTabViewItem*)_; +- (void) tabView:(NSTabView*)_ didSelectTabViewItem:(NSTabViewItem*)_; +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/TabViewDelegate.m b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/TabViewDelegate.m new file mode 100644 index 00000000000..fb517f03fcd --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/TabViewDelegate.m @@ -0,0 +1,99 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import "TabViewDelegate.h" +#import "BeatViewController.h" +#import "common/common.h" + +@implementation TabViewDelegate +- (id) initWithTabView:(NSTabView *)tabView + bundle:(NSBundle*)bundle + beats:(id)beats +{ + if (self = [super init]) { + self->selectedTab = nil; + self->tabView = tabView; + self->bundle = bundle; + self->beatsInterface = beats; + tabView.delegate = self; + } + return self; +} + +- (void) update +{ + [selectedTab update]; +} + +- (void) populateTabs:(NSArray*)beats withAuth:(id)auth +{ + // cache self->selectedTab, as it is going to change in this method + // (add|remove|select)TabViewItem methods call the NSTabViewDelegate callbacks + BeatViewController *selectedTab = self->selectedTab; + uint i; + NSArray *items; + NSString *selectedName = nil; + for (i=0, items = tabView.tabViewItems; items != nil && i < items.count; i++) { + NSTabViewItem *item = [items objectAtIndex:i]; + if (selectedTab != nil && item.viewController == selectedTab) { + selectedName = item.identifier; + } + [tabView removeTabViewItem:item]; + } + for (uint i=0; i < beats.count; i++) { + NSString *beatName = [beats objectAtIndex:i]; + id beat = [beatsInterface getBeat:beatName]; + if (beat == nil) { + // TODO: Investigate and repair. Why some beats seem to break. Seemingly after some time disabled + // they are unloaded from launchctl. + NSLog(@"Ignoring broken beat %@", beatName); + continue; + } + NSTabViewItem *item = [[NSTabViewItem alloc] initWithIdentifier:beatName]; + [item setLabel:[beat displayName]]; + BeatViewController *vc = [[BeatViewController alloc] + initWithBeat:[beatsInterface getBeat:beatName] auth:auth bundle:bundle beatsInterface:beatsInterface]; + [item setViewController:vc]; + [tabView addTabViewItem:item]; + if ([beatName isEqualToString:selectedName]) { + selectedTab = vc; + [tabView selectTabViewItem:item]; + } + } +} + +- (void) tabViewDidChangeNumberOfTabViewItems:(NSTabView*) tabView +{ + // ignore +} + +- (BOOL) tabView:(NSTabView*)tabView shouldSelectTabViewItem:(NSTabViewItem*)item +{ + return YES; +} + +- (void) tabView:(NSTabView*)tabView willSelectTabViewItem:(NSTabViewItem*)item +{ + [(BeatViewController*)[item viewController] update]; +} + +- (void) tabView:(NSTabView*)tabView didSelectTabViewItem:(NSTabViewItem*)item +{ + selectedTab = (BeatViewController*)[item viewController]; +} + +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/Beats.h b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/Beats.h new file mode 100644 index 00000000000..f98cec1943a --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/Beats.h @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import +#import "../Authorization.h" + +@protocol Beat +- (bool) isRunning; +- (bool) isBoot; +- (int) pid; +- (NSString*) name; +- (NSString*) displayName; +- (NSString*) plistPath; +- (NSString*) configFile; +- (NSString*) logsPath; +- (BOOL) startWithAuth:(id)auth; +- (BOOL) stopWithAuth:(id)auth; +- (BOOL) toggleRunAtBootWithAuth:(id)auth; +- (BOOL) uninstall; +@end + +@protocol Beats +- (NSArray*) listBeats; +- (id )getBeat:(NSString*)name; +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/BeatsService.h b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/BeatsService.h new file mode 100644 index 00000000000..5acd142ec7b --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/BeatsService.h @@ -0,0 +1,29 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import +#import "Beats.h" + +// BeatsService is the macOS implementation for the Beats protocol +@interface BeatsService : NSObject { + NSString *prefix; +} + +- (id)initWithPrefix:(NSString*)prefix; +- (NSArray*) listBeats; +- (id )getBeat:(NSString*)name; +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/BeatsService.m b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/BeatsService.m new file mode 100644 index 00000000000..2191f106f76 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/beats/BeatsService.m @@ -0,0 +1,294 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import "BeatsService.h" +#import "../common/common.h" +#import "config.h" + +static NSString *plistExtension = @"plist"; +static NSString *empty = @""; + +@interface ConcreteBeat : NSObject { + @public NSString *config; + @public NSString *logs; + @public NSString *name; + @public NSString *displayName; + @public bool running; + @public bool startAtBoot; + @public pid_t pid; + @public NSString *plistPath; + NSString *prefix; +} +- (id) initWithPrefix:(NSString*)prefix andName:(NSString*)name; +@end + +@implementation ConcreteBeat + +- (id) initWithPrefix:(NSString*)prefix andName:(NSString *)name { + if (self = [self init]) { + self->name = name; + self->displayName = [name capitalizedString]; + self->prefix = prefix; + self->config = nil; + self->logs = nil; + self->running = false; + self->startAtBoot = false; + self->pid = 0; + self->plistPath = nil; + } + return self; +} + +- (NSString *)configFile { + return self->config; +} + +- (bool)isRunning { + return self->running; +} + +- (NSString *)name { + return self->name; +} + +- (int)pid { + return self->pid; +} + +- (BOOL)uninstall { + // TODO + return NO; +} + +- (NSString *)logsPath { + return self->logs; +} + +- (bool)isBoot { + return self->startAtBoot; +} + +- (NSString*) serviceName { + return [NSString stringWithFormat:@"%@.%@", prefix, name]; +} + +- (NSString*) serviceNameWithDomain { + return [NSString stringWithFormat:@"system/%@", [self serviceName]]; +} + +// Executes a batch of commands using the helper app. +BOOL runHelperTaskList(id auth, NSArray *argList) { + BOOL __block failed = YES; + [argList enumerateObjectsUsingBlock:^(id obj, NSUInteger _, BOOL *stop) { + NSArray *args = (NSArray*)obj; + int res = [auth runHelperAsRootWithArgs:args]; + if (res != 0) { + NSLog(@"Error: running helper with args `%@` failed with code %d", + [args componentsJoinedByString:@" "], res); + *stop = failed = YES; + } + }]; + return !failed; +} + +- (BOOL)startWithAuth:(id)auth { + return runHelperTaskList(auth,@[ + @[ @"run", LAUNCHCTL_PATH, @"enable", [self serviceNameWithDomain] ], + @[ @"run", LAUNCHCTL_PATH, @"start", [self serviceName] ] + ]); +} + +- (BOOL)stopWithAuth:(id)auth { + return runHelperTaskList(auth,@[ + @[ @"run", LAUNCHCTL_PATH, @"disable", [self serviceNameWithDomain] ], + @[ @"run", LAUNCHCTL_PATH, @"stop", [self serviceName] ] + ]); +} + +- (BOOL)toggleRunAtBootWithAuth:(id)auth { + return runHelperTaskList(auth,@[ + @[ @"setboot", [self plistPath], self->startAtBoot? @"no" : @"yes"] + ]); +} + +- (NSString *)plistPath { + return self->plistPath; +} + +- (NSString *)displayName { + return self->displayName; +} + + +@end + +@implementation BeatsService + +- (id)initWithPrefix:(NSString*)prefix { + if (self = [self init]) { + self->prefix = prefix; + } + return self; +} + +- (NSArray *)listBeats { + uint64_t elapsed = getTimeMicroseconds(); + NSArray *result = [self doListBeats]; + if (result != nil) { + elapsed = getTimeMicroseconds() - elapsed; + NSLog(@"ListBeats took %llu us", elapsed); + } + return result; +} + +- (NSArray *)doListBeats { + NSError *error = nil; + NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:LAUNCHDAEMONS_PATH + error:&error]; + if (error != nil) { + NSLog(@"Error: Unable to list installed beats: %@", [error localizedDescription]); + return nil; + } + NSMutableArray *beats = [[NSMutableArray alloc] init]; + NSUInteger prefixLength = [prefix length]; + NSUInteger extensionLength = [plistExtension length]; + + [contents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + NSString *filename = (NSString *)obj; + NSUInteger nameLength =[filename length]; + + // Make sure the file is .something.plist + if ([filename hasPrefix:self->prefix] + && nameLength > prefixLength + extensionLength + 2 + && [filename characterAtIndex:prefixLength] == '.' + && [[[filename pathExtension] lowercaseString] isEqualToString:plistExtension]) { + NSString *beatName = [filename substringWithRange:NSMakeRange(prefixLength+1, nameLength - prefixLength - extensionLength - 2)]; + [beats addObject:beatName]; + } + }]; + return beats; +} + +NSString *parseLine(NSString *line, NSString **data) { + NSRange range = [line rangeOfString:@" = "]; + if (range.location != NSNotFound) { + unsigned int i = 0; + for(char c; i < range.location && ((c = [line characterAtIndex:i])==' ' || c == '\t'); i++) + ; + *data = [line substringFromIndex:range.location + range.length]; + return [line substringWithRange:NSMakeRange(i, range.location - i)]; + } + return nil; +} + +NSDictionary* parseLaunchctlPrint(NSString *label, NSSet *keys) { + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:[keys count]]; + executeAndGetOutput(LAUNCHCTL_PATH, @[@"print", label], ^(NSString *line) { + NSString *value; + NSString *key = parseLine(line, &value); + if (key != nil && [keys containsObject:key]) { + dict[key] = value; + } + return YES; + }); + return dict; +} + +- (id)getBeat:(NSString *)name { + uint64_t elapsed = getTimeMicroseconds(); + id result = [self doGetBeat:name]; + if (result != nil) { + elapsed = getTimeMicroseconds() - elapsed; + NSLog(@"GetBeat took %llu us", elapsed); + } + return result; +} + +- (id)doGetBeat:(NSString *)name { + // Get launch daemon runtime info (only if running) + NSString *label = [NSString stringWithFormat:@"system/%@.%@", self->prefix, name]; + NSSet *wantedKeys = [NSSet setWithObjects:@"pid", @"state", @"path", nil]; + NSDictionary * dict = parseLaunchctlPrint(label, wantedKeys); + + if (!dict[@"path"]) { + NSLog(@"Error: launch daemon %@ not installed", name); + return nil; + } + ConcreteBeat *beat = [[ConcreteBeat alloc] initWithPrefix:prefix andName:name]; + beat->plistPath = dict[@"path"]; + if (dict[@"pid"]) { + beat->pid = [ (NSString*)dict[@"pid"] intValue]; + } + // pid may be present after stopped + if (beat->pid > 0 && [@"running" isEqualToString:dict[@"state"]]) { + beat->running = true; + } + + // Get configuration paths + NSError *err; + NSInputStream *plistFile = [[NSInputStream alloc] initWithFileAtPath:dict[@"path"]]; + if (plistFile == nil) { + NSLog(@"Error: unable to open plist at path '%@'", dict[@"path"]); + return nil; + } + [plistFile open]; + if ( (err = [plistFile streamError]) != nil) { + NSLog(@"Error: unable to read plist at path '%@': %@", dict[@"path"], [err localizedDescription]); + return nil; + } + + NSDictionary *plist = [NSPropertyListSerialization propertyListWithStream:plistFile + options:NSPropertyListImmutable + format:nil + error:&err]; + if (plist == nil) { + NSLog(@"Error: unable to parse plist at path '%@'", dict[@"path"]); + return nil; + } + if (err != nil) { + NSLog(@"Error: failed parsing plist at path '%@': %@", dict[@"path"], [err localizedDescription]); + return nil; + } + [plistFile close]; + + NSNumber *runAtLoad = plist[@"RunAtLoad"]; + beat->startAtBoot = runAtLoad != nil && [runAtLoad boolValue] == YES; + NSArray *args = plist[@"ProgramArguments"]; + NSMutableDictionary *argsDict = [NSMutableDictionary new]; + NSString *key = nil; + for (unsigned long i = 0, count = [args count]; i < count; i++) { + NSString *arg = [args objectAtIndex:i]; + if (key != nil) { + argsDict[key] = arg; + key = nil; + } else if ([arg characterAtIndex:0] == '-') { + key = arg; + } + } + + beat->config = argsDict[@"-c"]; + if (beat->config == nil) { + beat->config = [NSString stringWithFormat:@"/etc/%@/%@.yml", name, name]; + } + beat->logs = argsDict[@"--path.logs"]; + if (beat->logs == nil) { + beat->logs = [NSString stringWithFormat:@"/var/log/%@", name]; + } + return beat; +} + +@end diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/common/common.h b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/common/common.h new file mode 100644 index 00000000000..f14cb842cc5 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/common/common.h @@ -0,0 +1,29 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import + +// executes the given `path` executable, passing `args` array. +// Callback is called for every line in the program's output. +// Returns the program exit status. +int executeAndGetOutput(NSString *path, NSArray *args, BOOL (^callback)(NSString*)); + +// Returns the current time in microseconds +uint64_t getTimeMicroseconds(void); + +// Returns the given string, or @"nil" if its nil. +NSString *strOrNil(NSString *str); diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/common/common.m b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/common/common.m new file mode 100644 index 00000000000..f6ecbb12f0e --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/common/common.m @@ -0,0 +1,75 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import "common.h" +#import +#import + +static void readLines(NSFileHandle *handle, BOOL (^callback)(NSString*)) { + const int readLength = 4096; + NSMutableData *buffer = [NSMutableData dataWithCapacity:readLength]; + + unsigned int length = 0; + for (NSData *readData; (readData = [handle readDataOfLength:readLength])!= nil && [readData length] > 0;) { + [buffer appendData:readData]; + unsigned int start = 0, // where the first line starts + base = length; // where it begins scan for newlines + length += [readData length]; + char *bytes = [buffer mutableBytes]; + for (unsigned int i=base; i < length; i++) { + if (bytes[i] == '\n') { + NSString *line = [[NSString alloc] initWithBytesNoCopy:&bytes[start] + length:(i - start) encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + callback(line); + start = i + 1; + } + } + // discard full lines + if (start != 0) { + [buffer replaceBytesInRange:NSMakeRange(0, start) withBytes:NULL length:0]; + length -= start; + } + } +} + +int executeAndGetOutput(NSString *path, NSArray* args, BOOL (^callback)(NSString*)) { + NSPipe *pipe = [NSPipe pipe]; + NSFileHandle *fHandle = pipe.fileHandleForReading; + NSTask *task = [[NSTask alloc] init]; + task.launchPath = path; + task.arguments = args; + task.standardOutput = pipe; + + [task launch]; + + readLines(fHandle, callback); + + [fHandle closeFile]; + [task waitUntilExit]; + return [task terminationStatus]; +} + +uint64_t getTimeMicroseconds(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec*1000000 + tv.tv_usec; +} + +NSString *strOrNil(NSString *str) { + return str != nil? str : @"(nil)"; +} diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/config.h b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/config.h new file mode 100644 index 00000000000..4119154075f --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/config.h @@ -0,0 +1,31 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Service prefix used by Beats launch daemons. Used for detection +#define BEATS_PREFIX @"co.elastic.beats" + +// How often daemons info is updated +#define UPDATE_INTERVAL_SECS 2.0 + +// Helper binary name +#define HELPER_BINARY @"helper" + +// Path where to look for launch services +#define LAUNCHDAEMONS_PATH @"/Library/LaunchDaemons" + +// Path to launchctl executable +#define LAUNCHCTL_PATH @"/bin/launchctl" diff --git a/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/en.lproj/beats_preference_pane.strings b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/en.lproj/beats_preference_pane.strings new file mode 100644 index 00000000000..e66b8e36388 --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/beats-preference-pane/en.lproj/beats_preference_pane.strings @@ -0,0 +1,3 @@ + +/* Class = "NSWindow"; title = "≪ do not localize ≫"; ObjectID = "F0z-JX-Cv5"; */ +"F0z-JX-Cv5.title" = "≪ do not localize ≫"; diff --git a/dev-tools/packer/platforms/darwin/preference-pane/helper/main.m b/dev-tools/packer/platforms/darwin/preference-pane/helper/main.m new file mode 100644 index 00000000000..95b9513371b --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/helper/main.m @@ -0,0 +1,72 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import +#import +#import +#include + +BOOL setRunAtBoot(NSString*,BOOL); + +/* This helper tool is used to launch actions with elevated privileges. + # helper run + # helper setboot [true|false] + */ +int main(int argc, const char * argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s [arguments...]\n", argv[0]); + return 1; + } + /* This is required for launchctl to connect to the right launchd + when executed via AuthorizationExecuteWithPrivileges */ + if (setuid(0) != 0) { + perror("setuid"); + return 2; + } + if (!strcmp(argv[1], "run")) { + if (argc < 3) { + fprintf(stderr, "Usage: %s run [arguments...]\n", argv[0]); + return 1; + } + fprintf(stderr, "Running `%s`", argv[2]); + for (int i=3; i \n", argv[0]); + return 1; + } + BOOL value; + if (!strcmp(argv[3], "yes")) { + value = YES; + } else if (!strcmp(argv[3], "no")) { + value = NO; + } else { + fprintf(stderr, "Unknown boot value: `%s`. Use `yes` or `no`\n", argv[3]); + return 1; + } + return setRunAtBoot([NSString stringWithUTF8String:argv[2]], value)? 0 : 4; + } else { + fprintf(stderr, "Unknown action: %s\n", argv[1]); + return 1; + } +} diff --git a/dev-tools/packer/platforms/darwin/preference-pane/helper/setboot.m b/dev-tools/packer/platforms/darwin/preference-pane/helper/setboot.m new file mode 100644 index 00000000000..9d9320e568f --- /dev/null +++ b/dev-tools/packer/platforms/darwin/preference-pane/helper/setboot.m @@ -0,0 +1,91 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#import + +static void fail(NSString *msg) { + fprintf(stderr, "%s\n", [msg cStringUsingEncoding:NSUTF8StringEncoding]); +} + +// setRunAtBoot loads a property list for a launch daemon, +// changes the value of the RunAtLoad property, and writes it +// down to disk again. +BOOL setRunAtBoot(NSString* plistPath, BOOL runAtBoot) { + // Mutable property list so it can be changed in-place + NSPropertyListMutabilityOptions opts = NSPropertyListMutableContainersAndLeaves; + NSPropertyListFormat format = 0; + NSError *err = nil; + NSInputStream *input = [[NSInputStream alloc] initWithFileAtPath:plistPath]; + if (input == nil) { + fail(@"Unable to open input file"); + return NO; + } + [input open]; + err = [input streamError]; + if (err != nil) { + fail([NSString stringWithFormat:@"Unable to open input stream. Code=%u `%@`", (unsigned int)[err code], [err localizedDescription]]); + return NO; + } + + NSMutableDictionary *dict = [NSPropertyListSerialization + propertyListWithStream:input + options:opts + format:&format + error:&err]; + if (err != nil) { + fail([NSString stringWithFormat:@"Error reading property list. Code=%u `%@`", (unsigned int)[err code], [err localizedDescription]]); + return NO; + } + [input close]; + NSNumber *curValue = dict[@"RunAtLoad"]; + if (curValue != nil && [curValue boolValue] == runAtBoot) { + fail(@"RunAtLoad setting already has required value"); + return YES; + } + NSNumber *newValue = [NSNumber numberWithBool:runAtBoot]; + [dict setValue:newValue forKey:@"RunAtLoad"]; + + NSOutputStream *output = [NSOutputStream outputStreamToMemory]; + [output open]; + err = [output streamError]; + if (err != nil) { + fail([NSString stringWithFormat:@"Error creating stream. Code=%u `%@`", (unsigned int)[err code], [err localizedDescription]]); + return NO; + } + + [NSPropertyListSerialization writePropertyList:dict + toStream:output + format:format + options:0 + error:&err]; + if (err == nil) { + err = [output streamError]; + } + if (err != nil) { + fail([NSString stringWithFormat:@"Error writting property-list. Code=%u `%@`", (unsigned int)[err code], [err localizedDescription]]); + return NO; + } + [output close]; + + NSData *data = [output propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + BOOL success = [data writeToFile:plistPath atomically:YES]; + if (!success) { + fail(@"Error overwritting plist file"); + return NO; + } + return YES; +}