Index: trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py
===================================================================
--- trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py	(revision 83362)
+++ trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py	(revision 83364)
@@ -35,6 +35,6 @@
 from testmanager.core.schedgroup        import SchedGroupData, SchedGroupDataEx;
 from testmanager.core.testgroup         import TestGroupData, TestGroupLogic;
-from testmanager.core.testbox           import TestBoxData;
-from testmanager.webui.wuicontentbase   import WuiFormContentBase, WuiListContentBase, WuiTmLink
+from testmanager.core.testbox           import TestBoxData, TestBoxLogic;
+from testmanager.webui.wuicontentbase   import WuiFormContentBase, WuiListContentBase, WuiTmLink, WuiRawHtml;
 
 
@@ -55,34 +55,42 @@
         WuiFormContentBase.__init__(self, oData, sMode, 'SchedGroup', oDisp, sTitle);
 
-        # Read additional bits form the DB.
-        self._aoAllTestGroups = TestGroupLogic(oDisp.getDb()).getAll();
-
-
-    def _populateForm(self, oForm, oData):
+        # Read additional bits form the DB, unless we're in
+        if sMode != WuiFormContentBase.ksMode_Show:
+            self._aoAllRelevantTestGroups = TestGroupLogic(oDisp.getDb()).getAll();
+            self._aoAllRelevantTestBoxes  = TestBoxLogic(oDisp.getDb()).getAll();
+        else:
+            self._aoAllRelevantTestGroups = [oMember.oTestGroup for oMember in oData.aoMembers];
+            self._aoAllRelevantTestBoxes  = [oMember.oTestBox   for oMember in oData.aoTestBoxes];
+
+    def _populateForm(self, oForm, oData): # type: (WuiHlpForm, SchedGroupDataEx) -> bool
         """
         Construct an HTML form
         """
 
-        oForm.addIntRO      (SchedGroupData.ksParam_idSchedGroup,     oData.idSchedGroup,     'ID')
-        oForm.addTimestampRO(SchedGroupData.ksParam_tsEffective,      oData.tsEffective,      'Last changed')
-        oForm.addTimestampRO(SchedGroupData.ksParam_tsExpire,         oData.tsExpire,         'Expires (excl)')
-        oForm.addIntRO      (SchedGroupData.ksParam_uidAuthor,        oData.uidAuthor,        'Changed by UID')
-        oForm.addText       (SchedGroupData.ksParam_sName,            oData.sName,            'Name')
-        oForm.addText       (SchedGroupData.ksParam_sDescription,     oData.sDescription,     'Description')
-        oForm.addCheckBox   (SchedGroupData.ksParam_fEnabled,         oData.fEnabled,         'Enabled')
-
-        oForm.addComboBox   (SchedGroupData.ksParam_enmScheduler,     oData.enmScheduler,     'Scheduler type',
-                             SchedGroupData.kasSchedulerDesc)
+        oForm.addIntRO(     SchedGroupData.ksParam_idSchedGroup,    oData.idSchedGroup,     'ID')
+        oForm.addTimestampRO(SchedGroupData.ksParam_tsEffective,    oData.tsEffective,      'Last changed')
+        oForm.addTimestampRO(SchedGroupData.ksParam_tsExpire,       oData.tsExpire,         'Expires (excl)')
+        oForm.addIntRO(     SchedGroupData.ksParam_uidAuthor,       oData.uidAuthor,        'Changed by UID')
+        oForm.addText(      SchedGroupData.ksParam_sName,           oData.sName,            'Name')
+        oForm.addText(      SchedGroupData.ksParam_sDescription,    oData.sDescription,     'Description')
+        oForm.addCheckBox(  SchedGroupData.ksParam_fEnabled,        oData.fEnabled,         'Enabled')
+
+        oForm.addComboBox(  SchedGroupData.ksParam_enmScheduler,    oData.enmScheduler,     'Scheduler type',
+                            SchedGroupData.kasSchedulerDesc)
 
         aoBuildSrcIds = BuildSourceLogic(self._oDisp.getDb()).fetchForCombo();
