FELIX-3769 Add support for top navigation categories
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1416231 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
index 16d8e56..9162f60 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
@@ -114,6 +114,24 @@
/**
+ * This method should return category string which will be used to render
+ * the plugin in the navigation menu. Default implementation returns null,
+ * which will result in the plugin link rendered as top level menu item.
+ * Concrete implementations wishing to be rendered as a sub-menu item under
+ * a category should override this method and return a string or define
+ * <code>felix.webconsole.category</code> OSGi property. Currently only
+ * single level categories are supported. So, this should be a simple
+ * String.
+ *
+ * @return category
+ */
+ public String getCategory()
+ {
+ return null;
+ }
+
+
+ /**
* Renders the web console page for the request. This consist of the
* following five parts called in order:
* <ol>
@@ -610,61 +628,18 @@
protected void renderTopNavigation( HttpServletRequest request, PrintWriter pw )
{
// assume pathInfo to not be null, else this would not be called
- boolean linkToCurrent = true;
String current = request.getPathInfo();
int slash = current.indexOf( "/", 1 ); //$NON-NLS-1$
if ( slash < 0 )
{
slash = current.length();
- linkToCurrent = false;
}
current = current.substring( 1, slash );
- boolean disabled = false;
String appRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
- Map labelMap = ( Map ) request.getAttribute( WebConsoleConstants.ATTR_LABEL_MAP );
- if ( labelMap != null )
- {
+ Map menuMap = ( Map ) request.getAttribute( WebConsoleConstants.ATTR_LABEL_MAP );
- // prepare the navigation
- SortedMap map = new TreeMap( String.CASE_INSENSITIVE_ORDER );
- for ( Iterator ri = labelMap.entrySet().iterator(); ri.hasNext(); )
- {
- Map.Entry labelMapEntry = ( Map.Entry ) ri.next();
- if ( labelMapEntry.getKey() == null )
- {
- // ignore renders without a label
- }
- else if ( disabled || current.equals( labelMapEntry.getKey() ) )
- {
- if ( linkToCurrent )
- {
- map.put( labelMapEntry.getValue(), "<div class='ui-state-active'><a href='" + appRoot + "/" //$NON-NLS-1$ //$NON-NLS-2$
- + labelMapEntry.getKey() + "'>" + labelMapEntry.getValue() + "</a></div>"); //$NON-NLS-1$ //$NON-NLS-2$
- }
- else
- {
- map.put( labelMapEntry.getValue(), "<div class='ui-state-active'><span>" + labelMapEntry.getValue() //$NON-NLS-1$
- + "</span></div>"); //$NON-NLS-1$
- }
- }
- else
- {
- map.put( labelMapEntry.getValue(), "<div class='ui-state-default'><a href='" + appRoot + "/" + labelMapEntry.getKey() + "'>" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- + labelMapEntry.getValue() + "</a></div>"); //$NON-NLS-1$
- }
- }
-
- // render the navigation
- pw.println("<div id='technav' class='ui-widget ui-widget-header'>"); //$NON-NLS-1$
- for ( Iterator li = map.values().iterator(); li.hasNext(); )
- {
- pw.print(' ');
- pw.println( li.next() );
- }
- pw.println( "</div>" ); //$NON-NLS-1$
-
- }
+ this.renderMenu( menuMap, appRoot, pw );
// render lang-box
Map langMap = (Map) request.getAttribute(WebConsoleConstants.ATTR_LANG_MAP);
@@ -705,6 +680,49 @@
}
}
+
+ protected void renderMenu( Map menuMap, String appRoot, PrintWriter pw )
+ {
+ if ( menuMap != null )
+ {
+ pw.println( "<ul id=\"navmenu\">" );
+ renderSubmenu( menuMap, appRoot, pw, 0 );
+ pw.println( "</ul>" );
+ }
+ }
+
+
+ private void renderMenu( Map menuMap, String appRoot, PrintWriter pw, int level )
+ {
+ pw.println( "<ul class=\"navMenuLevel-" + level + "\">" );
+ renderSubmenu( menuMap, appRoot, pw, level );
+ pw.println( "</ul>" );
+ }
+
+
+ private void renderSubmenu( Map menuMap, String appRoot, PrintWriter pw, int level )
+ {
+ String liStyleClass = " class=\"navMenuItem-" + level + "\"";
+ Iterator itr = menuMap.keySet().iterator();
+ while ( itr.hasNext() )
+ {
+ String key = ( String ) itr.next();
+ if ( key.startsWith( "category." ) )
+ {
+ pw.println( "<li" + liStyleClass + "><a href=\"#\">" + key.substring( key.indexOf( '.' ) + 1 ) + "</a>" );
+ renderMenu( ( Map ) menuMap.get( key ), appRoot, pw, level + 1 );
+ pw.println( "</li>" );
+ }
+ else
+ {
+ String label = key;
+ String title = ( String ) menuMap.get( label );
+ pw.println( "<li" + liStyleClass + "><a href=\"" + appRoot + "/" + label + "\">" + title + "</a></li>" );
+ }
+ }
+ }
+
+
private static final void printLocaleElement(PrintWriter pw, String appRoot,
Object langCode, Object langName)
{
@@ -847,7 +865,6 @@
return FOOTER;
}
-
/**
* Reads the <code>templateFile</code> as a resource through the class
* loader of this class converting the binary data into a string using
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/SimpleWebConsolePlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/SimpleWebConsolePlugin.java
index 8bb3a60..6195ed2 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/SimpleWebConsolePlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/SimpleWebConsolePlugin.java
@@ -48,6 +48,7 @@
// used for standard AbstractWebConsolePlugin implementation
private final String label;
private final String title;
+ private final String category;
private final String css[];
private final String labelRes;
private final int labelResLen;
@@ -61,7 +62,8 @@
/**
- * Creates new Simple Web Console Plugin.
+ * Creates new Simple Web Console Plugin with the default category
+ * ({@code null})
*
* @param label the front label. See
* {@link AbstractWebConsolePlugin#getLabel()}
@@ -72,6 +74,24 @@
*/
public SimpleWebConsolePlugin( String label, String title, String css[] )
{
+ this( label, title, null, css );
+ }
+
+
+ /**
+ * Creates new Simple Web Console Plugin with the given category.
+ *
+ * @param label the front label. See
+ * {@link AbstractWebConsolePlugin#getLabel()}
+ * @param title the plugin title . See
+ * {@link AbstractWebConsolePlugin#getTitle()}
+ * @param category the plugin's navigation category. See
+ * {@link AbstractWebConsolePlugin#getCategory()}
+ * @param css the additional plugin CSS. See
+ * {@link AbstractWebConsolePlugin#getCssReferences()}
+ */
+ public SimpleWebConsolePlugin( String label, String title, String category, String css[] )
+ {
if ( label == null )
{
throw new NullPointerException( "Null label" );
@@ -82,6 +102,7 @@
}
this.label = label;
this.title = title;
+ this.category = category;
this.css = css;
this.labelRes = '/' + label + '/';
this.labelResLen = labelRes.length() - 1;
@@ -107,6 +128,15 @@
/**
+ * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#getCategory()
+ */
+ public String getCategory()
+ {
+ return category;
+ }
+
+
+ /**
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#getCssReferences()
*/
protected final String[] getCssReferences()
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
index e4d3025..47168fc 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/WebConsoleConstants.java
@@ -62,6 +62,27 @@
public static final String PLUGIN_TITLE = "felix.webconsole.title"; //$NON-NLS-1$
/**
+ * The category under which the OSGi Manager plugin is listed in the top
+ * navigation by the OSGi Manager (value is "felix.webconsole.category").
+ * <p>
+ * For {@link #SERVICE_NAME Servlet} services not extending the
+ * {@link AbstractWebConsolePlugin} this property is required to declare a
+ * specific category. Otherwise the plugin is put into the default category.
+ * <p>
+ * For {@link #SERVICE_NAME Servlet} services extending from the
+ * {@link AbstractWebConsolePlugin} abstract class this property is not
+ * technically required. To support lazy service access with categorization,
+ * e.g. for plugins implemented using the OSGi <i>Service Factory</i>
+ * pattern, the use of this service registration property is strongly
+ * encouraged. If the property is missing the
+ * {@link AbstractWebConsolePlugin#getCategory()} is called which should be
+ * overritten.
+ *
+ * @since 3.1.3; Web Console Bundle 4.0.2
+ */
+ public static final String PLUGIN_CATEGORY = "felix.webconsole.category"; //$NON-NLS-1$
+
+ /**
* The property marking a service as a configuration printer.
* This can be any service having either a printConfiguration(PrintWriter)
* or printConfiguration(PrintWriter, String) method - this is according
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java
index c34d338..35bdf04 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/OsgiManagerPlugin.java
@@ -31,6 +31,17 @@
{
/**
+ * Category used for Web Console specific plugins.
+ */
+ public static final String CATEGORY_OSGI_MANAGER = "Web Console";
+
+ /**
+ * Category used for Web Console plugins related to OSGi support such
+ * as bundles, configurations, etc.
+ */
+ public static final String CATEGORY_OSGI = "OSGi";
+
+ /**
* This method is called from the Felix Web Console to ensure the
* AbstractWebConsolePlugin is correctly setup.
*
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
index 4e4ece5..bc5b98c 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
@@ -99,7 +99,7 @@
/** Default constructor */
public ConfigManager()
{
- super(LABEL, TITLE, CSS);
+ super(LABEL, TITLE, CATEGORY_OSGI, CSS);
// load templates
TEMPLATE = readTemplateFile( "/templates/config.html" ); //$NON-NLS-1$
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
index 8af2928..81f04f3 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
@@ -43,9 +43,9 @@
abstract class ConfigManagerBase extends SimpleWebConsolePlugin implements OsgiManagerPlugin
{
- ConfigManagerBase(String label, String title, String[] css)
+ ConfigManagerBase(String label, String title, String category, String[] css)
{
- super(label, title, css);
+ super(label, title, category, css);
}
private static final long serialVersionUID = -6691093960031418130L;
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/LogServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/LogServlet.java
index 9853b21..33609cf 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/LogServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/LogServlet.java
@@ -57,7 +57,7 @@
/** Default constructor */
public LogServlet()
{
- super(LABEL, TITLE, CSS);
+ super(LABEL, TITLE, CATEGORY_OSGI_MANAGER, CSS);
// load templates
TEMPLATE = readTemplateFile( "/templates/logs.html" ); //$NON-NLS-1$
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
index a24700a..fe741db 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java
@@ -125,7 +125,7 @@
/** Default constructor */
public BundlesServlet()
{
- super(NAME, TITLE, CSS);
+ super(NAME, TITLE, CATEGORY_OSGI, CSS);
// load templates
TEMPLATE_MAIN = readTemplateFile( "/templates/bundles.html" );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
index 2a41111..60f45f6 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/ServicesServlet.java
@@ -114,20 +114,20 @@
/** Default constructor */
public ServicesServlet() {
- super(LABEL, TITLE, CSS);
+ super(LABEL, TITLE, CATEGORY_OSGI, CSS);
// load templates
TEMPLATE = readTemplateFile( "/templates/services.html" ); //$NON-NLS-1$
}
-
+
private ServiceRegistration bipReg;
-
- public void activate(BundleContext bundleContext)
+
+ public void activate(BundleContext bundleContext)
{
super.activate(bundleContext);
bipReg = new ServicesUsedInfoProvider( bundleContext.getBundle() ).register( bundleContext );
}
-
+
public void deactivate() {
if ( null != bipReg )
{
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
index e000fac..5797fe9 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ConfigurationRender.java
@@ -73,7 +73,7 @@
/** Default constructor */
public ConfigurationRender( final ResourceBundleManager resourceBundleManager )
{
- super( LABEL, TITLE, CSS_REFS );
+ super( LABEL, TITLE, CATEGORY_OSGI_MANAGER, CSS_REFS );
this.resourceBundleManager = resourceBundleManager;
}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
index ca5c742..1e2bd07 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/LicenseServlet.java
@@ -69,7 +69,7 @@
*/
public LicenseServlet()
{
- super(LABEL, TITLE, CSS);
+ super(LABEL, TITLE, CATEGORY_OSGI_MANAGER, CSS);
// load templates
TEMPLATE = readTemplateFile( "/templates/license.html" );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
index ade46d8..e3a0970 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
@@ -19,12 +19,15 @@
package org.apache.felix.webconsole.internal.servlet;
+import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.ResourceBundle;
+import java.util.SortedMap;
+import java.util.TreeMap;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
@@ -234,16 +237,30 @@
*/
Map getLocalizedLabelMap( final ResourceBundleManager resourceBundleManager, final Locale locale )
{
- final Map map = new HashMap();
+ final SortedMap map = new TreeMap( MENU_ITEM_ORDER );
Plugin[] plugins = getPlugins();
for ( int i = 0; i < plugins.length; i++ )
{
final Plugin plugin = plugins[i];
- if (!plugin.isEnabled()) {
+ if ( !plugin.isEnabled() )
+ {
continue;
}
+ // support only one level for now
+ SortedMap categoryMap = null;
+ String category = plugin.getCategory();
+ if ( category == null || category.trim().length() == 0 )
+ {
+ // TODO: FELIX-3769; decide whether hard code or configurable
+ category = "Main";
+ }
+
+ // TODO: FELIX-3769; translate the Category
+
+ categoryMap = findCategoryMap( map, category );
+
final String label = plugin.getLabel();
String title = plugin.getTitle();
if ( title.startsWith( "%" ) )
@@ -259,13 +276,39 @@
/* ignore missing resource - use default title */
}
}
- map.put( label, title );
+ categoryMap.put( label, title );
}
return map;
}
+ private SortedMap findCategoryMap( Map map, String categoryPath )
+ {
+ SortedMap categoryMap = null;
+ Map searchMap = map;
+
+ String categories[] = categoryPath.split( "/" );
+
+ for ( int i = 0; i < categories.length; i++ )
+ {
+ String categoryKey = "category." + categories[i];
+ if ( searchMap.containsKey( categoryKey ) )
+ {
+ categoryMap = ( SortedMap ) searchMap.get( categoryKey );
+ }
+ else
+ {
+ categoryMap = new TreeMap( MENU_ITEM_ORDER );
+ searchMap.put( categoryKey, categoryMap );
+ }
+ searchMap = categoryMap;
+ }
+
+ return categoryMap;
+ }
+
+
/**
* Returns the bundle context of the Web Console itself.
* @return the bundle context of the Web Console itself.
@@ -416,6 +459,26 @@
return null;
}
+ /**
+ * A comparator that will compare plugin menu items to other menu items including categories.
+ */
+ private static final Comparator MENU_ITEM_ORDER = new Comparator() {
+
+ public int compare(Object o1, Object o2) {
+ String s1 = getLabel(o1.toString());
+ String s2 = getLabel(o2.toString());
+ return s1.compareToIgnoreCase(s2);
+ }
+
+ private String getLabel(String s) {
+ if(s.startsWith("category."))
+ return s.substring(s.indexOf('.')+1);
+ else
+ return s;
+ }
+
+ };
+
private static class Plugin implements ServletConfig
{
private final PluginHolder holder;
@@ -423,7 +486,6 @@
private String title;
private AbstractWebConsolePlugin consolePlugin;
-
protected Plugin( final PluginHolder holder, final String label )
{
this.holder = holder;
@@ -515,7 +577,6 @@
return title;
}
-
protected String doGetTitle()
{
// get the service now
@@ -527,6 +588,17 @@
return ( consolePlugin != null ) ? consolePlugin.getTitle() : null;
}
+ // methods added to support categories
+
+ final String getCategory() {
+ return doGetCategory();
+ }
+
+ protected String doGetCategory() {
+ // get the service now
+ final AbstractWebConsolePlugin consolePlugin = getConsolePlugin();
+ return ( consolePlugin != null ) ? consolePlugin.getCategory() : null;
+ }
final AbstractWebConsolePlugin getConsolePlugin()
{
@@ -643,6 +715,18 @@
return super.doGetTitle();
}
+ // added to support categories
+ protected String doGetCategory() {
+ // check service Reference
+ final String category = getProperty( serviceReference, WebConsoleConstants.PLUGIN_CATEGORY );
+ if ( category != null )
+ {
+ return category;
+ }
+
+ return super.doGetCategory();
+ }
+
protected AbstractWebConsolePlugin doGetConsolePlugin()
{
Object service = getHolder().getBundleContext().getService( serviceReference );
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
index 6da1852..6df41ac 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/system/VMStatPlugin.java
@@ -73,7 +73,7 @@
/** Default constructor */
public VMStatPlugin()
{
- super( LABEL, TITLE, CSS );
+ super( LABEL, TITLE, CATEGORY_OSGI_MANAGER, CSS );
// load templates
TPL_VM_MAIN = readTemplateFile( "/templates/vmstat.html" ); //$NON-NLS-1$
@@ -201,7 +201,7 @@
DateFormat format = DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG, request.getLocale() );
final String startTime = format.format( new Date( startDate ) );
final String upTime = formatPeriod( System.currentTimeMillis() - startDate );
-
+
JSONObject json = new JSONObject();
try
{
@@ -236,7 +236,7 @@
response.getWriter().print( body );
}
-
+
private static final String sysProp( String name )
{
String ret = System.getProperty( name );
diff --git a/webconsole/src/main/resources/res/ui/webconsole.css b/webconsole/src/main/resources/res/ui/webconsole.css
index fb1510a..7b1ce8a 100644
--- a/webconsole/src/main/resources/res/ui/webconsole.css
+++ b/webconsole/src/main/resources/res/ui/webconsole.css
@@ -66,6 +66,17 @@
white-space: nowrap;
}
+/* New Category Based Navigation Tree (FELIX-3769) */
+#navmenu {
+ width: auto;
+}
+#navmenu li.ui-menu-item {
+ width: auto;
+}
+#navmenu li.navMenuItem-0 {
+ display: inline-block;
+}
+
/* CENTRAL CONTENT AREA STYLING */
#content, .ui-widget, .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-size: 8pt }
diff --git a/webconsole/src/main/resources/templates/main_header.html b/webconsole/src/main/resources/templates/main_header.html
index fdf6eff..b6ddbfa 100644
--- a/webconsole/src/main/resources/templates/main_header.html
+++ b/webconsole/src/main/resources/templates/main_header.html
@@ -24,8 +24,22 @@
<script src="${appRoot}/res/lib/jquery.cookies-2.2.0.js" type="text/javascript"></script>
<script src="${appRoot}/res/lib/jquery.tablesorter-2.0.3.js" type="text/javascript"></script>
<script src="${appRoot}/res/lib/support.js" type="text/javascript"></script>
-
-
+
+ <script type="text/javascript">
+ // <![CDATA[
+ $(document).ready(function(){
+ $( '#navmenu' ).menu({
+ position: {
+ my: "left top",
+ at: "left bottom",
+ submenu: "ui-icon-carat-1-s"
+ }
+ });
+ });
+
+ // ]]>
+ </script>
+
<!-- FELIX-2188: backwards compatibility CSS -->
<link href="${appRoot}/res/ui/admin_compat.css" rel="stylesheet" type="text/css" />
</head>