Security improvements. Split pages to number of items:
BasePage - for resource references and keeping same css/overall layout
SecuredPage - for authenticated users with admin role
SidebarPage - for pages with sub menu
SinglePage - for pages without submenu on left side

git-svn-id: https://svn.apache.org/repos/asf/karaf/sandbox/webconsole/trunk@1164478 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/blueprint/src/main/java/org/apache/karaf/webconsole/blueprint/internal/BlueprintPage.java b/blueprint/src/main/java/org/apache/karaf/webconsole/blueprint/internal/BlueprintPage.java
index 62fb04b..2732417 100644
--- a/blueprint/src/main/java/org/apache/karaf/webconsole/blueprint/internal/BlueprintPage.java
+++ b/blueprint/src/main/java/org/apache/karaf/webconsole/blueprint/internal/BlueprintPage.java
@@ -19,13 +19,13 @@
 import java.util.List;
 
 import org.apache.karaf.webconsole.blueprint.internal.view.BlueprintDataTable;
-import org.apache.karaf.webconsole.core.BasePage;
+import org.apache.karaf.webconsole.core.page.SinglePage;
 import org.ops4j.pax.wicket.api.PaxWicketBean;
 import org.ops4j.pax.wicket.api.PaxWicketMountPoint;
 import org.osgi.framework.ServiceReference;
 
 @PaxWicketMountPoint(mountPoint = "/osgi/blueprint")
