fix: Empty dashboards are hidden (#599)
This commit is contained in:
parent
b2e7509959
commit
1357eae9b8
|
|
@ -0,0 +1,25 @@
|
|||
#
|
||||
# Integration Test Config: emptyDashboardsAreHidden
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: Ping {{ server.hostname }}
|
||||
shell: ping {{ server.hostname }}
|
||||
icon: ping
|
||||
entity: server
|
||||
|
||||
entities:
|
||||
- file: entities/servers.yaml
|
||||
name: server
|
||||
|
||||
|
||||
dashboards:
|
||||
- title: Empty Dashboard
|
||||
contents:
|
||||
- title: Ping {{ server.hostname }}
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { describe, it, before, after } from 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import { By, until, Condition } from 'selenium-webdriver'
|
||||
//import * as waitOn from 'wait-on'
|
||||
import {
|
||||
getRootAndWait,
|
||||
takeScreenshotOnFailure,
|
||||
} from '../lib/elements.js'
|
||||
|
||||
describe('config: empty dashboards are hidden', function () {
|
||||
before(async function () {
|
||||
await runner.start('emptyDashboardsAreHidden')
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
takeScreenshotOnFailure(this.currentTest, webdriver);
|
||||
});
|
||||
|
||||
it('Test hidden dashboard', async function () {
|
||||
await getRootAndWait()
|
||||
|
||||
const title = await webdriver.getTitle()
|
||||
expect(title).to.be.equal("OliveTin")
|
||||
|
||||
await webdriver.findElement(By.id('sidebar-toggler-button')).click()
|
||||
|
||||
const navigationLinks = await webdriver.findElements(By.css('#navigation-links a'))
|
||||
|
||||
console.log('navigationLinks', navigationLinks)
|
||||
|
||||
expect(navigationLinks).to.not.be.empty
|
||||
expect(navigationLinks.length).to.be.equal(1, 'Expected the nav to only have 1 link')
|
||||
|
||||
const firstLinkId = await navigationLinks[0].getAttribute('id')
|
||||
|
||||
expect(firstLinkId).to.be.equal('showActions', 'Expected the first link to be the actions link')
|
||||
})
|
||||
})
|
||||
|
|
@ -325,8 +325,6 @@ func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *apiv1.GetDa
|
|||
|
||||
log.Tracef("GetDashboardComponents: %v", res)
|
||||
|
||||
dashboardCfgToPb(res, cfg.Dashboards, cfg)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ import (
|
|||
"sort"
|
||||
)
|
||||
|
||||
type DashboardRenderRequest struct {
|
||||
AuthenticatedUser *acl.AuthenticatedUser
|
||||
AllowedActionTitles []string `json:"allows_action_titles"`
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func buildDashboardResponse(ex *executor.Executor, cfg *config.Config, user *acl.AuthenticatedUser) *apiv1.GetDashboardComponentsResponse {
|
||||
res := &apiv1.GetDashboardComponentsResponse{
|
||||
AuthenticatedUser: user.Username,
|
||||
|
|
@ -36,12 +42,29 @@ func buildDashboardResponse(ex *executor.Executor, cfg *config.Config, user *acl
|
|||
}
|
||||
})
|
||||
|
||||
rr := &DashboardRenderRequest{
|
||||
AuthenticatedUser: user,
|
||||
AllowedActionTitles: getActionTitles(res.Actions),
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
res.EffectivePolicy = buildEffectivePolicy(user.EffectivePolicy)
|
||||
res.Diagnostics = buildDiagnostics(res.EffectivePolicy.ShowDiagnostics)
|
||||
res.Dashboards = dashboardCfgToPb(rr)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func getActionTitles(actions []*apiv1.Action) []string {
|
||||
titles := make([]string, 0, len(actions))
|
||||
|
||||
for _, action := range actions {
|
||||
titles = append(titles, action.Title)
|
||||
}
|
||||
|
||||
return titles
|
||||
}
|
||||
|
||||
func buildEffectivePolicy(policy *config.ConfigurationPolicy) *apiv1.EffectivePolicy {
|
||||
ret := &apiv1.EffectivePolicy{
|
||||
ShowDiagnostics: policy.ShowDiagnostics,
|
||||
|
|
|
|||
|
|
@ -3,40 +3,76 @@ package grpcapi
|
|||
import (
|
||||
apiv1 "github.com/OliveTin/OliveTin/gen/grpc/olivetin/api/v1"
|
||||
config "github.com/OliveTin/OliveTin/internal/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func dashboardCfgToPb(res *apiv1.GetDashboardComponentsResponse, dashboards []*config.DashboardComponent, cfg *config.Config) {
|
||||
for _, dashboard := range dashboards {
|
||||
res.Dashboards = append(res.Dashboards, &apiv1.DashboardComponent{
|
||||
func dashboardCfgToPb(rr *DashboardRenderRequest) []*apiv1.DashboardComponent {
|
||||
ret := make([]*apiv1.DashboardComponent, 0)
|
||||
|
||||
for _, dashboard := range cfg.Dashboards {
|
||||
pbdb := &apiv1.DashboardComponent{
|
||||
Type: "dashboard",
|
||||
Title: dashboard.Title,
|
||||
Contents: getDashboardComponentContents(dashboard, cfg),
|
||||
})
|
||||
Contents: removeNulls(getDashboardComponentContents(dashboard, rr)),
|
||||
}
|
||||
|
||||
if len(pbdb.Contents) == 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"dashboard": dashboard.Title,
|
||||
"username": rr.AuthenticatedUser.Username,
|
||||
}).Debugf("Dashboard has no readable contents, so it will not be visible in the web ui")
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, pbdb)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getDashboardComponentContents(dashboard *config.DashboardComponent, cfg *config.Config) []*apiv1.DashboardComponent {
|
||||
func removeNulls(components []*apiv1.DashboardComponent) []*apiv1.DashboardComponent {
|
||||
ret := make([]*apiv1.DashboardComponent, 0)
|
||||
|
||||
for _, component := range components {
|
||||
if component == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, component)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getDashboardComponentContents(dashboard *config.DashboardComponent, rr *DashboardRenderRequest) []*apiv1.DashboardComponent {
|
||||
ret := make([]*apiv1.DashboardComponent, 0)
|
||||
|
||||
for _, subitem := range dashboard.Contents {
|
||||
if subitem.Type == "fieldset" && subitem.Entity != "" {
|
||||
ret = append(ret, buildEntityFieldsets(subitem.Entity, &subitem)...)
|
||||
continue
|
||||
ret = append(ret, buildEntityFieldsets(subitem.Entity, &subitem, rr)...)
|
||||
} else {
|
||||
ret = append(ret, buildDashboardComponentSimple(&subitem, rr))
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func buildDashboardComponentSimple(subitem *config.DashboardComponent, rr *DashboardRenderRequest) *apiv1.DashboardComponent {
|
||||
if !slices.Contains(rr.AllowedActionTitles, subitem.Title) {
|
||||
return nil
|
||||
}
|
||||
|
||||
newitem := &apiv1.DashboardComponent{
|
||||
Title: subitem.Title,
|
||||
Type: getDashboardComponentType(&subitem),
|
||||
Contents: getDashboardComponentContents(&subitem, cfg),
|
||||
Icon: getDashboardComponentIcon(&subitem, cfg),
|
||||
Type: getDashboardComponentType(subitem),
|
||||
Contents: getDashboardComponentContents(subitem, rr),
|
||||
Icon: getDashboardComponentIcon(subitem, rr.cfg),
|
||||
CssClass: subitem.CssClass,
|
||||
}
|
||||
|
||||
ret = append(ret, newitem)
|
||||
}
|
||||
|
||||
return ret
|
||||
return newitem
|
||||
}
|
||||
|
||||
func getDashboardComponentIcon(item *config.DashboardComponent, cfg *config.Config) string {
|
||||
|
|
|
|||
|
|
@ -4,48 +4,75 @@ import (
|
|||
apiv1 "github.com/OliveTin/OliveTin/gen/grpc/olivetin/api/v1"
|
||||
config "github.com/OliveTin/OliveTin/internal/config"
|
||||
sv "github.com/OliveTin/OliveTin/internal/stringvariables"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func buildEntityFieldsets(entityTitle string, tpl *config.DashboardComponent) []*apiv1.DashboardComponent {
|
||||
func buildEntityFieldsets(entityTitle string, tpl *config.DashboardComponent, rr *DashboardRenderRequest) []*apiv1.DashboardComponent {
|
||||
ret := make([]*apiv1.DashboardComponent, 0)
|
||||
|
||||
entityCount := sv.GetEntityCount(entityTitle)
|
||||
|
||||
for i := 0; i < entityCount; i++ {
|
||||
ret = append(ret, buildEntityFieldset(tpl, entityTitle, i))
|
||||
for i := range entityCount {
|
||||
fs := buildEntityFieldset(tpl, entityTitle, i, rr)
|
||||
|
||||
if len(fs.Contents) > 0 {
|
||||
ret = append(ret, fs)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func buildEntityFieldset(tpl *config.DashboardComponent, entityTitle string, entityIndex int) *apiv1.DashboardComponent {
|
||||
func buildEntityFieldset(tpl *config.DashboardComponent, entityTitle string, entityIndex int, rr *DashboardRenderRequest) *apiv1.DashboardComponent {
|
||||
prefix := sv.GetEntityPrefix(entityTitle, entityIndex)
|
||||
|
||||
return &apiv1.DashboardComponent{
|
||||
Title: sv.ReplaceEntityVars(prefix, tpl.Title),
|
||||
Type: "fieldset",
|
||||
Contents: buildEntityFieldsetContents(tpl.Contents, prefix),
|
||||
Contents: removeFieldsetIfHasNoLinks(buildEntityFieldsetContents(tpl.Contents, prefix, rr)),
|
||||
CssClass: sv.ReplaceEntityVars(prefix, tpl.CssClass),
|
||||
}
|
||||
}
|
||||
|
||||
func buildEntityFieldsetContents(contents []config.DashboardComponent, prefix string) []*apiv1.DashboardComponent {
|
||||
func removeFieldsetIfHasNoLinks(contents []*apiv1.DashboardComponent) []*apiv1.DashboardComponent {
|
||||
for _, subitem := range contents {
|
||||
if subitem.Type == "link" {
|
||||
return contents
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildEntityFieldsetContents(contents []config.DashboardComponent, prefix string, rr *DashboardRenderRequest) []*apiv1.DashboardComponent {
|
||||
ret := make([]*apiv1.DashboardComponent, 0)
|
||||
|
||||
for _, subitem := range contents {
|
||||
c := cloneItem(&subitem, prefix, rr)
|
||||
|
||||
if c != nil {
|
||||
ret = append(ret, c)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func cloneItem(subitem *config.DashboardComponent, prefix string, rr *DashboardRenderRequest) *apiv1.DashboardComponent {
|
||||
clone := &apiv1.DashboardComponent{}
|
||||
clone.CssClass = sv.ReplaceEntityVars(prefix, subitem.CssClass)
|
||||
|
||||
if subitem.Type == "" || subitem.Type == "link" {
|
||||
clone.Type = "link"
|
||||
clone.Title = sv.ReplaceEntityVars(prefix, subitem.Title)
|
||||
|
||||
if !slices.Contains(rr.AllowedActionTitles, clone.Title) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
clone.Title = sv.ReplaceEntityVars(prefix, subitem.Title)
|
||||
clone.Type = subitem.Type
|
||||
}
|
||||
|
||||
ret = append(ret, clone)
|
||||
}
|
||||
|
||||
return ret
|
||||
return clone
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<h1 id = "page-title">OliveTin</h1>
|
||||
|
||||
<nav hidden>
|
||||
<nav id = "mainnav" hidden>
|
||||
<ul id = "navigation-links">
|
||||
<li title = "Actions">
|
||||
<a id = "showActions">Actions</a>
|
||||
|
|
|
|||
Loading…
Reference in New Issue