Index: /trunk/src/VBox/ValidationKit/testmanager/core/useraccount.pgsql
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/useraccount.pgsql	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/useraccount.pgsql	(revision 65914)
@@ -108,5 +108,5 @@
 --
 CREATE OR REPLACE FUNCTION UserAccountLogic_addEntry(a_uidAuthor INTEGER, a_sUsername TEXT, a_sEmail TEXT, a_sFullName TEXT, 
-                                                     a_sLoginName TEXT) 
+                                                     a_sLoginName TEXT, a_fReadOnly BOOLEAN)
     RETURNS VOID AS $$
     DECLARE
@@ -120,5 +120,5 @@
 
 CREATE OR REPLACE FUNCTION UserAccountLogic_editEntry(a_uidAuthor INTEGER, a_uid INTEGER, a_sUsername TEXT, a_sEmail TEXT, 
-                                                      a_sFullName TEXT, a_sLoginName TEXT)  
+                                                      a_sFullName TEXT, a_sLoginName TEXT, a_fReadOnly BOOLEAN)
     RETURNS VOID AS $$
     BEGIN
@@ -127,6 +127,6 @@
 
         PERFORM UserAccountLogic_historizeEntry(a_uid, CURRENT_TIMESTAMP);
-        INSERT INTO Users (uid, uidAuthor, sUsername, sEmail, sFullName, sLoginName)
-            VALUES (a_uid, a_uidAuthor, a_sUsername, a_sEmail, a_sFullName, a_sLoginName);
+        INSERT INTO Users (uid, uidAuthor, sUsername, sEmail, sFullName, sLoginName, fReadOnly)
+            VALUES (a_uid, a_uidAuthor, a_sUsername, a_sEmail, a_sFullName, a_sLoginName, a_fReadOnly);
     END;
 $$ LANGUAGE plpgsql;
Index: /trunk/src/VBox/ValidationKit/testmanager/core/useraccount.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/useraccount.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/useraccount.py	(revision 65914)
@@ -53,4 +53,5 @@
     ksParam_sEmail              = 'UserAccount_sEmail'
     ksParam_sFullName           = 'UserAccount_sFullName'
+    ksParam_fReadOnly           = 'UserAccount_fReadOnly'
 
     kasAllowNullAttributes      = ['uid', 'tsEffective', 'tsExpire', 'uidAuthor'];
@@ -68,4 +69,5 @@
         self.sFullName      = None;
         self.sLoginName     = None;
+        self.fReadOnly      = None;
 
     def initFromDbRow(self, aoRow):
@@ -85,4 +87,5 @@
         self.sFullName      = aoRow[6];
         self.sLoginName     = aoRow[7];
+        self.fReadOnly      = aoRow[8];
         return self;
 
@@ -157,5 +160,5 @@
         """
         self._oDb.callProc('UserAccountLogic_addEntry',
-                           (uidAuthor, oData.sUsername, oData.sEmail, oData.sFullName, oData.sLoginName,));
+                           (uidAuthor, oData.sUsername, oData.sEmail, oData.sFullName, oData.sLoginName, oData.fReadOnly));
         self._oDb.maybeCommit(fCommit);
         return True;
@@ -166,5 +169,6 @@
         """
         self._oDb.callProc('UserAccountLogic_editEntry',
-                           (uidAuthor, oData.uid, oData.sUsername, oData.sEmail, oData.sFullName, oData.sLoginName,));
+                           ( uidAuthor, oData.uid, oData.sUsername, oData.sEmail,
+                             oData.sFullName, oData.sLoginName, oData.fReadOnly));
         self._oDb.maybeCommit(fCommit);
         return True;
Index: /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseComments.pgsql
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseComments.pgsql	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseComments.pgsql	(revision 65914)
@@ -55,4 +55,8 @@
 COMMENT ON COLUMN Users.sLoginName IS
   'The login name used by apache.';
+
+
+COMMENT ON COLUMN Users.fReadOnly IS
+  'Read access only.';
 
 
Index: /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql	(revision 65914)
@@ -125,4 +125,6 @@
     --- The login name used by apache.
     sLoginName          text        NOT NULL,
+    --- Read access only.
+    fReadOnly           BOOLEAN     NOT NULL DEFAULT FALSE,
 
     PRIMARY KEY (uid, tsExpire)
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py	(revision 65914)
@@ -58,8 +58,9 @@
     ksActionUserList                = 'UserList'
     ksActionUserAdd                 = 'UserAdd'
+    ksActionUserAddPost             = 'UserAddPost'
     ksActionUserEdit                = 'UserEdit'