-        oForm.addComboBox   (SchedGroupData.ksParam_idBuildSrc,       oData.idBuildSrc,       'Build source',  aoBuildSrcIds);
-        oForm.addComboBox   (SchedGroupData.ksParam_idBuildSrcTestSuite,
-                             oData.idBuildSrcTestSuite, 'Test suite', aoBuildSrcIds);
+        oForm.addComboBox(  SchedGroupData.ksParam_idBuildSrc,      oData.idBuildSrc,       'Build source',  aoBuildSrcIds);
+        oForm.addComboBox(  SchedGroupData.ksParam_idBuildSrcTestSuite,
+                            oData.idBuildSrcTestSuite, 'Test suite', aoBuildSrcIds);
 
         oForm.addListOfSchedGroupMembers(SchedGroupDataEx.ksParam_aoMembers,
-                                         oData.aoMembers, self._aoAllTestGroups, 'Test groups',
+                                         oData.aoMembers, self._aoAllRelevantTestGroups,    'Test groups',
                                          fReadOnly = self._sMode == WuiFormContentBase.ksMode_Show);
 
-        oForm.addMultilineText  (SchedGroupData.ksParam_sComment,     oData.sComment,         'Comment');
+        oForm.addListOfSchedGroupBoxes(SchedGroupDataEx.ksParam_aoTestBoxes,
+                                       oData.aoTestBoxes, self._aoAllRelevantTestBoxes,     'Test boxes',
+                                       fReadOnly = self._sMode == WuiFormContentBase.ksMode_Show);
+
+        oForm.addMultilineText(SchedGroupData.ksParam_sComment,     oData.sComment,         'Comment');
         oForm.addSubmit()
 
@@ -114,5 +122,5 @@
         """
         from testmanager.webui.wuiadmin import WuiAdmin
-        oEntry  = self._aoEntries[iEntry]
+        oEntry  = self._aoEntries[iEntry]   # type: SchedGroupDataEx
 
         oBuildSrc = None;
@@ -143,13 +151,16 @@
         # Test boxes.
         aoTestBoxes = [];
-        for oTestBox in oEntry.aoTestBoxes:
-            aoTestBoxes.append(WuiTmLink(oTestBox.sName, WuiAdmin.ksScriptName,
-                                         { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails,
-                                           TestBoxData.ksParam_idTestBox: oTestBox.idTestBox,
-                                           WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, },
-                                         sTitle = '#%s - %s / %s - %s.%s (%s)'
-                                                % (oTestBox.idTestBox, oTestBox.ip, oTestBox.uuidSystem, oTestBox.sOs,
-                                                   oTestBox.sCpuArch, oTestBox.sOsVersion,)));
-
+        for oRelation in oEntry.aoTestBoxes:
+            oTestBox = oRelation.oTestBox;
+            if oTestBox:
+                aoTestBoxes.append(WuiTmLink(oTestBox.sName, WuiAdmin.ksScriptName,
+                                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails,
+                                               TestBoxData.ksParam_idTestBox: oTestBox.idTestBox,
+                                               WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, },
+                                             sTitle = '#%s - %s / %s - %s.%s (%s)'
+                                                    % (oTestBox.idTestBox, oTestBox.ip, oTestBox.uuidSystem, oTestBox.sOs,
+                                                       oTestBox.sCpuArch, oTestBox.sOsVersion,)));
+            else:
+                aoTestBoxes.append(WuiRawHtml('#%s' % (oRelation.idTestBox,)));
 
         # Actions
Index: trunk/src/VBox/ValidationKit/testmanager/webui/wuihlpform.py
===================================================================
--- trunk/src/VBox/ValidationKit/testmanager/webui/wuihlpform.py	(revision 83362)
+++ trunk/src/VBox/ValidationKit/testmanager/webui/wuihlpform.py	(revision 83364)
@@ -40,4 +40,5 @@
 from testmanager.core.testcaseargs  import TestCaseArgsData;
 from testmanager.core.testgroup     import TestGroupMemberData, TestGroupDataEx;
+from testmanager.core.testbox       import TestBoxDataForSchedGroup;
 
 # Python 3 hacks:
@@ -654,5 +655,5 @@
         self._addLabel(sName, sLabel);
         if not aoAllTestCases:
-            return self._add('<li>No testcases available.</li>\n')
+            return self._add('<li>No testcases.</li>\n')
 
         self._add(u'<input name="%s" type="hidden" value="%s">\n'
@@ -763,5 +764,5 @@
                          u'</table>\n');
 
-    def addListOfSchedGroupMembers(self, sName, aoSchedGroupMembers, aoAllTestGroups,  # pylint: disable=too-many-locals
+    def addListOfSchedGroupMembers(self, sName, aoSchedGroupMembers, aoAllRelevantTestGroups,  # pylint: disable=too-many-locals
                                    sLabel, fReadOnly = True):
         """
