Skip to content

Commit

Permalink
Add log window to MunkiStatus
Browse files Browse the repository at this point in the history
  • Loading branch information
gregneagle committed Apr 19, 2016
1 parent 17c2c45 commit 6558090
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 13 deletions.
6 changes: 4 additions & 2 deletions code/apps/MunkiStatus/MunkiStatus.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
C002ECE51913F6D6003DD155 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C002ECE21913F6D6003DD155 /* Localizable.strings */; };
C00A4C57185FCEC9004EB3B7 /* FoundationPlist.py in Resources */ = {isa = PBXBuildFile; fileRef = C00A4C56185FCEC9004EB3B7 /* FoundationPlist.py */; };
C035275018A9C5BC004A5AE4 /* libpython2.6.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C035274F18A9C5BC004A5AE4 /* libpython2.6.dylib */; };
C046261B1A00015600AF1E48 /* MunkiStatus-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C09004FB16CDD84E00BE34CE /* MunkiStatus-Info.plist */; };
C046261E1A00019800AF1E48 /* MainMenu.strings in Resources */ = {isa = PBXBuildFile; fileRef = C002ECE01913F6D6003DD155 /* MainMenu.strings */; };
C05C3CEF188391F200095E65 /* munki.py in Resources */ = {isa = PBXBuildFile; fileRef = C05C3CEE188391F200095E65 /* munki.py */; };
C09004F216CDD84E00BE34CE /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09004F116CDD84E00BE34CE /* Cocoa.framework */; };
Expand All @@ -23,6 +22,7 @@
C094B6D61891826700E06897 /* BorderlessWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = C094B6D31891826700E06897 /* BorderlessWindow.m */; };
C094B6D71891826700E06897 /* ScaledImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = C094B6D51891826700E06897 /* ScaledImageView.m */; };
C0AE8658186D2DF900C87AE7 /* MunkiStatus.icns in Resources */ = {isa = PBXBuildFile; fileRef = C0AE8657186D2DF900C87AE7 /* MunkiStatus.icns */; };
C0D67B581CC55BFD009E8C2F /* MSULogWindowController.py in Resources */ = {isa = PBXBuildFile; fileRef = C0D67B571CC55BFD009E8C2F /* MSULogWindowController.py */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -92,6 +92,7 @@
C094B6D41891826700E06897 /* ScaledImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScaledImageView.h; sourceTree = "<group>"; };
C094B6D51891826700E06897 /* ScaledImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScaledImageView.m; sourceTree = "<group>"; };
C0AE8657186D2DF900C87AE7 /* MunkiStatus.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = MunkiStatus.icns; sourceTree = "<group>"; };
C0D67B571CC55BFD009E8C2F /* MSULogWindowController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSULogWindowController.py; sourceTree = "<group>"; };
E65810B31993C96E00E53A48 /* en_CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_CA; path = en_CA.lproj/Localizable.strings; sourceTree = "<group>"; };
E65810B51993C97500E53A48 /* en_CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_CA; path = en_CA.lproj/MainMenu.strings; sourceTree = "<group>"; };
E65810B61993C97C00E53A48 /* en_CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_CA; path = en_CA.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -171,6 +172,7 @@
C0AE8657186D2DF900C87AE7 /* MunkiStatus.icns */,
C090050916CDD84E00BE34CE /* MainMenu.xib */,
C09004FA16CDD84E00BE34CE /* Supporting Files */,
C0D67B571CC55BFD009E8C2F /* MSULogWindowController.py */,
);
path = MunkiStatus;
sourceTree = "<group>";
Expand Down Expand Up @@ -290,7 +292,6 @@
buildActionMask = 2147483647;
files = (
C09004FE16CDD84E00BE34CE /* InfoPlist.strings in Resources */,
C046261B1A00015600AF1E48 /* MunkiStatus-Info.plist in Resources */,
C090050616CDD84E00BE34CE /* main.py in Resources */,
C090050816CDD84E00BE34CE /* MSUAppDelegate.py in Resources */,
C094B6D1188F7CE100E06897 /* MSUStatusWindowController.py in Resources */,
Expand All @@ -300,6 +301,7 @@
C090050B16CDD84E00BE34CE /* MainMenu.xib in Resources */,
C00A4C57185FCEC9004EB3B7 /* FoundationPlist.py in Resources */,
C0AE8658186D2DF900C87AE7 /* MunkiStatus.icns in Resources */,
C0D67B581CC55BFD009E8C2F /* MSULogWindowController.py in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
1 change: 1 addition & 0 deletions code/apps/MunkiStatus/MunkiStatus/MSUAppDelegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class MSUAppDelegate(NSObject):
# pylint: disable=unused-argument

statusWindowController = IBOutlet()
logWindowController = IBOutlet()

def applicationWillFinishLaunching_(self, sender):
'''NSApplicationDelegate method
Expand Down
174 changes: 174 additions & 0 deletions code/apps/MunkiStatus/MunkiStatus/MSULogWindowController.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
#
# MSULogWindowController.py
# MunkiStatus
#
# Created by Greg Neagle on 4/18/16.
# Copyright (c) 2016 Munki Project. All rights reserved.
#
# Much code borrowed from https://github.com/MagerValp/LoginLog
# with the blessing of MagerValp
#

from objc import YES, NO, IBAction, IBOutlet
from Foundation import *
from AppKit import *

import munki
import os


class MSULogViewDataSource(NSObject):

"""Data source for an NSTableView that displays an array of text lines.\n"""
"""Line breaks are assumed to be LF, and partial lines from incremental """
"""reading is handled."""

logFileData = NSMutableArray.alloc().init()
filteredData = logFileData

lastLineIsPartial = False
filter = ''

def applyFilterToData(self):
if len(self.filter):
filterPredicate = NSPredicate.predicateWithFormat_('self CONTAINS[cd] %@', self.filter)
self.filteredData = self.logFileData.filteredArrayUsingPredicate_(filterPredicate)
else:
self.filteredData = self.logFileData

def addLine_partial_(self, line, isPartial):
if self.lastLineIsPartial:
joinedLine = self.logFileData.lastObject() + line
self.logFileData.removeLastObject()
self.logFileData.addObject_(joinedLine)
else:
self.logFileData.addObject_(line)
self.lastLineIsPartial = isPartial
self.applyFilterToData()

def removeAllLines(self):
self.logFileData.removeAllObjects()

def lineCount(self):
return self.filteredData.count()

def numberOfRowsInTableView_(self, tableView):
return self.lineCount()

def tableView_objectValueForTableColumn_row_(self, tableView, column, row):
if column.identifier() == 'data':
return self.filteredData.objectAtIndex_(row)
else:
return ''


class MSULogWindowController(NSObject):

window = IBOutlet()
logView = IBOutlet()
searchField = IBOutlet()
pathControl = IBOutlet()

logFileData = MSULogViewDataSource.alloc().init()

fileHandle = None
updateTimer = None

_logData = NSMutableArray.alloc().init()

@objc.accessor # PyObjC KVO hack
def logData(self):
return self._logData

@objc.accessor # PyObjC KVO hack
def setLogData_(self, newlist):
self._logData = newlist

@IBAction
def searchFilterChanged_(self, sender):
'''User changed the search field'''
filterString = self.searchField.stringValue().lower()
self.logFileData.filter = filterString
self.logFileData.applyFilterToData()
self.logView.reloadData()

def getWindowLevel(self):
'''Gets our NSWindowLevel. Works around issues with the loginwindow
PolicyBanner in 10.11+ Some code based on earlier work by Pepijn
Bruienne'''
window_level = NSScreenSaverWindowLevel - 1
# Get our Darwin major version
darwin_vers = int(os.uname()[2].split('.')[0])
have_policy_banner = False
for test_file in ['/Library/Security/PolicyBanner.txt',
'/Library/Security/PolicyBanner.rtf',
'/Library/Security/PolicyBanner.rtfd']:
if os.path.exists(test_file):
have_policy_banner = True
break
# bump our NSWindowLevel if we have a PolicyBanner in ElCap+
if have_policy_banner and darwin_vers > 14:
window_level = NSScreenSaverWindowLevel
return window_level

@IBAction
def showLogWindow_(self, notification):
# Show the log window.

consoleuser = munki.getconsoleuser()
if consoleuser == None or consoleuser == u"loginwindow":
self.window.setCanBecomeVisibleWithoutLogin_(True)
self.window.setLevel_(self.getWindowLevel())

screenRect = NSScreen.mainScreen().frame()
windowRect = screenRect.copy()
windowRect.origin.x = 100.0
windowRect.origin.y = 200.0
windowRect.size.width -= 200.0
windowRect.size.height -= 300.0

logfile = munki.pref('LogFile')
self.pathControl.setURL_(NSURL.fileURLWithPath_(logfile))
self.window.setTitle_(os.path.basename(logfile))
self.window.setFrame_display_(windowRect, NO)
self.window.makeKeyAndOrderFront_(self)
self.watchLogFile_(logfile)

def watchLogFile_(self, logFile):
# Display and continuously update a log file in the main window.
self.stopWatching()
self.logFileData.removeAllLines()
self.logView.setDataSource_(self.logFileData)
self.logView.reloadData()
self.fileHandle = NSFileHandle.fileHandleForReadingAtPath_(logFile)
self.refreshLog()
# Kick off a timer that updates the log view periodically.
self.updateTimer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
0.25, self, u"refreshLog", None, YES)

def stopWatching(self):
# Release the file handle and stop the update timer.
if self.fileHandle is not None:
self.fileHandle.closeFile()
self.fileHandle = None
if self.updateTimer is not None:
self.updateTimer.invalidate()
self.updateTimer = None

def refreshLog(self):
# Check for new available data, read it, and scroll to the bottom.
data = self.fileHandle.availableData()
if data.length():
utf8string = NSString.alloc().initWithData_encoding_(
data, NSUTF8StringEncoding)
for line in utf8string.splitlines(True):
if line.endswith(u"\n"):
self.logFileData.addLine_partial_(line.rstrip(u"\n"), False)
else:
self.logFileData.addLine_partial_(line, True)
self.logView.reloadData()
self.logView.scrollRowToVisible_(self.logFileData.lineCount() - 1)

def windowWillClose_(self, notification):
self.stopWatching()
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class MSUStatusWindowController(NSObject):
# pylint: disable=no-init

window = IBOutlet()
logWindow = IBOutlet()
messageFld = IBOutlet()
detailFld = IBOutlet()
progressIndicator = IBOutlet()
Expand Down Expand Up @@ -310,7 +311,8 @@ def updateStatus_(self, notification):
# we're at the loginwindow, there is a PolicyBanner, and we're running
# under 10.11+. Make sure we're in the front.
NSApp.activateIgnoringOtherApps_(YES)
self.window.orderFrontRegardless()
if not self.logWindow.visible():
self.window.orderFrontRegardless()

self.got_status_update = True
info = notification.userInfo()
Expand Down

0 comments on commit 6558090

Please sign in to comment.