-    ksActionUserAddPost             = 'UserAddPost'
     ksActionUserEditPost            = 'UserEditPost'
     ksActionUserDelPost             = 'UserDelPost'
+    ksActionUserDetails             = 'UserDetails'
 
     ksActionTestBoxList             = 'TestBoxList'
@@ -178,4 +179,5 @@
         self._dDispatch[self.ksActionUserAddPost]               = self._actionUserAddPost;
         self._dDispatch[self.ksActionUserEditPost]              = self._actionUserEditPost;
+        self._dDispatch[self.ksActionUserDetails]               = self._actionUserDetails;
         self._dDispatch[self.ksActionUserDelPost]               = self._actionUserDelPost;
 
@@ -457,4 +459,10 @@
         return self._actionGenericFormAdd(UserAccountData, WuiUserAccount)
 
+    def _actionUserDetails(self):
+        """ Action wrapper. """
+        from testmanager.core.useraccount              import UserAccountData, UserAccountLogic;
+        from testmanager.webui.wuiadminuseraccount     import WuiUserAccount;
+        return self._actionGenericFormDetails(UserAccountData, UserAccountLogic, WuiUserAccount, 'uid');
+
     def _actionUserEdit(self):
         """ Action wrapper. """
@@ -643,32 +651,36 @@
 
         self._sPageTitle = 'Regenerate All Scheduling Queues';
-        self._sPageBody  = '';
-        aoGroups = SchedGroupLogic(self._oDb).getAll();
-        for oGroup in aoGroups:
-            self._sPageBody += '<h3>%s (ID %#d)</h3>' % (webutils.escapeElem(oGroup.sName), oGroup.idSchedGroup);
-            try:
-                (aoErrors, asMessages) = SchedulerBase.recreateQueue(self._oDb, self._oCurUser.uid, oGroup.idSchedGroup, 2);
-            except Exception as oXcpt:
-                self._oDb.rollback();
-                self._sPageBody += '<p>SchedulerBase.recreateQueue threw an exception: %s</p>' \
-                                % (webutils.escapeElem(str(oXcpt)),);
-                self._sPageBody += cgitb.html(sys.exc_info());
-            else:
-                if len(aoErrors) == 0:
-                    self._sPageBody += '<p>Successfully regenerated.</p>';
+        if not self.isReadOnlyUser():
+            self._sPageBody  = '';
+            aoGroups = SchedGroupLogic(self._oDb).getAll();
+            for oGroup in aoGroups:
+                self._sPageBody += '<h3>%s (ID %#d)</h3>' % (webutils.escapeElem(oGroup.sName), oGroup.idSchedGroup);
+                try:
+                    (aoErrors, asMessages) = SchedulerBase.recreateQueue(self._oDb, self._oCurUser.uid, oGroup.idSchedGroup, 2);
+                except Exception as oXcpt:
+                    self._oDb.rollback();
+                    self._sPageBody += '<p>SchedulerBase.recreateQueue threw an exception: %s</p>' \
+                                    % (webutils.escapeElem(str(oXcpt)),);
+                    self._sPageBody += cgitb.html(sys.exc_info());
                 else:
-                    for oError in aoErrors:
-                        if oError[1] is None:
-                            self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),);
-                        ## @todo links.
-                        #elif isinstance(oError[1], TestGroupData):
-                        #    self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),);
-                        #elif isinstance(oError[1], TestGroupCase):
-                        #    self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),);
-                        else:
-                            self._sPageBody += '<p>%s. [Cannot link to %s]</p>' \
-                                             % (webutils.escapeElem(oError[0]), webutils.escapeElem(str(oError[1])));
-                for sMsg in asMessages:
-                    self._sPageBody += '<p>%s<p>\n' % (webutils.escapeElem(sMsg),);
+                    if len(aoErrors) == 0:
+                        self._sPageBody += '<p>Successfully regenerated.</p>';
+                    else:
+                        for oError in aoErrors:
+                            if oError[1] is None:
+                                self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),);
+                            ## @todo links.
+                            #elif isinstance(oError[1], TestGroupData):
+                            #    self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),);
+                            #elif isinstance(oError[1], TestGroupCase):
+                            #    self._sPageBody += '<p>%s.</p>' % (webutils.escapeElem(oError[0]),);
+                            else:
+                                self._sPageBody += '<p>%s. [Cannot link to %s]</p>' \
+                                                 % (webutils.escapeElem(oError[0]), webutils.escapeElem(str(oError[1])));
+                    for sMsg in asMessages:
+                        self._sPageBody += '<p>%s<p>\n' % (webutils.escapeElem(sMsg),);
+        else:
+            self._sPageBody = webutils.escapeElem('%s is a read only user and may not regenerate the scheduling queues!'
+                                                  % (self._oCurUser.sUsername,));
         return True;
 
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuild.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuild.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuild.py	(revision 65914)
@@ -105,25 +105,27 @@
                     BuildBlacklistData.ksParam_iFirstRevision: oEntry.iRevision,
                     BuildBlacklistData.ksParam_iLastRevision:  oEntry.iRevision }