@@ -770,12 +771,12 @@
         if fReadOnly is None or self._fReadOnly:
             fReadOnly = self._fReadOnly;
-        assert len(aoSchedGroupMembers) <= len(aoAllTestGroups);
+        assert len(aoSchedGroupMembers) <= len(aoAllRelevantTestGroups);
         self._addLabel(sName, sLabel);
-        if not aoAllTestGroups:
-            return self._add(u'<li>No test groups available.</li>\n')
+        if not aoAllRelevantTestGroups:
+            return self._add(u'<li>No test groups.</li>\n')
 
         self._add(u'<input name="%s" type="hidden" value="%s">\n'
                   % ( SchedGroupDataEx.ksParam_aidTestGroups,
-                      ','.join([unicode(oTestGroup.idTestGroup) for oTestGroup in aoAllTestGroups]), ));
+                      ','.join([unicode(oTestGroup.idTestGroup) for oTestGroup in aoAllRelevantTestGroups]), ));
 
         self._add(u'<table class="tmformtbl">\n'
@@ -797,6 +798,6 @@
         oDefMember = SchedGroupMemberData();
         aoSchedGroupMembers = list(aoSchedGroupMembers); # Copy it so we can pop.
-        for iTestGroup, _ in enumerate(aoAllTestGroups):
-            oTestGroup = aoAllTestGroups[iTestGroup];
+        for iTestGroup, _ in enumerate(aoAllRelevantTestGroups):
+            oTestGroup = aoAllRelevantTestGroups[iTestGroup];
 
             # Is it a member?
@@ -844,5 +845,5 @@
                           ' selected' if oMember is None or oMember.idTestGroupPreReq is None else '',
                           ));
-            for oTestGroup2 in aoAllTestGroups:
+            for oTestGroup2 in aoAllRelevantTestGroups:
                 if oTestGroup2 != oTestGroup:
                     fSelected = oMember is not None and oTestGroup2.idTestGroup == oMember.idTestGroupPreReq;
@@ -862,4 +863,80 @@
                          u'</table>\n');
 
