diff --git a/examples/nginx-app/module.js b/examples/nginx-app/module.js
index 9e061df04cc..1805d1a0fad 100644
--- a/examples/nginx-app/module.js
+++ b/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,
};
});
diff --git a/examples/nginx-app/partials/logs.html b/examples/nginx-app/partials/logs.html
new file mode 100644
index 00000000000..38ad06f4384
--- /dev/null
+++ b/examples/nginx-app/partials/logs.html
@@ -0,0 +1,4 @@
+
+
Nginx logs view
+
+Logs!
diff --git a/examples/nginx-app/partials/stream.html b/examples/nginx-app/partials/stream.html
index a89afaced57..fc0f367046f 100644
--- a/examples/nginx-app/partials/stream.html
+++ b/examples/nginx-app/partials/stream.html
@@ -1,12 +1,4 @@
-
-
-
-
+Nginx stream view
+testing!
diff --git a/examples/nginx-app/plugin.json b/examples/nginx-app/plugin.json
index 6941fe87f17..7e3cdd7ad08 100644
--- a/examples/nginx-app/plugin.json
+++ b/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": {
diff --git a/pkg/api/dtos/apps.go b/pkg/api/dtos/apps.go
index d10883a26d7..7a1e18148e2 100644
--- a/pkg/api/dtos/apps.go
+++ b/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 {
diff --git a/pkg/api/index.go b/pkg/api/index.go
index 522b53b9026..9eea59fa4d2 100644
--- a/pkg/api/index.go
+++ b/pkg/api/index.go
@@ -86,10 +86,6 @@ 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})
}
diff --git a/pkg/plugins/app_plugin.go b/pkg/plugins/app_plugin.go
index 547cd91283c..cb1349047da 100644
--- a/pkg/plugins/app_plugin.go
+++ b/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
}
diff --git a/public/app/app.ts b/public/app/app.ts
index d9ad3a449d3..a4e1bebca6e 100644
--- a/public/app/app.ts
+++ b/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
diff --git a/public/app/core/core.ts b/public/app/core/core.ts
index 55c0f4ec049..361dea334ef 100644
--- a/public/app/core/core.ts
+++ b/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';
diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts
index 8dd05a83ab0..518d5a8af56 100644
--- a/public/app/core/directives/plugin_component.ts
+++ b/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);
diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js
deleted file mode 100644
index 2d313d7322f..00000000000
--- a/public/app/core/routes/all.js
+++ /dev/null
@@ -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'
- });
- });
-
-});
diff --git a/public/app/core/routes/routes.ts b/public/app/core/routes/routes.ts
new file mode 100644
index 00000000000..95fecce664d
--- /dev/null
+++ b/public/app/core/routes/routes.ts
@@ -0,0 +1,171 @@
+///
+
+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);
diff --git a/public/app/features/apps/all.ts b/public/app/features/apps/all.ts
index fcdd27dff4d..9d54165e56b 100644
--- a/public/app/features/apps/all.ts
+++ b/public/app/features/apps/all.ts
@@ -1,2 +1,3 @@
import './edit_ctrl';
+import './page_ctrl';
import './list_ctrl';
diff --git a/public/app/features/apps/page_ctrl.ts b/public/app/features/apps/page_ctrl.ts
new file mode 100644
index 00000000000..ac04a59f43f
--- /dev/null
+++ b/public/app/features/apps/page_ctrl.ts
@@ -0,0 +1,23 @@
+///
+
+import angular from 'angular';
+import _ from 'lodash';
+
+export class AppPageCtrl {
+ page: any;
+ appModel: any;
+
+ /** @ngInject */
+ constructor(private backendSrv, private $routeParams: any, private $rootScope) {
+ this.backendSrv.get(`/api/org/apps/${this.$routeParams.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);
+
diff --git a/public/app/features/apps/partials/list.html b/public/app/features/apps/partials/list.html
index 6d2d8cc24df..59c1054816f 100644
--- a/public/app/features/apps/partials/list.html
+++ b/public/app/features/apps/partials/list.html
@@ -24,7 +24,7 @@
-
+
{{app.name}}
diff --git a/public/app/features/apps/partials/page.html b/public/app/features/apps/partials/page.html
new file mode 100644
index 00000000000..0f111ce9398
--- /dev/null
+++ b/public/app/features/apps/partials/page.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ App page
+
+
+
diff --git a/public/views/index.html b/public/views/index.html
index 40be796b306..724afd33f33 100644
--- a/public/views/index.html
+++ b/public/views/index.html
@@ -50,7 +50,6 @@
window.grafanaBootData = {
user:[[.User]],
settings: [[.Settings]],
- pluginModules: [[.PluginModules]],
mainNavLinks: [[.MainNavLinks]]
};