-        aoActions += [
-            WuiTmLink('Blacklist', WuiAdmin.ksScriptName, dParams),
-            WuiTmLink('Details', WuiAdmin.ksScriptName,
-                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDetails,
-                        BuildData.ksParam_idBuild: oEntry.idBuild,
-                        WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }),
-            WuiTmLink('Clone', WuiAdmin.ksScriptName,
-                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildClone,
-                        BuildData.ksParam_idBuild: oEntry.idBuild,
-                        WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }),
-        ];
-        if isDbTimestampInfinity(oEntry.tsExpire):
+
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
             aoActions += [
-                WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildEdit,
-                            BuildData.ksParam_idBuild: oEntry.idBuild }),
-                WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDoRemove,
-                            BuildData.ksParam_idBuild: oEntry.idBuild },
-                          sConfirm = 'Are you sure you want to remove build #%d?' % (oEntry.idBuild,) ),
+                WuiTmLink('Blacklist', WuiAdmin.ksScriptName, dParams),
+                WuiTmLink('Details', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDetails,
+                            BuildData.ksParam_idBuild: oEntry.idBuild,
+                            WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }),
+                WuiTmLink('Clone', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildClone,
+                            BuildData.ksParam_idBuild: oEntry.idBuild,
+                            WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }),
             ];
+            if isDbTimestampInfinity(oEntry.tsExpire):
+                aoActions += [
+                    WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                              { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildEdit,
+                                BuildData.ksParam_idBuild: oEntry.idBuild }),
+                    WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                              { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDoRemove,
+                                BuildData.ksParam_idBuild: oEntry.idBuild },
+                              sConfirm = 'Are you sure you want to remove build #%d?' % (oEntry.idBuild,) ),
+                ];
 
         return [ oEntry.idBuild,
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildblacklist.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildblacklist.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildblacklist.py	(revision 65914)
@@ -121,6 +121,25 @@
         oEntry = self._aoEntries[iEntry]
 
-        sShortFailReason = \
-            FailureReasonLogic(TMDatabaseConnection()).getById(oEntry.idFailureReason).sShort
+        sShortFailReason = FailureReasonLogic(TMDatabaseConnection()).getById(oEntry.idFailureReason).sShort
+
+        aoActions = [
+            WuiTmLink('Details', WuiAdmin.ksScriptName,
+                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistDetails,
+                        BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting }),
+        ];
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            aoActions += [
+              WuiTmLink('Edit', WuiAdmin.ksScriptName,
+                        { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistEdit,
+                          BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting }),
+              WuiTmLink('Clone', WuiAdmin.ksScriptName,
+                        { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistClone,
+                          BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting,
+                          WuiAdmin.ksParamEffectiveDate: oEntry.tsEffective,  }),
+              WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                        { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistDoRemove,
+                          BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting },
+                        sConfirm = 'Are you sure you want to remove black list entry #%d?' % (oEntry.idBlacklisting,)),
+             ];
 
         return [ oEntry.idBlacklisting,
@@ -132,18 +151,4 @@
                  oEntry.iFirstRevision,
                  oEntry.iLastRevision,
-                 [ WuiTmLink('Details', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistDetails,
-                               BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting }),
-                   WuiTmLink('Edit', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistEdit,
-                               BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting }),
-                   WuiTmLink('Clone', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistClone,
-                               BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting,
-                               WuiAdmin.ksParamEffectiveDate: oEntry.tsEffective,  }),
-                   WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildBlacklistDoRemove,
-                               BuildBlacklistData.ksParam_idBlacklisting: oEntry.idBlacklisting },
-                             sConfirm = 'Are you sure you want to remove black list entry #%d?' % (oEntry.idBlacklisting,)),
-                  ]
+                 aoActions
         ];
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildcategory.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildcategory.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildcategory.py	(revision 65914)
@@ -57,11 +57,14 @@
                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildCategoryDetails,
                         BuildCategoryData.ksParam_idBuildCategory: oEntry.idBuildCategory, }),
