| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- package api
- import (
- "sort"
- "strconv"
- apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
- acl "github.com/OliveTin/OliveTin/internal/acl"
- config "github.com/OliveTin/OliveTin/internal/config"
- entities "github.com/OliveTin/OliveTin/internal/entities"
- "github.com/OliveTin/OliveTin/internal/tpl"
- log "github.com/sirupsen/logrus"
- "golang.org/x/exp/slices"
- )
- func renderDashboard(rr *DashboardRenderRequest, dashboardTitle string) *apiv1.Dashboard {
- if dashboardTitle == "default" {
- return buildDefaultDashboard(rr)
- }
- return findAndRenderDashboard(rr, dashboardTitle)
- }
- func getEntityFromRequest(rr *DashboardRenderRequest) *entities.Entity {
- if rr.EntityType == "" || rr.EntityKey == "" {
- return nil
- }
- entityInstances := entities.GetEntityInstances(rr.EntityType)
- if entity, ok := entityInstances[rr.EntityKey]; ok {
- return entity
- }
- return nil
- }
- func findAndRenderDashboard(rr *DashboardRenderRequest, dashboardTitle string) *apiv1.Dashboard {
- if dashboard := findDashboardByTitle(rr, dashboardTitle); dashboard != nil {
- return renderDashboardIfValid(dashboard, rr)
- }
- return renderDirectoryDashboard(rr, dashboardTitle)
- }
- func findDashboardByTitle(rr *DashboardRenderRequest, dashboardTitle string) *config.DashboardComponent {
- for _, dashboard := range rr.cfg.Dashboards {
- if dashboard.Title == dashboardTitle {
- return dashboard
- }
- }
- return nil
- }
- func renderDashboardIfValid(dashboard *config.DashboardComponent, rr *DashboardRenderRequest) *apiv1.Dashboard {
- if len(dashboard.Contents) == 0 {
- logEmptyDashboard(dashboard.Title, rr.AuthenticatedUser.Username)
- return nil
- }
- return buildDashboardFromConfig(dashboard, rr)
- }
- func renderDirectoryDashboard(rr *DashboardRenderRequest, dashboardTitle string) *apiv1.Dashboard {
- directoryComponent := findDirectoryComponent(rr, dashboardTitle)
- if directoryComponent == nil {
- return nil
- }
- entity := getEntityFromRequest(rr)
- return buildDashboardFromConfigWithEntity(directoryComponent, rr, entity)
- }
- func findDirectoryComponent(rr *DashboardRenderRequest, title string) *config.DashboardComponent {
- for _, dashboard := range rr.cfg.Dashboards {
- if component := searchDirectoryInComponent(dashboard, title); component != nil {
- return component
- }
- }
- return nil
- }
- func searchDirectoryInComponent(component *config.DashboardComponent, title string) *config.DashboardComponent {
- if isMatchingDirectory(component, title) {
- return component
- }
- return searchDirectoryInSubcomponents(component.Contents, title)
- }
- func isMatchingDirectory(component *config.DashboardComponent, title string) bool {
- return component.Title == title && len(component.Contents) > 0 && component.Type != "fieldset"
- }
- func searchDirectoryInSubcomponents(contents []*config.DashboardComponent, title string) *config.DashboardComponent {
- for _, subitem := range contents {
- if found := searchDirectoryInComponent(subitem, title); found != nil {
- return found
- }
- }
- return nil
- }
- func logEmptyDashboard(dashboardTitle, username string) {
- log.WithFields(log.Fields{
- "dashboard": dashboardTitle,
- "username": username,
- }).Debugf("Dashboard has no readable contents, so it will not be visible in the web ui")
- }
- func buildDashboardFromConfig(dashboard *config.DashboardComponent, rr *DashboardRenderRequest) *apiv1.Dashboard {
- return buildDashboardFromConfigWithEntity(dashboard, rr, nil)
- }
- func buildDashboardFromConfigWithEntity(dashboard *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) *apiv1.Dashboard {
- contents, root := getDashboardComponentContentsWithEntity(dashboard, rr, entity)
- return &apiv1.Dashboard{
- Title: dashboard.Title,
- Contents: orderTopLevelDashboardComponents(removeNulls(contents), root),
- }
- }
- //gocyclo:ignore
- func buildDefaultDashboard(rr *DashboardRenderRequest) *apiv1.Dashboard {
- db := &apiv1.Dashboard{
- Title: "Actions",
- Contents: make([]*apiv1.DashboardComponent, 0),
- }
- fieldset := &apiv1.DashboardComponent{
- Type: "fieldset",
- Title: "Actions",
- Contents: make([]*apiv1.DashboardComponent, 0),
- }
- for _, binding := range rr.ex.MapActionBindings {
- if binding == nil || binding.Action == nil || binding.Action.Hidden {
- continue
- }
- if binding.IsOnConfiguredDashboard() {
- continue
- }
- if !acl.IsAllowedView(rr.cfg, rr.AuthenticatedUser, binding.Action) {
- continue
- }
- action := buildAction(binding, rr)
- if action == nil {
- continue
- }
- comp := &apiv1.DashboardComponent{
- Type: "link",
- Title: action.Title,
- Icon: action.Icon,
- Action: action,
- }
- if binding.Entity != nil {
- comp.EntityKey = binding.Entity.UniqueKey
- }
- fieldset.Contents = append(fieldset.Contents, comp)
- }
- if len(fieldset.Contents) > 0 {
- fieldset.Contents = sortDashboardComponents(fieldset.Contents)
- db.Contents = append(db.Contents, fieldset)
- }
- return db
- }
- func entityKeyLess(a, b string) bool {
- ai, errA := strconv.ParseInt(a, 10, 64)
- bi, errB := strconv.ParseInt(b, 10, 64)
- if errA == nil && errB == nil {
- return ai < bi
- }
- return a < b
- }
- //gocyclo:ignore
- func sortDashboardComponents(components []*apiv1.DashboardComponent) []*apiv1.DashboardComponent {
- sort.Slice(components, func(i, j int) bool {
- if components[i].Action == nil || components[j].Action == nil {
- if components[i].EntityKey != "" && components[j].EntityKey != "" &&
- components[i].EntityKey != components[j].EntityKey {
- return entityKeyLess(components[i].EntityKey, components[j].EntityKey)
- }
- return components[i].Title < components[j].Title
- }
- if components[i].Action.Order != components[j].Action.Order {
- return components[i].Action.Order < components[j].Action.Order
- }
- if components[i].EntityKey != components[j].EntityKey {
- return entityKeyLess(components[i].EntityKey, components[j].EntityKey)
- }
- return components[i].Action.Title < components[j].Action.Title
- })
- return components
- }
- 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 isNonEntityFieldset(component *apiv1.DashboardComponent) bool {
- return component != nil && component.Type == "fieldset" && component.EntityType == ""
- }
- func isRegularFieldset(component *apiv1.DashboardComponent, root *apiv1.DashboardComponent) bool {
- if !isNonEntityFieldset(component) {
- return false
- }
- return root == nil || component != root
- }
- func partitionTopLevelComponents(components []*apiv1.DashboardComponent, root *apiv1.DashboardComponent) (regular, sortables []*apiv1.DashboardComponent, isRegular []bool) {
- regular = make([]*apiv1.DashboardComponent, 0)
- sortables = make([]*apiv1.DashboardComponent, 0)
- isRegular = make([]bool, len(components))
- for i, c := range components {
- anchor := isRegularFieldset(c, root)
- isRegular[i] = anchor
- if anchor {
- regular = append(regular, c)
- } else {
- sortables = append(sortables, c)
- }
- }
- return regular, sortables, isRegular
- }
- func mergeOrderedTopLevelComponents(regular, sortables []*apiv1.DashboardComponent, isRegular []bool) []*apiv1.DashboardComponent {
- out := make([]*apiv1.DashboardComponent, 0, len(isRegular))
- regIdx, sortIdx := 0, 0
- for _, anchor := range isRegular {
- if anchor {
- out = append(out, regular[regIdx])
- regIdx++
- } else {
- out = append(out, sortables[sortIdx])
- sortIdx++
- }
- }
- return out
- }
- func orderTopLevelDashboardComponents(components []*apiv1.DashboardComponent, root *apiv1.DashboardComponent) []*apiv1.DashboardComponent {
- if len(components) == 0 {
- return components
- }
- regular, sortables, isRegular := partitionTopLevelComponents(components, root)
- sortDashboardComponents(sortables)
- return mergeOrderedTopLevelComponents(regular, sortables, isRegular)
- }
- func getDashboardComponentContentsWithEntity(dashboard *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) ([]*apiv1.DashboardComponent, *apiv1.DashboardComponent) {
- ret := make([]*apiv1.DashboardComponent, 0)
- rootFieldset := createRootFieldset()
- for _, subitem := range dashboard.Contents {
- processDashboardSubitemWithEntity(subitem, rr, &ret, rootFieldset, entity)
- }
- if len(rootFieldset.Contents) > 0 {
- ret = append(ret, rootFieldset)
- return ret, rootFieldset
- }
- return ret, nil
- }
- func createRootFieldset() *apiv1.DashboardComponent {
- return &apiv1.DashboardComponent{
- Type: "fieldset",
- Title: "Actions",
- Contents: make([]*apiv1.DashboardComponent, 0),
- }
- }
- func appendComponentIfNotNil(components *[]*apiv1.DashboardComponent, comp *apiv1.DashboardComponent) {
- if comp != nil {
- *components = append(*components, comp)
- }
- }
- func getDashboardComponentOrNil(subitem *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) *apiv1.DashboardComponent {
- if len(subitem.Contents) == 0 && rr.findActionForEntity(subitem.Title, entity) == nil {
- if !isAllowedType(subitem.Type) {
- return nil
- }
- }
- return buildDashboardComponentSimpleWithEntity(subitem, rr, entity)
- }
- func processDashboardSubitemWithEntity(subitem *config.DashboardComponent, rr *DashboardRenderRequest, ret *[]*apiv1.DashboardComponent, rootFieldset *apiv1.DashboardComponent, entity *entities.Entity) {
- if subitem.Type != "fieldset" {
- appendComponentIfNotNil(&rootFieldset.Contents, getDashboardComponentOrNil(subitem, rr, entity))
- return
- }
- if subitem.Entity != "" {
- *ret = append(*ret, buildEntityFieldsets(subitem.Entity, subitem, rr)...)
- } else {
- appendComponentIfNotNil(ret, getDashboardComponentOrNil(subitem, rr, entity))
- }
- }
- func buildDashboardComponentSimpleWithEntity(subitem *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) *apiv1.DashboardComponent {
- var contents []*apiv1.DashboardComponent
- if len(subitem.Contents) > 0 {
- contents, _ = getDashboardComponentContentsWithEntity(subitem, rr, entity)
- }
- action := rr.findActionForEntity(subitem.Title, entity)
- componentType := getDashboardComponentType(subitem, action)
- title := subitem.Title
- if entity != nil {
- title = tpl.ParseTemplateOfActionBeforeExec(subitem.Title, entity)
- }
- newitem := &apiv1.DashboardComponent{
- Title: title,
- Type: componentType,
- Contents: contents,
- Icon: getDashboardComponentIcon(subitem, rr.cfg),
- CssClass: subitem.CssClass,
- Action: action,
- }
- return newitem
- }
- func getDashboardComponentIcon(item *config.DashboardComponent, cfg *config.Config) string {
- if item.Icon == "" {
- return cfg.DefaultIconForDirectories
- }
- return item.Icon
- }
- func getDashboardComponentType(item *config.DashboardComponent, action *apiv1.Action) string {
- if hasContents(item) {
- return getTypeForComponentWithContents(item)
- }
- if isAllowedType(item.Type) {
- return item.Type
- }
- return getDefaultType(action)
- }
- func hasContents(item *config.DashboardComponent) bool {
- return len(item.Contents) > 0
- }
- func getTypeForComponentWithContents(item *config.DashboardComponent) string {
- if item.Type != "fieldset" {
- return "directory"
- }
- return "fieldset"
- }
- func isAllowedType(itemType string) bool {
- allowedTypes := []string{
- "stdout-most-recent-execution",
- "display",
- }
- return slices.Contains(allowedTypes, itemType)
- }
- func getDefaultType(action *apiv1.Action) string {
- if action == nil {
- return "display"
- }
- return "link"
- }
|