diff options
11 files changed, 1145 insertions, 57 deletions
diff --git a/swat/apps/swat/source/class/swat/main/Main.js b/swat/apps/swat/source/class/swat/main/Main.js index 85e2140215..dc3bbc031e 100644 --- a/swat/apps/swat/source/class/swat/main/Main.js +++ b/swat/apps/swat/source/class/swat/main/Main.js @@ -29,6 +29,10 @@ function() 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", @@ -53,10 +57,7 @@ qx.Proto.initialize = function() var moduleList = swat.module.Module.getList(); for (moduleName in moduleList) { - // ... add the module's name to the module object, ... - moduleList[moduleName].name = moduleName; - - // ... and call the module's buildInitialFsm() function + // ... call the module's buildInitialFsm() function var module = moduleList[moduleName]["class"].getInstance(); module.buildInitialFsm(moduleList[moduleName]); } diff --git a/swat/apps/swat/source/class/swat/module/AbstractModule.js b/swat/apps/swat/source/class/swat/module/AbstractModule.js index 2effa54b05..19bcc88a13 100644 --- a/swat/apps/swat/source/class/swat/module/AbstractModule.js +++ b/swat/apps/swat/source/class/swat/module/AbstractModule.js @@ -22,22 +22,12 @@ function() * 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 a "visible" event when the + * 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 {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 - * + * @param module {swat.module.Module} + * The module descriptor for the module. */ qx.Proto.buildInitialFsm = function(module) { @@ -115,6 +105,20 @@ qx.Proto.buildInitialFsm = function(module) // 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(); }; diff --git a/swat/apps/swat/source/class/swat/module/AbstractModuleFsm.js b/swat/apps/swat/source/class/swat/module/AbstractModuleFsm.js index 8f3b7caa80..57b8baaac9 100644 --- a/swat/apps/swat/source/class/swat/module/AbstractModuleFsm.js +++ b/swat/apps/swat/source/class/swat/module/AbstractModuleFsm.js @@ -35,8 +35,8 @@ qx.Proto.addAwaitRpcResultState = function(module) * - disable any objects in group "swat.module.fsmUtils.disable_during_rpc" * * Actions upon exit: - * - disable any objects in group "group_enable_during_rpc" - * - enable any objects in group "group_disable_during_rpc" + * - 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) @@ -173,11 +173,11 @@ qx.Proto.addAwaitRpcResultState = function(module) var request = fsm.getObject("swat.module.fsmUtils.request"); // Generate the result for a completed request - request.result = - { - type : "complete", - data : event.getData() - }; + request.setUserData("result", + { + type : "complete", + data : event.getData() + }); } }); state.addTransition(trans); @@ -200,18 +200,12 @@ qx.Proto.addAwaitRpcResultState = function(module) var request = fsm.getObject("swat.module.fsmUtils.request"); // Generate the result for a completed request - request.result = - { - type : "failed", - data : event.getData() - }; + request.setUserData("result", + { + type : "failed", + data : event.getData() + }); } }); state.addTransition(trans); }; - - -/** - * Singleton Instance Getter - */ -qx.Class.getInstance = qx.util.Return.returnInstance; diff --git a/swat/apps/swat/source/class/swat/module/Module.js b/swat/apps/swat/source/class/swat/module/Module.js index e1f02caaba..e7180d2895 100644 --- a/swat/apps/swat/source/class/swat/module/Module.js +++ b/swat/apps/swat/source/class/swat/module/Module.js @@ -10,6 +10,33 @@ /** * 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) @@ -21,6 +48,9 @@ function(moduleName, class) this.fsm = null; this.gui = null; + // Save the module name + this.name = moduleName; + // Save this class name this.class = class; diff --git a/swat/apps/swat/source/class/swat/module/documentation/Documentation.js b/swat/apps/swat/source/class/swat/module/documentation/Documentation.js index eba62904a7..03191cacc2 100644 --- a/swat/apps/swat/source/class/swat/module/documentation/Documentation.js +++ b/swat/apps/swat/source/class/swat/module/documentation/Documentation.js @@ -25,7 +25,8 @@ function() * appear. Creation of the module's GUI has been deferred until it was * actually needed (now), so we need to create it. * - * @param module {Object} @see AbstractModule + * @param module {swat.module.Module} + * The module descriptor for the module. */ qx.Proto.initialAppear = function(module) { diff --git a/swat/apps/swat/source/class/swat/module/ldbbrowse/Fsm.js b/swat/apps/swat/source/class/swat/module/ldbbrowse/Fsm.js new file mode 100644 index 0000000000..9362ef7687 --- /dev/null +++ b/swat/apps/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/swat/apps/swat/source/class/swat/module/ldbbrowse/Gui.js b/swat/apps/swat/source/class/swat/module/ldbbrowse/Gui.js new file mode 100644 index 0000000000..9e86be25e9 --- /dev/null +++ b/swat/apps/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/swat/apps/swat/source/class/swat/module/ldbbrowse/LdbBrowse.js b/swat/apps/swat/source/class/swat/module/ldbbrowse/LdbBrowse.js new file mode 100644 index 0000000000..45a4c48930 --- /dev/null +++ b/swat/apps/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/swat/apps/swat/source/class/swat/module/statistics/Fsm.js b/swat/apps/swat/source/class/swat/module/statistics/Fsm.js index 486463d648..771044172e 100644 --- a/swat/apps/swat/source/class/swat/module/statistics/Fsm.js +++ b/swat/apps/swat/source/class/swat/module/statistics/Fsm.js @@ -74,10 +74,10 @@ qx.Proto.buildFsm = function(module) // Display the result var gui = swat.module.statistics.Gui.getInstance(); - gui.displayData(module, request.result); + gui.displayData(module, request.getUserData("result")); - // Dispose of the request (and result) - request.result = null; + // Dispose of the request + request.dispose(); request = null; // Restart the timer. @@ -143,13 +143,12 @@ qx.Proto.buildFsm = function(module) { var rpc = fsm.getObject("swat.module.rpc"); - rpc.setUrl("/services/"); rpc.setServiceName("samba.management"); - - var request = - rpc.callAsyncListeners(true, // coalesce failure events - "get_statistics", - true, true); + 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); } }); @@ -201,15 +200,6 @@ qx.Proto.buildFsm = function(module) // Add the AwaitRpcResult state and all of its transitions this.addAwaitRpcResultState(module); - - // Allocate an RPC object - o = new qx.io.remote.Rpc(); - o.setTimeout(10000); - o.addEventListener("completed", fsm.eventListener, fsm); - o.addEventListener("failed", fsm.eventListener, fsm); - o.addEventListener("timeout", fsm.eventListener, fsm); - o.addEventListener("aborted", fsm.eventListener, fsm); - fsm.addObject("swat.module.rpc", o); }; diff --git a/swat/apps/swat/source/class/swat/module/statistics/Gui.js b/swat/apps/swat/source/class/swat/module/statistics/Gui.js index 050c488c5c..777caa7328 100644 --- a/swat/apps/swat/source/class/swat/module/statistics/Gui.js +++ b/swat/apps/swat/source/class/swat/module/statistics/Gui.js @@ -303,8 +303,8 @@ qx.Proto.buildGui = function(module) /** * Populate the graphical user interface with the specified data * - * @param module {Object} - * The descriptor containing our module-specific information + * @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 diff --git a/swat/apps/swat/source/class/swat/module/statistics/Statistics.js b/swat/apps/swat/source/class/swat/module/statistics/Statistics.js index 00adf2f866..1618ab73c5 100644 --- a/swat/apps/swat/source/class/swat/module/statistics/Statistics.js +++ b/swat/apps/swat/source/class/swat/module/statistics/Statistics.js @@ -25,7 +25,8 @@ function() * 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 {Object} @see AbstractModule + * @param module {swat.module.Module} + * The module descriptor for the module. */ qx.Proto.initialAppear = function(module) { |