-            WuiTmLink('Clone', WuiAdmin.ksScriptName,
-                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildCategoryClone,
-                        BuildCategoryData.ksParam_idBuildCategory: oEntry.idBuildCategory, }),
-            WuiTmLink('Try Remove', WuiAdmin.ksScriptName,
-                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildCategoryDoRemove,
-                        BuildCategoryData.ksParam_idBuildCategory: oEntry.idBuildCategory, }),
         ];
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            aoActions += [
+                WuiTmLink('Clone', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildCategoryClone,
+                            BuildCategoryData.ksParam_idBuildCategory: oEntry.idBuildCategory, }),
+                WuiTmLink('Try Remove', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildCategoryDoRemove,
+                            BuildCategoryData.ksParam_idBuildCategory: oEntry.idBuildCategory, }),
+            ];
 
         sHtml = '<ul class="tmshowall">\n';
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildsource.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildsource.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminbuildsource.py	(revision 65914)
@@ -118,19 +118,22 @@
                         BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc,
                         WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }),
-            WuiTmLink('Clone', WuiAdmin.ksScriptName,
-                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcClone,
-                        BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc,
-                        WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }),
         ];
-        if isDbTimestampInfinity(oEntry.tsExpire):
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
             aoActions += [
-                WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcEdit,
-                            BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc } ),
-                WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcDoRemove,
-                            BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc },
-                          sConfirm = 'Are you sure you want to remove build source #%d?' % (oEntry.idBuildSrc,) )
+                WuiTmLink('Clone', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcClone,
+                            BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc,
+                            WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }),
             ];
+            if isDbTimestampInfinity(oEntry.tsExpire):
+                aoActions += [
+                    WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                              { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcEdit,
+                                BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc } ),
+                    WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                              { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildSrcDoRemove,
+                                BuildSourceData.ksParam_idBuildSrc: oEntry.idBuildSrc },
+                              sConfirm = 'Are you sure you want to remove build source #%d?' % (oEntry.idBuildSrc,) )
+                ];
 
         return [ oEntry.idBuildSrc,
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurecategory.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurecategory.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurecategory.py	(revision 65914)
@@ -121,16 +121,24 @@
         oEntry = self._aoEntries[iEntry]
 
+        aoActions = [
+            WuiTmLink('Details', WuiAdmin.ksScriptName,
+                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryDetails,
+                        FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory }),
+        ];
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            aoActions += [
+                WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryEdit,
+                            FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory }),
+                WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryDoRemove,
+                            FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory },
+                          sConfirm = 'Do you really want to remove failure cateogry #%d?' % (oEntry.idFailureCategory,)),
+            ]
+
         return [ oEntry.idFailureCategory,
                  oEntry.sShort,
                  oEntry.sFull,
-                 [ WuiTmLink('Details', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryDetails,
-                               FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory }),
-                   WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryEdit,
-                               FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory }),
-                   WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureCategoryDoRemove,
-                               FailureCategoryData.ksParam_idFailureCategory: oEntry.idFailureCategory },
-                             sConfirm = 'Do you really want to remove failure cateogry #%d?' % (oEntry.idFailureCategory,)),
-        ] ];
+                 aoActions,
+        ];
+
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurereason.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurereason.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminfailurereason.py	(revision 65914)
@@ -140,4 +140,20 @@
         oEntry = self._aoEntries[iEntry]
 
+        aoActions = [
+            WuiTmLink('Details', WuiAdmin.ksScriptName,
+                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDetails,
+                        FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason } ),
+        ];
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            aoActions += [
+                WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonEdit,
+                            FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason } ),
+                WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDoRemove,
+                            FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason },
+                          sConfirm = 'Are you sure you want to remove failure reason #%d?' % (oEntry.idFailureReason,)),
+            ];
+
         return [ oEntry.idFailureReason,
                  WuiFailureReasonCategoryLink(oEntry.idFailureCategory, sName = oEntry.oCategory.sShort, fBracketed = False),
@@ -146,13 +162,4 @@
                  oEntry.iTicket,
                  oEntry.asUrls,
-                 [ WuiTmLink('Details', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDetails,
-                               FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason } ),
-                   WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonEdit,
-                               FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason } ),
-                   WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDoRemove,
-                               FailureReasonData.ksParam_idFailureReason: oEntry.idFailureReason },
-                             sConfirm = 'Are you sure you want to remove failure reason #%d?' % (oEntry.idFailureReason,)),
-               ] ]
+                 aoActions,
+        ]
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminglobalrsrc.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminglobalrsrc.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminglobalrsrc.py	(revision 65914)
@@ -101,15 +101,20 @@
         oEntry = self._aoEntries[iEntry]
 
