Commit 608e3bf8 authored by Joel Rennich's avatar Joel Rennich

Actions in working order

parent 9d72f98e
......@@ -113,15 +113,15 @@
<CommandLineArguments>
<CommandLineArgument
argument = "-v"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-shares"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-rawLDAP"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
......
......@@ -18,9 +18,13 @@ An action is comprised of some meta data and then four phases. Each phase has a
| Action | Command Set that make up the actual Action itself | Array | no
| Post | Command Set that will happen after the Action commands are run | Array | no
| GUID | Unique ID for the Action | String | no
|Connected | If the action set should only be run when connected to the AD domain | Bool | true
|Timer| Length in minutes between firing the Action | Int | 15
|ToolTip| The text to be shown when hovering over the menu item | String | Click here for support
* Note that the Title command set can only have one command
* An Action with the Name of "Separtor" will become a separator bar in the menu.
* If the Title command returns "false" or "true" the text of the title won't be updated. Instead a red, in the case of "false", or green dot will be next to the menu item and the title will be the Name of the action set.
* An Action with the Name of "Separator" will become a separator bar in the menu.
## Commands
......@@ -34,11 +38,10 @@ Each command has a CommandOptions value that determines what the command does. A
| app | Launch an app at a specific file path | The path to the application
| url | Launch a URL in the user's default browser | The URL to launch
| ping | Ping a host, will return false if the host is unpingable | The host to ping
| srv | Lookup up SRV records, returning false if they can't be found | The SRV records to lookup
| adgroup | Determine if the current user is a member of an AD group | The group to test with
| alert | Display a modal dialog to the user | Text of the dialog
* Note that Post action sets with an alert will automatically show the results of the Action set.
|notify| Display a notification in the notification center | Text of the notification
|false| A command that always returns false | Anything
## Workflow
......@@ -53,7 +56,6 @@ Each command has a CommandOptions value that determines what the command does. A
There's a few more features that we'd like to get down before release. Currently all of these are achievable.
* Timers - Schedule the execution of an Action based upon a repeating time. Note that you can have "silent" Actions that do not show up in the Actions Menu, but do execute on a repeated schedule.
* Triggers - Trigger actions based upon system events such as:
* Network change
* Status images - Red/yellow/green dots next to menu items based upon the Show command set of the item. Currently you can "cheat" and use emoji in your menu titles.
\ No newline at end of file
* Action -> Post - Allow actions to send messages/status to the Post command set
......@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.3.865.865.865.867.867.869.869.869</string>
<string>1.1.3</string>
<key>CFBundleShortVersionString-orginal</key>
<string>1.1.3.865.865.865.867.867.869.869.869</string>
<string>1.1.3.871.871.871.871.871.871</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
......@@ -21,21 +21,28 @@ class NoMADAction : NSObject {
// actions
var showTest: [Dictionary<String, String?>]? = nil
var show: [Dictionary<String, String?>]? = nil
var title: Dictionary<String, String?>? = nil
var action : [Dictionary<String, String?>]? = nil
var post : [Dictionary<String, String?>]? = nil
var preType: String? = nil
var preTypeOptions: [String]? = nil
// timers and triggers
var timer : Int? = nil
var timerObject : Timer? = nil
var trigger : [String]? = nil
// status
var actionType: String? = nil
var actionTypeOptions: [String]? = nil
var status: String? = nil
var visible: Bool = true
var connected: Bool = false
// globals
var display : Bool = false
var text : String = "action item"
var tip : String = ""
// init
......@@ -73,19 +80,16 @@ class NoMADAction : NSObject {
return actionName
}
return runActionCommand(action: title!["Command"] as? String ?? "none", options: title!["CommandOptions"] as? String ?? "none")
}
func preTest() {
if preType != nil {
switch preType {
case "group"? :
if defaults.array(forKey: Preferences.groups)?.contains(where: { $0 as! String == "admin" }) ?? false {
print("group passed")
}
default :
break
}
let result = runActionCommand(action: title!["Command"] as? String ?? "none", options: title!["CommandOptions"] as? String ?? "none")
if result == "true" {
status = "green"
return actionName
} else if result == "false" {
status = "red"
return actionName
} else {
return result
}
}
......@@ -104,8 +108,9 @@ class NoMADAction : NSObject {
if post != nil {
// run any post commands
// TODO: add in a way to report on result of Action
_ = runCommand(commands: post)
}
}
......
......@@ -58,6 +58,28 @@ public func runActionCommand( action: String, options: String) -> String {
} else {
return "false"
}
case "alert" :
// show an alert
let myAlert = NSAlert()
myAlert.messageText = options
// move to the foreground since we're displaying UI
DispatchQueue.main.async {
myAlert.runModal()
}
case "notify" :
let notification = NSUserNotification()
notification.informativeText = options
notification.hasReplyButton = false
notification.hasActionButton = false
notification.soundName = NSUserNotificationDefaultSoundName
NSUserNotificationCenter.default.deliver(notification)
case "false" :
return "false"
default :
break
}
......
......@@ -10,6 +10,8 @@ import Cocoa
let nActionMenu = NoMADActionMenu()
let actionMenuQueue = DispatchQueue(label: "menu.nomad.NoMAD.actions", attributes: [])
// class to create a menu of all the actions
......@@ -17,7 +19,7 @@ let nActionMenu = NoMADActionMenu()
// globals
@objc public let actionMenu = NSMenu()
@objc public var actionMenu = NSMenu()
var actions = [NoMADAction]()
let sharePrefs: UserDefaults? = UserDefaults.init(suiteName: "menu.nomad.actions")
......@@ -27,9 +29,9 @@ let nActionMenu = NoMADActionMenu()
static let kPrefVersion = "Version"
static let kPrefActions = "Actions"
// update actions
// load the actions
func update() {
func load() {
// read in the preferences
......@@ -50,13 +52,20 @@ let nActionMenu = NoMADActionMenu()
for action in rawPrefs {
// if we already know about it bail
guard let actionName = action["Name"] as? String else { continue }
let newAction = NoMADAction.init(actionName, guid: action["GUID"] as? String ?? nil)
newAction.showTest = action["ShowTest"] as? [Dictionary<String,String?>] ?? nil
newAction.show = action["Show"] as? [Dictionary<String,String?>] ?? nil
newAction.action = action["Action"] as? [Dictionary<String,String?>] ?? nil
newAction.title = action["Title"] as? Dictionary<String,String?> ?? nil
newAction.post = action["Post"] as? [Dictionary<String,String?>] ?? nil
newAction.timer = action["Timer"] as? Int ?? nil
newAction.tip = action["ToolTip"] as? String ?? ""
newAction.connected = action["Connected"] as? Bool ?? false
// add in all options
......@@ -64,38 +73,107 @@ let nActionMenu = NoMADActionMenu()
}
}
@objc func updateActions(_ connected: Bool=false) {
if actions.count < 1 {
// nothing to update
return
}
actionMenuQueue.async(execute: {
for action in self.actions {
if action.connected && !connected {
action.display = false
continue
}
if action.timerObject == nil && action.timer != nil {
// set up the timer
action.timerObject = Timer.init(timeInterval: TimeInterval.init(action.timer! * 60), target: action, selector: #selector(action.runAction), userInfo: nil, repeats: true)
RunLoop.main.add(action.timerObject!, forMode: .commonModes)
}
action.display = action.runCommand(commands: action.show)
action.text = action.getTitle()
}
})
}
// create menu
@objc func createMenu() {
for action in self.actions {
//let itemAction = #selector(action.action)
if action.actionName.lowercased() == "separator" {
let separator = NSMenuItem.separator()
self.actionMenu.addItem(separator)
} else {
let menuItem = NSMenuItem.init()
menuItem.title = action.text
if action.status != nil {
switch action.status {
case "red"? :
menuItem.image = NSImage.init(imageLiteralResourceName: NSImage.Name.statusUnavailable.rawValue)
case "green"? :
menuItem.image = NSImage.init(imageLiteralResourceName: NSImage.Name.statusAvailable.rawValue)
case "yellow"? :
menuItem.image = NSImage.init(imageLiteralResourceName: NSImage.Name.statusPartiallyAvailable.rawValue)
default:
break
}
}
if !action.display {
menuItem.isHidden = true
}
menuItem.target = action
menuItem.action = #selector(action.runAction)
menuItem.isEnabled = true
menuItem.toolTip = action.tip
menuItem.state = NSControl.StateValue(rawValue: 0)
self.actionMenu.addItem(menuItem)
}
}
}
func updateMenu() {
actionMenu.removeAllItems()
if actionMenu.items.count == 0 {
return
}
for action in actions {
for i in 0...(actionMenu.items.count - 1 ) {
actionMenu.items[i].title = actions[i].text
//let itemAction = #selector(action.action)
if !action.runCommand(commands: action.showTest) {
continue
if actions[i].status != nil {
switch actions[i].status {
case "red"? :
actionMenu.items[i].image = NSImage.init(imageLiteralResourceName: NSImage.Name.statusUnavailable.rawValue)
case "green"? :
actionMenu.items[i].image = NSImage.init(imageLiteralResourceName: NSImage.Name.statusAvailable.rawValue)
case "yellow"? :
actionMenu.items[i].image = NSImage.init(imageLiteralResourceName: NSImage.Name.statusPartiallyAvailable.rawValue)
default:
break
}
}
if action.actionName.lowercased() == "separator" {
let separator = NSMenuItem.separator()
actionMenu.addItem(separator)
if !actions[i].display {
actionMenu.items[i].isHidden = true
} else {
let menuItem = NSMenuItem.init()
menuItem.title = action.getTitle()
menuItem.target = action
menuItem.action = #selector(action.runAction)
menuItem.isEnabled = true
menuItem.toolTip = "A NoMAD custom action"
menuItem.state = NSControl.StateValue(rawValue: 0)
actionMenu.addItem(menuItem)
actionMenu.items[i].isHidden = false
}
}
//print(actionMenu)
//actionMenu.autoenablesItems = false
// return actionMenu
}
@IBAction func actionClick(_ sender: AnyObject) {
......
......@@ -151,6 +151,10 @@ class NoMADMenuController: NSObject, LoginWindowDelegate, PasswordChangeDelegate
shareMounterMenu.updateShares(connected: self.userInformation.connected)
// load up the actions
nActionMenu.load()
print(defaults.integer(forKey: Preferences.autoRenewCert))
// set up Icons - we need 2 sets of 2 for light and dark modes
......@@ -1408,19 +1412,33 @@ class NoMADMenuController: NSObject, LoginWindowDelegate, PasswordChangeDelegate
// ACTIONS
nActionMenu.update()
// update the actions first
if !self.NoMADMenu.items.contains(self.myActionsMenu) {
nActionMenu.updateActions(self.userInformation.connected)
self.myActionsMenu.title = defaults.string(forKey: Preferences.menuActions)
nActionMenu.createMenu()
self.myActionsMenu.submenu = nActionMenu.actionMenu
self.NoMADMenu.addItem(self.myActionsMenu)
self.myActionsMenu.isEnabled = true
} else {
// pivot on if the menu exists or not
if !self.NoMADMenu.items.contains(self.myActionsMenu) {
nActionMenu.createMenu()
if nActionMenu.actionMenu.items.count > 0 {
// we have a menu add it to the main menu
self.myActionsMenu.title = defaults.string(forKey: Preferences.menuActions) ?? "Actions"
self.myActionsMenu.submenu = nActionMenu.actionMenu
let lockIndex = self.NoMADMenu.index(of: self.NoMADMenuLockScreen)
self.NoMADMenu.insertItem(self.myActionsMenu, at: (lockIndex + 1 ))
}
} else {
nActionMenu.updateMenu()
}
if self.userInformation.status == "Logged In" {
self.myShareMenuItem.title = defaults.string(forKey: Preferences.menuFileServers) ?? "FileServers".translate
......
......@@ -87,7 +87,7 @@ class ShareMounterMenu: NSObject {
}
if CommandLine.arguments.contains("-shares") {
print("***Share Menuu***")
print("***Share Menu***")
print(myShareMenu)
}
......
......@@ -9,6 +9,8 @@
<dict>
<key>Name</key>
<string>update test</string>
<key>Connected</key>
<true/>
<key>Title</key>
<dict>
<key>Command</key>
......@@ -16,7 +18,7 @@
<key>CommandOptions</key>
<string>/bin/cat /tmp/titleText</string>
</dict>
<key>ShowTest</key>
<key>Show</key>
<array>
<dict>
<key>Command</key>
......@@ -38,29 +40,40 @@
<dict>
<key>Name</key>
<string>non-working ping test</string>
<key>ShowTest</key>
<key>ToolTip</key>
<string>This is a ping test</string>
<key>Title</key>
<dict>
<key>Command</key>
<string>ping</string>
<key>CommandOptions</key>
<string>dc1.nomad.test</string>
</dict>
<key>Action</key>
<array>
<dict>
<key>Command</key>
<string>ping</string>
<string>path</string>
<key>CommandOptions</key>
<string>dc8.nomad.test</string>
<string>/usr/bin/touch /tmp/action1</string>
</dict>
</array>
<key>Action</key>
<key>Post</key>
<array>
<dict>
<key>Command</key>
<string>path</string>
<string>alert</string>
<key>CommandOptions</key>
<string>/usr/bin/touch /tmp/action1</string>
<string>This is just a test</string>
</dict>
</array>
</dict>
<dict>
<key>Name</key>
<string>Working ping test</string>
<key>ShowTest</key>
<key>Timer</key>
<integer>5</integer>
<key>Show</key>
<array>
<dict>
<key>Command</key>
......@@ -78,10 +91,29 @@
<string>/usr/bin/touch /tmp/action2</string>
</dict>
</array>
<key>Post</key>
<array>
<dict>
<key>Command</key>
<string>alert</string>
<key>CommandOptions</key>
<string>This is just a test!</string>
</dict>
<dict>
<key>Command</key>
<string>notify</string>
<key>CommandOptions</key>
<string>Notification test</string>
</dict>
</array>
</dict>
<dict>
<key>Name</key>
<string>separator</string>
</dict>
<dict>
<key>Name</key>
<string>Placeholder</string>
<string>Just a message</string>
</dict>
</array>
</dict>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment