فهرست منبع

feat(apps): lots of more work on apps, changed app_plugin to app_settings in order to to confuse the app plugin model (definition) and app org settings

Torkel Ödegaard 10 سال پیش
والد
کامیت
c1e94e61d0

+ 5 - 4
pkg/api/api.go

@@ -41,8 +41,8 @@ func Register(r *macaron.Macaron) {
 	r.Get("/admin/orgs", reqGrafanaAdmin, Index)
 	r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
 
-	r.Get("/org/apps", reqSignedIn, Index)
-	r.Get("/org/apps/edit/*", reqSignedIn, Index)
+	r.Get("/apps", reqSignedIn, Index)
+	r.Get("/apps/edit/*", reqSignedIn, Index)
 
 	r.Get("/dashboard/*", reqSignedIn, Index)
 	r.Get("/dashboard-solo/*", reqSignedIn, Index)
@@ -119,8 +119,9 @@ func Register(r *macaron.Macaron) {
 			r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
 
 			// apps
-			r.Get("/apps", wrap(GetAppPlugins))
-			r.Post("/apps", bind(m.UpdateAppPluginCmd{}), wrap(UpdateAppPlugin))
+			r.Get("/apps", wrap(GetOrgAppsList))
+			r.Get("/apps/:appId/settings", wrap(GetAppSettingsById))
+			r.Post("/apps/:appId/settings", bind(m.UpdateAppSettingsCmd{}), wrap(UpdateAppSettings))
 		}, reqOrgAdmin)
 
 		// create new org

+ 0 - 63
pkg/api/app_plugin.go

@@ -1,63 +0,0 @@
-package api
-
-import (
-	"github.com/grafana/grafana/pkg/api/dtos"
-	"github.com/grafana/grafana/pkg/bus"
-	"github.com/grafana/grafana/pkg/middleware"
-	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/plugins"
-)
-
-func GetAppPlugins(c *middleware.Context) Response {
-	query := m.GetAppPluginsQuery{OrgId: c.OrgId}
-
-	if err := bus.Dispatch(&query); err != nil {
-		return ApiError(500, "Failed to list Plugin Bundles", err)
-	}
-
-	translateToDto := func(app *plugins.AppPlugin) *dtos.AppPlugin {
-		return &dtos.AppPlugin{
-			Name:    app.Name,
-			Type:    app.Type,
-			Enabled: app.Enabled,
-			Pinned:  app.Pinned,
-			Module:  app.Module,
-			Info:    &app.Info,
-		}
-	}
-
-	seenApps := make(map[string]bool)
-	result := make([]*dtos.AppPlugin, 0)
-	for _, orgApp := range query.Result {
-		if def, ok := plugins.Apps[orgApp.Type]; ok {
-			pluginDto := translateToDto(def)
-			pluginDto.Enabled = orgApp.Enabled
-			pluginDto.JsonData = orgApp.JsonData
-			result = append(result, pluginDto)
-			seenApps[orgApp.Type] = true
-		}
-	}
-
-	for _, app := range plugins.Apps {
-		if _, ok := seenApps[app.Type]; !ok {
-			result = append(result, translateToDto(app))
-		}
-	}
-
-	return Json(200, result)
-}
-
-func UpdateAppPlugin(c *middleware.Context, cmd m.UpdateAppPluginCmd) Response {
-	cmd.OrgId = c.OrgId
-
-	if _, ok := plugins.Apps[cmd.Type]; !ok {
-		return ApiError(404, "App type not installed.", nil)
-	}
-
-	err := bus.Dispatch(&cmd)
-	if err != nil {
-		return ApiError(500, "Failed to update App Plugin", err)
-	}
-
-	return ApiSuccess("App updated")
-}

+ 59 - 0
pkg/api/app_settings.go

@@ -0,0 +1,59 @@
+package api
+
+import (
+	"github.com/grafana/grafana/pkg/api/dtos"
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/middleware"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/plugins"
+)
+
+func GetOrgAppsList(c *middleware.Context) Response {
+	orgApps, err := plugins.GetOrgAppSettings(c.OrgId)
+
+	if err != nil {
+		return ApiError(500, "Failed to list of apps", err)
+	}
+
+	result := make([]*dtos.AppSettings, 0)
+	for _, app := range plugins.Apps {
+		orgApp := orgApps[app.Id]
+		result = append(result, dtos.NewAppSettingsDto(app, orgApp))
+	}
+
+	return Json(200, result)
+}
+
+func GetAppSettingsById(c *middleware.Context) Response {
+	appId := c.Params(":appId")
+
+	if pluginDef, exists := plugins.Apps[appId]; !exists {
+		return ApiError(404, "PluginId not found, no installed plugin with that id", nil)
+	} else {
+		orgApps, err := plugins.GetOrgAppSettings(c.OrgId)
+		if err != nil {
+			return ApiError(500, "Failed to get org app settings ", nil)
+		}
+		orgApp := orgApps[appId]
+
+		return Json(200, dtos.NewAppSettingsDto(pluginDef, orgApp))
+	}
+}
+
+func UpdateAppSettings(c *middleware.Context, cmd m.UpdateAppSettingsCmd) Response {
+	appId := c.Params(":appId")
+
+	cmd.OrgId = c.OrgId
+	cmd.AppId = appId
+
+	if _, ok := plugins.Apps[cmd.AppId]; !ok {
+		return ApiError(404, "App type not installed.", nil)
+	}
+
+	err := bus.Dispatch(&cmd)
+	if err != nil {
+		return ApiError(500, "Failed to update App Plugin", err)
+	}
+
+	return ApiSuccess("App updated")
+}

+ 9 - 10
pkg/api/datasources.go

@@ -118,18 +118,17 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
 func GetDataSourcePlugins(c *middleware.Context) {
 	dsList := make(map[string]*plugins.DataSourcePlugin)
 
-	orgApps := m.GetAppPluginsQuery{OrgId: c.OrgId}
-	err := bus.Dispatch(&orgApps)
-	if err != nil {
+	if enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId); err != nil {
 		c.JsonApiErr(500, "Failed to get org apps", err)
-	}
-	enabledPlugins := plugins.GetEnabledPlugins(orgApps.Result)
+		return
+	} else {
 
-	for key, value := range enabledPlugins.DataSources {
-		if !value.BuiltIn {
-			dsList[key] = value
+		for key, value := range enabledPlugins.DataSources {
+			if !value.BuiltIn {
+				dsList[key] = value
+			}
 		}
-	}
 
-	c.JSON(200, dsList)
+		c.JSON(200, dsList)
+	}
 }

+ 0 - 13
pkg/api/dtos/app_plugin.go

@@ -1,13 +0,0 @@
-package dtos
-
-import "github.com/grafana/grafana/pkg/plugins"
-
-type AppPlugin struct {
-	Name     string                 `json:"name"`
-	Type     string                 `json:"type"`
-	Enabled  bool                   `json:"enabled"`
-	Pinned   bool                   `json:"pinned"`
-	Module   string                 `json:"module"`
-	Info     *plugins.PluginInfo    `json:"info"`
-	JsonData map[string]interface{} `json:"jsonData"`
-}

+ 31 - 0
pkg/api/dtos/apps.go

@@ -0,0 +1,31 @@
+package dtos
+
+import (
+	"github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/plugins"
+)
+
+type AppSettings struct {
+	Name     string                 `json:"name"`
+	AppId    string                 `json:"appId"`
+	Enabled  bool                   `json:"enabled"`
+	Pinned   bool                   `json:"pinned"`
+	Info     *plugins.PluginInfo    `json:"info"`
+	JsonData map[string]interface{} `json:"jsonData"`
+}
+
+func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings {
+	dto := &AppSettings{
+		AppId: def.Id,
+		Name:  def.Name,
+		Info:  &def.Info,
+	}
+
+	if data != nil {
+		dto.Enabled = data.Enabled
+		dto.Pinned = data.Pinned
+		dto.Info = &def.Info
+	}
+
+	return dto
+}

+ 1 - 4
pkg/api/frontendsettings.go

@@ -29,14 +29,11 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
 	datasources := make(map[string]interface{})
 	var defaultDatasource string
 
-	orgApps := m.GetAppPluginsQuery{OrgId: c.OrgId}
-	err := bus.Dispatch(&orgApps)
+	enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId)
 	if err != nil {
 		return nil, err
 	}
 