+        aoActions = [ ];
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            aoActions += [
+                WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionGlobalRsrcShowEdit,
+                            GlobalResourceData.ksParam_idGlobalRsrc: oEntry.idGlobalRsrc }),
+                WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionGlobalRsrcDel,
+                            GlobalResourceData.ksParam_idGlobalRsrc: oEntry.idGlobalRsrc },
+                          sConfirm = 'Are you sure you want to remove global resource #%d?' % (oEntry.idGlobalRsrc,)),
+            ];
+
         return [ oEntry.idGlobalRsrc,
                  oEntry.sName,
                  oEntry.sDescription,
                  oEntry.fEnabled,
-                 [ WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionGlobalRsrcShowEdit,
-                               GlobalResourceData.ksParam_idGlobalRsrc: oEntry.idGlobalRsrc }),
-                   WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionGlobalRsrcDel,
-                               GlobalResourceData.ksParam_idGlobalRsrc: oEntry.idGlobalRsrc },
-                             sConfirm = 'Are you sure you want to remove global resource #%d?' % (oEntry.idGlobalRsrc,)),
-               ] ]
+                 aoActions, ];
 
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminschedgroup.py	(revision 65914)
@@ -158,18 +158,20 @@
                                   SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup,
                                   WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, } ),];
-        if isDbTimestampInfinity(oEntry.tsExpire):
-            aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupEdit,
-                                         SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup } ));
-        aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName,
-                                   { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupClone,
-                                     SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup,
-                                     WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, } ));
-        if isDbTimestampInfinity(oEntry.tsExpire):
-            aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupDoRemove,
-                                         SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup },
-                                       sConfirm = 'Are you sure you want to remove scheduling group #%d?'
-                                                % (oEntry.idSchedGroup,)));
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+
+            if isDbTimestampInfinity(oEntry.tsExpire):
+                aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                                           { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupEdit,
+                                             SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup } ));
+            aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName,
+                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupClone,
+                                         SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup,
+                                         WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, } ));
+            if isDbTimestampInfinity(oEntry.tsExpire):
+                aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                                           { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupDoRemove,
+                                             SchedGroupData.ksParam_idSchedGroup: oEntry.idSchedGroup },
+                                           sConfirm = 'Are you sure you want to remove scheduling group #%d?'
+                                                    % (oEntry.idSchedGroup,)));
 
         return [
@@ -185,2 +187,3 @@
             aoActions,
         ];
+
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemlog.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemlog.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminsystemlog.py	(revision 65914)
@@ -52,22 +52,22 @@
         oEntry  = self._aoEntries[iEntry];
 
-        if    oEntry.sEvent == SystemLogData.ksEvent_TestBoxUnknown \
-          and oEntry.sLogText.find('addr=') >= 0 \
-          and oEntry.sLogText.find('uuid=') >= 0:
-            sUuid = (oEntry.sLogText[(oEntry.sLogText.find('uuid=') + 5):])[:36];
-            sAddr = (oEntry.sLogText[(oEntry.sLogText.find('addr=') + 5):]).split(' ')[0];
-            oAction = WuiTmLink('Add TestBox', WuiAdmin.ksScriptName,
-                                { WuiAdmin.ksParamAction:         WuiAdmin.ksActionTestBoxAdd,
-                                  TestBoxData.ksParam_uuidSystem: sUuid,
-                                  TestBoxData.ksParam_ip:         sAddr });
+        oAction = ''; # pylint: disable=R0204
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            if    oEntry.sEvent == SystemLogData.ksEvent_TestBoxUnknown \
+              and oEntry.sLogText.find('addr=') >= 0 \
+              and oEntry.sLogText.find('uuid=') >= 0:
+                sUuid = (oEntry.sLogText[(oEntry.sLogText.find('uuid=') + 5):])[:36];
+                sAddr = (oEntry.sLogText[(oEntry.sLogText.find('addr=') + 5):]).split(' ')[0];
+                oAction = WuiTmLink('Add TestBox', WuiAdmin.ksScriptName,
+                                    { WuiAdmin.ksParamAction:         WuiAdmin.ksActionTestBoxAdd,
+                                      TestBoxData.ksParam_uuidSystem: sUuid,
+                                      TestBoxData.ksParam_ip:         sAddr });
 