-public class BlueprintPage extends BasePage {
+public class BlueprintPage extends SinglePage {
 
     @PaxWicketBean(name = "containers")
     private List<ServiceReference> containers;
diff --git a/blueprint/src/main/java/org/apache/karaf/webconsole/blueprint/internal/details/DetailsPage.java b/blueprint/src/main/java/org/apache/karaf/webconsole/blueprint/internal/details/DetailsPage.java
index e2912f6..d42a5b3 100644
--- a/blueprint/src/main/java/org/apache/karaf/webconsole/blueprint/internal/details/DetailsPage.java
+++ b/blueprint/src/main/java/org/apache/karaf/webconsole/blueprint/internal/details/DetailsPage.java
@@ -22,7 +22,7 @@
 import java.util.Collection;
 import java.util.List;
 
-import org.apache.karaf.webconsole.core.BasePage;
+import org.apache.karaf.webconsole.core.page.SinglePage;
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.list.ListItem;
@@ -38,7 +38,7 @@
 import org.osgi.service.blueprint.reflect.ComponentMetadata;
 
 @PaxWicketMountPoint(mountPoint = "/osgi/blueprint/details")
-public class DetailsPage extends BasePage {
+public class DetailsPage extends SinglePage {
 
     @PaxWicketBean(name = "blueprintBundleContext")
     private BundleContext context;
diff --git a/core/pom.xml b/core/pom.xml
index f6d1862..5de5abc 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -56,6 +56,11 @@
             <version>${ops4j.paxwicket.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.karaf.jaas</groupId>
+            <artifactId>org.apache.karaf.jaas.modules</artifactId>
+            <version>${karaf.version}</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -79,6 +84,7 @@
                             org.apache.wicket.request,
                             org.apache.wicket.ajax,
                             org.apache.wicket.markup.html.link,
+                            org.apache.wicket.protocol.http,
 
                             <!-- then wicket stuff -->
                             org.ops4j.pax.wicket.api,
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/BasePage.java b/core/src/main/java/org/apache/karaf/webconsole/core/BasePage.java
index 1444c95..6f15fcf 100644
--- a/core/src/main/java/org/apache/karaf/webconsole/core/BasePage.java
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/BasePage.java
@@ -1,19 +1,3 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
 package org.apache.karaf.webconsole.core;
 
 import java.util.Arrays;
@@ -22,25 +6,18 @@
 
 import org.apache.karaf.webconsole.core.brand.BrandProvider;
 import org.apache.karaf.webconsole.core.internal.LanguagePanel;
-import org.apache.karaf.webconsole.core.navigation.ConsoleTabProvider;
-import org.apache.karaf.webconsole.core.navigation.markup.NavigationPanel;
 import org.apache.wicket.behavior.IBehavior;
 import org.apache.wicket.markup.html.CSSPackageResource;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.panel.FeedbackPanel;
 import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.util.ListModel;
 import org.ops4j.pax.wicket.api.PaxWicketBean;
 
 public class BasePage extends WebPage {
 
-    @PaxWicketBean(name = "tabs")
-    private List<ConsoleTabProvider> tabs;
-
     @PaxWicketBean(name = "brandProvider")
-    private BrandProvider brandProvider;
+    protected BrandProvider brandProvider;
 
     // list of supported Locales - should be replaced by resolver/detector or something similar
     private IModel<List<Locale>> supportedLocales = new ListModel<Locale>(Arrays.asList(Locale.FRENCH, Locale.ENGLISH));
@@ -56,22 +33,8 @@
 
         add(new LanguagePanel("languagePanel", supportedLocales));
 
-       add(new NavigationPanel("navigationPanel", new LoadableDetachableModel<List<ConsoleTabProvider>>() {
-            @Override
-            protected List<ConsoleTabProvider> load() {
-                return tabs;
-            }
-        }));
-
         for (IBehavior behavior : brandProvider.getBehaviors()) {
             add(behavior);
         }
-
-        add(new FeedbackPanel("feedback"));
     }
-
-    public void get() {
-
-    }
-
 }
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/dashboard/DashboardPage.java b/core/src/main/java/org/apache/karaf/webconsole/core/dashboard/DashboardPage.java
index f54931d..d3f8217 100644
--- a/core/src/main/java/org/apache/karaf/webconsole/core/dashboard/DashboardPage.java
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/dashboard/DashboardPage.java
@@ -18,21 +18,24 @@
 
 import java.util.List;
 
-import org.apache.karaf.webconsole.core.BasePage;
+import org.apache.karaf.webconsole.core.page.SinglePage;
 import org.apache.karaf.webconsole.core.widget.WidgetProvider;
+import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.CSSPackageResource;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.model.util.ListModel;
 import org.ops4j.pax.wicket.api.PaxWicketBean;
+import org.ops4j.pax.wicket.api.PaxWicketMountPoint;
 
-public class DashboardPage extends BasePage {
+@PaxWicketMountPoint(mountPoint = "/dashboard")
+public class DashboardPage extends SinglePage {
 
     @PaxWicketBean(name = "widgets")
     private List<WidgetProvider> widgets;
 
-    public DashboardPage() {
+    public DashboardPage(PageParameters parameters) {
         add(CSSPackageResource.getHeaderContribution(DashboardPage.class, "dashboard.css"));
 
         add(new Label("noWidgets", "So far there is no widgets to display") {
@@ -48,7 +51,10 @@
                 item.add(item.getModelObject().getWidgetPanel("widget"));
             }
         });
+    }
 
+    public DashboardPage() {
+        this(null);
     }
 
 }
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/internal/WebConsoleApplication.java b/core/src/main/java/org/apache/karaf/webconsole/core/internal/WebConsoleApplication.java
index dfdd708..a6baede 100644
--- a/core/src/main/java/org/apache/karaf/webconsole/core/internal/WebConsoleApplication.java
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/internal/WebConsoleApplication.java
@@ -17,17 +17,35 @@
 package org.apache.karaf.webconsole.core.internal;
 
 import org.apache.karaf.webconsole.core.dashboard.DashboardPage;
-import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.karaf.webconsole.core.page.LoginPage;
+import org.apache.karaf.webconsole.core.security.JaasWebSession;
+import org.apache.wicket.authentication.AuthenticatedWebApplication;
+import org.apache.wicket.authentication.AuthenticatedWebSession;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.pages.AccessDeniedPage;
+import org.apache.wicket.markup.html.pages.PageExpiredErrorPage;
 
 /**
  * Root class for wicket.
  */
-public class WebConsoleApplication extends WebApplication {
+public class WebConsoleApplication extends AuthenticatedWebApplication {
 
     public WebConsoleApplication() {
         super();
     }
 
+    @Override
+    protected void init() {
+        super.init();
+
+        mountBookmarkablePage("/login", LoginPage.class);
+        mountBookmarkablePage("/error/401", AccessDeniedPage.class);
+        mountBookmarkablePage("/error/404", PageExpiredErrorPage.class);
+
+        getApplicationSettings().setAccessDeniedPage(AccessDeniedPage.class);
+        getApplicationSettings().setPageExpiredErrorPage(PageExpiredErrorPage.class);
+    }
+
     /**
      * @see org.apache.wicket.Application#getHomePage()
      */
@@ -36,4 +54,14 @@
         return DashboardPage.class;
     }
 
+    @Override
+    protected Class<? extends WebPage> getSignInPageClass() {
+        return LoginPage.class;
+    }
+
+    @Override
+    protected Class<? extends AuthenticatedWebSession> getWebSessionClass() {
+        return JaasWebSession.class;
+    }
+
 }
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/page/LoginPage.java b/core/src/main/java/org/apache/karaf/webconsole/core/page/LoginPage.java
new file mode 100644
index 0000000..6ceea19
--- /dev/null
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/page/LoginPage.java
@@ -0,0 +1,17 @@
+package org.apache.karaf.webconsole.core.page;
+
+import org.apache.karaf.webconsole.core.BasePage;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.authentication.panel.SignInPanel;
+
+public class LoginPage extends BasePage {
+
+    public LoginPage() {
+        this(null);
+    }
+
+    public LoginPage(PageParameters parameters) {
+        add(new SignInPanel("signIn"));
+    }
+
+}
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/page/SecuredPage.java b/core/src/main/java/org/apache/karaf/webconsole/core/page/SecuredPage.java
new file mode 100644
index 0000000..500073e
--- /dev/null
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/page/SecuredPage.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.webconsole.core.page;
+
+import java.util.List;
+
+import org.apache.karaf.webconsole.core.BasePage;
+import org.apache.karaf.webconsole.core.navigation.ConsoleTabProvider;
+import org.apache.karaf.webconsole.core.navigation.markup.NavigationPanel;
+import org.apache.wicket.RequestCycle;
+import org.apache.wicket.authentication.AuthenticatedWebSession;
+import org.apache.wicket.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.ops4j.pax.wicket.api.PaxWicketBean;
+
+@AuthorizeInstantiation("admin")
+public class SecuredPage extends BasePage {
+
+    @PaxWicketBean(name = "tabs")
+    private List<ConsoleTabProvider> tabs;
+
+    public SecuredPage() {
+        add(new NavigationPanel("navigationPanel", new LoadableDetachableModel<List<ConsoleTabProvider>>() {
+            @Override
+            protected List<ConsoleTabProvider> load() {
+                return tabs;
+            }
+        }));
+
+        add(new FeedbackPanel("feedback"));
+
+        add(new Link<Void>("logoutLink") {
+            @Override
+            public void onClick() {
+                AuthenticatedWebSession.get().invalidateNow();
+                RequestCycle.get().setResponsePage(LoginPage.class);
+            }
+        });
+    }
+}
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/SidebarPage.java b/core/src/main/java/org/apache/karaf/webconsole/core/page/SidebarPage.java
similarity index 93%
rename from core/src/main/java/org/apache/karaf/webconsole/core/SidebarPage.java
rename to core/src/main/java/org/apache/karaf/webconsole/core/page/SidebarPage.java
index 45e85dd..b305094 100644
--- a/core/src/main/java/org/apache/karaf/webconsole/core/SidebarPage.java
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/page/SidebarPage.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.karaf.webconsole.core;
+package org.apache.karaf.webconsole.core.page;
 
 import java.util.Collections;
 import java.util.List;
@@ -24,7 +24,7 @@
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.util.ListModel;
 
-public class SidebarPage extends BasePage {
+public class SidebarPage extends SecuredPage {
 
     private Panel sidebar;
 
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/page/SinglePage.java b/core/src/main/java/org/apache/karaf/webconsole/core/page/SinglePage.java
new file mode 100644
index 0000000..6744cca
--- /dev/null
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/page/SinglePage.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.webconsole.core.page;
+
+public class SinglePage extends SecuredPage {
+
+    public SinglePage() {
+        super();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/security/JaasWebSession.java b/core/src/main/java/org/apache/karaf/webconsole/core/security/JaasWebSession.java
new file mode 100644
index 0000000..1869578
--- /dev/null
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/security/JaasWebSession.java
@@ -0,0 +1,87 @@
+package org.apache.karaf.webconsole.core.security;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.karaf.jaas.modules.RolePrincipal;
+import org.apache.wicket.Request;
+import org.apache.wicket.authentication.AuthenticatedWebSession;
+import org.apache.wicket.authorization.strategies.role.Roles;
+
+public class JaasWebSession extends AuthenticatedWebSession {
+    public static final String ROLES_GROUP_NAME = "ROLES";
+
+    public static final String ROLES_PREFIX = "ROLE_";
+
+    public static final String APPLICATION_POLICY_NAME = "karaf";
+
+    private Roles roles = new Roles();
+
+    public JaasWebSession(Request request) {
+        super(request);
+    }
+
+    public boolean authenticate(String username, String password) {
+        boolean authenticated = false;
+        LoginCallbackHandler handler = new LoginCallbackHandler(username, password);
+
+        try {
+            LoginContext ctx = new LoginContext(APPLICATION_POLICY_NAME, handler);
+            ctx.login();
+            authenticated = true;
+
+            for (Principal p : ctx.getSubject().getPrincipals()) {
+                if (p instanceof RolePrincipal) {
+                    roles.add(p.getName());
+                }
+            }
+        } catch (LoginException e) {
+            authenticated = false;
+        }
+        return authenticated;
+    }
+
+    protected boolean isRole(Principal p) {
+        return p.getName().startsWith(ROLES_PREFIX);
+    }
+
+    public Roles getRoles() {
+        return roles;
+    }
+
+    private class LoginCallbackHandler implements CallbackHandler {
+
+        private String username;
+
+        private String password;
+
+        public LoginCallbackHandler(String username, String password) {
+            this.username = username;
+            this.password = password;
+        }
+
+        public void handle(Callback[] callbacks) throws IOException,
+                UnsupportedCallbackException {
+            for (int i = 0; i < callbacks.length; i++) {
+                Callback callback = callbacks[i];
+                if (callback instanceof NameCallback) {
+                    ((NameCallback) callback).setName(username);
+                } else if (callback instanceof PasswordCallback) {
+                    PasswordCallback pwCallback = (PasswordCallback) callback;
+                    pwCallback.setPassword(password.toCharArray());
+                } else {
+                    throw new UnsupportedCallbackException(callbacks[i], "Callback type not supported");
+                }
+            }
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/karaf/webconsole/core/table/ActionsPanel.java b/core/src/main/java/org/apache/karaf/webconsole/core/table/ActionsPanel.java
index faa1052..7cd9cc8 100644
--- a/core/src/main/java/org/apache/karaf/webconsole/core/table/ActionsPanel.java
+++ b/core/src/main/java/org/apache/karaf/webconsole/core/table/ActionsPanel.java
@@ -47,7 +47,7 @@
 
                 if (item.getIndex() == 0) {
                     item.add(new SimpleAttributeModifier("class", "first"));
-                } else if (item.getIndex() -1 == links.size()) {
+                } else if (item.getIndex() + 1 == links.size()) {
                     item.add(new SimpleAttributeModifier("class", "last"));
                 } else {
                     item.add(new SimpleAttributeModifier("class", "node"));
diff --git a/core/src/main/resources/org/apache/karaf/webconsole/core/BasePage.html b/core/src/main/resources/org/apache/karaf/webconsole/core/BasePage.html
index 04a8b9d..a22af94 100644
--- a/core/src/main/resources/org/apache/karaf/webconsole/core/BasePage.html
+++ b/core/src/main/resources/org/apache/karaf/webconsole/core/BasePage.html
@@ -32,18 +32,9 @@
 
             <div wicket:id="languagePanel" id="languages">Languages go here</div>
         </div>
+        <div class="clear"></div>
 
-        <div class="clear"></div>
-        <div class="grid_12">
-            <div wicket:id="navigationPanel">Navigation goes here</div>
-        </div>
-
-        <div class="clear"></div>
-        <div class="grid_12">
-            <span wicket:id="feedback">Feedback stuff</span>
-            <wicket:child />
-        </div>
-        <div class="clear"></div>
+        <wicket:child />
 
         <div class="grid_12">
             <div wicket:id="footer">Footer</div>
diff --git a/core/src/main/resources/org/apache/karaf/webconsole/core/page/LoginPage.html b/core/src/main/resources/org/apache/karaf/webconsole/core/page/LoginPage.html
new file mode 100644
index 0000000..1b2dd28
--- /dev/null
+++ b/core/src/main/resources/org/apache/karaf/webconsole/core/page/LoginPage.html
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
+
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <title>Karaf WebConsole</title>
+</head>
+
+<body>
+    <wicket:extend>
+        <br /><br /><br /><br /><br /><br />
+        <div class="grid_4 prefix_4 suffix_4">
+            <div wicket:id="signIn">
+                Here will be sign in form.
+            </div>
+        </div>
+        <br /><br /><br /><br /><br /><br />
+    </wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html b/core/src/main/resources/org/apache/karaf/webconsole/core/page/SecuredPage.html
similarity index 64%
copy from core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html
copy to core/src/main/resources/org/apache/karaf/webconsole/core/page/SecuredPage.html
index 62c1642..4fac146 100644
--- a/core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html
+++ b/core/src/main/resources/org/apache/karaf/webconsole/core/page/SecuredPage.html
@@ -23,35 +23,17 @@
 </head>
 
 <body>
-    <div class="container container_12">
-        <div class="grid_3">
-            <img wicket:id="logo" />
-        </div>
-        <div class="grid_9">
-            <h3>Administration console</h3>
 
-            <div wicket:id="languagePanel" id="languages">Languages go here</div>
-        </div>
-
-        <div class="clear"></div>
+    <wicket:extend>
+        <a href="#" wicket:id="logoutLink">Logout</a>
 
         <div class="grid_12">
             <div wicket:id="navigationPanel">Navigation goes here</div>
         </div>
-
-        <div class="clear"></div>
-        <div class="grid_3">
-            <div wicket:id="sidebar">Sidebar goes here</div>
-        </div>
-        <div class="grid_9">
-            <span wicket:id="feedback">Feedback stuff</span>
-            <wicket:child />
-        </div>
         <div class="clear"></div>
 
-        <div class="grid_12">
-            <div wicket:id="footer">Footer</div>
-        </div>
-    </div>
+        <wicket:child />
+    </wicket:extend>
+
 </body>
 </html>
\ No newline at end of file
diff --git a/core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html b/core/src/main/resources/org/apache/karaf/webconsole/core/page/SidebarPage.html
similarity index 70%
rename from core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html
rename to core/src/main/resources/org/apache/karaf/webconsole/core/page/SidebarPage.html
index 62c1642..dcbb937 100644
--- a/core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html
+++ b/core/src/main/resources/org/apache/karaf/webconsole/core/page/SidebarPage.html
@@ -23,23 +23,8 @@
 </head>
 
 <body>
-    <div class="container container_12">
-        <div class="grid_3">
-            <img wicket:id="logo" />
-        </div>
-        <div class="grid_9">
-            <h3>Administration console</h3>
 
-            <div wicket:id="languagePanel" id="languages">Languages go here</div>
-        </div>
-
-        <div class="clear"></div>
-
-        <div class="grid_12">
-            <div wicket:id="navigationPanel">Navigation goes here</div>
-        </div>
-
-        <div class="clear"></div>
+    <wicket:extend>
         <div class="grid_3">
             <div wicket:id="sidebar">Sidebar goes here</div>
         </div>
@@ -48,10 +33,7 @@
             <wicket:child />
         </div>
         <div class="clear"></div>
+    </wicket:extend>
 
-        <div class="grid_12">
-            <div wicket:id="footer">Footer</div>
-        </div>
-    </div>
 </body>
 </html>
\ No newline at end of file
diff --git a/core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html b/core/src/main/resources/org/apache/karaf/webconsole/core/page/SinglePage.html
similarity index 64%
copy from core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html
copy to core/src/main/resources/org/apache/karaf/webconsole/core/page/SinglePage.html
index 62c1642..1077866 100644
--- a/core/src/main/resources/org/apache/karaf/webconsole/core/SidebarPage.html
+++ b/core/src/main/resources/org/apache/karaf/webconsole/core/page/SinglePage.html
@@ -23,35 +23,14 @@
 </head>
 
 <body>
-    <div class="container container_12">
-        <div class="grid_3">
-            <img wicket:id="logo" />
-        </div>
-        <div class="grid_9">
-            <h3>Administration console</h3>
 
-            <div wicket:id="languagePanel" id="languages">Languages go here</div>
-        </div>
-
-        <div class="clear"></div>
-
+    <wicket:extend>
         <div class="grid_12">
-            <div wicket:id="navigationPanel">Navigation goes here</div>
-        </div>
-
-        <div class="clear"></div>
-        <div class="grid_3">
-            <div wicket:id="sidebar">Sidebar goes here</div>
-        </div>
-        <div class="grid_9">
             <span wicket:id="feedback">Feedback stuff</span>
             <wicket:child />
         </div>
         <div class="clear"></div>
+    </wicket:extend>
 
-        <div class="grid_12">
-            <div wicket:id="footer">Footer</div>
-        </div>
-    </div>
 </body>
 </html>
\ No newline at end of file
diff --git a/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/feature/FeaturesPage.java b/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/feature/FeaturesPage.java
index a4afab6..cea0d5e 100644
--- a/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/feature/FeaturesPage.java
+++ b/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/feature/FeaturesPage.java
@@ -24,7 +24,7 @@
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
-import org.apache.karaf.webconsole.core.BasePage;
+import org.apache.karaf.webconsole.core.page.SinglePage;
 import org.apache.karaf.webconsole.karaf.internal.FeaturesProvider;
 import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
@@ -45,7 +45,7 @@
  * Features
  */
 @PaxWicketMountPoint(mountPoint = "/karaf/features")
-public class FeaturesPage extends BasePage {
+public class FeaturesPage extends SinglePage {
 
     @PaxWicketBean(name = "featuresService")
     private FeaturesService featuresService;
diff --git a/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/repository/AddRepositoryPage.java b/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/repository/AddRepositoryPage.java
index 4f83f7f..cf52abf 100644
--- a/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/repository/AddRepositoryPage.java
+++ b/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/repository/AddRepositoryPage.java
@@ -16,7 +16,7 @@
  */
 package org.apache.karaf.webconsole.karaf.internal.repository;
 
-import org.apache.karaf.webconsole.core.SidebarPage;
+import org.apache.karaf.webconsole.core.page.SidebarPage;
 import org.ops4j.pax.wicket.api.PaxWicketMountPoint;
 
 @PaxWicketMountPoint(mountPoint = "/karaf/repositories/add")
diff --git a/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/repository/RepositoriesPage.java b/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/repository/RepositoriesPage.java
index b3e1a02..0c64e4c 100644
--- a/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/repository/RepositoriesPage.java
+++ b/karaf/src/main/java/org/apache/karaf/webconsole/karaf/internal/repository/RepositoriesPage.java
@@ -21,7 +21,7 @@
 
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
-import org.apache.karaf.webconsole.core.SidebarPage;
+import org.apache.karaf.webconsole.core.page.SidebarPage;
 import org.apache.karaf.webconsole.core.table.OrdinalColumn;
 import org.apache.karaf.webconsole.karaf.internal.RepositoriesProvider;
 import org.apache.wicket.Page;
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/OsgiPage.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/OsgiPage.java
index d68234f..6901e58 100644
--- a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/OsgiPage.java
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/OsgiPage.java
@@ -19,7 +19,7 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import org.apache.karaf.webconsole.core.SidebarPage;
+import org.apache.karaf.webconsole.core.page.SidebarPage;
 import org.apache.karaf.webconsole.osgi.internal.configuration.ConfigurationsPage;
 import org.apache.karaf.webconsole.osgi.internal.event.EventsPage;
 import org.apache.wicket.Page;
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/BundlesPage.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/BundlesPage.java
index d8180b8..ce61233 100644
--- a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/BundlesPage.java
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/BundlesPage.java
@@ -64,6 +64,12 @@
             }
         });
         columns.add(new PropertyColumnExt<Bundle>("Bundle Id", "bundleId"));
+        columns.add(new AbstractColumn<Bundle>(of("State")) {
+            public void populateItem(Item<ICellPopulator<Bundle>> cellItem, final String componentId, final IModel<Bundle> rowModel) {
+                cellItem.add(new Label(componentId, State.of(rowModel.getObject().getState()).name()));
+            }
+            
+        });
         columns.add(new AbstractColumn<Bundle>(of("Start level")) {
             public void populateItem(Item<ICellPopulator<Bundle>> cellItem, final String componentId, final IModel<Bundle> rowModel) {
                 cellItem.add(new Label(componentId, of(startLevel.getBundleStartLevel(rowModel.getObject()))));
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/DetailsPage.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/DetailsPage.java
index adf3e2f..d062b98 100644
--- a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/DetailsPage.java
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/DetailsPage.java
@@ -19,7 +19,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-import org.apache.karaf.webconsole.core.BasePage;
+import org.apache.karaf.webconsole.core.page.SinglePage;
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.list.ListItem;
@@ -34,7 +34,7 @@
 import org.osgi.framework.ServiceReference;
 
 @PaxWicketMountPoint(mountPoint = "/osgi/bundle/detail")
-public class DetailsPage extends BasePage {
+public class DetailsPage extends SinglePage {
 
     @PaxWicketBean(name = "blueprintBundleContext")
     private BundleContext context;
diff --git a/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/State.java b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/State.java
new file mode 100644
index 0000000..e67c76a
--- /dev/null
+++ b/osgi/src/main/java/org/apache/karaf/webconsole/osgi/internal/bundle/State.java
@@ -0,0 +1,29 @@
+package org.apache.karaf.webconsole.osgi.internal.bundle;
+
+public enum State {
+    UNINSTALLED(1),
+    INSTALLED(2),
+    RESOLVED(4),
+    STARTING(8),
+    STOPPING(16),
+    ACTIVE(32),
+
+    UNKNOWN(-1);
+
+    private final int mask;
+
+    private State(int mask) {
+        this.mask = mask;
+        
+    }
+
+    public static State of(int state) {
+        for (State enumeration : values()) {
+            if (enumeration.mask == state) {
+                return enumeration;
+            }
+        }
+        return UNKNOWN;
+    }
+    
+}
diff --git a/servicemix/src/main/java/org/apache/karaf/webconsole/servicemix/internal/ServiceMixPage.java b/servicemix/src/main/java/org/apache/karaf/webconsole/servicemix/internal/ServiceMixPage.java
index fea37d3..c7b942b 100644
--- a/servicemix/src/main/java/org/apache/karaf/webconsole/servicemix/internal/ServiceMixPage.java
+++ b/servicemix/src/main/java/org/apache/karaf/webconsole/servicemix/internal/ServiceMixPage.java
@@ -5,7 +5,7 @@
 import java.util.List;
 import java.util.Map;
 
-import org.apache.karaf.webconsole.core.BasePage;
+import org.apache.karaf.webconsole.core.page.SinglePage;
 import org.apache.karaf.webconsole.core.table.OrdinalColumn;
 import org.apache.karaf.webconsole.core.table.PropertyColumnExt;
 import org.apache.servicemix.nmr.api.Endpoint;
@@ -19,7 +19,7 @@
 import org.apache.wicket.model.Model;
 import org.ops4j.pax.wicket.api.PaxWicketBean;
 
-public class ServiceMixPage extends BasePage {
+public class ServiceMixPage extends SinglePage {
 
     @PaxWicketBean(name = "nmr")
     private NMR nmr;