Forráskód Böngészése

Major refactorings around searching, moved to seperate package, trying to move stuff out of models package, extend search support searching different types of entities and different types of dashboards, #960

Torkel Ödegaard 10 éve
szülő
commit
448a8b8d1c

+ 1 - 2
conf/defaults.ini

@@ -216,7 +216,6 @@ exchange = grafana_events
 #################################### Dashboard JSON files ##########################
 #################################### Dashboard JSON files ##########################
 [dashboards.json]
 [dashboards.json]
 enabled = false
 enabled = false
-path = dashboards
-orgs = *
+path = /var/lib/grafana/dashboards
 
 
 
 

+ 8 - 0
conf/sample.ini

@@ -211,3 +211,11 @@
 ;enabled = false
 ;enabled = false
 ;rabbitmq_url = amqp://localhost/
 ;rabbitmq_url = amqp://localhost/
 ;exchange = grafana_events
 ;exchange = grafana_events
+
+;#################################### Dashboard JSON files ##########################
+[dashboards.json]
+;enabled = false
+;path = /var/lib/grafana/dashboards
+
+
+

+ 1 - 1
main.go

@@ -14,8 +14,8 @@ import (
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/plugins"
 	"github.com/grafana/grafana/pkg/plugins"
+	"github.com/grafana/grafana/pkg/search"
 	"github.com/grafana/grafana/pkg/services/eventpublisher"
 	"github.com/grafana/grafana/pkg/services/eventpublisher"
-	"github.com/grafana/grafana/pkg/services/search"
 	"github.com/grafana/grafana/pkg/services/sqlstore"
 	"github.com/grafana/grafana/pkg/services/sqlstore"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/social"
 	"github.com/grafana/grafana/pkg/social"

+ 1 - 1
pkg/api/dashboard.go

@@ -10,7 +10,7 @@ import (
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/services/search"
+	"github.com/grafana/grafana/pkg/search"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util"
 	"github.com/grafana/grafana/pkg/util"
 )
 )

+ 1 - 1
pkg/api/search.go

