فهرست منبع

:merge branch 'app-pages'

Conflicts:
	examples/nginx-app/module.js
	examples/nginx-app/partials/stream.html
	examples/nginx-app/plugin.json
Torkel Ödegaard 10 سال پیش
والد
کامیت
1acbd5b7d8

+ 10 - 18
examples/nginx-app/module.js

@@ -1,28 +1,20 @@
 define([
-  'angular',
-  'app/app'
-], function(angular, app)  {
+], function()  {
+  'use strict';
 
-  var module = angular.module('nginx-app', []);
-  app.default.useModule(module);
+  function StreamPageCtrl() {}
+  StreamPageCtrl.templateUrl = 'public/plugins/nginx-app/partials/stream.html';
 
-  module.config(function($routeProvider) {
-    $routeProvider
-      .when('/nginx/stream', {
-        templateUrl: 'public/plugins/nginx-app/partials/stream.html',
-      });
-  });
+  function LogsPageCtrl() {}
+  LogsPageCtrl.templateUrl = 'public/plugins/nginx-app/partials/logs.html';
 
-  function NginxConfigCtrl() {
-    this.appEditCtrl.beforeUpdate = function() {
-      alert('before!');
-    };
-  }
+  function NginxConfigCtrl() {}
   NginxConfigCtrl.templateUrl = 'public/plugins/nginx-app/partials/config.html';
 
-
   return {
-    ConfigCtrl: NginxConfigCtrl
+    ConfigCtrl: NginxConfigCtrl,
+    StreamPageCtrl: StreamPageCtrl,
+    LogsPageCtrl: LogsPageCtrl,
   };
 
 });

+ 2 - 0
examples/nginx-app/partials/logs.html

@@ -0,0 +1,2 @@
+
+Logs!

+ 1 - 12
examples/nginx-app/partials/stream.html

@@ -1,12 +1 @@
-<topnav title="Nginx" icon="fa fa-fw fa-cubes" subnav="true">
-	<ul class="nav">
-		<li class="active" ><a href="org/apps">Overview</a></li>
-	</ul>
-</topnav>
-
-<div class="page-container" style="background: transparent; border: 0;">
-  <div class="page-wide" ng-init="ctrl.init()">
-		<h1>NGINX app</h1>
-	</div>
-</div>
-
+streams!

+ 2 - 2
examples/nginx-app/plugin.json

@@ -6,8 +6,8 @@
   "staticRoot": ".",
 
   "pages": [
-    {"name": "Live stream", "url": "nginx/stream", "reqRole": "Editor"},
-    {"name": "Log view", "url": "nginx/log", "reqRole": "Editor"}
+    { "name": "Live stream", "component": "StreamPageCtrl", "role": "Editor"},
+    { "name": "Log view", "component": "LogsPageCtrl", "role": "Viewer"}
   ],
 
   "css": {

+ 9 - 9
pkg/api/dtos/apps.go

@@ -6,15 +6,15 @@ import (
 )
 
 type AppSettings struct {
-	Name     string                   `json:"name"`
-	AppId    string                   `json:"appId"`
-	Enabled  bool                     `json:"enabled"`
-	Pinned   bool                     `json:"pinned"`
-	Module   string                   `json:"module"`
-	Info     *plugins.PluginInfo      `json:"info"`
-	Pages    []plugins.AppPluginPage  `json:"pages"`
-	Includes []plugins.AppIncludeInfo `json:"includes"`
-	JsonData map[string]interface{}   `json:"jsonData"`
+	Name     string                    `json:"name"`
+	AppId    string                    `json:"appId"`
+	Enabled  bool                      `json:"enabled"`
+	Pinned   bool                      `json:"pinned"`
+	Module   string                    `json:"module"`
+	Info     *plugins.PluginInfo       `json:"info"`
+	Pages    []*plugins.AppPluginPage  `json:"pages"`
+	Includes []*plugins.AppIncludeInfo `json:"includes"`
+	JsonData map[string]interface{}    `json:"jsonData"`
 }
 
 func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings {

+ 5 - 4
pkg/api/dtos/index.go

@@ -19,8 +19,9 @@ type PluginCss struct {
 }
 
 type NavLink struct {
-	Text string `json:"text"`
-	Icon string `json:"icon"`
-	Img  string `json:"img"`
-	Url  string `json:"url"`
+	Text     string     `json:"text"`
+	Icon     string     `json:"icon"`
+	Img      string     `json:"img"`
+	Url      string     `json:"url"`
+	Children []*NavLink `json:"children"`
 }

+ 21 - 21
pkg/api/index.go

@@ -51,32 +51,27 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 	data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
 		Text: "Dashboards",
 		Icon: "fa fa-fw fa-th-large",
-		Url:  "/",
+		Url:  setting.AppSubUrl + "/",
+		// Children: []*dtos.NavLink{
+		// 	{Text: "Playlists", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/playlists"},
+		// 	{Text: "Snapshots", Icon: "fa-fw icon-gf icon-gf-snapshot", Url: setting.AppSubUrl + "/dashboard/snapshots"},
+		// },
 	})
 