-	enabledPlugins := plugins.GetEnabledPlugins(orgApps.Result)
-
 	for _, ds := range orgDataSources {
 		url := ds.Url
 

+ 1 - 5
pkg/api/index.go

@@ -2,7 +2,6 @@ package api
 
 import (
 	"github.com/grafana/grafana/pkg/api/dtos"
-	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/plugins"
@@ -69,14 +68,11 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 		})
 	}
 
-	orgApps := m.GetAppPluginsQuery{OrgId: c.OrgId}
-	err = bus.Dispatch(&orgApps)
+	enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId)
 	if err != nil {
 		return nil, err
 	}
 
-	enabledPlugins := plugins.GetEnabledPlugins(orgApps.Result)
-
 	for _, plugin := range enabledPlugins.Apps {
 		if plugin.Module != "" {
 			data.PluginModules = append(data.PluginModules, plugin.Module)

+ 7 - 8
pkg/models/app_plugin.go → pkg/models/app_settings.go

@@ -2,9 +2,9 @@ package models
 
 import "time"
 
-type AppPlugin struct {
+type AppSettings struct {
 	Id       int64
-	Type     string
+	AppId    string
 	OrgId    int64
 	Enabled  bool
 	Pinned   bool
@@ -18,19 +18,18 @@ type AppPlugin struct {
 // COMMANDS
 
 // Also acts as api DTO
-type UpdateAppPluginCmd struct {
-	Type     string                 `json:"type" binding:"Required"`
+type UpdateAppSettingsCmd struct {
 	Enabled  bool                   `json:"enabled"`
 	Pinned   bool                   `json:"pinned"`
 	JsonData map[string]interface{} `json:"jsonData"`
 
-	Id    int64 `json:"-"`
-	OrgId int64 `json:"-"`
+	AppId string `json:"-"`
+	OrgId int64  `json:"-"`
 }
 
 // ---------------------
 // QUERIES
-type GetAppPluginsQuery struct {
+type GetAppSettingsQuery struct {
 	OrgId  int64
-	Result []*AppPlugin
+	Result []*AppSettings
 }

+ 0 - 76
pkg/plugins/plugins.go

@@ -13,7 +13,6 @@ import (
 	"text/template"
 
 	"github.com/grafana/grafana/pkg/log"
-	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util"
 )
@@ -180,78 +179,3 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
 
 	return loader.Load(jsonParser, currentDir)
 }
-
-func GetEnabledPlugins(orgApps []*models.AppPlugin) EnabledPlugins {
-	enabledPlugins := NewEnabledPlugins()
-
-	orgAppsMap := make(map[string]*models.AppPlugin)
-	for _, orgApp := range orgApps {
-		orgAppsMap[orgApp.Type] = orgApp
-	}
-	seenPanels := make(map[string]bool)
-	seenApi := make(map[string]bool)
-
-	for appType, installedApp := range Apps {
-		var app AppPlugin
-		app = *installedApp
-
-		// check if the app is stored in the DB for this org and if so, use the
-		// state stored there.
-		if b, ok := orgAppsMap[appType]; ok {
-			app.Enabled = b.Enabled
-			app.Pinned = b.Pinned
-		}
-
-		// if app.Enabled {
-		// 	for _, d := range app.DatasourcePlugins {
-		// 		if ds, ok := DataSources[d]; ok {
-		// 			enabledPlugins.DataSourcePlugins[d] = ds
-		// 		}
-		// 	}
-		// 	for _, p := range app.PanelPlugins {
-		// 		if panel, ok := Panels[p]; ok {
-		// 			if _, ok := seenPanels[p]; !ok {
-		// 				seenPanels[p] = true
-		// 				enabledPlugins.PanelPlugins = append(enabledPlugins.PanelPlugins, panel)
-		// 			}
-		// 		}
-		// 	}
-		// 	for _, a := range app.ApiPlugins {
-		// 		if api, ok := ApiPlugins[a]; ok {
-		// 			if _, ok := seenApi[a]; !ok {
-		// 				seenApi[a] = true
-		// 				enabledPlugins.ApiPlugins = append(enabledPlugins.ApiPlugins, api)
-		// 			}
-		// 		}
-		// 	}
-		// 	enabledPlugins.AppPlugins = append(enabledPlugins.AppPlugins, &app)
-		// }
-	}
-
-	// add all plugins that are not part of an App.
-	for d, installedDs := range DataSources {
-		if installedDs.App == "" {
-			enabledPlugins.DataSources[d] = installedDs
-		}
-	}
-
-	for p, panel := range Panels {
-		if panel.App == "" {
-			if _, ok := seenPanels[p]; !ok {
-				seenPanels[p] = true
-				enabledPlugins.Panels = append(enabledPlugins.Panels, panel)
-			}
-		}
-	}
-
-	for a, api := range ApiPlugins {
-		if api.App == "" {
-			if _, ok := seenApi[a]; !ok {
-				seenApi[a] = true
-				enabledPlugins.ApiList = append(enabledPlugins.ApiList, api)
-			}
-		}
-	}
-
-	return enabledPlugins
-}

+ 96 - 0
pkg/plugins/queries.go

@@ -0,0 +1,96 @@
+package plugins
+
+import (
+	"github.com/grafana/grafana/pkg/bus"
+	m "github.com/grafana/grafana/pkg/models"
+)
+
+func GetOrgAppSettings(orgId int64) (map[string]*m.AppSettings, error) {
+	query := m.GetAppSettingsQuery{OrgId: orgId}
+
+	if err := bus.Dispatch(&query); err != nil {
+		return nil, err
+	}
+
+	orgAppsMap := make(map[string]*m.AppSettings)
+	for _, orgApp := range query.Result {
+		orgAppsMap[orgApp.AppId] = orgApp
+	}
+
+	return orgAppsMap, nil
+}
+
+func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
+	enabledPlugins := NewEnabledPlugins()
+	orgApps, err := GetOrgAppSettings(orgId)
+	if err != nil {
+		return nil, err
+	}
+
+	seenPanels := make(map[string]bool)
+	seenApi := make(map[string]bool)
+
+	for appType, installedApp := range Apps {
+		var app AppPlugin
+		app = *installedApp
+
+		// check if the app is stored in the DB for this org and if so, use the
+		// state stored there.
+		if b, ok := orgApps[appType]; ok {
+			app.Enabled = b.Enabled
+			app.Pinned = b.Pinned
+		}
+
+		// if app.Enabled {
+		// 	for _, d := range app.DatasourcePlugins {
+		// 		if ds, ok := DataSources[d]; ok {
+		// 			enabledPlugins.DataSourcePlugins[d] = ds
+		// 		}
+		// 	}
+		// 	for _, p := range app.PanelPlugins {
+		// 		if panel, ok := Panels[p]; ok {
+		// 			if _, ok := seenPanels[p]; !ok {
+		// 				seenPanels[p] = true
+		// 				enabledPlugins.PanelPlugins = append(enabledPlugins.PanelPlugins, panel)
+		// 			}
+		// 		}
+		// 	}
+		// 	for _, a := range app.ApiPlugins {
+		// 		if api, ok := ApiPlugins[a]; ok {
+		// 			if _, ok := seenApi[a]; !ok {
+		// 				seenApi[a] = true
+		// 				enabledPlugins.ApiPlugins = append(enabledPlugins.ApiPlugins, api)
+		// 			}
+		// 		}
+		// 	}
+		// 	enabledPlugins.AppPlugins = append(enabledPlugins.AppPlugins, &app)
+		// }
+	}
+
+	// add all plugins that are not part of an App.
+	for d, installedDs := range DataSources {
+		if installedDs.App == "" {
+			enabledPlugins.DataSources[d] = installedDs
+		}
+	}
+
+	for p, panel := range Panels {
+		if panel.App == "" {
+			if _, ok := seenPanels[p]; !ok {
+				seenPanels[p] = true
+				enabledPlugins.Panels = append(enabledPlugins.Panels, panel)
+			}
+		}
+	}
+
+	for a, api := range ApiPlugins {
+		if api.App == "" {
+			if _, ok := seenApi[a]; !ok {
+				seenApi[a] = true
+				enabledPlugins.ApiList = append(enabledPlugins.ApiList, api)
+			}
+		}
+	}
+
+	return &enabledPlugins, nil
+}

+ 9 - 9
pkg/services/sqlstore/app_plugin.go → pkg/services/sqlstore/app_settings.go

@@ -8,27 +8,27 @@ import (
 )
 
 func init() {
-	bus.AddHandler("sql", GetAppPlugins)
-	bus.AddHandler("sql", UpdateAppPlugin)
+	bus.AddHandler("sql", GetAppSettings)
+	bus.AddHandler("sql", UpdateAppSettings)
 }
 
-func GetAppPlugins(query *m.GetAppPluginsQuery) error {
+func GetAppSettings(query *m.GetAppSettingsQuery) error {
 	sess := x.Where("org_id=?", query.OrgId)
 
-	query.Result = make([]*m.AppPlugin, 0)
+	query.Result = make([]*m.AppSettings, 0)
 	return sess.Find(&query.Result)
 }
 
-func UpdateAppPlugin(cmd *m.UpdateAppPluginCmd) error {
+func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error {
 	return inTransaction2(func(sess *session) error {
-		var app m.AppPlugin
+		var app m.AppSettings
 
-		exists, err := sess.Where("org_id=? and type=?", cmd.OrgId, cmd.Type).Get(&app)
+		exists, err := sess.Where("org_id=? and app_id=?", cmd.OrgId, cmd.AppId).Get(&app)
 		sess.UseBool("enabled")
 		sess.UseBool("pinned")
 		if !exists {
-			app = m.AppPlugin{
-				Type:     cmd.Type,
+			app = m.AppSettings{
+				AppId:    cmd.AppId,
 				OrgId:    cmd.OrgId,
 				Enabled:  cmd.Enabled,
 				Pinned:   cmd.Pinned,

+ 7 - 7
pkg/services/sqlstore/migrations/app_plugin.go → pkg/services/sqlstore/migrations/app_settings.go

@@ -2,14 +2,14 @@ package migrations
 
 import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
 
-func addAppPluginMigration(mg *Migrator) {
+func addAppSettingsMigration(mg *Migrator) {
 
-	var appPluginV2 = Table{
-		Name: "app_plugin",
+	appSettingsV1 := Table{
+		Name: "app_settings",
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "org_id", Type: DB_BigInt, Nullable: true},
-			{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "app_id", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "enabled", Type: DB_Bool, Nullable: false},
 			{Name: "pinned", Type: DB_Bool, Nullable: false},
 			{Name: "json_data", Type: DB_Text, Nullable: true},
@@ -17,12 +17,12 @@ func addAppPluginMigration(mg *Migrator) {
 			{Name: "updated", Type: DB_DateTime, Nullable: false},
 		},
 		Indices: []*Index{
-			{Cols: []string{"org_id", "type"}, Type: UniqueIndex},
+			{Cols: []string{"org_id", "app_id"}, Type: UniqueIndex},
 		},
 	}
 
-	mg.AddMigration("create app_plugin table v2", NewAddTableMigration(appPluginV2))
+	mg.AddMigration("create app_settings table v1", NewAddTableMigration(appSettingsV1))
 
 	//-------  indexes ------------------
-	addTableIndicesMigrations(mg, "v2", appPluginV2)
+	addTableIndicesMigrations(mg, "v3", appSettingsV1)
 }

+ 1 - 1
pkg/services/sqlstore/migrations/migrations.go

@@ -18,7 +18,7 @@ func AddMigrations(mg *Migrator) {
 	addApiKeyMigrations(mg)
 	addDashboardSnapshotMigrations(mg)
 	addQuotaMigration(mg)
-	addAppPluginMigration(mg)
+	addAppSettingsMigration(mg)
 	addSessionMigration(mg)
 }
 

+ 1 - 1
public/app/core/routes/all.js

@@ -138,7 +138,7 @@ define([
         controllerAs: 'ctrl',
         resolve: loadAppsBundle,
       })
-      .when('/apps/edit/:type', {
+      .when('/apps/edit/:appId', {
         templateUrl: 'app/features/apps/partials/edit.html',
         controller: 'AppEditCtrl',
         controllerAs: 'ctrl',

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

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

+ 1 - 4
public/app/features/apps/app_srv.ts

@@ -15,9 +15,6 @@ export class AppSrv {
   }
 
   get(type) {
-    if (this.apps[type]) {
-      return this.$q.when(this.apps[type]);
-    }
     return this.getAll().then(() => {
       return this.apps[type];
     });
@@ -38,7 +35,7 @@ export class AppSrv {
 
   update(app) {
     return this.backendSrv.post('api/org/apps', app).then(resp => {
-      this.apps[app.type] = app;
+
     });
   }
 }

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

@@ -8,20 +8,36 @@ export class AppEditCtrl {
   appModel: any;
 
   /** @ngInject */
-  constructor(private appSrv: any, private $routeParams: any) {}
+  constructor(private backendSrv: any, private $routeParams: any) {}
 
   init() {
     this.appModel = {};
-    this.appSrv.get(this.$routeParams.type).then(result => {
-      this.appModel = _.clone(result);
+    this.backendSrv.get(`/api/org/apps/${this.$routeParams.appId}/settings`).then(result => {
+      this.appModel = result;
     });
   }
 
-  update() {
-    this.appSrv.update(this.appModel).then(function() {
-      window.location.href = config.appSubUrl + "org/apps";
+  update(options) {
+    var updateCmd = _.extend({
+      appId: this.appModel.appId,
+      orgId: this.appModel.orgId,
+      enabled: this.appModel.enabled,
+      pinned: this.appModel.pinned,
+      jsonData: this.appModel.jsonData,
+    }, options);
+
+    this.backendSrv.post(`/api/org/apps/${this.$routeParams.appId}/settings`, updateCmd).then(function() {
+      window.location.href = window.location.href;
     });
   }
+
+  toggleEnabled() {
+    this.update({enabled: this.appModel.enabled});
+  }
+
+  togglePinned() {
+    this.update({pinned: this.appModel.pinned});
+  }
 }
 
 angular.module('grafana.controllers').controller('AppEditCtrl', AppEditCtrl);

+ 3 - 3
public/app/features/apps/list_ctrl.ts

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

+ 6 - 4
public/app/features/apps/partials/edit.html

@@ -1,7 +1,7 @@
 <topnav title="Apps" icon="fa fa-fw fa-cubes" subnav="true">
 	<ul class="nav">
-		<li ><a href="org/apps">Overview</a></li>
-		<li class="active" ><a href="org/apps/edit/{{ctrl.current.type}}">Edit</a></li>
+		<li ><a href="apps">Overview</a></li>
+		<li class="active" ><a href="apps/edit/{{ctrl.current.type}}">Edit</a></li>
 	</ul>
 </topnav>
 
@@ -25,10 +25,12 @@
 		<em>
 			{{ctrl.appModel.info.description}}
 		</em>
+		<br><br>
 
 		<div class="form-inline">
-			<editor-checkbox text="Enabled" model="ctrl.appModel.enabled" change="enabledChanged()"></editor-checkbox>
-			<editor-checkbox text="Pinned" model="ctrl.appModel.pinned" change="enabledChanged()"></editor-checkbox>
+			<editor-checkbox text="Enabled" model="ctrl.appModel.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
+			&nbsp; &nbsp; &nbsp;
+			<editor-checkbox text="Pinned" model="ctrl.appModel.pinned" change="ctrl.togglePinned()"></editor-checkbox>
 		</div>
 
 			<app-config-loader></app-config-loader>

+ 4 - 1
public/app/features/apps/partials/list.html

@@ -15,10 +15,13 @@
 		<ul class="filter-list">
       <li ng-repeat="app in ctrl.apps">
         <ul class="filter-list-card">
+					<li class="filter-list-card-image">
+						<img src="{{app.info.logos.small}}">
+					</li>
           <li>
             <div class="filter-list-card-controls">
               <div class="filter-list-card-config">
-								<a href="apps/edit/{{app.type}}">
+								<a href="apps/edit/{{app.appId}}">
 									<i class="fa fa-cog"></i>
 								</a>
               </div>

+ 6 - 0
public/less/filter-list.less

@@ -52,6 +52,12 @@
   font-weight: normal;
 }
 
+.filter-list-card-image {
+  width: 50px;
+  padding: 5px 50px 5px 5px;
+}
+
+
 .filter-list-card-status {
   color: #777;
   font-size: 12px;

+ 3 - 0
tasks/options/watch.js

@@ -33,6 +33,9 @@ module.exports = function(config, grunt) {
       grunt.config(option, result);
       grunt.task.run('typescript:build');
       grunt.task.run('tslint');
+      // copy ts file also used by source maps
+      newPath = filepath.replace(/^public/, 'public_gen');
+      grunt.file.copy(filepath, newPath);
     }
   });