-        elif oEntry.sEvent == SystemLogData.ksEvent_UserAccountUnknown:
-            sUserName = oEntry.sLogText[oEntry.sLogText.find('(') + 1:
-                                      oEntry.sLogText.find(')')]
-            oAction = WuiTmLink('Add User', WuiAdmin.ksScriptName,
-                                { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserAdd,
-                                  UserAccountData.ksParam_sLoginName: sUserName });
-        else:
-            oAction = ''; # pylint: disable=R0204
+            elif oEntry.sEvent == SystemLogData.ksEvent_UserAccountUnknown:
+                sUserName = oEntry.sLogText[oEntry.sLogText.find('(') + 1:
+                                          oEntry.sLogText.find(')')]
+                oAction = WuiTmLink('Add User', WuiAdmin.ksScriptName,
+                                    { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserAdd,
+                                      UserAccountData.ksParam_sLoginName: sUserName });
 
         return [oEntry.tsCreated, oEntry.sEvent, oEntry.sLogText, oAction];
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py	(revision 65914)
@@ -357,17 +357,18 @@
             ]
 
-        if isDbTimestampInfinity(oEntry.tsExpire):
-            aoActions += [
-                WuiTmLink('Edit', WuiAdmin.ksScriptName,
-                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxEdit,
-                            TestBoxData.ksParam_idTestBox: oEntry.idTestBox, } ),
-                WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxRemovePost,
-                            TestBoxData.ksParam_idTestBox: oEntry.idTestBox },
-                          sConfirm = 'Are you sure that you want to remove %s (%s)?' % (oEntry.sName, oEntry.ip) ),
-            ]
-
-        if oEntry.sOs not in [ 'win', 'os2', ] and oEntry.ip is not None:
-            aoActions.append(WuiLinkBase('ssh', 'ssh://vbox@%s' % (oEntry.ip,),));
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            if isDbTimestampInfinity(oEntry.tsExpire):
+                aoActions += [
+                    WuiTmLink('Edit', WuiAdmin.ksScriptName,
+                              { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxEdit,
+                                TestBoxData.ksParam_idTestBox: oEntry.idTestBox, } ),
+                    WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                              { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxRemovePost,
+                                TestBoxData.ksParam_idTestBox: oEntry.idTestBox },
+                              sConfirm = 'Are you sure that you want to remove %s (%s)?' % (oEntry.sName, oEntry.ip) ),
+                ]
+
+            if oEntry.sOs not in [ 'win', 'os2', ] and oEntry.ip is not None:
+                aoActions.append(WuiLinkBase('ssh', 'ssh://vbox@%s' % (oEntry.ip,),));
 
         return [ self._getCheckBoxColumn(iEntry, oEntry.idTestBox),
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestcase.py	(revision 65914)
@@ -161,16 +161,17 @@
                                 { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDetails,
                                   TestCaseData.ksParam_idGenTestCase: oEntry.idGenTestCase }), ];
-        if isDbTimestampInfinity(oEntry.tsExpire):
-            aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseEdit,
-                                         TestCaseData.ksParam_idTestCase: oEntry.idTestCase }));
-        aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName,
-                                   { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseClone,
-                                     TestCaseData.ksParam_idGenTestCase: oEntry.idGenTestCase }));
-        if isDbTimestampInfinity(oEntry.tsExpire):
-            aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDoRemove,
-                                         TestCaseData.ksParam_idTestCase: oEntry.idTestCase },
-                                       sConfirm = 'Are you sure you want to remove test case #%d?' % (oEntry.idTestCase,)));
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            if isDbTimestampInfinity(oEntry.tsExpire):
+                aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                                           { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseEdit,
+                                             TestCaseData.ksParam_idTestCase: oEntry.idTestCase }));
+            aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName,
+                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseClone,
+                                         TestCaseData.ksParam_idGenTestCase: oEntry.idGenTestCase }));
+            if isDbTimestampInfinity(oEntry.tsExpire):
+                aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                                           { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDoRemove,
+                                             TestCaseData.ksParam_idTestCase: oEntry.idTestCase },
+                                           sConfirm = 'Are you sure you want to remove test case #%d?' % (oEntry.idTestCase,)));
         aoRet.append(aoActions);
 
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestgroup.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestgroup.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestgroup.py	(revision 65914)
@@ -113,5 +113,7 @@
                                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseEdit,
                                        TestCaseData.ksParam_idTestCase: oMember.oTestCase.idTestCase, } ).toHtml()