-	data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
-		Text: "Playlists",
-		Icon: "fa fa-fw fa-list",
-		Url:  "/playlists",
-	})
-
-	data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
-		Text: "Snapshots",
-		Icon: "fa-fw icon-gf icon-gf-snapshot",
-		Url:  "/dashboard/snapshots",
-	})
+	data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: "Playlists", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/playlists"})
+	data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: "Snapshots", Icon: "fa-fw icon-gf icon-gf-snapshot", Url: setting.AppSubUrl + "/dashboard/snapshots"})
 
 	if c.OrgRole == m.ROLE_ADMIN {
 		data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
 			Text: "Data Sources",
 			Icon: "fa fa-fw fa-database",
-			Url:  "/datasources",
+			Url:  setting.AppSubUrl + "/datasources",
 		})
 
 		data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
 			Text: "Apps",
 			Icon: "fa fa-fw fa-cubes",
-			Url:  "/apps",
+			Url:  setting.AppSubUrl + "/apps",
 		})
 	}
 
@@ -86,20 +81,25 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 	}
 
 	for _, plugin := range enabledPlugins.Apps {
-		if plugin.Module != "" {
-			data.PluginModules = append(data.PluginModules, plugin.Module)
-		}
-
 		if plugin.Css != nil {
 			data.PluginCss = append(data.PluginCss, &dtos.PluginCss{Light: plugin.Css.Light, Dark: plugin.Css.Dark})
 		}
 
 		if plugin.Pinned {
-			data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
+			pageLink := &dtos.NavLink{
 				Text: plugin.Name,
-				Url:  "/apps/edit/" + plugin.Id,
+				Url:  setting.AppSubUrl + "/apps/" + plugin.Id + "/edit",
 				Img:  plugin.Info.Logos.Small,
-			})
+			}
+
+			for _, page := range plugin.Pages {
+				pageLink.Children = append(pageLink.Children, &dtos.NavLink{
+					Url:  setting.AppSubUrl + "/apps/" + plugin.Id + "/page/" + page.Slug,
+					Text: page.Name,
+				})
+			}
+
+			data.MainNavLinks = append(data.MainNavLinks, pageLink)
 		}
 	}
 

+ 14 - 6
pkg/plugins/app_plugin.go