@@ -3,7 +3,7 @@ package api
 import (
 import (
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/middleware"
-	"github.com/grafana/grafana/pkg/services/search"
+	"github.com/grafana/grafana/pkg/search"
 )
 )
 
 
 func Search(c *middleware.Context) {
 func Search(c *middleware.Context) {

+ 10 - 0
pkg/models/dashboards.go

@@ -126,3 +126,13 @@ type GetDashboardQuery struct {
 
 
 	Result *Dashboard
 	Result *Dashboard
 }
 }
+
+type DashboardTagCloudItem struct {
+	Term  string `json:"term"`
+	Count int    `json:"count"`
+}
+
+type GetDashboardTagsQuery struct {
+	OrgId  int64
+	Result []*DashboardTagCloudItem
+}

+ 1 - 28
pkg/models/search.go

@@ -1,12 +1,6 @@
 package models
 package models
 
 
-type SearchResult struct {
-	Dashboards []*DashboardSearchHit    `json:"dashboards"`
-	Tags       []*DashboardTagCloudItem `json:"tags"`
-	TagsOnly   bool                     `json:"tagsOnly"`
-}
-
-type DashboardSearchHit struct {
+type SearchHit struct {
 	Id        int64    `json:"id"`
 	Id        int64    `json:"id"`
 	Title     string   `json:"title"`
 	Title     string   `json:"title"`
 	Uri       string   `json:"uri"`
 	Uri       string   `json:"uri"`
@@ -14,24 +8,3 @@ type DashboardSearchHit struct {
 	Tags      []string `json:"tags"`
 	Tags      []string `json:"tags"`
 	IsStarred bool     `json:"isStarred"`
 	IsStarred bool     `json:"isStarred"`
 }
 }
-
-type DashboardTagCloudItem struct {
-	Term  string `json:"term"`
-	Count int    `json:"count"`
-}
-
-type SearchDashboardsQuery struct {
-	Title     string
-	Tag       string
-	OrgId     int64
-	UserId    int64
-	Limit     int
-	IsStarred bool
-
-	Result []*DashboardSearchHit
-}
-
-type GetDashboardTagsQuery struct {
-	OrgId  int64
-	Result []*DashboardTagCloudItem
-}

+ 7 - 16
pkg/services/search/search.go → pkg/search/handlers.go

@@ -2,23 +2,13 @@ package search
 
 
 import (
 import (
 	"path/filepath"
 	"path/filepath"
+	"sort"
 
 
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
 )
 )
 
 
-type Query struct {
-	Title     string
-	Tag       string
-	OrgId     int64
-	UserId    int64
-	Limit     int
-	IsStarred bool
-
-	Result []*m.DashboardSearchHit
-}
-
 var jsonDashIndex *JsonDashIndex
 var jsonDashIndex *JsonDashIndex
 
 
 func Init() {
 func Init() {
@@ -33,15 +23,14 @@ func Init() {
 			jsonFilesPath = filepath.Join(setting.HomePath, jsonFilesPath)
 			jsonFilesPath = filepath.Join(setting.HomePath, jsonFilesPath)
 		}
 		}
 
 
-		orgIds := jsonIndexCfg.Key("org_ids").String()
-		jsonDashIndex = NewJsonDashIndex(jsonFilesPath, orgIds)
+		jsonDashIndex = NewJsonDashIndex(jsonFilesPath)
 	}
 	}
 }
 }
 
 
 func searchHandler(query *Query) error {
 func searchHandler(query *Query) error {
-	hits := make([]*m.DashboardSearchHit, 0)
+	hits := make(HitList, 0)
 
 
-	dashQuery := m.SearchDashboardsQuery{
+	dashQuery := FindPersistedDashboardsQuery{
 		Title:     query.Title,
 		Title:     query.Title,
 		Tag:       query.Tag,
 		Tag:       query.Tag,
 		UserId:    query.UserId,
 		UserId:    query.UserId,
@@ -65,6 +54,8 @@ func searchHandler(query *Query) error {
 		hits = append(hits, jsonHits...)
 		hits = append(hits, jsonHits...)
 	}
 	}
 
 
+	sort.Sort(hits)
+
 	if err := setIsStarredFlagOnSearchResults(query.UserId, hits); err != nil {
 	if err := setIsStarredFlagOnSearchResults(query.UserId, hits); err != nil {
 		return err
 		return err
 	}
 	}
@@ -73,7 +64,7 @@ func searchHandler(query *Query) error {
 	return nil
 	return nil
 }
 }
 
 
-func setIsStarredFlagOnSearchResults(userId int64, hits []*m.DashboardSearchHit) error {
+func setIsStarredFlagOnSearchResults(userId int64, hits []*Hit) error {
 	query := m.GetUserStarsQuery{UserId: userId}
 	query := m.GetUserStarsQuery{UserId: userId}
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
 		return err
 		return err

+ 7 - 8
pkg/services/search/json_index.go → pkg/search/json_index.go

@@ -11,9 +11,8 @@ import (
 )
 )
 
 
 type JsonDashIndex struct {
 type JsonDashIndex struct {
-	path    string
-	orgsIds []int64
-	items   []*JsonDashIndexItem
+	path  string
+	items []*JsonDashIndexItem
 }
 }
 
 
 type JsonDashIndexItem struct {
 type JsonDashIndexItem struct {
@@ -23,7 +22,7 @@ type JsonDashIndexItem struct {
 	Dashboard  *m.Dashboard
 	Dashboard  *m.Dashboard
 }
 }
 
 
-func NewJsonDashIndex(path string, orgIds string) *JsonDashIndex {
+func NewJsonDashIndex(path string) *JsonDashIndex {
 	log.Info("Creating json dashboard index for path: ", path)
 	log.Info("Creating json dashboard index for path: ", path)
 
 
 	index := JsonDashIndex{}
 	index := JsonDashIndex{}
@@ -32,8 +31,8 @@ func NewJsonDashIndex(path string, orgIds string) *JsonDashIndex {
 	return &index
 	return &index
 }
 }
 
 
-func (index *JsonDashIndex) Search(query *Query) ([]*m.DashboardSearchHit, error) {
-	results := make([]*m.DashboardSearchHit, 0)
+func (index *JsonDashIndex) Search(query *Query) ([]*Hit, error) {
+	results := make([]*Hit, 0)
 
 
 	for _, item := range index.items {
 	for _, item := range index.items {
 		if len(results) > query.Limit {
 		if len(results) > query.Limit {
@@ -49,8 +48,8 @@ func (index *JsonDashIndex) Search(query *Query) ([]*m.DashboardSearchHit, error
 
 
 		// add results with matchig title filter
 		// add results with matchig title filter
 		if strings.Contains(item.TitleLower, query.Title) {
 		if strings.Contains(item.TitleLower, query.Title) {
-			results = append(results, &m.DashboardSearchHit{
-				Type:  m.DashTypeJson,
+			results = append(results, &Hit{
+				Type:  DashHitJson,
 				Title: item.Dashboard.Title,
 				Title: item.Dashboard.Title,
 				Tags:  item.Dashboard.GetTags(),
 				Tags:  item.Dashboard.GetTags(),
 				Uri:   "file/" + item.Path,
 				Uri:   "file/" + item.Path,

+ 1 - 1
pkg/services/search/json_index_test.go → pkg/search/json_index_test.go

@@ -9,7 +9,7 @@ import (
 func TestJsonDashIndex(t *testing.T) {
 func TestJsonDashIndex(t *testing.T) {
 
 
 	Convey("Given the json dash index", t, func() {
 	Convey("Given the json dash index", t, func() {
-		index := NewJsonDashIndex("../../../public/dashboards/", "*")
+		index := NewJsonDashIndex("../../public/dashboards/", "*")
 
 
 		Convey("Should be able to update index", func() {
 		Convey("Should be able to update index", func() {
 			err := index.updateIndex()
 			err := index.updateIndex()

+ 47 - 0
pkg/search/models.go

@@ -0,0 +1,47 @@
+package search
+
+type HitType string
+
+const (
+	DashHitDB       HitType = "dash-db"
+	DashHitHome     HitType = "dash-home"
+	DashHitJson     HitType = "dash-json"
+	DashHitScripted HitType = "dash-scripted"
+)
+
+type Hit struct {
+	Id        int64    `json:"id"`
+	Title     string   `json:"title"`
+	Uri       string   `json:"uri"`
+	Type      HitType  `json:"type"`
+	Tags      []string `json:"tags"`
+	IsStarred bool     `json:"isStarred"`
+}
+
+type HitList []*Hit
+
+func (s HitList) Len() int           { return len(s) }
+func (s HitList) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+func (s HitList) Less(i, j int) bool { return s[i].Title < s[j].Title }
+
+type Query struct {
+	Title     string
+	Tag       string
+	OrgId     int64
+	UserId    int64
+	Limit     int
+	IsStarred bool
+
+	Result HitList
+}
+
+type FindPersistedDashboardsQuery struct {
+	Title     string
+	Tag       string
+	OrgId     int64
+	UserId    int64
+	Limit     int
+	IsStarred bool
+
+	Result HitList
+}

+ 6 - 5
pkg/services/sqlstore/dashboard.go

@@ -8,6 +8,7 @@ import (
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/metrics"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/search"
 )
 )
 
 
 func init() {
 func init() {
@@ -119,7 +120,7 @@ type DashboardSearchProjection struct {
 	Term  string
 	Term  string
 }
 }
 
 
-func SearchDashboards(query *m.SearchDashboardsQuery) error {
+func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
 	var sql bytes.Buffer
 	var sql bytes.Buffer
 	params := make([]interface{}, 0)
 	params := make([]interface{}, 0)
 
 
@@ -166,17 +167,17 @@ func SearchDashboards(query *m.SearchDashboardsQuery) error {
 		return err
 		return err
 	}
 	}
 
 
-	query.Result = make([]*m.DashboardSearchHit, 0)
-	hits := make(map[int64]*m.DashboardSearchHit)
+	query.Result = make([]*search.Hit, 0)
+	hits := make(map[int64]*search.Hit)
 
 
 	for _, item := range res {
 	for _, item := range res {
 		hit, exists := hits[item.Id]
 		hit, exists := hits[item.Id]
 		if !exists {
 		if !exists {
-			hit = &m.DashboardSearchHit{
+			hit = &search.Hit{
 				Id:    item.Id,
 				Id:    item.Id,
 				Title: item.Title,
 				Title: item.Title,
 				Uri:   "db/" + item.Slug,
 				Uri:   "db/" + item.Slug,
-				Type:  m.DashTypeDB,
+				Type:  search.DashHitDB,
 				Tags:  []string{},
 				Tags:  []string{},
 			}
 			}
 			query.Result = append(query.Result, hit)
 			query.Result = append(query.Result, hit)

+ 5 - 4
pkg/services/sqlstore/dashboard_test.go

@@ -6,6 +6,7 @@ import (
 	. "github.com/smartystreets/goconvey/convey"
 	. "github.com/smartystreets/goconvey/convey"
 
 
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/search"
 )
 )
 
 
 func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
 func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
@@ -85,7 +86,7 @@ func TestDashboardDataAccess(t *testing.T) {
 			})
 			})
 
 
 			Convey("Should be able to search for dashboard", func() {
 			Convey("Should be able to search for dashboard", func() {
-				query := m.SearchDashboardsQuery{
+				query := search.FindPersistedDashboardsQuery{
 					Title: "test",
 					Title: "test",
 					OrgId: 1,
 					OrgId: 1,
 				}
 				}
@@ -99,8 +100,8 @@ func TestDashboardDataAccess(t *testing.T) {
 			})
 			})
 
 
 			Convey("Should be able to search for dashboards using tags", func() {
 			Convey("Should be able to search for dashboards using tags", func() {
-				query1 := m.SearchDashboardsQuery{Tag: "webapp", OrgId: 1}
-				query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", OrgId: 1}
+				query1 := search.FindPersistedDashboardsQuery{Tag: "webapp", OrgId: 1}
+				query2 := search.FindPersistedDashboardsQuery{Tag: "tagdoesnotexist", OrgId: 1}
 
 
 				err := SearchDashboards(&query1)
 				err := SearchDashboards(&query1)
 				err = SearchDashboards(&query2)
 				err = SearchDashboards(&query2)
@@ -146,7 +147,7 @@ func TestDashboardDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("Should be able to search for starred dashboards", func() {
 				Convey("Should be able to search for starred dashboards", func() {
-					query := m.SearchDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true}
+					query := search.FindPersistedDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true}
 					err := SearchDashboards(&query)
 					err := SearchDashboards(&query)
 
 
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)

+ 9 - 28
public/app/controllers/search.js

@@ -40,15 +40,15 @@ function (angular, _, config) {
         $scope.moveSelection(-1);
         $scope.moveSelection(-1);
       }
       }
       if (evt.keyCode === 13) {
       if (evt.keyCode === 13) {
-        if ($scope.query.tagcloud) {
-          var tag = $scope.results.tags[$scope.selectedIndex];
+        if ($scope.tagMode) {
+          var tag = $scope.results[$scope.selectedIndex];
           if (tag) {
           if (tag) {
             $scope.filterByTag(tag.term);
             $scope.filterByTag(tag.term);
           }
           }
           return;
           return;
         }
         }
 
 
-        var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
+        var selectedDash = $scope.results[$scope.selectedIndex];
         if (selectedDash) {
         if (selectedDash) {
           $location.search({});
           $location.search({});
           $location.path(selectedDash.url);
           $location.path(selectedDash.url);
@@ -57,7 +57,9 @@ function (angular, _, config) {
     };
     };
 
 
     $scope.moveSelection = function(direction) {
     $scope.moveSelection = function(direction) {
-      $scope.selectedIndex = Math.max(Math.min($scope.selectedIndex + direction, $scope.resultCount - 1), 0);
+      var max = ($scope.results || []).length;
+      var newIndex = $scope.selectedIndex + direction;
+      $scope.selectedIndex = ((newIndex %= max) < 0) ? newIndex + max : newIndex;
     };
     };
 
 
     $scope.searchDashboards = function() {
     $scope.searchDashboards = function() {
@@ -68,14 +70,13 @@ function (angular, _, config) {
       return backendSrv.search($scope.query).then(function(results) {
       return backendSrv.search($scope.query).then(function(results) {
         if (localSearchId < $scope.currentSearchId) { return; }
         if (localSearchId < $scope.currentSearchId) { return; }
 
 
-        $scope.resultCount = results.length;
         $scope.results = _.map(results, function(dash) {
         $scope.results = _.map(results, function(dash) {
           dash.url = 'dashboard/' + dash.uri;
           dash.url = 'dashboard/' + dash.uri;
           return dash;
           return dash;
         });
         });
 
 
         if ($scope.queryHasNoFilters()) {
         if ($scope.queryHasNoFilters()) {
-          $scope.results.unshift({ title: 'Home', url: config.appSubUrl + '/', isHome: true });
+          $scope.results.unshift({ title: 'Home', url: config.appSubUrl + '/', type: 'dash-home' });
         }
         }
       });
       });
     };
     };
@@ -97,10 +98,10 @@ function (angular, _, config) {
     };
     };
 
 
     $scope.getTags = function() {
     $scope.getTags = function() {
-      $scope.tagsMode = true;
       return backendSrv.get('/api/dashboards/tags').then(function(results) {
       return backendSrv.get('/api/dashboards/tags').then(function(results) {
-        $scope.resultCount = results.length;
+        $scope.tagsMode = true;
         $scope.results = results;
         $scope.results = results;
+        $scope.giveSearchFocus = $scope.giveSearchFocus + 1;
       });
       });
     };
     };
 
 
@@ -116,26 +117,6 @@ function (angular, _, config) {
       $scope.searchDashboards();
       $scope.searchDashboards();
     };
     };
 
 
-    $scope.addMetricToCurrentDashboard = function (metricId) {
-      $scope.dashboard.rows.push({
-        title: '',
-        height: '250px',
-        editable: true,
-        panels: [
-      {
-        type: 'graphite',
-        title: 'test',
-        span: 12,
-        targets: [{ target: metricId }]
-      }
-      ]
-      });
-    };
-
-    $scope.toggleImport = function () {
-      $scope.showImport = !$scope.showImport;
-    };
-
     $scope.newDashboard = function() {
     $scope.newDashboard = function() {
       $location.url('dashboard/new');
       $location.url('dashboard/new');
     };
     };

+ 2 - 2
public/app/features/dashlinks/module.js

@@ -133,12 +133,12 @@ function (angular, _) {
 
 
     $scope.searchDashboards = function(link) {
     $scope.searchDashboards = function(link) {
       return backendSrv.search({tag: link.tag}).then(function(results) {
       return backendSrv.search({tag: link.tag}).then(function(results) {
-        return _.reduce(results.dashboards, function(memo, dash) {
+        return _.reduce(results, function(memo, dash) {
           // do not add current dashboard
           // do not add current dashboard
           if (dash.id !== currentDashId) {
           if (dash.id !== currentDashId) {
             memo.push({
             memo.push({
               title: dash.title,
               title: dash.title,
-              url: 'dashboard/db/'+ dash.slug,
+              url: 'dashboard/' + dash.uri,
               icon: 'fa fa-th-large',
               icon: 'fa fa-th-large',
               keepTime: link.keepTime,
               keepTime: link.keepTime,
               includeVars: link.includeVars
               includeVars: link.includeVars

+ 1 - 1
public/app/panels/dashlist/module.html

@@ -1,7 +1,7 @@
 <grafana-panel>
 <grafana-panel>
 	<div class="dashlist">
 	<div class="dashlist">
 	<div class="dashlist-item" ng-repeat="dash in dashList">
 	<div class="dashlist-item" ng-repeat="dash in dashList">
-		<a class="dashlist-link" href="dashboard/{{dash.uri}}">
+		<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
 			<span class="dashlist-title">
 			<span class="dashlist-title">
 				{{dash.title}}
 				{{dash.title}}
 			</span>
 			</span>

+ 1 - 1
public/app/panels/dashlist/module.js

@@ -62,7 +62,7 @@ function (angular, app, _, config, PanelMeta) {
       }
       }
 
 
       return backendSrv.search(params).then(function(result) {
       return backendSrv.search(params).then(function(result) {
-        $scope.dashList = result.dashboards;
+        $scope.dashList = result;
       });
       });
     };
     };
 
 

+ 25 - 27
public/app/partials/search.html

@@ -24,41 +24,39 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<div ng-if="!showImport">
-		<div class="search-results-container" ng-if="tagsMode">
-			<div class="row">
-				<div class="span6 offset1">
-					<div ng-repeat="tag in results" class="pointer" style="width: 180px; float: left;"
-						ng-class="{'selected': $index === selectedIndex }"
-						ng-click="filterByTag(tag.term, $event)">
-						<a class="search-result-tag label label-tag" tag-color-from-name tag="tag.term">
-							<i class="fa fa-tag"></i>
-							<span>{{tag.term}} &nbsp;({{tag.count}})</span>
-						</a>
-					</div>
+	<div class="search-results-container" ng-if="tagsMode">
+		<div class="row">
+			<div class="span6 offset1">
+				<div ng-repeat="tag in results" class="pointer" style="width: 180px; float: left;"
+					ng-class="{'selected': $index === selectedIndex }"
+					ng-click="filterByTag(tag.term, $event)">
+					<a class="search-result-tag label label-tag" tag-color-from-name tag="tag.term">
+						<i class="fa fa-tag"></i>
+						<span>{{tag.term}} &nbsp;({{tag.count}})</span>
+					</a>
 				</div>
 				</div>
 			</div>
 			</div>
 		</div>
 		</div>
+	</div>
 
 
-		<div class="search-results-container" ng-if="!tagsMode">
-			<h6 ng-hide="results.length">No dashboards matching your query were found.</h6>
+	<div class="search-results-container" ng-if="!tagsMode">
+		<h6 ng-hide="results.length">No dashboards matching your query were found.</h6>
 
 
-			<a class="search-result-item pointer search-result-item-{{row.type}}" bindonce ng-repeat="row in results"
-				ng-class="{'selected': $index == selectedIndex}" ng-href="{{row.url}}">
+		<a class="search-item pointer search-item-{{row.type}}" bindonce ng-repeat="row in results"
+			ng-class="{'selected': $index == selectedIndex}" ng-href="{{row.url}}">
 
 
-				<span class="search-result-tags">
-					<span ng-click="filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name tag="tag"  class="label label-tag">
-						{{tag}}
-					</span>
-					<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
+			<span class="search-result-tags">
+				<span ng-click="filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name tag="tag"  class="label label-tag">
+					{{tag}}
 				</span>
 				</span>
+				<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
+			</span>
 
 
-				<span class="search-result-link">
-					<i class="search-result-icon"></i>
-					<span bo-text="row.title"></span>
-				</span>
-			</a>
-		</div>
+			<span class="search-result-link">
+				<i class="fa search-result-icon"></i>
+				<span bo-text="row.title"></span>
+			</span>
+		</a>
 	</div>
 	</div>
 
 
 	<div class="search-button-row">
 	<div class="search-button-row">

+ 9 - 2
public/css/less/search.less

@@ -41,7 +41,7 @@
   display: block;
   display: block;
   line-height: 28px;
   line-height: 28px;
 
 
-  .search-result-item:hover, .search-result-item.selected {
+  .search-item:hover, .search-item.selected {
     background-color: @grafanaListHighlight;
     background-color: @grafanaListHighlight;
   }
   }
 
 
@@ -67,12 +67,19 @@
     }
     }
   }
   }
 
 
-  .search-result-item {
+  .search-item {
     display: block;
     display: block;
     padding: 3px 10px;
     padding: 3px 10px;
     white-space: nowrap;
     white-space: nowrap;
     background-color: @grafanaListBackground;
     background-color: @grafanaListBackground;
     margin-bottom: 4px;
     margin-bottom: 4px;
+    .search-result-icon:before {
+      content: "\f009";
+    }
+
+    &.search-item-dash-home .search-result-icon:before {
+      content: "\f015";
+    }
   }
   }
 
 
   .search-result-tags {
   .search-result-tags {