-                           if isDbTimestampInfinity(oMember.oTestCase.tsExpire) else '',
+                           if     isDbTimestampInfinity(oMember.oTestCase.tsExpire)
+                              and self._oDisp is not None
+                              and not self._oDisp.isReadOnlyUser() else '',
                            );
 
@@ -155,21 +157,23 @@
                                   TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup,
                                   WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }) ];
-        if isDbTimestampInfinity(oEntry.tsExpire):
-            aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupEdit,
-                                         TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup }));
-            aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName,
-                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupClone,
-                                         TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup,
-                                         WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }));
-            aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupDoRemove,
-                                         TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup },
-                                       sConfirm = 'Do you really want to remove test group #%d?' % (oEntry.idTestGroup,)));
-        else:
-            aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName,
-                                       { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupClone,
-                                         TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup,
-                                         WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }));
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+
+            if isDbTimestampInfinity(oEntry.tsExpire):
+                aoActions.append(WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                                           { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupEdit,
+                                             TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup }));
+                aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName,
+                                           { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupClone,
+                                             TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup,
+                                             WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }));
+                aoActions.append(WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                                           { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupDoRemove,
+                                             TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup },
+                                           sConfirm = 'Do you really want to remove test group #%d?' % (oEntry.idTestGroup,)));
+            else:
+                aoActions.append(WuiTmLink('Clone', WuiAdmin.ksScriptName,
+                                           { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestGroupClone,
+                                             TestGroupData.ksParam_idTestGroup: oEntry.idTestGroup,
+                                             WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, }));
 
 
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminuseraccount.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminuseraccount.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuiadminuseraccount.py	(revision 65914)
@@ -57,4 +57,5 @@
         oForm.addText(       UserAccountData.ksParam_sFullName,   oData.sFullName,   'Full name')
         oForm.addText(       UserAccountData.ksParam_sEmail,      oData.sEmail,      'E-mail')
+        oForm.addCheckBox(   UserAccountData.ksParam_fReadOnly,   oData.fReadOnly,   'Only read access')
         if self._sMode != WuiFormContentBase.ksMode_Show:
             oForm.addSubmit('Add User' if self._sMode == WuiFormContentBase.ksMode_Add else 'Change User');
@@ -71,19 +72,29 @@
                                     sTitle = 'Registered User Accounts', sId = 'users', fnDPrint = fnDPrint, oDisp = oDisp,
                                     aiSelectedSortColumns = aiSelectedSortColumns);
-        self._asColumnHeaders = ['User ID', 'Name', 'E-mail', 'Full Name', 'Login Name', 'Actions'];
+        self._asColumnHeaders = ['User ID', 'Name', 'E-mail', 'Full Name', 'Login Name', 'Access', 'Actions'];
         self._asColumnAttribs = ['align="center"', 'align="center"', 'align="center"', 'align="center"', 'align="center"',
-                                 'align="center"'];
+                                 'align="center"', 'align="center"', ];
 
     def _formatListEntry(self, iEntry):
         from testmanager.webui.wuiadmin import WuiAdmin;
         oEntry  = self._aoEntries[iEntry];
+        aoActions = [
+            WuiTmLink('Details', WuiAdmin.ksScriptName,
+                      { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserDetails,
+                        UserAccountData.ksParam_uid: oEntry.uid } ),
+        ];
+        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
+            aoActions += [
+                WuiTmLink('Modify', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserEdit,
+                            UserAccountData.ksParam_uid: oEntry.uid } ),
+                WuiTmLink('Remove', WuiAdmin.ksScriptName,
+                          { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserDelPost,
+                            UserAccountData.ksParam_uid: oEntry.uid },
+                          sConfirm = 'Are you sure you want to remove user #%d?' % (oEntry.uid,)),
+            ];
+
         return [ oEntry.uid, oEntry.sUsername, oEntry.sEmail, oEntry.sFullName, oEntry.sLoginName,
-                 [ WuiTmLink('Modify', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserEdit,
-                               UserAccountData.ksParam_uid: oEntry.uid } ),
-                   WuiTmLink('Remove', WuiAdmin.ksScriptName,
-                             { WuiAdmin.ksParamAction: WuiAdmin.ksActionUserDelPost,
-                               UserAccountData.ksParam_uid: oEntry.uid },
-                             sConfirm = 'Are you sure you want to remove user #%d?' % (oEntry.uid,)),
-               ] ];
+                 'read only' if oEntry.fReadOnly else 'full',
+                 aoActions, ];
 
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuibase.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuibase.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuibase.py	(revision 65914)
@@ -729,4 +729,13 @@
 
     #