@@ -4,13 +4,15 @@ import (
 	"encoding/json"
 	"strings"
 
+	"github.com/gosimple/slug"
 	"github.com/grafana/grafana/pkg/models"
 )
 
 type AppPluginPage struct {
-	Name    string          `json:"name"`
-	Url     string          `json:"url"`
-	ReqRole models.RoleType `json:"reqRole"`
+	Name      string          `json:"name"`
+	Slug      string          `json:"slug"`
+	Component string          `json:"component"`
+	Role      models.RoleType `json:"role"`
 }
 
 type AppPluginCss struct {
@@ -27,9 +29,9 @@ type AppIncludeInfo struct {
 type AppPlugin struct {
 	FrontendPluginBase
 	Css      *AppPluginCss     `json:"css"`
-	Pages    []AppPluginPage   `json:"pages"`
+	Pages    []*AppPluginPage  `json:"pages"`
 	Routes   []*AppPluginRoute `json:"routes"`
-	Includes []AppIncludeInfo  `json:"-"`
+	Includes []*AppIncludeInfo `json:"-"`
 
 	Pinned  bool `json:"-"`
 	Enabled bool `json:"-"`
@@ -67,7 +69,7 @@ func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error {
 	for _, panel := range Panels {
 		if strings.HasPrefix(panel.PluginDir, app.PluginDir) {
 			panel.IncludedInAppId = app.Id
-			app.Includes = append(app.Includes, AppIncludeInfo{
+			app.Includes = append(app.Includes, &AppIncludeInfo{
 				Name: panel.Name,
 				Id:   panel.Id,
 				Type: panel.Type,
@@ -75,6 +77,12 @@ func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error {
 		}
 	}
 
+	for _, page := range app.Pages {
+		if page.Slug == "" {
+			page.Slug = slug.Make(page.Name)
+		}
+	}
+
 	Apps[app.Id] = app
 	return nil
 }

+ 0 - 6
public/app/app.ts

@@ -72,12 +72,6 @@ export class GrafanaApp {
     this.useModule(coreModule);
 
     var preBootRequires = [System.import('app/features/all')];
-    var pluginModules = config.bootData.pluginModules || [];
-
-    // add plugin modules
-    for (var i = 0; i < pluginModules.length; i++) {
-      preBootRequires.push(System.import(pluginModules[i]));
-    }
 
     Promise.all(preBootRequires).then(() => {
       // disable tool tip animation

+ 8 - 1
public/app/core/components/sidemenu/sidemenu.html

@@ -1,6 +1,6 @@
 <ul class="sidemenu sidemenu-main">
 
-	<li class="sidemenu-org-section dropdown" ng-if="ctrl.isSignedIn">
+	<li class="sidemenu-org-section" ng-if="ctrl.isSignedIn">
 		<div class="sidemenu-org" data-toggle="dropdown" ng-click="ctrl.openUserDropdown()">
 			<div class="sidemenu-org-avatar">
 				<img ng-src="{{ctrl.user.gravatarUrl}}">
@@ -44,6 +44,13 @@
 			</span>
 			<span class="sidemenu-item-text">{{item.text}}</span>
 		</a>
+		<ul class="dropdown-menu" role="menu" ng-if="item.children">
+			<li ng-repeat="child in item.children">
+				<a href="{{child.url}}">
+					{{child.text}}
+				</a>
+			</li>
+		</ul>
 	</li>
 
 	<ul class="sidemenu sidemenu-small" style="margin-top:50px" ng-if="ctrl.systemSection">

+ 3 - 4
public/app/core/components/sidemenu/sidemenu.ts

@@ -16,7 +16,7 @@ export class SideMenuCtrl {
   appSubUrl: string;
 
   /** @ngInject */
-  constructor(private $scope, private $location, private contextSrv, private backendSrv) {
+  constructor(private $scope, private $location, private contextSrv, private backendSrv, private $element) {
     this.isSignedIn = contextSrv.isSignedIn;
     this.user = contextSrv.user;
     this.appSubUrl = config.appSubUrl;
@@ -29,6 +29,7 @@ export class SideMenuCtrl {
         this.contextSrv.sidemenu = false;
       }
     });
+
   }
 
  getUrl(url) {
@@ -36,9 +37,7 @@ export class SideMenuCtrl {
  }
 
  setupMainNav() {
-   this.mainLinks = config.bootData.mainNavLinks.map(item => {
-     return {text: item.text, icon: item.icon, img: item.img, url: this.getUrl(item.url)};
-   });
+   this.mainLinks = config.bootData.mainNavLinks;
  }
 
  openUserDropdown() {

+ 1 - 1
public/app/core/core.ts

@@ -28,7 +28,7 @@ import {navbarDirective} from './components/navbar/navbar';
 import {arrayJoin} from './directives/array_join';
 import 'app/core/controllers/all';
 import 'app/core/services/all';
-import 'app/core/routes/all';
+import 'app/core/routes/routes';
 import './filters/filters';
 import coreModule from './core_module';
 

+ 15 - 2
public/app/core/directives/plugin_component.ts

@@ -143,15 +143,28 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
       }
       // AppConfigCtrl
       case 'app-config-ctrl': {
-        return System.import(scope.ctrl.appModel.module).then(function(appModule) {
+        let appModel = scope.ctrl.appModel;
+        return System.import(appModel.module).then(function(appModule) {
           return {
-            name: 'app-config-' + scope.ctrl.appModel.appId,
+            name: 'app-config-' + appModel.appId,
             bindings: {appModel: "=", appEditCtrl: "="},
             attrs: {"app-model": "ctrl.appModel", "app-edit-ctrl": "ctrl"},
             Component: appModule.ConfigCtrl,
           };
         });
       }
+      // App Page
+      case 'app-page': {
+        let appModel = scope.ctrl.appModel;
+        return System.import(appModel.module).then(function(appModule) {
+          return {
+            name: 'app-page-' + appModel.appId + '-' + scope.ctrl.page.slug,
+            bindings: {appModel: "="},
+            attrs: {"app-model": "ctrl.appModel"},
+            Component: appModule[scope.ctrl.page.component],
+          };
+        });
+      }
       // Panel
       case 'panel': {
         return loadPanelComponentInfo(scope, attrs);

+ 0 - 166
public/app/core/routes/all.js

@@ -1,166 +0,0 @@
-define([
-  'angular',
-  '../core_module',
-  './bundle_loader',
-  './dashboard_loaders',
-], function(angular, coreModule, BundleLoader) {
-  "use strict";
-
-  coreModule.default.config(function($routeProvider, $locationProvider) {
-    $locationProvider.html5Mode(true);
-
-    var loadOrgBundle = new BundleLoader.BundleLoader('app/features/org/all');
-    var loadAppsBundle = new BundleLoader.BundleLoader('app/features/apps/all');
-
-    $routeProvider
-      .when('/', {
-        templateUrl: 'public/app/partials/dashboard.html',
-        controller : 'LoadDashboardCtrl',
-        reloadOnSearch: false,
-      })
-      .when('/dashboard/:type/:slug', {
-        templateUrl: 'public/app/partials/dashboard.html',
-        controller : 'LoadDashboardCtrl',
-        reloadOnSearch: false,
-      })
-      .when('/dashboard-solo/:type/:slug', {
-        templateUrl: 'public/app/features/panel/partials/soloPanel.html',
-        controller : 'SoloPanelCtrl',
-      })
-      .when('/dashboard-import/:file', {
-        templateUrl: 'public/app/partials/dashboard.html',
-        controller : 'DashFromImportCtrl',
-        reloadOnSearch: false,
-      })
-      .when('/dashboard/new', {
-        templateUrl: 'public/app/partials/dashboard.html',
-        controller : 'NewDashboardCtrl',
-        reloadOnSearch: false,
-      })
-      .when('/import/dashboard', {
-        templateUrl: 'public/app/features/dashboard/partials/import.html',
-        controller : 'DashboardImportCtrl',
-      })
-      .when('/datasources', {
-        templateUrl: 'public/app/features/datasources/partials/list.html',
-        controller : 'DataSourcesCtrl',
-        resolve: loadOrgBundle,
-      })
-      .when('/datasources/edit/:id', {
-        templateUrl: 'public/app/features/datasources/partials/edit.html',
-        controller : 'DataSourceEditCtrl',
-        resolve: loadOrgBundle,
-      })
-      .when('/datasources/new', {
-        templateUrl: 'public/app/features/datasources/partials/edit.html',
-        controller : 'DataSourceEditCtrl',
-        resolve: loadOrgBundle,
-      })
-      .when('/org', {
-        templateUrl: 'public/app/features/org/partials/orgDetails.html',
-        controller : 'OrgDetailsCtrl',
-        resolve: loadOrgBundle,
-      })
-      .when('/org/new', {
-        templateUrl: 'public/app/features/org/partials/newOrg.html',
-        controller : 'NewOrgCtrl',
-        resolve: loadOrgBundle,
-      })
-      .when('/org/users', {
-        templateUrl: 'public/app/features/org/partials/orgUsers.html',
-        controller : 'OrgUsersCtrl',
-        resolve: loadOrgBundle,
-      })
-      .when('/org/apikeys', {
-        templateUrl: 'public/app/features/org/partials/orgApiKeys.html',
-        controller : 'OrgApiKeysCtrl',
-        resolve: loadOrgBundle,
-      })
-      .when('/profile', {
-        templateUrl: 'public/app/features/profile/partials/profile.html',
-        controller : 'ProfileCtrl',
-      })
-      .when('/profile/password', {
-        templateUrl: 'public/app/features/profile/partials/password.html',
-        controller : 'ChangePasswordCtrl',
-      })
-      .when('/profile/select-org', {
-        templateUrl: 'public/app/features/profile/partials/select_org.html',
-        controller : 'SelectOrgCtrl',
-      })
-      .when('/admin/settings', {
-        templateUrl: 'public/app/features/admin/partials/settings.html',
-        controller : 'AdminSettingsCtrl',
-      })
-      .when('/admin/users', {
-        templateUrl: 'public/app/features/admin/partials/users.html',
-        controller : 'AdminListUsersCtrl',
-      })
-      .when('/admin/users/create', {
-        templateUrl: 'public/app/features/admin/partials/new_user.html',
-        controller : 'AdminEditUserCtrl',
-      })
-      .when('/admin/users/edit/:id', {
-        templateUrl: 'public/app/features/admin/partials/edit_user.html',
-        controller : 'AdminEditUserCtrl',
-      })
-      .when('/admin/orgs', {
-        templateUrl: 'public/app/features/admin/partials/orgs.html',
-        controller : 'AdminListOrgsCtrl',
-      })
-      .when('/admin/orgs/edit/:id', {
-        templateUrl: 'public/app/features/admin/partials/edit_org.html',
-        controller : 'AdminEditOrgCtrl',
-      })
-      .when('/admin/stats', {
-        templateUrl: 'public/app/features/admin/partials/stats.html',
-        controller : 'AdminStatsCtrl',
-        controllerAs: 'ctrl',
-      })
-      .when('/login', {
-        templateUrl: 'public/app/partials/login.html',
-        controller : 'LoginCtrl',
-      })
-      .when('/invite/:code', {
-        templateUrl: 'public/app/partials/signup_invited.html',
-        controller : 'InvitedCtrl',
-      })
-      .when('/signup', {
-        templateUrl: 'public/app/partials/signup_step2.html',
-        controller : 'SignUpCtrl',
-      })
-      .when('/user/password/send-reset-email', {
-        templateUrl: 'public/app/partials/reset_password.html',
-        controller : 'ResetPasswordCtrl',
-      })
-      .when('/user/password/reset', {
-        templateUrl: 'public/app/partials/reset_password.html',
-        controller : 'ResetPasswordCtrl',
-      })
-      .when('/dashboard/snapshots', {
-        templateUrl: 'public/app/features/snapshot/partials/snapshots.html',
-        controller : 'SnapshotsCtrl',
-        controllerAs: 'ctrl',
-      })
-      .when('/apps', {
-        templateUrl: 'public/app/features/apps/partials/list.html',
-        controller: 'AppListCtrl',
-        controllerAs: 'ctrl',
-        resolve: loadAppsBundle,
-      })
-      .when('/apps/edit/:appId', {
-        templateUrl: 'public/app/features/apps/partials/edit.html',
-        controller: 'AppEditCtrl',
-        controllerAs: 'ctrl',
-        resolve: loadAppsBundle,
-      })
-      .when('/global-alerts', {
-        templateUrl: 'public/app/features/dashboard/partials/globalAlerts.html',
-      })
-      .otherwise({
-        templateUrl: 'public/app/partials/error.html',
-        controller: 'ErrorCtrl'
-      });
-  });
-
-});

+ 173 - 0
public/app/core/routes/routes.ts

@@ -0,0 +1,173 @@
+///<reference path="../../headers/common.d.ts" />
+
+import './dashboard_loaders';
+
+import angular from 'angular';
+import coreModule from 'app/core/core_module';
+import {BundleLoader} from './bundle_loader';
+
+/** @ngInject **/
+function setupAngularRoutes($routeProvider, $locationProvider) {
+  $locationProvider.html5Mode(true);
+
+  var loadOrgBundle = new BundleLoader('app/features/org/all');
+  var loadAppsBundle = new BundleLoader('app/features/apps/all');
+
+  $routeProvider
+  .when('/', {
+    templateUrl: 'public/app/partials/dashboard.html',
+    controller : 'LoadDashboardCtrl',
+    reloadOnSearch: false,
+  })
+  .when('/dashboard/:type/:slug', {
+    templateUrl: 'public/app/partials/dashboard.html',
+    controller : 'LoadDashboardCtrl',
+    reloadOnSearch: false,
+  })
+  .when('/dashboard-solo/:type/:slug', {
+    templateUrl: 'public/app/features/panel/partials/soloPanel.html',
+    controller : 'SoloPanelCtrl',
+  })
+  .when('/dashboard-import/:file', {
+    templateUrl: 'public/app/partials/dashboard.html',
+    controller : 'DashFromImportCtrl',
+    reloadOnSearch: false,
+  })
+  .when('/dashboard/new', {
+    templateUrl: 'public/app/partials/dashboard.html',
+    controller : 'NewDashboardCtrl',
+    reloadOnSearch: false,
+  })
+  .when('/import/dashboard', {
+    templateUrl: 'public/app/features/dashboard/partials/import.html',
+    controller : 'DashboardImportCtrl',
+  })
+  .when('/datasources', {
+    templateUrl: 'public/app/features/datasources/partials/list.html',
+    controller : 'DataSourcesCtrl',
+    resolve: loadOrgBundle,
+  })
+  .when('/datasources/edit/:id', {
+    templateUrl: 'public/app/features/datasources/partials/edit.html',
+    controller : 'DataSourceEditCtrl',
+    resolve: loadOrgBundle,
+  })
+  .when('/datasources/new', {
+    templateUrl: 'public/app/features/datasources/partials/edit.html',
+    controller : 'DataSourceEditCtrl',
+    resolve: loadOrgBundle,
+  })
+  .when('/org', {
+    templateUrl: 'public/app/features/org/partials/orgDetails.html',
+    controller : 'OrgDetailsCtrl',
+    resolve: loadOrgBundle,
+  })
+  .when('/org/new', {
+    templateUrl: 'public/app/features/org/partials/newOrg.html',
+    controller : 'NewOrgCtrl',
+    resolve: loadOrgBundle,
+  })
+  .when('/org/users', {
+    templateUrl: 'public/app/features/org/partials/orgUsers.html',
+    controller : 'OrgUsersCtrl',
+    resolve: loadOrgBundle,
+  })
+  .when('/org/apikeys', {
+    templateUrl: 'public/app/features/org/partials/orgApiKeys.html',
+    controller : 'OrgApiKeysCtrl',
+    resolve: loadOrgBundle,
+  })
+  .when('/profile', {
+    templateUrl: 'public/app/features/profile/partials/profile.html',
+    controller : 'ProfileCtrl',
+  })
+  .when('/profile/password', {
+    templateUrl: 'public/app/features/profile/partials/password.html',
+    controller : 'ChangePasswordCtrl',
+  })
+  .when('/profile/select-org', {
+    templateUrl: 'public/app/features/profile/partials/select_org.html',
+    controller : 'SelectOrgCtrl',
+  })
+  .when('/admin/settings', {
+    templateUrl: 'public/app/features/admin/partials/settings.html',
+    controller : 'AdminSettingsCtrl',
+  })
+  .when('/admin/users', {
+    templateUrl: 'public/app/features/admin/partials/users.html',
+    controller : 'AdminListUsersCtrl',
+  })
+  .when('/admin/users/create', {
+    templateUrl: 'public/app/features/admin/partials/new_user.html',
+    controller : 'AdminEditUserCtrl',
+  })
+  .when('/admin/users/edit/:id', {
+    templateUrl: 'public/app/features/admin/partials/edit_user.html',
+    controller : 'AdminEditUserCtrl',
+  })
+  .when('/admin/orgs', {
+    templateUrl: 'public/app/features/admin/partials/orgs.html',
+    controller : 'AdminListOrgsCtrl',
+  })
+  .when('/admin/orgs/edit/:id', {
+    templateUrl: 'public/app/features/admin/partials/edit_org.html',
+    controller : 'AdminEditOrgCtrl',
+  })
+  .when('/admin/stats', {
+    templateUrl: 'public/app/features/admin/partials/stats.html',
+    controller : 'AdminStatsCtrl',
+    controllerAs: 'ctrl',
+  })
+  .when('/login', {
+    templateUrl: 'public/app/partials/login.html',
+    controller : 'LoginCtrl',
+  })
+  .when('/invite/:code', {
+    templateUrl: 'public/app/partials/signup_invited.html',
+    controller : 'InvitedCtrl',
+  })
+  .when('/signup', {
+    templateUrl: 'public/app/partials/signup_step2.html',
+    controller : 'SignUpCtrl',
+  })
+  .when('/user/password/send-reset-email', {
+    templateUrl: 'public/app/partials/reset_password.html',
+    controller : 'ResetPasswordCtrl',
+  })
+  .when('/user/password/reset', {
+    templateUrl: 'public/app/partials/reset_password.html',
+    controller : 'ResetPasswordCtrl',
+  })
+  .when('/dashboard/snapshots', {
+    templateUrl: 'public/app/features/snapshot/partials/snapshots.html',
+    controller : 'SnapshotsCtrl',
+    controllerAs: 'ctrl',
+  })
+  .when('/apps', {
+    templateUrl: 'public/app/features/apps/partials/list.html',
+    controller: 'AppListCtrl',
+    controllerAs: 'ctrl',
+    resolve: loadAppsBundle,
+  })
+  .when('/apps/:appId/edit', {
+    templateUrl: 'public/app/features/apps/partials/edit.html',
+    controller: 'AppEditCtrl',
+    controllerAs: 'ctrl',
+    resolve: loadAppsBundle,
+  })
+  .when('/apps/:appId/page/:slug', {
+    templateUrl: 'public/app/features/apps/partials/page.html',
+    controller: 'AppPageCtrl',
+    controllerAs: 'ctrl',
+    resolve: loadAppsBundle,
+  })
+  .when('/global-alerts', {
+    templateUrl: 'public/app/features/dashboard/partials/globalAlerts.html',
+  })
+  .otherwise({
+    templateUrl: 'public/app/partials/error.html',
+    controller: 'ErrorCtrl'
+  });
+}
+
+coreModule.config(setupAngularRoutes);

+ 1 - 0
public/app/features/apps/all.ts

@@ -1,2 +1,3 @@
 import './edit_ctrl';
+import './page_ctrl';
 import './list_ctrl';

+ 8 - 6
public/app/features/apps/edit_ctrl.ts

@@ -5,19 +5,21 @@ import _ from 'lodash';
 
 export class AppEditCtrl {
   appModel: any;
+  appId: any;
   includedPanels: any;
 
   /** @ngInject */
   constructor(private backendSrv: any, private $routeParams: any) {
     this.appModel = {};
+    this.appId = $routeParams.appId;
 
-    this.backendSrv.get(`/api/org/apps/${this.$routeParams.appId}/settings`).then(result => {
+    this.backendSrv.get(`/api/org/apps/${this.appId}/settings`).then(result => {
       this.appModel = result;
       this.includedPanels = _.where(result.includes, {type: 'panel'});
     });
   }
 
-  update(options) {
+  update() {
     var updateCmd = _.extend({
       appId: this.appModel.appId,
       orgId: this.appModel.orgId,
@@ -25,19 +27,19 @@ export class AppEditCtrl {
       pinned: this.appModel.pinned,
       jsonData: this.appModel.jsonData,
       secureJsonData: this.appModel.secureJsonData,
-    }, options);
+    }, {});
 
-    this.backendSrv.post(`/api/org/apps/${this.$routeParams.appId}/settings`, updateCmd).then(function() {
+    this.backendSrv.post(`/api/org/apps/${this.appId}/settings`, updateCmd).then(function() {
       window.location.href = window.location.href;
     });
   }
 
   toggleEnabled() {
-    this.update({enabled: this.appModel.enabled});
+    this.update();
   }
 
   togglePinned() {
-    this.update({pinned: this.appModel.pinned});
+    this.update();
   }
 }
 

+ 1 - 2
public/app/features/apps/list_ctrl.ts

@@ -6,9 +6,8 @@ export class AppListCtrl {
   apps: any[];
 
   /** @ngInject */
-  constructor(private backendSrv: any) {}
+  constructor(private backendSrv: any) {
 
-  init() {
     this.backendSrv.get('api/org/apps').then(apps => {
       this.apps = apps;
     });

+ 26 - 0
public/app/features/apps/page_ctrl.ts

@@ -0,0 +1,26 @@
+///<reference path="../../headers/common.d.ts" />
+
+import angular from 'angular';
+import _ from 'lodash';
+
+export class AppPageCtrl {
+  page: any;
+  appId: any;
+  appModel: any;
+
+  /** @ngInject */
+  constructor(private backendSrv, private $routeParams: any, private $rootScope) {
+    this.appId = $routeParams.appId;
+
+    this.backendSrv.get(`/api/org/apps/${this.appId}/settings`).then(app => {
+      this.appModel = app;
+      this.page = _.findWhere(app.pages, {slug: this.$routeParams.slug});
+      if (!this.page) {
+        $rootScope.appEvent('alert-error', ['App Page Not Found', '']);
+      }
+    });
+  }
+}
+
+angular.module('grafana.controllers').controller('AppPageCtrl', AppPageCtrl);
+

+ 1 - 1
public/app/features/apps/partials/edit.html

@@ -78,7 +78,7 @@
 				</div>
 				<ul>
 					<li ng-repeat="page in ctrl.appModel.pages">
-						<a href="{{page.url}}" class="external-link">{{page.name}}</a>
+						<a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external-link">{{page.name}}</a>
 					</li>
 				</ul>
 			</div>

+ 3 - 3
public/app/features/apps/partials/list.html

@@ -2,7 +2,7 @@
 </navbar>
 
 <div class="page-container">
-  <div class="page-wide" ng-init="ctrl.init()">
+  <div class="page-wide">
     <h1>Apps</h1>
 
 		<div ng-if="!ctrl.apps">
@@ -18,13 +18,13 @@
           <li>
             <div class="filter-list-card-controls">
               <div class="filter-list-card-config">
-								<a href="apps/edit/{{app.appId}}">
+								<a href="apps/{{app.appId}}/edit">
 									<i class="fa fa-cog"></i>
 								</a>
               </div>
             </div>
 						<span class="filter-list-card-title">
-							<a href="apps/edit/{{app.appId}}">
+							<a href="apps/{{app.appId}}/edit">
 								{{app.name}}
 							</a>
 							&nbsp; &nbsp;

+ 17 - 0
public/app/features/apps/partials/page.html

@@ -0,0 +1,17 @@
+<navbar icon="fa fa-fw fa-cubes" title="{{ctrl.appModel.name}}" title-url="apps/{{ctrl.appId}}/edit" subnav="true">
+	<ul class="nav">
+		<li class="active"><a href="apps/{{ctrl.appId}}/ctrl.page.slug">{{ctrl.page.name}}</a></li>
+	</ul>
+</navbar>
+
+<div class="page-container">
+	<div class="page-wide">
+		<h1>{{ctrl.page.name}}</h1>
+
+		<div ng-if="ctrl.page">
+			<plugin-component type="app-page">
+			</plugin-component>
+		</div>
+
+	</div>
+</div>

+ 0 - 1
public/less/login.less

@@ -84,7 +84,6 @@
     min-width: 150px;
     display: inline-block;
     border: 1px solid lighten(@btnInverseBackground, 10%);
-    color: @grayLight;
   }
 }
 

+ 5 - 5
public/less/sidemenu.less

@@ -41,8 +41,8 @@
   .top-nav-menu-btn {
     a {
       background-color: @sideMenuBackground;
-      padding-right: 46px;
-      padding-left: 24px;
+      padding-right: 54px;
+      padding-left: 16px;
     }
     .icon-gf-grafana_wordmark  {
       display: inline-block;
@@ -88,6 +88,7 @@
   vertical-align: middle;
   overflow: hidden;
   text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 .icon-circle {
@@ -111,9 +112,8 @@
 .sidemenu-item {
   color: @linkColor;
   line-height: 47px;
-  padding: 0px 10px 0px 20px;
+  padding: 0px 10px 0px 10px;
   display: block;
-  white-space: nowrap;
 
   &:hover {
     background-color: @sideMenuBackgroundHighlight;
@@ -193,7 +193,7 @@
 .sidemenu-org {
   border-bottom: @sideMenuBorder;
   border-top: @sideMenuBorder;
-  padding: 17px 10px 15px 21px;
+  padding: 17px 10px 15px 14px;
   box-sizing: border-box;
   cursor: pointer;
   &:hover {

+ 0 - 1
public/views/index.html

@@ -50,7 +50,6 @@
 		window.grafanaBootData = {
 			user:[[.User]],
 			settings: [[.Settings]],
-			pluginModules: [[.PluginModules]],
 			mainNavLinks: [[.MainNavLinks]]
 		};
 	</script>