r20399: More SWAT updates
authorDerrell Lipman <derrell@samba.org>
Fri, 29 Dec 2006 04:15:45 +0000 (04:15 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 19:30:23 +0000 (14:30 -0500)
The LDB Browser has now been rewritten as a SWAT module.  That brings the
current number of modules to three: Samba Statistics/Status, LDB Browser and
API Documentation.  This should provide a good working example for Brad and
his class, for how additional modules can be added.

The basic infrastructure for SWAT is now entirely in place.  Next step is to
deal with putting the qooxdoo developers' kit someplace, as it is required for
making changes to SWAT, and reorganizing the swat directory hierarchy to
remove (or move to elsewhere) a bunch of old cruft.  The final step will be
ensuring that SWAT installs during a Samba build, so that it becomes useful.
(Much of this "next" and "final" step stuff will likely have to wait until
after New Years.

Derrell
(This used to be commit 1f33d1276c1b207a1e235f347fef66f316a88f65)

swat/apps/swat/source/class/swat/main/Main.js
swat/apps/swat/source/class/swat/module/AbstractModule.js
swat/apps/swat/source/class/swat/module/AbstractModuleFsm.js
swat/apps/swat/source/class/swat/module/Module.js
swat/apps/swat/source/class/swat/module/documentation/Documentation.js
swat/apps/swat/source/class/swat/module/ldbbrowse/Fsm.js [new file with mode: 0644]
swat/apps/swat/source/class/swat/module/ldbbrowse/Gui.js [new file with mode: 0644]
swat/apps/swat/source/class/swat/module/ldbbrowse/LdbBrowse.js [new file with mode: 0644]
swat/apps/swat/source/class/swat/module/statistics/Fsm.js
swat/apps/swat/source/class/swat/module/statistics/Gui.js
swat/apps/swat/source/class/swat/module/statistics/Statistics.js

index 85e21402154d333e8b7e231c68265c2b07d8c8a4..dc3bbc031e73969294b3f7c028bd9f8928e12e85 100644 (file)
@@ -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]);
   }
index 2effa54b0549ec6f88bbd22ef2c0069a7d83c8c8..19bcc88a13e971560973141531bcf056eab89f86 100644 (file)
@@ -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();
 };
index 8f3b7caa8074dea9262d2a23571230d5b57bf6e1..57b8baaac94d304aca1462049bdf1cd0f957ba0f 100644 (file)
@@ -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;
index e1f02caabad6e3ccfcd8a6144b1788d1e78672db..e7180d2895613ccfc34029e4880049de0d13ca40 100644 (file)
 /**
  * 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;
 
index eba62904a75b75162764f5bfb296802372abfad9..03191cacc2f58783004f7eb868687577afbff22f 100644 (file)
@@ -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 (file)
index 0000000..9362ef7
--- /dev/null
@@ -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 (file)
index 0000000..9e86be2
--- /dev/null
@@ -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 (file)
index 0000000..45a4c48
--- /dev/null
@@ -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;
index 486463d6487e5b00c394580750718c83848b55a6..771044172e092a326b22e6d72be45f4811f9cd60 100644 (file)
@@ -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);
 };
 
 
index 050c488c5cc55af3623847ac624e6018250a90e1..777caa73288c03686f0687eb157f0792263c2e66 100644 (file)
@@ -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
index 00adf2f8665e866150259fde10311a6af72c4d82..1618ab73c50bcba13033bc676b0ebe0f44ecec16 100644 (file)
@@ -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)
 {