+    # User related stuff.
+    #
+
+    def isReadOnlyUser(self):
+        """ Returns true if the logged in user is read-only or if no user is logged in. """
+        return self._oCurUser is None or self._oCurUser.fReadOnly;
+
+
+    #
     # Debugging
     #
@@ -910,4 +919,6 @@
 
         try:
+            if self.isReadOnlyUser():
+                raise Exception('"%s" is a read only user!' % (self._oCurUser.sUsername,));
             self._sPageTitle  = None
             self._sPageBody   = None
@@ -1045,5 +1056,12 @@
             enmValidateFor = oData.ksValidateFor_Edit;
         dErrors = oData.validateAndConvert(self._oDb, enmValidateFor);
-        if len(dErrors) == 0:
+
+        # Check that the user can do this.
+        sErrorMsg = None;
+        assert self._oCurUser is not None;
+        if self.isReadOnlyUser():
+            sErrorMsg = 'User %s is not allowed to modify anything!' % (self._oCurUser.sUsername,)
+
+        if len(dErrors) == 0 and sErrorMsg is None:
             oData.convertFromParamNull();
 
@@ -1069,5 +1087,5 @@
             oForm = oFormType(oData, sMode, oDisp = self);
             oForm.setRedirectTo(sRedirectTo);
-            (self._sPageTitle, self._sPageBody) = oForm.showForm(dErrors = dErrors);
+            (self._sPageTitle, self._sPageBody) = oForm.showForm(dErrors = dErrors, sErrorMsg = sErrorMsg);
         return True;
 
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py	(revision 65914)
@@ -415,5 +415,5 @@
         self._sTitle        = sTitle;
         self._sId           = sId if sId is not None else (type(oData).__name__.lower() + 'form');
-        self._fEditable     = fEditable;
+        self._fEditable     = fEditable and (oDisp is None or not oDisp.isReadOnlyUser())
         self._sSubmitAction = sSubmitAction;
         if sSubmitAction is None and sMode != self.ksMode_Show:
Index: /trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py	(revision 65913)
+++ /trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py	(revision 65914)
@@ -175,5 +175,5 @@
         # Format bits for adding or editing the failure reason.  Level 0 is handled at the top of the page.
         sChangeReason = '';
-        if oTestResult.cErrors > 0 and iDepth > 0:
+        if oTestResult.cErrors > 0 and iDepth > 0 and self._oDisp is not None and not self._oDisp.isReadOnlyUser():
             dTmp = {
                 self._oDisp.ksParamAction: self._oDisp.ksActionTestResultFailureAdd if oTestResult.oReason is None else
@@ -406,6 +406,7 @@
                                             WuiMain.ksActionTestResultFailureAddPost if oData is None else
                                             WuiMain.ksActionTestResultFailureEditPost )
+            fReadOnly = not self._oDisp or self._oDisp.isReadOnlyUser();
             oForm = WuiHlpForm('failure-reason', sFormActionUrl,
-                               sOnSubmit = WuiHlpForm.ksOnSubmit_AddReturnToFieldWithCurrentUrl);
+                               sOnSubmit = WuiHlpForm.ksOnSubmit_AddReturnToFieldWithCurrentUrl, fReadOnly = fReadOnly);
             oForm.addTextHidden(TestResultFailureData.ksParam_idTestResult, oTestResultTree.idTestResult);
             oForm.addTextHidden(TestResultFailureData.ksParam_idTestSet, oTestSet.idTestSet);
@@ -414,5 +415,6 @@
                                   aoFailureReasons,
                                   sPostHtml = u' ' + WuiFailureReasonDetailsLink(oData.idFailureReason).toHtml()
-                                            + u' ' + WuiFailureReasonAddLink('New', fBracketed = False).toHtml());
+                                            + (u' ' + WuiFailureReasonAddLink('New', fBracketed = False).toHtml()
+                                               if not fReadOnly else u''));
                 oForm.addMultilineText(TestResultFailureData.ksParam_sComment, oData.sComment, 'Comment')
 
@@ -429,5 +431,5 @@
             else:
                 oForm.addComboBox(TestResultFailureData.ksParam_idFailureReason, -1, 'Reason', aoFailureReasons,
-                                  sPostHtml = ' ' + WuiFailureReasonAddLink('New').toHtml());
+                                  sPostHtml = ' ' + WuiFailureReasonAddLink('New').toHtml() if not fReadOnly else '');
                 oForm.addMultilineText(TestResultFailureData.ksParam_sComment, '', 'Comment');
                 oForm.addTextHidden(TestResultFailureData.ksParam_tsEffective, '');
