mirror of
https://github.com/grafana/grafana.git
synced 2024-12-28 01:41:24 -06:00
WIP: rough prototype of dashboard folders
Breaks some stuff like selected dash in the search result. In dashboard search, if the user is not searching then the result is returned as a tree structure. No ACL's or user group ux yet.
This commit is contained in:
parent
d10d897d65
commit
1248728d7f
@ -14,6 +14,7 @@ func Search(c *middleware.Context) {
|
||||
tags := c.QueryStrings("tag")
|
||||
starred := c.Query("starred")
|
||||
limit := c.QueryInt("limit")
|
||||
browseMode := c.Query("browseMode")
|
||||
|
||||
if limit == 0 {
|
||||
limit = 1000
|
||||
@ -35,6 +36,7 @@ func Search(c *middleware.Context) {
|
||||
IsStarred: starred == "true",
|
||||
OrgId: c.OrgId,
|
||||
DashboardIds: dbids,
|
||||
BrowseMode: browseMode == "true",
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&searchQuery)
|
||||
|
@ -18,6 +18,14 @@ var (
|
||||
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
|
||||
)
|
||||
|
||||
type PermissionType int
|
||||
|
||||
const (
|
||||
PERMISSION_EDIT PermissionType = 4
|
||||
PERMISSION_READ_ONLY_EDIT PermissionType = 2
|
||||
PERMISSION_VIEW PermissionType = 1
|
||||
)
|
||||
|
||||
type UpdatePluginDashboardError struct {
|
||||
PluginId string
|
||||
}
|
||||
@ -47,6 +55,8 @@ type Dashboard struct {
|
||||
|
||||
UpdatedBy int64
|
||||
CreatedBy int64
|
||||
ParentId int64
|
||||
IsFolder bool
|
||||
|
||||
Title string
|
||||
Data *simplejson.Json
|
||||
@ -111,6 +121,8 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
|
||||
dash.UpdatedBy = userId
|
||||
dash.OrgId = cmd.OrgId
|
||||
dash.PluginId = cmd.PluginId
|
||||
dash.IsFolder = cmd.IsFolder
|
||||
dash.ParentId = cmd.ParentId
|
||||
dash.UpdateSlug()
|
||||
return dash
|
||||
}
|
||||
@ -138,6 +150,8 @@ type SaveDashboardCommand struct {
|
||||
OrgId int64 `json:"-"`
|
||||
RestoredFrom int `json:"-"`
|
||||
PluginId string `json:"-"`
|
||||
ParentId int64 `json:"parentId"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
|
||||
Result *Dashboard
|
||||
}
|
||||
|
13
pkg/models/user_group.go
Normal file
13
pkg/models/user_group.go
Normal file
@ -0,0 +1,13 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// UserGroup model
|
||||
type UserGroup struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
Name string
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
@ -44,6 +44,7 @@ func searchHandler(query *Query) error {
|
||||
IsStarred: query.IsStarred,
|
||||
OrgId: query.OrgId,
|
||||
DashboardIds: query.DashboardIds,
|
||||
BrowseMode: query.BrowseMode,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&dashQuery); err != nil {
|
||||
|
@ -19,6 +19,9 @@ func TestSearch(t *testing.T) {
|
||||
&Hit{Id: 16, Title: "CCAA", Tags: []string{"BB", "AA"}},
|
||||
&Hit{Id: 10, Title: "AABB", Tags: []string{"CC", "AA"}},
|
||||
&Hit{Id: 15, Title: "BBAA", Tags: []string{"EE", "AA", "BB"}},
|
||||
&Hit{Id: 17, Title: "FOLDER", Dashboards: []Hit{
|
||||
{Id: 18, Title: "ZZAA", Tags: []string{"ZZ"}},
|
||||
}},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -57,5 +60,17 @@ func TestSearch(t *testing.T) {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Convey("That returns result in browse mode", func() {
|
||||
query.BrowseMode = true
|
||||
err := searchHandler(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("should return correct results", func() {
|
||||
So(query.Result[3].Title, ShouldEqual, "FOLDER")
|
||||
So(len(query.Result[3].Dashboards), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -7,15 +7,18 @@ const (
|
||||
DashHitHome HitType = "dash-home"
|
||||
DashHitJson HitType = "dash-json"
|
||||
DashHitScripted HitType = "dash-scripted"
|
||||
DashHitFolder HitType = "dash-folder"
|
||||
)
|
||||
|
||||
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"`
|
||||
Id int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Uri string `json:"uri"`
|
||||
Type HitType `json:"type"`
|
||||
Tags []string `json:"tags"`
|
||||
IsStarred bool `json:"isStarred"`
|
||||
ParentId int64 `json:"parentId"`
|
||||
Dashboards []Hit `json:"dashboards"`
|
||||
}
|
||||
|
||||
type HitList []*Hit
|
||||
@ -32,6 +35,7 @@ type Query struct {
|
||||
Limit int
|
||||
IsStarred bool
|
||||
DashboardIds []int
|
||||
BrowseMode bool
|
||||
|
||||
Result HitList
|
||||
}
|
||||
@ -42,6 +46,7 @@ type FindPersistedDashboardsQuery struct {
|
||||
UserId int64
|
||||
IsStarred bool
|
||||
DashboardIds []int
|
||||
BrowseMode bool
|
||||
|
||||
Result HitList
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
Convey("Testing Alerting data access", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
testDash := insertTestDashboard("dashboard with alerts", 1, "alert")
|
||||
testDash := insertTestDashboard("dashboard with alerts", 1, 0, false, "alert")
|
||||
|
||||
items := []*m.Alert{
|
||||
{
|
||||
|
@ -148,13 +148,15 @@ func GetDashboard(query *m.GetDashboardQuery) error {
|
||||
}
|
||||
|
||||
type DashboardSearchProjection struct {
|
||||
Id int64
|
||||
Title string
|
||||
Slug string
|
||||
Term string
|
||||
Id int64
|
||||
Title string
|
||||
Slug string
|
||||
Term string
|
||||
IsFolder bool
|
||||
ParentId int64
|
||||
}
|
||||
|
||||
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
@ -162,7 +164,9 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
dashboard.id,
|
||||
dashboard.title,
|
||||
dashboard.slug,
|
||||
dashboard_tag.term
|
||||
dashboard_tag.term,
|
||||
dashboard.is_folder,
|
||||
dashboard.parent_id
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id`)
|
||||
|
||||
@ -200,8 +204,16 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000"))
|
||||
|
||||
var res []DashboardSearchProjection
|
||||
|
||||
err := x.Sql(sql.String(), params...).Find(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
res, err := findDashboards(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -213,11 +225,12 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
hit, exists := hits[item.Id]
|
||||
if !exists {
|
||||
hit = &search.Hit{
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + item.Slug,
|
||||
Type: search.DashHitDB,
|
||||
Tags: []string{},
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + item.Slug,
|
||||
Type: getHitType(item),
|
||||
ParentId: item.ParentId,
|
||||
Tags: []string{},
|
||||
}
|
||||
query.Result = append(query.Result, hit)
|
||||
hits[item.Id] = hit
|
||||
@ -227,9 +240,52 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
}
|
||||
}
|
||||
|
||||
if query.BrowseMode {
|
||||
convertToDashboardFolders(query)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func getHitType(item DashboardSearchProjection) search.HitType {
|
||||
var hitType search.HitType
|
||||
if item.IsFolder {
|
||||
hitType = search.DashHitFolder
|
||||
} else {
|
||||
hitType = search.DashHitDB
|
||||
}
|
||||
|
||||
return hitType
|
||||
}
|
||||
|
||||
func convertToDashboardFolders(query *search.FindPersistedDashboardsQuery) error {
|
||||
root := make(map[int64]*search.Hit)
|
||||
var keys []int64
|
||||
|
||||
// Add dashboards and folders that should be at the root level
|
||||
for _, item := range query.Result {
|
||||
if item.Type == search.DashHitFolder || item.ParentId == 0 {
|
||||
root[item.Id] = item
|
||||
keys = append(keys, item.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// Populate folders with their child dashboards
|
||||
for _, item := range query.Result {
|
||||
if item.Type == search.DashHitDB && item.ParentId > 0 {
|
||||
root[item.ParentId].Dashboards = append(root[item.ParentId].Dashboards, *item)
|
||||
}
|
||||
}
|
||||
|
||||
query.Result = make([]*search.Hit, 0)
|
||||
|
||||
for _, key := range keys {
|
||||
query.Result = append(query.Result, root[key])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
sql := `SELECT
|
||||
COUNT(*) as count,
|
||||
|
@ -11,9 +11,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
)
|
||||
|
||||
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
|
||||
func insertTestDashboard(title string, orgId int64, parentId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
OrgId: orgId,
|
||||
ParentId: parentId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
@ -33,14 +35,23 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
InitTestDB(t)
|
||||
|
||||
Convey("Given saved dashboard", func() {
|
||||
savedDash := insertTestDashboard("test dash 23", 1, "prod", "webapp")
|
||||
insertTestDashboard("test dash 45", 1, "prod")
|
||||
insertTestDashboard("test dash 67", 1, "prod", "webapp")
|
||||
savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
savedDash := insertTestDashboard("test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
insertTestDashboard("test dash 45", 1, savedFolder.Id, false, "prod")
|
||||
insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
|
||||
|
||||
Convey("Should return dashboard model", func() {
|
||||
So(savedDash.Title, ShouldEqual, "test dash 23")
|
||||
So(savedDash.Slug, ShouldEqual, "test-dash-23")
|
||||
So(savedDash.Id, ShouldNotEqual, 0)
|
||||
So(savedDash.IsFolder, ShouldBeFalse)
|
||||
So(savedDash.ParentId, ShouldBeGreaterThan, 0)
|
||||
|
||||
So(savedFolder.Title, ShouldEqual, "1 test dash folder")
|
||||
So(savedFolder.Slug, ShouldEqual, "1-test-dash-folder")
|
||||
So(savedFolder.Id, ShouldNotEqual, 0)
|
||||
So(savedFolder.IsFolder, ShouldBeTrue)
|
||||
So(savedFolder.ParentId, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to get dashboard", func() {
|
||||
@ -54,10 +65,11 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
So(query.Result.Title, ShouldEqual, "test dash 23")
|
||||
So(query.Result.Slug, ShouldEqual, "test-dash-23")
|
||||
So(query.Result.IsFolder, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Should be able to delete dashboard", func() {
|
||||
insertTestDashboard("delete me", 1, "delete this")
|
||||
insertTestDashboard("delete me", 1, 0, false, "delete this")
|
||||
|
||||
dashboardSlug := slug.Make("delete me")
|
||||
|
||||
@ -114,12 +126,45 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
hit := query.Result[0]
|
||||
So(len(hit.Tags), ShouldEqual, 2)
|
||||
So(hit.Type, ShouldEqual, search.DashHitDB)
|
||||
So(hit.ParentId, ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to search for dashboard folder", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
Title: "1 test dash folder",
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
hit := query.Result[0]
|
||||
So(hit.Type, ShouldEqual, search.DashHitFolder)
|
||||
})
|
||||
|
||||
Convey("Should be able to browse dashboard folders", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
BrowseMode: true,
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
hit := query.Result[0]
|
||||
So(hit.Type, ShouldEqual, search.DashHitFolder)
|
||||
So(len(hit.Dashboards), ShouldEqual, 2)
|
||||
So(hit.Dashboards[0].Title, ShouldEqual, "test dash 23")
|
||||
|
||||
})
|
||||
|
||||
Convey("Should be able to search for dashboard by dashboard ids", func() {
|
||||
Convey("should be able to find two dashboards by id", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
DashboardIds: []int{1, 2},
|
||||
DashboardIds: []int{2, 3},
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
@ -171,7 +216,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
|
||||
starredDash := insertTestDashboard("starred dash", 1)
|
||||
starredDash := insertTestDashboard("starred dash", 1, 0, false)
|
||||
StarDashboard(&m.StarDashboardCommand{
|
||||
DashboardId: starredDash.Id,
|
||||
UserId: 10,
|
||||
|
@ -136,4 +136,40 @@ func addDashboardMigration(mg *Migrator) {
|
||||
mg.AddMigration("Update dashboard_tag table charset", NewTableCharsetMigration("dashboard_tag", []*Column{
|
||||
{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
|
||||
}))
|
||||
|
||||
// add column to store parent_id for dashboard folder structure
|
||||
mg.AddMigration("Add column parent_id in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
Name: "parent_id", Type: DB_BigInt, Nullable: true,
|
||||
}))
|
||||
|
||||
mg.AddMigration("Add column isFolder in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
Name: "is_folder", Type: DB_Bool, Nullable: false, Default: "0",
|
||||
}))
|
||||
|
||||
dashboardAclV1 := Table{
|
||||
Name: "dashboard_acl",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "dashboard_id", Type: DB_BigInt},
|
||||
{Name: "user_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "user_group_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "permissions", Type: DB_SmallInt, Default: "4"},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"dashboard_id", "user_id"}, Type: UniqueIndex},
|
||||
{Cols: []string{"dashboard_id", "user_group_id"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create dashboard acl table", NewAddTableMigration(dashboardAclV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add unique index dashboard_acl_org_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[0]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id_user_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[1]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id_group_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[2]))
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ func AddMigrations(mg *Migrator) {
|
||||
addAnnotationMig(mg)
|
||||
addTestDataMigrations(mg)
|
||||
addDashboardVersionMigration(mg)
|
||||
addUserGroupMigrations(mg)
|
||||
}
|
||||
|
||||
func addMigrationLogMigrations(mg *Migrator) {
|
||||
|
48
pkg/services/sqlstore/migrations/user_group_mig.go
Normal file
48
pkg/services/sqlstore/migrations/user_group_mig.go
Normal file
@ -0,0 +1,48 @@
|
||||
package migrations
|
||||
|
||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addUserGroupMigrations(mg *Migrator) {
|
||||
userGroupV1 := Table{
|
||||
Name: "user_group",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "name"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create user group table", NewAddTableMigration(userGroupV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index user_group.org_id", NewAddIndexMigration(userGroupV1, userGroupV1.Indices[0]))
|
||||
mg.AddMigration("add unique index user_group_org_id_name", NewAddIndexMigration(userGroupV1, userGroupV1.Indices[1]))
|
||||
|
||||
userGroupMemberV1 := Table{
|
||||
Name: "user_group_member",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "user_group_id", Type: DB_BigInt},
|
||||
{Name: "user_id", Type: DB_BigInt},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "user_group_id", "user_id"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create user group member table", NewAddTableMigration(userGroupMemberV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index user_group_member.org_id", NewAddIndexMigration(userGroupMemberV1, userGroupMemberV1.Indices[0]))
|
||||
mg.AddMigration("add unique index user_group_member_org_id_user_group_id_user_id", NewAddIndexMigration(userGroupMemberV1, userGroupMemberV1.Indices[1]))
|
||||
}
|
@ -56,22 +56,38 @@
|
||||
<div class="search-results-container" ng-if="!ctrl.tagsMode">
|
||||
<h6 ng-hide="ctrl.results.length">No dashboards matching your query were found.</h6>
|
||||
|
||||
<a class="search-item pointer search-item-{{row.type}}" bindonce ng-repeat="row in ctrl.results"
|
||||
ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{row.url}}">
|
||||
<div bindonce ng-repeat="row in ctrl.results">
|
||||
<a class="search-item pointer search-item-{{row.type}}"
|
||||
ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{row.url}}">
|
||||
|
||||
<span class="search-result-tags">
|
||||
<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name="tag" class="label label-tag">
|
||||
{{tag}}
|
||||
</span>
|
||||
<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
|
||||
</span>
|
||||
<span class="search-result-tags">
|
||||
<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name="tag" class="label label-tag">
|
||||
{{tag}}
|
||||
</span>
|
||||
<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
|
||||
</span>
|
||||
|
||||
<span class="search-result-link">
|
||||
<i class="fa 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 class="search-item search-item-child pointer search-item-{{child.type}}" ng-repeat="child in row.dashboards"
|
||||
ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{'dashboard/' + child.uri}}">
|
||||
<span class="search-result-tags">
|
||||
<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in child.tags" tag-color-from-name="tag" class="label label-tag">
|
||||
{{tag}}
|
||||
</span>
|
||||
<i class="fa" ng-class="{'fa-star': child.isStarred, 'fa-star-o': !child.isStarred}"></i>
|
||||
</span>
|
||||
|
||||
<span class="search-result-link">
|
||||
<i class="fa search-result-icon"></i>
|
||||
<span bo-text="child.title"></span>
|
||||
</span>
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-button-row">
|
||||
<a class="btn btn-secondary" href="dashboard/new" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
|
||||
|
@ -104,6 +104,8 @@ export class SearchCtrl {
|
||||
this.currentSearchId = this.currentSearchId + 1;
|
||||
var localSearchId = this.currentSearchId;
|
||||
|
||||
this.query.browseMode = this.queryHasNoFilters();
|
||||
|
||||
return this.backendSrv.search(this.query).then((results) => {
|
||||
if (localSearchId < this.currentSearchId) { return; }
|
||||
|
||||
|
@ -118,10 +118,6 @@
|
||||
content: "\f009";
|
||||
}
|
||||
|
||||
&.search-item-dash-home .search-result-icon::before {
|
||||
content: "\f015";
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $tight-form-func-bg;
|
||||
@include left-brand-border-gradient();
|
||||
@ -142,6 +138,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.search-item-child {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.search-item-dash-home > .search-result-link > .search-result-icon::before {
|
||||
content: "\f015";
|
||||
}
|
||||
|
||||
.search-item-dash-folder > .search-result-link > .search-result-icon::before {
|
||||
content: "\f07c";
|
||||
}
|
||||
|
||||
.search-button-row {
|
||||
padding: $spacer*2;
|
||||
|
Loading…
Reference in New Issue
Block a user