+    def addListOfSchedGroupBoxes(self, sName, aoSchedGroupBoxes, aoAllRelevantTestBoxes,  # pylint: disable=too-many-locals
+                                 sLabel, fReadOnly = True): # (str, list[TestBoxDataEx], list[TestBoxDataEx], str, bool) -> str
+        """
+        For WuiAdminSchedGroup.
+        """
+        if fReadOnly is None or self._fReadOnly:
+            fReadOnly = self._fReadOnly;
+        assert len(aoSchedGroupBoxes) <= len(aoAllRelevantTestBoxes);
+        self._addLabel(sName, sLabel);
+        if not aoAllRelevantTestBoxes:
+            return self._add(u'<li>No test boxes.</li>\n')
+
+        self._add(u'<input name="%s" type="hidden" value="%s">\n'
+                  % ( SchedGroupDataEx.ksParam_aidTestBoxes,
+                      ','.join([unicode(oTestBox.idTestBox) for oTestBox in aoAllRelevantTestBoxes]), ));
+
+        ## @todo replace with tmform-field-list tricks.
+        self._add(u'<table class="tmformtbl">\n'
+                  u' <thead>\n'
+                  u'  <tr>\n'
+                  u'    <th></th>\n'
+                  u'    <th>Test Box</th>\n'
+                  u'    <th>Priority [0..31]</th>\n'
+                  u'  </tr>\n'
+                  u' </thead>\n'
+                  u' <tbody>\n'
+                  );
+
+        sCheckBoxAttr = u' readonly onclick="return false" onkeydown="return false"' if fReadOnly else '';
+
+        oDefMember = TestBoxDataForSchedGroup();
+        aoSchedGroupBoxes = list(aoSchedGroupBoxes); # Copy it so we can pop.
+        for iTestBox, _ in enumerate(aoAllRelevantTestBoxes):
+            oTestBox = aoAllRelevantTestBoxes[iTestBox];
+
+            # Is it a member?
+            oMember = None;
+            for i, _ in enumerate(aoSchedGroupBoxes):
+                if aoSchedGroupBoxes[i].oTestBox and aoSchedGroupBoxes[i].oTestBox.idTestBox == oTestBox.idTestBox:
+                    oMember = aoSchedGroupBoxes.pop(i);
+                    break;
+
+            # Start on the rows...
+            sPrefix = u'%s[%d]' % (sName, oTestBox.idTestBox,);
+            self._add(u'  <tr class="%s">\n'
+                      u'    <td>\n'
+                      u'      <input name="%s[%s]" type="hidden" value="%s">\n' # idTestBox
+                      u'      <input name="%s[%s]" type="hidden" value="%s">\n' # idSchedGroup
+                      u'      <input name="%s[%s]" type="hidden" value="%s">\n' # tsExpire
+                      u'      <input name="%s[%s]" type="hidden" value="%s">\n' # tsEffective
+                      u'      <input name="%s[%s]" type="hidden" value="%s">\n' # uidAuthor
+                      u'      <input name="%s" type="checkbox"%s%s value="%d" class="tmform-checkbox" title="#%d - %s">\n' #(list)
+                      u'    </td>\n'
+                      % ( 'tmodd' if iTestBox & 1 else 'tmeven',
+                          sPrefix, TestBoxDataForSchedGroup.ksParam_idTestBox,    oTestBox.idTestBox,
+                          sPrefix, TestBoxDataForSchedGroup.ksParam_idSchedGroup, -1 if oMember is None else oMember.idSchedGroup,
+                          sPrefix, TestBoxDataForSchedGroup.ksParam_tsExpire,     '' if oMember is None else oMember.tsExpire,
+                          sPrefix, TestBoxDataForSchedGroup.ksParam_tsEffective,  '' if oMember is None else oMember.tsEffective,
+                          sPrefix, TestBoxDataForSchedGroup.ksParam_uidAuthor,    '' if oMember is None else oMember.uidAuthor,
+                          SchedGroupDataEx.ksParam_aoTestBoxes, '' if oMember is None else ' checked', sCheckBoxAttr,
+                          oTestBox.idTestBox, oTestBox.idTestBox, escapeElem(oTestBox.sName),
+                          ));
+            self._add(u'    <td align="left">%s</td>\n' % ( escapeElem(oTestBox.sName), ));
+
+            self._add(u'    <td align="center">\n'
+                      u'      <input name="%s[%s]" type="text" value="%s" style="max-width:3em;" %s>\n'
+                      u'    </td>\n'
+                      % ( sPrefix,
+                          TestBoxDataForSchedGroup.ksParam_iSchedPriority,
+                          (oMember if oMember is not None else oDefMember).iSchedPriority,
+                          ' readonly class="tmform-input-readonly"' if fReadOnly else '', ));
+
+            self._add(u'  </tr>\n');
+        return self._add(u' </tbody>\n'
+                         u'</table>\n');
+
     def addListOfSchedGroupsForTestBox(self, sName, aoInSchedGroups, aoAllSchedGroups, sLabel,  # pylint: disable=too-many-locals
                                        fReadOnly = None):
@@ -880,5 +957,5 @@
         self._addLabel(sName, sLabel);
         if not aoAllSchedGroups:
-            return self._add('<li>No scheduling groups available.</li>\n')
+            return self._add('<li>No scheduling groups.</li>\n')
 
         # Add special parameter with all the scheduling group IDs in the form.
