diff options
Diffstat (limited to 'webapps/swat/source')
-rw-r--r-- | webapps/swat/source/.ignore | 1 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/main/Gui.js | 205 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/main/Main.js | 94 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/AbstractModule.js | 172 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/AbstractModuleFsm.js | 211 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/Module.js | 74 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/documentation/Documentation.js | 59 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/documentation/Fsm.js | 70 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/ldbbrowse/Fsm.js | 383 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/ldbbrowse/Gui.js | 632 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/ldbbrowse/LdbBrowse.js | 52 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/statistics/Fsm.js | 209 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/statistics/Gui.js | 464 | ||||
-rw-r--r-- | webapps/swat/source/class/swat/module/statistics/Statistics.js | 44 | ||||
-rw-r--r-- | webapps/swat/source/index.html | 14 |
15 files changed, 2684 insertions, 0 deletions
diff --git a/webapps/swat/source/.ignore b/webapps/swat/source/.ignore new file mode 100644 index 0000000000..84f7e31d99 --- /dev/null +++ b/webapps/swat/source/.ignore @@ -0,0 +1 @@ +script diff --git a/webapps/swat/source/class/swat/main/Gui.js b/webapps/swat/source/class/swat/main/Gui.js new file mode 100644 index 0000000000..8d18f5f38a --- /dev/null +++ b/webapps/swat/source/class/swat/main/Gui.js @@ -0,0 +1,205 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * The graphical user interface for the main menu + */ +qx.OO.defineClass("swat.main.Gui"); + +qx.Class.currentCanvas = null; + +qx.Class.buildGui = function(moduleList) +{ + var o; + + // Header colors + var topColor = new qx.renderer.color.Color("#ffff64"); + var bottomColor = new qx.renderer.color.Color("#3878cd"); + + // Create the yellow bar at the top + o = new qx.ui.basic.Terminator(); + o.set({ + top: 0, + left: 0, + right: 0, + height: 40 + }); + o.setBackgroundColor(topColor); + o.addToDocument(); + + // Create the tag line + var title = new qx.ui.basic.Label("Samba Web Administration Tool"); + title.set({ + top: 12, + left: 20 + }); + title.setBackgroundColor(topColor); + title.setFont("bold"); + title.addToDocument(); + + // Create a small black separator between the yellow and blue bars + o = new qx.ui.basic.Terminator(); + o.set({ + top: 40, + left: 0, + right: 0, + height: 1 + }); + o.setBackgroundColor("black"); + o.addToDocument(); + + // Create the yellow bar at the top + o = new qx.ui.basic.Terminator(); + o.set({ + top: 41, + left: 0, + right: 0, + height: 60 + }); + o.setBackgroundColor(bottomColor); + o.addToDocument(); + + // Create the "Samba" image + o = new qx.ui.basic.Image("../../../images/logo.png"); + o.set({ + top: 16, + right: 20 + }); + o.setZIndex(100000000); + o.addToDocument(); + + // Create a menu bar + var menubar = new qx.ui.toolbar.ToolBar(); + menubar.set({ + top: 100, + height: 20, + left: 0, + right: 0 + }); + menubar.addToDocument(); + + // Create a menu item for selecting the requested module + var menubutton = new qx.ui.toolbar.MenuButton("Modules"); + menubar.add(menubutton); + + // Create a Command to be executed upon any menu selection + var command = new qx.client.Command(); + // command.addEventListener("execute", fsm.eventListener, fsm); + + // Create a radio manager for selecting one of the modules + var moduleManager = new qx.manager.selection.RadioManager("main.gui.Module"); + + // Create a menu containing the subitems + var menu = new qx.ui.menu.Menu(); + + // We'll also track the current module's canvas in the modules object + swat.main.Gui.currentCanvas = null; + + // For each menu item... + for (moduleName in moduleList) + { + // create a radio button menu item + o = new qx.ui.menu.RadioButton(moduleName, null, command); + + // Associate this button menu item with the module list + o.moduleName = moduleName; + + // Associate the menu item with the radio manager + moduleManager.add(o); + + // Create this module's canvas + var canvas = new qx.ui.layout.CanvasLayout(); + canvas.set({ + top: 120, + bottom: 0, + left: 0, + right: 0 + }); + canvas.setBackgroundColor("white"); + canvas.setDisplay(false); // initially not displayed + + var fsm = moduleList[moduleName].fsm; + fsm.addObject("swat.module.canvas", canvas); + canvas.addEventListener("appear", fsm.eventListener, fsm); + canvas.addEventListener("disappear", fsm.eventListener, fsm); + + // Save the canvas + moduleList[moduleName].canvas = canvas; + + // Add the canvas to the document + canvas.addToDocument(); + + // When a Module menu item is selected: + o.addEventListener("changeChecked", function(e) + { + var canvas = moduleList[this.moduleName].canvas; + + // If there's a current canvas, ... + if (swat.main.Gui.currentCanvas) + { + // ... then remove display of it. + swat.main.Gui.currentCanvas.setDisplay(false); + + // Dispatch an event on the canvas to notify old + // module it's coming into disuse. + canvas.createDispatchEvent("disappear"); + } + + // If we are being selected... + if (e.getData()) + { + // then display our canvas + var canvas = moduleList[this.moduleName].canvas; + canvas.setDisplay(true); + + // Track the current canvas (now ours) + swat.main.Gui.currentCanvas = canvas; + + // Dispatch an event on the canvas to notify new + // module it's coming into use. + canvas.createDispatchEvent("appear"); + + // Set the application title + title.setHtml("<span>" + + "Samba Web Administration Tool" + + " » " + + this.moduleName + + "</span>"); + + // Set the browser title as well + document.title = + "Swat: " + this.moduleName; + + } + }); + + // Add the menu item to the menu + menu.add(o); + } + + // We've built a complete menu. Add it to the document. + menu.addToDocument(); + + // Specify that the menu is to be displayed upon menu button selection + menubutton.setMenu(menu); + + // Create a menu item for selecting debug options + var menubutton = new qx.ui.toolbar.MenuButton("Debug"); + menubar.add(menubutton); + + // Create a menu containing the subitems + var menu = new qx.ui.menu.Menu; + var menu_01 = new qx.ui.menu.Button("Show Debug Window", null, command); + menu.add(menu_01); + menu.addToDocument(); + + // Specify that the menu is to be displayed upon menu button selection + menubutton.setMenu(menu); + +}; diff --git a/webapps/swat/source/class/swat/main/Main.js b/webapps/swat/source/class/swat/main/Main.js new file mode 100644 index 0000000000..dc3bbc031e --- /dev/null +++ b/webapps/swat/source/class/swat/main/Main.js @@ -0,0 +1,94 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/* +#require(swat.module.Module) +#require(swat.module.AbstractModule) +*/ + +/** + * Swat main menu + */ +qx.OO.defineClass("swat.main.Main", qx.component.AbstractApplication, +function() +{ + qx.component.AbstractApplication.call(this); +}); + +/* + * Register our supported modules + */ + +//#require(swat.module.statistics.Statistics) +new swat.module.Module("Statistics", + swat.module.statistics.Statistics); + +//#require(swat.module.ldbbrowse.LdbBrowse) +new swat.module.Module("LDB Browser", + swat.module.ldbbrowse.LdbBrowse); + +//#require(swat.module.documentation.Documentation) +//#require(api.Viewer) +new swat.module.Module("API Documentation", + swat.module.documentation.Documentation); + + +/* +--------------------------------------------------------------------------- + METHODS +--------------------------------------------------------------------------- +*/ + +qx.Proto.initialize = function() +{ + // Set the resource URI + qx.Settings.setCustom("resourceUri", "./resource"); + + // Turn on JSON debugging for the time being + qx.Settings.setCustomOfClass("qx.io.Json", "enableDebug", true); + + // For each module... + var moduleList = swat.module.Module.getList(); + for (moduleName in moduleList) + { + // ... call the module's buildInitialFsm() function + var module = moduleList[moduleName]["class"].getInstance(); + module.buildInitialFsm(moduleList[moduleName]); + } +}; + + +qx.Proto.main = function() +{ + var moduleList = swat.module.Module.getList(); + + // Initialize the gui for the main menu + swat.main.Gui.buildGui(moduleList); + + // Similarly, now that we have a canvas for each module, ... + for (moduleName in moduleList) + { + // ... call the module's buildInitialGui() function + var module = moduleList[moduleName]["class"].getInstance(); + module.buildInitialGui(moduleList[moduleName]); + } +}; + + +qx.Proto.finalize = function() +{ + // Call each module's finalization function + var moduleList = swat.module.Module.getList(); + for (moduleName in moduleList) + { + var module = moduleList[moduleName]["class"].getInstance(); + module.finalize(moduleList[moduleName]); + } +}; + diff --git a/webapps/swat/source/class/swat/module/AbstractModule.js b/webapps/swat/source/class/swat/module/AbstractModule.js new file mode 100644 index 0000000000..19bcc88a13 --- /dev/null +++ b/webapps/swat/source/class/swat/module/AbstractModule.js @@ -0,0 +1,172 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Abstract Module class. All modules should extend this class. + */ +qx.OO.defineClass("swat.module.AbstractModule", qx.core.Object, +function() +{ + qx.core.Object.call(this); + this.debug("AbstractModule constructor"); +}); + + +/** + * Build the initial finite state machine. + * + * In order to prevent long load times, as minimal as possible of an initial + * FSM should be created. The FSM will receive an "appear" event when the + * module is first selected (and each subsequent time), and the FSM can use + * that event to build the complete FSM. + * + * @param module {swat.module.Module} + * The module descriptor for the module. + */ +qx.Proto.buildInitialFsm = function(module) +{ + // Create a new finite state machine + var fsm = new qx.util.fsm.FiniteStateMachine(module.name); + + // For this simple example application, show all debug messages. + qx.Settings.setCustomOfClass( + "qx.util.fsm.FiniteStateMachine", + "debugFlags", + (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS | + qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS | + qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL | + qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND)); + + /* + * State: Idle + * + * Transition on: + * "appear" on swat.module.canvas + */ + var state = new qx.util.fsm.State( + "State_Idle", + { + "events" : + { + // When we get an appear event the first time, run the transition + // that will load the module's finite state machine and graphical + // user interface. + "appear" : + { + "swat.module.canvas" : + "Transition_Idle_to_Idle_Load_Gui" + } + } + }); + fsm.addState(state); + + /* + * Transition: Idle to (replaced) Idle + * + * Cause: "appear" on canvas for the first time + * + * Action: + * Load module's finite state machine and graphical user interface + */ + var thisModule = this; + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_Idle_Load_Gui", + { + "nextState" : + qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE, + + "ontransition" : + function(fsm, event) + { + // Make the "Loading" message go away. (We need to learn how to + // remove it entirely. Just doing canvas.removeAll() leaves + // something in the widget queue and we get spurious error + // messages.) + var children = module.canvas.getVisibleChildren(); + for (var child in children) + { + children[child].hide(); + } + + // Call the module's initialAppear function to build FSM and GUI. + // That function should *replace* this state, State_Idle, to which + // we'll transition. + thisModule.initialAppear(module); + } + }); + state.addTransition(trans); + + // Save the finite state machine for this module + module.fsm = fsm; + + // Save the module descriptor in the finite state machine + fsm.addObject("swat.module.module", module); + + // Create an RPC object for use by this module + module.rpc = new qx.io.remote.Rpc(); + module.rpc.setUrl("/services/"); + module.rpc.setTimeout(10000); + module.rpc.setCrossDomain(false); + module.rpc.addEventListener("completed", fsm.eventListener, fsm); + module.rpc.addEventListener("failed", fsm.eventListener, fsm); + module.rpc.addEventListener("timeout", fsm.eventListener, fsm); + module.rpc.addEventListener("aborted", fsm.eventListener, fsm); + fsm.addObject("swat.module.rpc", module.rpc); + + // Start the finite state machine + fsm.start(); +}; + +/** + * Build the initial graphical user interface. + * + * In order to prevent long load times, as minimal as possible of an initial + * GUI should be created. Generally, this is just a "Loading..." message. + * + * @param module {Object} + * An object containing at least the following properties: + * fsm - + * The finite state machine for this module. It should be filled in + * by this function. + * canvas - + * The canvas on which to create the gui for this module + * name - + * The name of this module + * class - + * The class for this module + * + */ +qx.Proto.buildInitialGui = function(module) +{ + // For now, just create the "Loading" text + var o = new qx.ui.basic.Label("Loading module '" + module.name + "'..."); + o.set({ + top: 12, + left: 20 + }); + o.setFont("bold"); + module.canvas.add(o); +}; + +qx.Proto.finalize = function(module) +{ + this.debug("AbstractModule.finalize()"); +}; + + +/* +--------------------------------------------------------------------------- + DEFER SINGLETON INSTANCE +--------------------------------------------------------------------------- +*/ + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/class/swat/module/AbstractModuleFsm.js b/webapps/swat/source/class/swat/module/AbstractModuleFsm.js new file mode 100644 index 0000000000..57b8baaac9 --- /dev/null +++ b/webapps/swat/source/class/swat/module/AbstractModuleFsm.js @@ -0,0 +1,211 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Common facilities for modules' finite state machines. Each module's FSM + * should extend this class. + */ +qx.OO.defineClass("swat.module.AbstractModuleFsm", qx.core.Object, function() +{ + qx.core.Object.call(this); +}); + + +qx.Proto.buildFsm = function(module) +{ + throw new Error("Module must overload buildFsm() " + + "to build its custom finite state machine."); +}; + +qx.Proto.addAwaitRpcResultState = function(module) +{ + var fsm = module.fsm; + + /* + * State: AwaitRpcResult + * + * Actions upon entry: + * - enable any objects in group "swat.module.fsmUtils.enable_during_rpc" + * - disable any objects in group "swat.module.fsmUtils.disable_during_rpc" + * + * Actions upon exit: + * - disable any objects in group "swat.module.fsmUtils.enable_during_rpc" + * - enable any objects in group "swat.module.fsmUtils.disable_during_rpc" + * + * Transition on: + * "completed" (on RPC) + * "failed" (on RPC) + * "execute" on swat.module.fsmUtils.abort_rpc + */ + var state = new qx.util.fsm.State( + "State_AwaitRpcResult", + { + "autoActionsBeforeOnentry" : + { + // The name of a function. + "setEnabled" : + [ + { + // We want to enable objects in the group + // swat.module.fsmUtils.enable_during_rpc + "parameters" : [ true ], + + // Call this.getObject(<object>).setEnabled(true) on + // state entry, for each <object> in the group called + // "swat.module.fsmUtils.enable_during_rpc". + "groups" : [ "swat.module.fsmUtils.enable_during_rpc" ] + }, + + { + // We want to disable objects in the group + // swat.module.fsmUtils.disable_during_rpc + "parameters" : [ false ], + + // Call this.getObject(<object>).setEnabled(false) on + // state entry, for each <object> in the group called + // "swat.module.fsmUtils.disable_during_rpc". + "groups" : [ "swat.module.fsmUtils.disable_during_rpc" ] + } + ] + }, + + "autoActionsBeforeOnexit" : + { + // The name of a function. + "setEnabled" : + [ + { + // We want to re-disable objects we had enabled, in the group + // swat.module.fsmUtils.enable_during_rpc + "parameters" : [ false ], + + // Call this.getObject(<object>).setEnabled(false) on + // state entry, for each <object> in the group called + // "swat.module.fsmUtils.enable_during_rpc". + "groups" : [ "swat.module.fsmUtils.enable_during_rpc" ] + }, + + { + // We want to re-enable objects we had disabled, in the group + // swat.module.fsmUtils.disable_during_rpc + "parameters" : [ true ], + + // Call this.getObject(<object>).setEnabled(true) on + // state entry, for each <object> in the group called + // "swat.module.fsmUtils.disable_during_rpc". + "groups" : [ "swat.module.fsmUtils.disable_during_rpc" ] + } + ] + }, + + "onentry" : + function(fsm, state) + { + // If we're coming from some other start... + if (fsm.getPreviousState() != "State_AwaitRpcResult") + { + // ... then push the previous state onto the state stack + fsm.pushState(false); + } + }, + + "events" : + { + "execute" : + { + "swat.module.fsmUtils.abort_rpc" : + "Transition_AwaitRpcResult_to_AwaitRpcResult_via_button_abort" + }, + + "completed" : + "Transition_AwaitRpcResult_to_PopStack_via_complete", + + "failed" : + "Transition_AwaitRpcResult_to_PopStack_via_failed" + } + }); + fsm.addState(state); + + /* + * Transition: AwaitRpcResult to AwaitRpcResult + * + * Cause: "execute" on swat.module.fsmUtils.abort_rpc + */ + var trans = new qx.util.fsm.Transition( + "Transition_AwaitRpcResult_to_AwaitRpcResult_via_button_abort", + { + "nextState" : + "State_AwaitRpcResult", + + "ontransition" : + function(fsm, event) + { + // Get the request object + var request = fsm.getObject("swat.module.fsmUtils.request"); + + // Issue an abort for the pending request + request.abort(); + } + }); + state.addTransition(trans); + + /* + * Transition: AwaitRpcResult to PopStack + * + * Cause: "complete" (on RPC) + */ + var trans = new qx.util.fsm.Transition( + "Transition_AwaitRpcResult_to_PopStack_via_complete", + { + "nextState" : + qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK, + + "ontransition" : + function(fsm, event) + { + // Get the request object + var request = fsm.getObject("swat.module.fsmUtils.request"); + + // Generate the result for a completed request + request.setUserData("result", + { + type : "complete", + data : event.getData() + }); + } + }); + state.addTransition(trans); + + /* + * Transition: AwaitRpcResult to PopStack + * + * Cause: "failed" (on RPC) + */ + var trans = new qx.util.fsm.Transition( + "Transition_AwaitRpcResult_to_PopStack_via_failed", + { + "nextState" : + qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK, + + "ontransition" : + function(fsm, event) + { + // Get the request object + var request = fsm.getObject("swat.module.fsmUtils.request"); + + // Generate the result for a completed request + request.setUserData("result", + { + type : "failed", + data : event.getData() + }); + } + }); + state.addTransition(trans); +}; diff --git a/webapps/swat/source/class/swat/module/Module.js b/webapps/swat/source/class/swat/module/Module.js new file mode 100644 index 0000000000..e7180d2895 --- /dev/null +++ b/webapps/swat/source/class/swat/module/Module.js @@ -0,0 +1,74 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * This class defines a module descriptor (the registration of a module) and + * maintains the list of modules that have been registered. + * + * A Module object contains the following public properties which may be + * accessed directly by name: + * + * fsm - + * The finite state machine for this module. + * + * canvas - + * The canvas on which to create the gui for this module + * + * name - + * The name of this module + * + * class - + * The class for this module + * + * @param moduleName {string} + * The name of the module being registered. This is the name that will + * appear in the Modules menu. + * + * @param class {class} + * The class which contains the module implementation. That class must + * extend swat.module.AbstractModule and implement a singleton interface + * that provides a public method called initialAppear() which takes this + * Module object as a parameter, and creates the finite state machine for + * the module (if applicable) and builds the graphical user interface for + * the module. + */ +qx.OO.defineClass("swat.module.Module", qx.core.Object, +function(moduleName, class) +{ + qx.core.Object.call(this); + + // Initialize commonly-used properties of a module + this.canvas = null; + this.fsm = null; + this.gui = null; + + // Save the module name + this.name = moduleName; + + // Save this class name + this.class = class; + + // Add this new module to the module list. + swat.module.Module._list[moduleName] = this; +}); + + +/** + * Return the list of modules + */ +qx.Class.getList = function() +{ + return swat.module.Module._list; +}; + + +/** + * The list of modules which have been registered. + */ +qx.Class._list = { }; diff --git a/webapps/swat/source/class/swat/module/documentation/Documentation.js b/webapps/swat/source/class/swat/module/documentation/Documentation.js new file mode 100644 index 0000000000..03191cacc2 --- /dev/null +++ b/webapps/swat/source/class/swat/module/documentation/Documentation.js @@ -0,0 +1,59 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Swat statistics class + */ +qx.OO.defineClass("swat.module.documentation.Documentation", + swat.module.AbstractModule, +function() +{ + swat.module.AbstractModule.call(this); +}); + + +/** + * Load the documentation data + * + * This function is called the first time a module is actually selected to + * appear. Creation of the module's GUI has been deferred until it was + * actually needed (now), so we need to create it. + * + * @param module {swat.module.Module} + * The module descriptor for the module. + */ +qx.Proto.initialAppear = function(module) +{ + qx.manager.object.AliasManager.getInstance().add("api", "./resource/image"); + + // Include CSS file. + // (This is the hard way; I can't get qx.dom.StyleSheet.includeFile to load) + var el = document.createElement("link"); + el.type = "text/css"; + el.rel = "stylesheet"; + el.href = "./resource/css/apiviewer.css"; + var head = document.getElementsByTagName("head")[0]; + head.appendChild(el); + + // avoid redundant naming by api viewer + qx.Settings.setCustomOfClass("apiviewer.Viewer", "title", ""); + + var viewer = new api.Viewer(); + module.canvas.add(viewer); + viewer.load("script/data.js"); + + // Replace the existing (temporary) finite state machine with a null one + swat.module.documentation.Fsm.getInstance().buildFsm(module); +}; + + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/class/swat/module/documentation/Fsm.js b/webapps/swat/source/class/swat/module/documentation/Fsm.js new file mode 100644 index 0000000000..9df878b4f1 --- /dev/null +++ b/webapps/swat/source/class/swat/module/documentation/Fsm.js @@ -0,0 +1,70 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Swat statistics class finite state machine + */ +qx.OO.defineClass("swat.module.documentation.Fsm", + swat.module.AbstractModuleFsm, +function() +{ + swat.module.AbstractModuleFsm.call(this); +}); + + +qx.Proto.buildFsm = function(module) +{ + var fsm = module.fsm; + + /* + * State: Idle + * + * This is a null state to replace the one that loads the API viewer. The + * API viewer does not use the finite state machine. + */ + var state = new qx.util.fsm.State( + "State_Idle", + { + "events" : + { + // We need at least one event listed due to FSM requirements + "appear" : + { + "swat.module.canvas" : + "Transition_Idle_to_Idle_via_appear" + } + } + }); + + // Replace the initial Idle state with this one + fsm.replaceState(state, true); + + /* + * Transition: Idle to Idle + * + * Cause: "appear" on canvas + * + * Action: + * None. + */ + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_Idle_via_appear", + { + "nextState" : + "State_Idle" + }); + state.addTransition(trans); + +}; + + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/class/swat/module/ldbbrowse/Fsm.js b/webapps/swat/source/class/swat/module/ldbbrowse/Fsm.js new file mode 100644 index 0000000000..9362ef7687 --- /dev/null +++ b/webapps/swat/source/class/swat/module/ldbbrowse/Fsm.js @@ -0,0 +1,383 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Swat LDB Browser class finite state machine + */ +qx.OO.defineClass("swat.module.ldbbrowse.Fsm", swat.module.AbstractModuleFsm, +function() +{ + swat.module.AbstractModuleFsm.call(this); +}); + + +qx.Proto.buildFsm = function(module) +{ + var fsm = module.fsm; + + /* + * State: Idle + * + * Actions upon entry + * - if returning from RPC, display the result + * + * Transition on: + * "execute" on find button + * "treeopenwhileempty" on tree + * "changeselection" on tree + */ + var state = new qx.util.fsm.State( + "State_Idle", + { + "onentry" : + function(fsm, state) + { + // Did we just return from an RPC request? + if (fsm.getPreviousState() == "State_AwaitRpcResult") + { + // Yup. Display the result. We need to get the request object + var request = fsm.getObject("swat.module.fsmUtils.request"); + + // We don't need the request object to be saved any more + fsm.removeObject("swat.module.fsmUtils.request"); + + // Display the result + var gui = swat.module.ldbbrowse.Gui.getInstance(); + gui.displayData(module, request); + + // Dispose of the request + request.dispose(); + request = null; + } + }, + + "events" : + { + // If the find button is activated, issue a find request + "execute" : + { + "find" : + "Transition_Idle_to_AwaitRpcResult_via_find" + }, + + // If a previously unexpanded tree node is expanded, issue a request + // to retrieve its contents. + "treeOpenWhileEmpty": + { + "tree" : + "Transition_Idle_to_AwaitRpcResult_via_tree_open" + }, + + // If the selection changes, issue a request to retrieve contents to + // populate the attribute/value table. + "changeSelection": + { + "tree:manager" : + "Transition_Idle_to_AwaitRpcResult_via_tree_selection_changed", + + "dbName": + "Transition_Idle_to_AwaitRpcResult_via_db_changed" + } + } + }); + + // Replace the initial Idle state with this one + fsm.replaceState(state, true); + + /* + * Transition: Idle to AwaitRpcResult + * + * Cause: "execute" on find button + * + * Action: + * Issue a search request + */ + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_AwaitRpcResult_via_find", + { + "nextState" : + "State_AwaitRpcResult", + + "ontransition" : + function(fsm, event) + { + // Obtain the RPC object + var rpc = fsm.getObject("swat.module.rpc"); + + // Get our module descriptor + var module = fsm.getObject("swat.module.module"); + + // Retrieve the database handle + var dbHandle = module.dbHandle; + + // Retrieve the search expression + var searchExpr = fsm.getObject("searchExpr").getValue(); + + // Retrieve the base DN + var baseDN = fsm.getObject("baseDN").getValue(); + + // Retrieve the selected scope + var scope = fsm.getObject("scope").getSelected().getValue(); + + // We want all attributes + var attributes = [ "*" ]; + + rpc.setServiceName("samba.ldb"); + var request = rpc.callAsyncListeners(true, // coalesce failure events + "search", + dbHandle, + searchExpr, + baseDN, + scope, + attributes); + + // When we get the result, we'll need to know what type of request + // we made. + request.setUserData("requestType", "find"); + + // Save the request object + fsm.addObject("swat.module.fsmUtils.request", request); + } + }); + state.addTransition(trans); + + /* + * Transition: Idle to AwaitRpcResult + * + * Cause: "treeOpenWhileEmpty" on tree + * + * Action: + * Issue a search request + */ + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_AwaitRpcResult_via_tree_open", + { + "nextState" : + "State_AwaitRpcResult", + + "ontransition" : + function(fsm, event) + { + var parent = event.getData(); + var hierarchy = parent.getHierarchy(new Array()); + + parent.debug("Requesting children..."); + + // Strip off the root node + hierarchy.shift(); + + // Get the tree object + var tree = fsm.getObject("tree"); + + // Determine the children. Differs depending on root or otherwise + var attributes; + var scope; + var baseDN; + + // If parent is the root... + if (parent == tree) + { + // ... then we want the defaultNamingContext, ... + attributes = [ "defaultNamingContext" ]; + + // ... and we want only base scope + scope = "base"; + + // ... and an empty base DN + baseDN = ""; + } + else + { + // otherwise, retrieve the DN, + attributes = [ "dn" ]; + + // ... and we want one level of scope + scope = "one"; + + // ... and base DN is the parent + baseDN = hierarchy.reverse().join(","); + } + + // Build the search expression + var searchExpr = "(objectclass=*)"; + + // Obtain the RPC object + var rpc = fsm.getObject("swat.module.rpc"); + + // Get our module descriptor + var module = fsm.getObject("swat.module.module"); + + // Retrieve the database handle + var dbHandle = module.dbHandle; + + rpc.setServiceName("samba.ldb"); + var request = rpc.callAsyncListeners(true, // coalesce failure events + "search", + dbHandle, + searchExpr, + baseDN, + scope, + attributes); + + // When we get the result, we'll need to know what type of request + // we made. + request.setUserData("requestType", "tree_open"); + + // We'll also need some of our parameters + request.setUserData("parent", parent); + request.setUserData("attributes", attributes); + + // Save the request object + fsm.addObject("swat.module.fsmUtils.request", request); + } + }); + state.addTransition(trans); + + /* + * Transition: Idle to AwaitRpcResult + * + * Cause: "changeSelection" on tree + * + * Action: + * Issue a search request + */ + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_AwaitRpcResult_via_tree_selection_changed", + { + "nextState" : + "State_AwaitRpcResult", + + "predicate" : + function(fsm, event) + { + var element = event.getData()[0]; + var hierarchy = element.getHierarchy(new Array()); + + // Strip off the root node + hierarchy.shift(); + + // Get the tree object + var tree = fsm.getObject("tree"); + + // If element is the root... + if (element == tree) + { + // ... then just clear out the attribute/value table. + var tableModel = fsm.getObject("tableModel:browse"); + tableModel.setData([]); + return null; // don't search additional transitionis + } + + return true; + }, + + "ontransition" : + function(fsm, event) + { + var element = event.getData()[0]; + var hierarchy = element.getHierarchy(new Array()); + + // Strip off the root node + hierarchy.shift(); + + // Get the tree object + var tree = fsm.getObject("tree"); + + // Determine the children. Differs depending on root or otherwise + var attributes; + var scope; + var baseDN; + + // We want all attributes + attributes = [ "*" ]; + + // We want the attributes only for the current element + scope = "base"; + + // Base DN is the current element + baseDN = hierarchy.reverse().join(","); + + // Build the search expression + var searchExpr = "(objectclass=*)"; + + // Obtain the RPC object + var rpc = fsm.getObject("swat.module.rpc"); + + // Get our module descriptor + var module = fsm.getObject("swat.module.module"); + + // Retrieve the database handle + var dbHandle = module.dbHandle; + + rpc.setServiceName("samba.ldb"); + var request = rpc.callAsyncListeners(true, // coalesce failure events + "search", + dbHandle, + searchExpr, + baseDN, + scope, + attributes); + + // When we get the result, we'll need to know what type of request + // we made. + request.setUserData("requestType", "tree_selection_changed"); + + // Save the request object + fsm.addObject("swat.module.fsmUtils.request", request); + } + }); + state.addTransition(trans); + + /* + * Transition: Idle to AwaitRpcResult + * + * Cause: "changeSelection" on dbName + * + * Action: + * Issue a connect request + */ + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_AwaitRpcResult_via_db_changed", + { + "nextState" : + "State_AwaitRpcResult", + + "ontransition" : + function(fsm, event) + { + // Obtain the RPC object + var rpc = fsm.getObject("swat.module.rpc"); + + // Obtain the name of the database to be connected to + var dbName = fsm.getObject("dbName").getValue(); + + rpc.setServiceName("samba.ldb"); + var request = rpc.callAsyncListeners(true, // coalesce failure events + "connect", + dbName); + + // When we get the result, we'll need to know what type of request + // we made. + request.setUserData("requestType", "database_name_changed"); + + // Save the request object + fsm.addObject("swat.module.fsmUtils.request", request); + } + }); + state.addTransition(trans); + + // Add the AwaitRpcResult state and all of its transitions + this.addAwaitRpcResultState(module); +}; + + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/class/swat/module/ldbbrowse/Gui.js b/webapps/swat/source/class/swat/module/ldbbrowse/Gui.js new file mode 100644 index 0000000000..9e86be25e9 --- /dev/null +++ b/webapps/swat/source/class/swat/module/ldbbrowse/Gui.js @@ -0,0 +1,632 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Swat LDB Browser class graphical user interface + */ +qx.OO.defineClass("swat.module.ldbbrowse.Gui", qx.core.Object, +function() +{ + qx.core.Object.call(this); +}); + + +/** + * Build the raw graphical user interface. + */ +qx.Proto.buildGui = function(module) +{ + var o; + var fsm = module.fsm; + + // We need a horizontal box layout for the database name + var hlayout = new qx.ui.layout.HorizontalBoxLayout(); + hlayout.set({ + top: 20, + left: 20, + right: 20, + height: 30 + }); + + // Create a label for the database name + o = new qx.ui.basic.Atom("Database:"); + o.setWidth(100); + o.setHorizontalChildrenAlign("right"); + + // Add the label to the horizontal layout + hlayout.add(o); + + // Create a combo box for for the database name + o = new qx.ui.form.ComboBox(); + o.getField().setWidth("100%"); + o.setEditable(false); + + // Add our global database name (the only option, for now) + var item = new qx.ui.form.ListItem(module.dbFile); + o.add(item); + + // We want to be notified if the selection changes + o.addEventListener("changeSelection", fsm.eventListener, fsm); + + // Save the database name object so we can react to changes + fsm.addObject("dbName", o); + + // Add the combo box to the horizontal layout + hlayout.add(o); + + // Add the database name selection to the canvas + module.canvas.add(hlayout); + + // Create and position the tabview + var tabView_ = new qx.ui.pageview.tabview.TabView; + tabView_.set({ + top: 60, + left: 20, + right: 20, + bottom: 20 + }); + + // Create each of the tabs + var tabView_Search = + new qx.ui.pageview.tabview.Button("Search"); + var tabView_Browse = + new qx.ui.pageview.tabview.Button("Browse"); + + // Specify the initially-selected tab + tabView_Search.setChecked(true); + + // Add each of the tabs to the tabview + tabView_.getBar().add(tabView_Search, tabView_Browse); + + // Create the pages to display when each tab is selected + var tabViewPage_Search = + new qx.ui.pageview.tabview.Page(tabView_Search); + var tabViewPage_Browse = + new qx.ui.pageview.tabview.Page(tabView_Browse); + + // Build the search page + this._buildPageSearch(module, tabViewPage_Search); + + // Build the browse page + this._buildPageBrowse(module, tabViewPage_Browse); + + // Add the pages to the tabview + tabView_.getPane().add(tabViewPage_Search, tabViewPage_Browse); + + // Add the tabview to our canvas + module.canvas.add(tabView_); +}; + + +/** + * Populate the graphical user interface with the specified data + * + * @param module {swat.module.Module} + * The module descriptor for the module. + * + * @result {Object} + * The result returned by SAMBA to our request. We display the data + * provided by this result. + */ +qx.Proto.displayData = function(module, request) +{ + var gui = module.gui; + var fsm = module.fsm; + var result = request.getUserData("result") + var requestType = request.getUserData("requestType"); + + // Did the request fail? + if (result.type == "failed") + { + // Yup. We're not going to do anything particularly elegant... + alert("Async(" + result.id + ") exception: " + result.data); + return; + } + + // Dispatch to the appropriate handler, depending on the request type + switch(requestType) + { + case "find": + this._displayFindResults(module, request); + break; + + case "tree_open": + this._displayTreeOpenResults(module, request); + break; + + case "tree_selection_changed": + this._displayTreeSelectionChangedResults(module, request); + break; + + case "database_name_changed": + this._clearAllFields(module, request); + break; + + default: + throw new Error("Unexpected request type: " + requestType); + } + + // Force flushing of pending DOM updates. This is actually a + // work-around for a bug. Without this, occasionally, updates to the + // gui aren't displayed until some 'action' takes place, e.g. key + // press or mouse movement. + qx.ui.core.Widget.flushGlobalQueues(); +}; + + +qx.Proto._setAppearances = function() +{ + // Modify the default appearance of a ComboBox for use in Search tab: + // use more of the available width. + // + // If we had multiple uses, we'd create a new appearance which didn't + // contain a width. That way, we'd be able to assign a specific width to + // each ComboBox instance. Since we don't have multiple of them, we can + // just modify this default appearance. + // + // See http://qooxdoo.org/documentation/user_manual/appearance for an + // explanation of what's going on here. The missing significant point in + // the discussion is that in the current qooxdoo appearance + // implementation, it's not possible to override a specific widget's + // appearance with explicit settings just for that widget (stupid!). I + // expect that to change in a future version. + var appMgr = qx.manager.object.AppearanceManager.getInstance(); + var theme = appMgr.getAppearanceTheme(); + var appearance = theme._appearances["combo-box"]; + if (! appearance) + { + return; + } + var oldInitial = appearance.initial; + appearance.initial = function(vTheme) + { + var res = oldInitial ? oldInitial.apply(this, arguments) : {}; + res.width = "80%"; + return res; + } +}; + + +qx.Proto._buildPageSearch = function(module, page) +{ + var fsm = module.fsm; + + // We need a vertical box layout for the various input fields + var vlayout = new qx.ui.layout.VerticalBoxLayout(); + vlayout.setWidth("100%"); + + // We need a horizontal box layout for the search combo box and its label + var hlayout = new qx.ui.layout.HorizontalBoxLayout(); + hlayout.setWidth("100%"); + hlayout.setHeight(25); + + // Create a label for the list of required attributes + var label = new qx.ui.basic.Atom("Search Expression:"); + label.setWidth(100); + label.setHorizontalChildrenAlign("right"); + + // Add the label to the horizontal layout + hlayout.add(label); + + // Create a combo box for entry of the search expression + var search = new qx.ui.form.ComboBox(); + search.getField().setWidth("100%"); + search.setEditable(true); + fsm.addObject("searchExpr", search); + + // Add the combo box to the horizontal layout + hlayout.add(search); + + // Add the horizontal layout to the vertical layout + vlayout.add(hlayout); + + // We need a horizontal box layout for the base combo box and its label + hlayout = new qx.ui.layout.HorizontalBoxLayout(); + hlayout.setWidth("100%"); + hlayout.setHeight(25); + + // Create a label for the list of required attributes + var label = new qx.ui.basic.Atom("Base:"); + label.setWidth(100); + label.setHorizontalChildrenAlign("right"); + + // Add the label to the horizontal layout + hlayout.add(label); + + // Create a combo box for entry of the search expression + var base = new qx.ui.form.ComboBox(); + base.getField().setWidth("100%"); + base.setEditable(true); + fsm.addObject("baseDN", base); + + // Add the combo box to the horizontal layout + hlayout.add(base); + + // Add the horizontal layout to the vertical layout + vlayout.add(hlayout); + + // We need a horizontal box layout for scope radio buttons + hlayout = new qx.ui.layout.HorizontalBoxLayout(); + hlayout.setWidth("100%"); + hlayout.setHeight(25); + + // Create a label for the list of required attributes + var label = new qx.ui.basic.Atom("Scope:"); + label.setWidth(100); + label.setHorizontalChildrenAlign("right"); + + // Add the label to the horizontal layout + hlayout.add(label); + + // Create a radio button for each scope + var rbDefault = new qx.ui.form.RadioButton("Default", "default"); + var rbBase = new qx.ui.form.RadioButton("Base", "base"); + var rbOne = new qx.ui.form.RadioButton("One Level", "one"); + var rbSubtree = new qx.ui.form.RadioButton("Subtree", "subtree"); + + // Use a default of "Default" + rbBase.setChecked(true); + + // Add the radio buttons to the horizontal layout + hlayout.add(rbDefault, rbBase, rbOne, rbSubtree); + + // Group the radio buttons so only one in the group may be selected + var scope = new qx.manager.selection.RadioManager("scope", + [ + rbDefault, + rbBase, + rbOne, + rbSubtree + ]); + fsm.addObject("scope", scope); + + // Right-justify the 'Find' button + var spacer = new qx.ui.basic.HorizontalSpacer; + hlayout.add(spacer); + + // Create the 'Find' button + var find = new qx.ui.form.Button('Find'); + find.setWidth(100); + find.addEventListener("execute", fsm.eventListener, fsm); + + // We'll be receiving events on the find object, so save its friendly name + fsm.addObject("find", find, "swat.module.fsmUtils.disable_during_rpc"); + + hlayout.add(find); + + // Add the Find button line to the vertical layout + vlayout.add(hlayout); + + // Add the horizontal box layout to the page + page.add(vlayout); + + // Create a simple table model + var tableModel = new qx.ui.table.SimpleTableModel(); + tableModel.setColumns([ "Distinguished Name", "Attribute", "Value" ]); + + tableModel.setColumnEditable(0, false); + tableModel.setColumnEditable(1, false); + tableModel.setColumnEditable(2, false); + fsm.addObject("tableModel:search", tableModel); + + // Create a table + var table = new qx.ui.table.Table(tableModel); + table.set({ + top: 80, + left: 0, + right: 0, + bottom: 10, + statusBarVisible: false, + columnVisibilityButtonVisible: false + }); + table.setColumnWidth(0, 300); + table.setColumnWidth(1, 180); + table.setColumnWidth(2, 240); + table.setMetaColumnCounts([ 1, -1 ]);// h-scroll attribute and value together + fsm.addObject("table:search", table); + + page.add(table); +}; + +qx.Proto._buildPageBrowse = function(module, page) +{ + var fsm = module.fsm; + + // Create a vertical splitpane for tree (top) and table (bottom) + var splitpane = new qx.ui.splitpane.VerticalSplitPane("1*", "2*"); + splitpane.setEdge(0); + + // Create a tree row structure for the tree root + var trsInstance = qx.ui.treefullcontrol.TreeRowStructure.getInstance(); + var trs = trsInstance.standard(module.dbFile); + + // Create the tree and set its characteristics + var tree = new qx.ui.treefullcontrol.Tree(trs); + tree.set({ + backgroundColor: 255, + border: qx.renderer.border.BorderPresets.getInstance().inset, + overflow: "auto", + height: null, + top: 10, + left: 0, + right: 0, + bottom: 10, + open: false, + alwaysShowPlusMinusSymbol: true + }); + + // All subtrees will use this root node's event listeners. Create an event + // listener for an open while empty. + tree.addEventListener("treeOpenWhileEmpty", fsm.eventListener, fsm); + + // All subtrees will use this root node's event listeners. Create an event + // listener for selection changed, to populate attribute/value table + tree.getManager().addEventListener("changeSelection", + fsm.eventListener, + fsm); + + // We'll be receiving events on the tree object, so save its friendly name + fsm.addObject("tree", tree); + fsm.addObject("tree:manager", tree.getManager()); + + // Add the tree to the page. + splitpane.addTop(tree); + + // Create a simple table model + var tableModel = new qx.ui.table.SimpleTableModel(); + tableModel.setColumns([ "Attribute", "Value" ]); + + tableModel.setColumnEditable(0, false); + tableModel.setColumnEditable(1, false); + fsm.addObject("tableModel:browse", tableModel); + + // Create a table + var table = new qx.ui.table.Table(tableModel); + table.set({ + top: 10, + left: 0, + right: 0, + bottom: 10, + statusBarVisible: false, + columnVisibilityButtonVisible: false + }); + table.setColumnWidth(0, 200); + table.setColumnWidth(1, 440); + table.setMetaColumnCounts([1, -1]); + fsm.addObject("table:browse", table); + + // Add the table to the bottom portion of the splitpane + splitpane.addBottom(table); + + // Add the first splitpane to the page + page.add(splitpane); +}; + + +qx.Proto._displayFindResults = function(module, request) +{ + var rowData = []; + var fsm = module.fsm; + + // Track the maximum length of the attribute values + var maxLen = 0; + + // Obtain the result object + result = request.getUserData("result").data; + + if (result && result["length"]) + { + len = result["length"]; + for (var i = 0; i < result["length"]; i++) + { + var o = result[i]; + if (typeof(o) != "object") + { + alert("Found unexpected result, type " + + typeof(o) + + ", " + + o + + "\n"); + continue; + } + for (var field in o) + { + // skip dn and distinguishedName fields; + // they're shown in each row anyway. + if (field == "dn" || field == "distinguishedName") + { + continue; + } + + // If it's multi-valued (type is an array)... + if (typeof(o[field]) == "object") + { + // ... then add each value with same name + var a = o[field]; + for (var i = 0; i < a.length; i++) + { + if (a[i].length > maxLen) + { + maxLen = a[i].length; + } + rowData.push( [ + o["dn"], + field, + a[i] + ] ); + } + } + else // single-valued + { + // ... add its name and value to the table + // dataset + if (o[field].length > maxLen) + { + maxLen = o[field].length; + } + rowData.push( [ + o["dn"], + field, + o[field] + ] ); + } + } + + // Obtain the table and tableModel objects + var table = fsm.getObject("table:search"); + var tableModel = fsm.getObject("tableModel:search"); + + // Adjust the width of the value column based on + // maxLen + table.setColumnWidth(2, maxLen * 7); + + // Tell the table to use the new data + tableModel.setData(rowData); + } + } + else + { + alert("No rows returned."); + } +}; + + +qx.Proto._displayTreeOpenResults = function(module, request) +{ + var t; + var trs; + var child; + + // Obtain the result object + var result = request.getUserData("result").data; + + // We also need some of the original parameters passed to the request + var parent = request.getUserData("parent"); + var attributes = request.getUserData("attributes"); + + // Any children? + if (! result || result["length"] == 0) + { + // Nope. Allow parent's expand/contract button to be removed + parent.setAlwaysShowPlusMinusSymbol(false); + return; + } + + for (var i = 0; i < result.length; i++) + { + var name; + + child = result[i]; + + // Determine name for new tree row. If first level, use entire + // DN. Otherwise, strip off first additional component. + if (attributes == "defaultNamingContext") + { + name = child["defaultNamingContext"]; + } + else + { + name = child["dn"].split(",")[0]; + } + + // Build a standard tree row + trs = qx.ui.treefullcontrol.TreeRowStructure.getInstance().standard(name); + + // This row is a "folder" (it can have children) + t = new qx.ui.treefullcontrol.TreeFolder(trs); + t.setAlwaysShowPlusMinusSymbol(true); + + // Add this row to its parent + parent.add(t); + } +}; + + +qx.Proto._displayTreeSelectionChangedResults = function(module, request) +{ + var fsm = module.fsm; + + // Obtain the result object + var result = request.getUserData("result").data; + + // If we received an empty list, ... + if (result == null) + { + // ... then just clear the attribute/value table. + tableModel.setData([ ]); + return; + } + + // Start with an empty table dataset + var rowData = [ ]; + + // The result contains a single object: attributes + var attributes = result[0]; + + // Track the maximum length of the attribute values + var maxLen = 0; + + // For each attribute we received... + for (var attr in attributes) + { + // If it's multi-valued (type is an array)... + if (typeof(attributes[attr]) == "object") + { + // ... then add each value with same name + var a = attributes[attr]; + for (var i = 0; i < a.length; i++) + { + if (a[i].length > maxLen) + { + maxLen = a[i].length; + } + rowData.push([ attr, a[i] ]); + } + } + else // single-valued + { + // ... add its name and value to the table dataset + if (attributes[attr].length > maxLen) + { + maxLen = attributes[attr].length; + } + rowData.push([ attr, attributes[attr] ]); + } + } + + // Obtain the table and tableModel objects + var table = fsm.getObject("table:browse"); + var tableModel = fsm.getObject("tableModel:browse"); + + // Adjust the width of the value column based on maxLen + table.setColumnWidth(1, maxLen * 7); + + // Add the dataset to the table + tableModel.setData(rowData); +}; + + +qx.Proto._clearAllFields = function(module, request) +{ + // Obtain the result object + var result = request.getUserData("result").data; + + // Retrieve the database handle + module.dbHandle = result; + + // In the future, when we support more than one database, we'll want to + // clear all fields here. For now, there's no need. +}; + + + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/class/swat/module/ldbbrowse/LdbBrowse.js b/webapps/swat/source/class/swat/module/ldbbrowse/LdbBrowse.js new file mode 100644 index 0000000000..45a4c48930 --- /dev/null +++ b/webapps/swat/source/class/swat/module/ldbbrowse/LdbBrowse.js @@ -0,0 +1,52 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Swat LDB Browser class + */ +qx.OO.defineClass("swat.module.ldbbrowse.LdbBrowse", + swat.module.AbstractModule, +function() +{ + swat.module.AbstractModule.call(this); +}); + + +/** + * Create the module's finite state machine and graphical user interface. + * + * This function is called the first time a module is actually selected to + * appear. Creation of the module's actual FSM and GUI have been deferred + * until they were actually needed (now) so we need to create them. + * + * @param module {swat.module.Module} + * The module descriptor for the module. + */ +qx.Proto.initialAppear = function(module) +{ + // Initial database to open + module.dbFile = "sam.ldb"; + + // Replace the existing (temporary) finite state machine with the real one + swat.module.ldbbrowse.Fsm.getInstance().buildFsm(module); + + // Create the real gui + swat.module.ldbbrowse.Gui.getInstance().buildGui(module); + + // Force the global database to be opened + var dbName = module.fsm.getObject("dbName"); + dbName.setSelected(dbName.getList().getFirstChild()); + dbName.dispatchEvent(new qx.event.type.Event("changeSelection"), true); +}; + + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/class/swat/module/statistics/Fsm.js b/webapps/swat/source/class/swat/module/statistics/Fsm.js new file mode 100644 index 0000000000..771044172e --- /dev/null +++ b/webapps/swat/source/class/swat/module/statistics/Fsm.js @@ -0,0 +1,209 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Swat statistics class finite state machine + */ +qx.OO.defineClass("swat.module.statistics.Fsm", swat.module.AbstractModuleFsm, +function() +{ + swat.module.AbstractModuleFsm.call(this); +}); + + +qx.Class._startTimer = function(fsm) +{ + // Create a timer instance to expire in a few seconds + var timer = new qx.client.Timer(5000); + timer.addEventListener("interval", fsm.eventListener, fsm); + fsm.addObject("timer", timer); + timer.start(); +}; + + +qx.Class._stopTimer = function(fsm) +{ + // ... then stop the timer. Get the timer object. + var timer = fsm.getObject("timer"); + + // If it still exists... + if (timer) + { + // ... then dispose of it. + timer.dispose(); + fsm.removeObject("timer"); + } +}; + + +qx.Proto.buildFsm = function(module) +{ + var fsm = module.fsm; + var thisClass = this; + + /* + * State: Idle + * + * Actions upon entry + * - if returning from RPC, display the result + * - start an interval timer to request statistics again in a while + * + * Transition on: + * "interval" on interval_timer + */ + var state = new qx.util.fsm.State( + "State_Idle", + { + "onentry" : + function(fsm, state) + { + // Did we just return from an RPC request? + if (fsm.getPreviousState() == "State_AwaitRpcResult") + { + // Yup. Display the result. We need to get the request object + var request = fsm.getObject("swat.module.fsmUtils.request"); + + // We don't need the request object to be saved any more + fsm.removeObject("swat.module.fsmUtils.request"); + + // Display the result + var gui = swat.module.statistics.Gui.getInstance(); + gui.displayData(module, request.getUserData("result")); + + // Dispose of the request + request.dispose(); + request = null; + + // Restart the timer. + swat.module.statistics.Fsm._startTimer(fsm); + } + }, + + "onexit" : + function(fsm, state) + { + // If we're not coming right back into this state... + if (fsm.getNextState() != "State_Idle") + { + // ... then stop the timer. + swat.module.statistics.Fsm._stopTimer(fsm); + } + }, + + "events" : + { + // If the timer expires, send a new statistics request + "interval" : + { + "timer" : + "Transition_Idle_to_AwaitRpcResult_via_request_statistics" + }, + + // When we get an appear event, start our timer + "appear" : + { + "swat.module.canvas" : + "Transition_Idle_to_Idle_via_appear" + }, + + // When we get a disappear event, stop our timer + "disappear" : + { + "swat.module.canvas" : + "Transition_Idle_to_Idle_via_disappear" + } + } + }); + + // Replace the initial Idle state with this one + fsm.replaceState(state, true); + + /* + * Transition: Idle to AwaitRpcResult + * + * Cause: "interval" on timer + * + * Action: + * Issue a Get Statistics request + */ + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_AwaitRpcResult_via_request_statistics", + { + "nextState" : + "State_AwaitRpcResult", + + "ontransition" : + function(fsm, event) + { + var rpc = fsm.getObject("swat.module.rpc"); + + rpc.setServiceName("samba.management"); + var request = rpc.callAsyncListeners(true, // coalesce failure events + "get_statistics", + true, + true); + // Make the request object available to the AwaitRpcResult state + fsm.addObject("swat.module.fsmUtils.request", request); + } + }); + state.addTransition(trans); + + /* + * Transition: Idle to Idle + * + * Cause: "appear" on canvas + * + * Action: + * Start our timer + */ + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_Idle_via_appear", + { + "nextState" : + "State_Idle", + + "ontransition" : + function(fsm, event) + { + swat.module.statistics.Fsm._startTimer(fsm); + } + }); + state.addTransition(trans); + + /* + * Transition: Idle to Idle + * + * Cause: "disappear" on canvas + * + * Action: + * Stop our timer + */ + var trans = new qx.util.fsm.Transition( + "Transition_Idle_to_Idle_via_disappear", + { + "nextState" : + "State_Idle", + + "ontransition" : + function(fsm, event) + { + swat.module.statistics.Fsm._stopTimer(fsm); + } + }); + state.addTransition(trans); + + // Add the AwaitRpcResult state and all of its transitions + this.addAwaitRpcResultState(module); +}; + + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/class/swat/module/statistics/Gui.js b/webapps/swat/source/class/swat/module/statistics/Gui.js new file mode 100644 index 0000000000..777caa7328 --- /dev/null +++ b/webapps/swat/source/class/swat/module/statistics/Gui.js @@ -0,0 +1,464 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Swat statistics class graphical user interface + */ +qx.OO.defineClass("swat.module.statistics.Gui", qx.core.Object, +function() +{ + qx.core.Object.call(this); +}); + + +/* + * The result of our request for statistics is in this form: + * + * rpc: Object + * status: INACTIVE + * smb: Object + * tcons: Array + * 0: Object + * share_name: tmp + * last_use_time: 1167186771 + * client_ip: 127.0.0.1 + * tid: 10928 + * connect_time: 1167186757 + * connections: 1 + * sessions: Array + * 0: Object + * auth_time: 1167186757 + * vuid: 24588 + * last_use_time: 1167186771 + * client_ip: 127.0.0.1 + * connect_time: 1167186757 + * account_name: Administrator + * domain_name: WORKGROUP + * status: RUNNING + * ldap: Object + * status: INACTIVE + * wins: Object + * status: DISABLED + * nbt: Object + * status: RUNNING + * statistics: Object + * total_received: 32 + * total_sent: 4 + * query_count: 0 + * release_count: 0 + * register_count: 0 + * kdc: Object + * status: INACTIVE + * cldap: Object + * status: RUNNING + */ + +/** + * Build the raw graphical user interface. + */ +qx.Proto.buildGui = function(module) +{ + var o; + var fsm = module.fsm; + var canvas = module.canvas; + + canvas.setOverflow("auto"); + + // Create a gui object where we'll put each widget handle that has varying + // data to be displayed. + module.gui = { }; + + var addCaptionedText = function(caption, dest) + { + // Add a row to the destination grid + dest.addRow(); + var row = dest.getRowCount() - 1; + dest.setRowHeight(row, 16); + + // Add the caption + o = new qx.ui.basic.Label(caption); + dest.add(o, 0, row); + + // Add the text field that will contain varying data + o = new qx.ui.basic.Label(""); + dest.add(o, 1, row); + + // Give 'em the varying data label + return o; + }; + + var addGroup = function(legend, top, height, width, left, right, dest) + { + // Add a groupbox + var group = new qx.ui.groupbox.GroupBox(legend); + group.setTop(top); + if (left >= 0) + { + group.setLeft(left); + } + if (right >= 0) + { + group.setRight(right); + } + if (height >= 0) + { + group.setHeight(height); + } + if (typeof(width) == "string" || width >= 0) + { + group.setWidth(width); + } + group.setBackgroundColor("white"); + group.getLegendObject().setBackgroundColor("white"); + + var grid = new qx.ui.layout.GridLayout(); + grid.setLocation(0, 0); + grid.setDimension("100%", "100%"); + grid.setPadding(0, 0); + grid.setRowCount(0); + grid.setColumnCount(2); + grid.setColumnWidth(0, 100); + grid.setColumnWidth(1, 200); + + group.add(grid); + dest.add(group); + + return grid; + }; + + // Add the RPC Service group box and its status + var group = addGroup("RPC Service", 40, 60, "46%", 20, -1, canvas); + module.gui.rpc = + { + status : addCaptionedText("Status:", group) + }; + + // Add the KDC Service group box and its status + var group = addGroup("KDC Service", 40, 60, "46%", -1, 20, canvas); + module.gui.kdc = + { + status : addCaptionedText("Status:", group) + }; + + // Add the LDAP Service group box and its status + var group = addGroup("LDAP Service", 120, 60, "46%", 20, -1, canvas); + module.gui.ldap = + { + status : addCaptionedText("Status:", group) + }; + + // Add the CLDAP Service group box and its status + var group = addGroup("CLDAP Service", 120, 60, "46%", -1, 20, canvas); + module.gui.cldap = + { + status : addCaptionedText("Status:", group) + }; + + // Add the WINS Service group box and its status + var group = addGroup("WINS Service", 200, 60, "46%", 20, -1, canvas); + module.gui.wins = + { + status : addCaptionedText("Status:", group) + }; + + // Add the NBT Service group box and its status, and the statistics + var group = addGroup("NBT Service", 200, 140, "46%", -1, 20, canvas); + module.gui.nbt = + { + status : addCaptionedText("Status:", group), + total_received : addCaptionedText("Total received:", group), + total_sent : addCaptionedText("Total sent:", group), + query_count : addCaptionedText("Query count:", group), + release_count : addCaptionedText("Release count:", group), + register_count : addCaptionedText("Register count:", group) + }; + + // Add the SMB Service group box (sans grid) and its status + var group = new qx.ui.groupbox.GroupBox("SMB Service"); + group.set({ + top: 360, + height: 400, + left: 20, + right: 20 + }); + group.setBackgroundColor("white"); + group.getLegendObject().setBackgroundColor("white"); + + // Create the Status block + o = new qx.ui.basic.Label("Status:"); + o.set({ + top : 0, + left : 0, + width : 100 + }); + group.add(o); + + o = new qx.ui.basic.Label(""); + o.set({ + top : 0, + left : 100, + width : 200 + }); + group.add(o); + + // Add the status and create the table models for sessions and connections + module.gui.smb = + { + status : o, + sessions : new qx.ui.table.SimpleTableModel(), + tcons : new qx.ui.table.SimpleTableModel() + }; + + // Begin the Sessions section + o = new qx.ui.basic.Label("Sessions"); + o.set({ + top : 20, + left : 20 + }); + group.add(o); + + // Set column labels + var tableModel = module.gui.smb.sessions; + tableModel.setColumns([ + "User", + "Client", + "Connected at", + "Authenticated at", + "Last used at", + "VUID" + ]); + tableModel.setData([ ]); + + // Create the table for sessions + var table = new qx.ui.table.Table(tableModel); + table.set({ + top : 40, + left : 20, + right : 20, + height : 160 + }); + table.setMetaColumnCounts([1, -1]); + table.setStatusBarVisible(false); + table.setColumnVisibilityButtonVisible(false); + table.setColumnWidth(0, 260); + table.setColumnWidth(1, 80); + table.setColumnWidth(2, 120); + table.setColumnWidth(3, 120); + table.setColumnWidth(4, 120); + table.setColumnWidth(5, 60); + + // Add the table to the groupbox + group.add(table); + canvas.add(group); + + // Begin the Connections section + o = new qx.ui.basic.Label("Connections"); + o.set({ + top : 220, + left : 20 + }); + group.add(o); + + // Create the table model for tcons + var tableModel = module.gui.smb.tcons; + tableModel.setColumns([ + "Share", + "Client", + "Connected at", + "Last used at", + "TID" + ]); + tableModel.setData([ ]); + + // Create the table for sessions + var table = new qx.ui.table.Table(tableModel); + table.set({ + top : 240, + left : 20, + right : 20, + bottom : 20 + }); + table.setMetaColumnCounts([1, -1]); + table.setStatusBarVisible(false); + table.setColumnVisibilityButtonVisible(false); + table.setColumnWidth(0, 260); + table.setColumnWidth(1, 80); + table.setColumnWidth(2, 120); + table.setColumnWidth(3, 120); + table.setColumnWidth(4, 60); + + // Add the table to the groupbox + group.add(table); + canvas.add(group); + +}; + + +/** + * Populate the graphical user interface with the specified data + * + * @param module {swat.module.Module} + * The module descriptor for the module. + * + * @result {Object} + * The result returned by SAMBA to our request for statistics. We display + * the data provided by this result. + */ +qx.Proto.displayData = function(module, result) +{ + var gui = module.gui; + + if (result.type == "failed") + { + // Have we already put up the FAILED message? + if (gui.failed) + { + // Yup. + gui.failed.setDisplay(true); + return; + } + + // Create a semi-transparent layover o which to display a failure message + gui.failed = new qx.ui.layout.CanvasLayout(); + gui.failed.set({ + top: 0, + bottom: 0, + left: 0, + right: 0 + }); + gui.failed.setBackgroundColor("white"); + gui.failed.setDisplay(true); // initially displayed + gui.failed.setOpacity(0.7); // semi-transparent + + // Add the failure message + var style = + "color: red;" + + "font-size: large;" + + "font-weight: bold;"; + var o = new qx.ui.basic.Label("<span style='" + style + "'>" + + "Communication with SAMBA failed!", + "</span>"); + o.set({ + top : 0, + left : 20 + }); + gui.failed.add(o); + + // Add the failed layover to the canvas + module.canvas.add(gui.failed); + + return; + } + + // Successful RPC request. + // If the failure message was displayed, we no longer need it. + if (gui.failed) + { + gui.failed.setDisplay(false); + } + + // Create a function for formatting dates + var dateFormat = function(unixepoch) + { + var d = new Date(unixepoch * 1000); + return (d.getFullYear() + "-" + + ("0" + (d.getMonth() + 1)).substr(-2) + "-" + + ("0" + d.getDate()).substr(-2) + " " + + ("0" + d.getHours()).substr(-2) + ":" + + ("0" + d.getMinutes()).substr(-2)); + } + + // Set the status values + gui.rpc.status.setHtml(result.data.rpc.status); + gui.kdc.status.setHtml(result.data.kdc.status); + gui.ldap.status.setHtml(result.data.ldap.status); + gui.cldap.status.setHtml(result.data.cldap.status); + gui.wins.status.setHtml(result.data.wins.status); + gui.nbt.status.setHtml(result.data.nbt.status); + gui.smb.status.setHtml(result.data.smb.status); + + // If the NBT service is running... + if (result.data.nbt.status == "RUNNING") + { + // ... then output the statistics + gui.nbt.total_received.setHtml( + result.data.nbt.statistics.total_received.toString()); + gui.nbt.total_sent.setHtml( + result.data.nbt.statistics.total_sent.toString()); + gui.nbt.query_count.setHtml( + result.data.nbt.statistics.query_count.toString()); + gui.nbt.release_count.setHtml( + result.data.nbt.statistics.release_count.toString()); + gui.nbt.register_count.setHtml( + result.data.nbt.statistics.register_count.toString()); + } + else + { + // otherwise, clear the statistics fields + gui.nbt.total_received.setHtml(""); + gui.nbt.total_sent.setHtml(""); + gui.nbt.query_count.setHtml(""); + gui.nbt.release_count.setHtml(""); + gui.nbt.register_count.setHtml(""); + } + + // Initialize data for sessions list + var rowData = []; + + // If there are any sessions... + if (result.data.smb.sessions instanceof Array) + { + // ... then for each session... + for (var i = 0; i < result.data.smb.sessions.length; i++) + { + // ... add its info to the table data + var sess = result.data.smb.sessions[i]; + rowData.push([ + sess.account_name + "/" + sess.domain_name, + sess.client_ip, + dateFormat(sess.connect_time), + dateFormat(sess.auth_time), + dateFormat(sess.last_use_time), + sess.vuid.toString() + ]); + } + } + + // Whether there were sessions or not, reset the session table data + gui.smb.sessions.setData(rowData); + + // Initialize data for tcons list + var rowData = []; + + // If there are any tcons... + if (result.data.smb.tcons instanceof Array) + { + // ... then for each tcon... + for (var i = 0; i < result.data.smb.tcons.length; i++) + { + // ... add its info to the table data + var conn = result.data.smb.tcons[i]; + rowData.push([ + conn.share_name, + conn.client_ip, + dateFormat(conn.connect_time), + dateFormat(conn.last_use_time), + conn.tid.toString() + ]); + } + } + + // Whether there were tcons or not, reset the tcon table data + gui.smb.tcons.setData(rowData); +}; + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/class/swat/module/statistics/Statistics.js b/webapps/swat/source/class/swat/module/statistics/Statistics.js new file mode 100644 index 0000000000..1618ab73c5 --- /dev/null +++ b/webapps/swat/source/class/swat/module/statistics/Statistics.js @@ -0,0 +1,44 @@ +/* + * Copyright: + * (C) 2006 by Derrell Lipman + * All rights reserved + * + * License: + * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/ + */ + +/** + * Swat statistics class + */ +qx.OO.defineClass("swat.module.statistics.Statistics", + swat.module.AbstractModule, +function() +{ + swat.module.AbstractModule.call(this); +}); + + +/** + * Create the module's finite state machine and graphical user interface. + * + * This function is called the first time a module is actually selected to + * appear. Creation of the module's actual FSM and GUI have been deferred + * until they were actually needed (now) so we need to create them. + * + * @param module {swat.module.Module} + * The module descriptor for the module. + */ +qx.Proto.initialAppear = function(module) +{ + // Replace the existing (temporary) finite state machine with the real one + swat.module.statistics.Fsm.getInstance().buildFsm(module); + + // Create the real gui + swat.module.statistics.Gui.getInstance().buildGui(module); +}; + + +/** + * Singleton Instance Getter + */ +qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/webapps/swat/source/index.html b/webapps/swat/source/index.html new file mode 100644 index 0000000000..ea7bcfe871 --- /dev/null +++ b/webapps/swat/source/index.html @@ -0,0 +1,14 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Swat</title> + <script type="text/javascript" src="script/swat.js"></script> + </head> + <body> + <script type="text/javascript"> + + qx.core.Init.getInstance().setApplication(swat.main.Main); + + </script> + </body> +</html> |