This documentation and the Cisco Observability Platform functionalities it describes are subject to change. Data saved on the platform may disappear and APIs may change without notice.


Introduction to DashUI

DashUI does not require writing, building, or deploying Javascript to create User Interfaces. This is unlike conventional UI frameworks such as React. It allows the composition of pages through configuration files in JSON format. This UI framework is tightly integrated with the Flexible Meta Model (FMM) and Unified Query Engine (UQE). Therefore, developers do not need to write queries and complex data binding.

User Interfaces are plugged together from atomic widgets, and the framework infers the necessary queries to the backend to populate them with data. It bundles and optimizes the data requests of the individual widgets so that the number of roundtrips and redundant data requests is minimized.

Because of the modular system, you can combine atomic widgets to form reusable, composite building blocks, which you can use to build pages. You can render the pages in the live UI once the corresponding configuration objects (called templates) are stored in the central Knowledge Store. An online authoring tool even allows rendering templates using actual data while being developed.

One feature of DashUI is the ability to extend templates across solutions. This allows solution developers to surface their content on existing pages. For example, they can achieve this by adding a column to a table or adding new pages to the navigation structure.

Entity Centric Pages

Similar to the data model, the user interface of Cisco Observability Platform applications is mostly entity-centric. The Observe page initially shows the entities of all domains to which a Tenant is subscribed. Each domain has a section that contains one group of bubbles for every entity type (the bubbles themself represent the entity instances grouped by health, indicated by the color of the respective bubble). When users click on such a bubble or an entire group of bubbles, they navigate to an Entity Centric Page (ECP), allowing them to examine this particular set of entities.

ECP shows a selection of entities (also called the scope of the ECP), which depends on the navigation history. For example, if you click on the green bubble in the services group of the APM section, you have selected the set of all healthy services. However, if you first click on the set of unhealthy service instances and then on the red services bubble in the Relationship Map, you navigate to an ECP displaying only the unhealthy services related to the unhealthy service instances in the previous ECP.

The scope can include:

  • A set of entities (List view).
  • A single entity (Detail view).
  • A grouped set of entities (grouped list view).

Depending on the entity type and the nature of the scope (set/single/grouped), the structure of the ECP will be different. That is, a set of services is represented differently than a single service instance or a set of backends.

In addition to the structure, the content of the ECP will also differ depending on the scope, specifically on filters and traversals in the navigation history (similar to the previous example of unhealthy services related to a set of unhealthy service instances). It also differs on additional filters set by the user in the filter bar.

It is important to understand that the Relationship Map behaves like the rest of the ECP. It is not an absolute navigation tool but allows you to navigate to entities related to the currently selected (and filtered) set of entities.

Such a set of entities is referred as a topology context. The scope consists of a topology context in addition to grouping information.

Custom Slots for Entity Centric Pages

Using a CustomSlot allows you to delegate template selection to the entity page mechanism. This creates scope dependent content. CustomSlots are defined similar to an entity page, the only difference is that slots are defined with a custom slot name, instead of a scopeType. To use a CustomSlot, the slot name must be prefixed with the solution.

When you use a CustomSlot, you can determine the rendering target in two ways:

  1. By using the global scope.
  2. By using the static reference of entities consisting of an entityType, an entity ID, or an array with a combination of the two.

Scenario for using a CustomSlot:

Solution A has a detailed entity page, and Solution B wants to add their content to that page. In order for Solution B to do this, Solution B can extend the template that is used to render the page. However, the template might be used in other places where Solution B's content should not appear. Therefore, the best way for Solution B to add content to that page is by using a CustomSlot.

The following is an example of a CustomSlot in a template:

{
  "kind": "template",
  "name": "foo:bar",
  "view": "default",
  "target": "*",
  "element": {
    "instanceOf": "entityPage",
    "slot": "dashui:myCustomSlot"
    "entityRefs": ["k8s:pod"]
  }
}

Macros for Entity Centric Pages

You can add macros to a List view of an ECP. The DashUI solution provides a CustomSlot, which uses the template dashui:macroList. If you want to add a macro to a page, then you use this slot with your entityPagePropSets.

Defintions

Name Description
dashui:macroList The template that provides a scroll box container and exposes props for macros. If no props or macros are provided, then nothing is rendered when this custom slot is called. This macro slot should not be overwritten.

The following is an example of dashui:macroList:

{
    "kind": "entityPage",
    "scopeType": "dashui:macroList",
    "entityType": "*",
    "template": "dashui:macroList"
}

The following is the associated template with the macro slot:

{
  "kind": "template",
  "name": "dashui:macroList",
  "props": {
    "macros": "[]"
  },
  "propDefs": {
    "macros": {
      "reduce": {
        "type": "concat",
        "sort": "index"
      }
    }
  },
  "preprocess": "$count($props.macros) > 0 ? $ ~> | elements | { elements: $props.macros.element } | : $ ~> | $ | { instanceOf: empty } |",
  "element": {
    "instanceOf": "buttonScroll",
    "elements": {
      "instanceOf": "html",
      "style": {
        "display": "flex",
        "gap": 12,
        "alignItems": "stretch",
        "padding": "12px 1px"
      }
    }
  }
}

You can call the macro slot the same way you call other template elements. The ecpList has the macros property that can be initialized using the left and right bars. The following is an example of calling the macro slot:

{
    "kind": "template",
    "name": "spacefleet:spacecraftEcpList",
    "view": "default",
    "target": "*",
    "element": {
        "instanceOf": "ocpList",
        "left": {
            "instanceOf": "spacefleet:spacecraftEcpRelationshipMap"
        },
        "right": {
            "instanceOf": "spacefleet:spacecraftEcpListInspector"
        },
        "macro": {
            "instanceOf": "entityPage",
            "slot": "dashui:macroList"
        },
        "elements": "PLACEHOLDER"
    }
}

To add a PropSet to the Custom slot, you must define a new dashui:entityPagePropSet and target the macro slot dashui:macroList. For the example below, the entity type in entityRef is specified. 

{
  "kind": "dashui:entityPagePropSet",
  "id": "listMacroProps",
  "slot": "dashui:macroList",
  "entityRef": "spacefleet:spacecraft",
  "enabled": true,
  "props": {
    "macros": [
      {
        "index": 1,
        "element": {
          "instanceOf": "macro",
          "type": "scope",
          "title": "My Test Macro",
          "description": "This is an example",
          "commands": [
             "..."
          ],
        },
      },
    ],
  },
}

Configurable Filter

You can use the DashUI filter for the following:

  • To add or remove the filter bar from ECP pages, and the Observe page using the template.
  • To configure your own filter keys such as attributes, tags, type, isActive, and you can populate those key values.
  • To configure operators that you want to hide or expose.

The following is an example of how to add or remove the filter bar from ECP pages and from the Observe page:

{
    "kind": "template",
    "name": "k8s:clusterEcpList",
    "view": "default",
    "target": "*",
    "element": {
      "instanceOf": "ocpList",
      "left": { "instanceOf": "k8s:clusterEcpRelationshipMap" },
      "right": { "instanceOf": "k8s:clusterEcpListInspector" },
      "filterInput": {
        "instanceOf": "empty"
      },
      "elements": [
        {
          "instanceOf": "card",
          "props": {
            "style": {
              "width": "100%",
              "height": "calc(100% - 298px)",
              "padding": 0
            }
          },
          "elements": [{ "instanceOf": "k8s:clusterGridTable" }]
        }
      ]
    }
  },

The following is an example of how to implement your own filter keys :

import { buildInstantiator, defineInstantiatorFactory } from '@appd/config-driven';
import { FilterInput } from '@/modules/shared';

interface ScopeFilterElement {
  excludeKeys?: string[];
  excludeOperators?: string[];
  attributeOperators?: string[];
  tagOperators?: string[];
  typeOperators?: string[];
}

export const scopeFilter = defineInstantiatorFactory(({ element }) => {
  const { excludeKeys, excludeOperators, attributeOperators, tagOperators, typeOperators } = element as ScopeFilterElement;

  const customOperators = {
    attributes: attributeOperators,
    tags: tagOperators,
    types: typeOperators,
  };

  return Promise.resolve(
    buildInstantiator(() => <FilterInput customOperators={customOperators} hiddenSubjects={excludeKeys} excludeOperators={excludeOperators} />),
  );
});

The following is an example of excluding the aws.region tags for clusterECPList using the ~ operator:

{
    "kind": "template",
    "name": "k8s:clusterEcpList",
    "view": "default",
    "target": "*",
    "element": {
      "instanceOf": "ocpList",
      "left": { "instanceOf": "k8s:clusterEcpRelationshipMap" },
      "right": { "instanceOf": "k8s:clusterEcpListInspector" },
      "filterInput": {
        "instanceOf": "scopeFilter",
        "excludeKeys": ["tags(aws.region)"],
        "excludeOperators": ["~"]
      },
      "elements": [
        {
          "instanceOf": "card",
          "props": {
            "style": {
              "width": "100%",
              "height": "calc(100% - 298px)",
              "padding": 0
            }
          },
          "elements": [{ "instanceOf": "k8s:clusterGridTable" }]
        }
      ]
    }
  },

Configurable Filter Using scopeGroupBy

The scopeGroupBy building block renders the group by using the input component.

Properties Description
groupingCategories This is the array listing each grouping category to be enabled. By default, globalTags are enabled.
excludeKeys This is an array of tag keys that are excluded. For example, if you want exclude the tag aws.region, then use excludeKeys=["aws.region"].
customKeys This is an array of tag keys that can be included. For example, if you want to include tags(customKey1) and tags(customKey2), then use customKeys=["customKey1", "customKey2"].

The following is an example format of the scopeGroupBy building block:

{
  "instanceOf": "scopeGroupBy"
}

The following is an example using a sub-set of grouping categories:

{
  "instanceOf": "scopeGroupBy",
  "groupingCategories": ["regularTags"]
}

The following is an example using excludeKeys and customKeys:

"groupByInput": {
        "instanceOf": "scopeGroupBy",
        "customKeys": ["customKey1", "customKey2"],
        "excludeKeys": ["aws.region"]
      },

The following is an example of removing groupBy from a page:

"groupByInput": {
        "instanceOf": "empty"
      },

Templates

As the underlying data model of a domain is not hard-coded but modeled, the Entity Centric Pages visualizing this data are configured as JSON artifacts, which are named UI templates.

Templates:

  • Are stored in the Knowledge Store as part of a solution.
  • Define the arrangement, configuration, and data binding of their components.
  • May contain complete pages with a pre-built design, composite components, atomic components, styling (such as fonts, colors, theme effects, background styles, and icons), and more.
  • Can be nested. Hence, users can use a template within another template.
  • Streamline the process of UI development by providing working units that can be composed without writing JavaScript code.

You write a template to apply to a topology context with a specific entity type, not arbitrary selection criteria. It does not contain an absolute query but instead displays the content it gets from the topology context. However, it does rely on the entity type and cardinality of that topology context for the attributes, metrics, and associations that it can render.

Therefore, a template consists of header information and template element. The header information contains the name and expected type of the topology context (target), whereas the template element contains the actual configuration data.

The following is an example of a simple template to render a single entity of type service:

{
  "kind": "template",
  "target": "apm:service",
  "element": {
    "instanceOf": "string",
    "path": "attributes(service.name)"
  }
}
  • kind refers to the document type of the template (as required by the Knowledge Store).
  • target specifies that it applies to entities of the type apm:service.
  • element represents the actual template content. Template elements are always configurations of building blocks. The type of building block is indicated by the key instanceOf (here, it's string). Building blocks can either be atomic or other templates. All other keys in the element reflect the respective building block’s configuration parameters. The building block type string has a parameter path which contains the UQL fragment required to get the content rendered from the entity in the topology context. The only attribute required here is service.name. A building block of type string is an active building block. It does not require any explicit data binding information beyond the path. It knows how to collect the information specified by path from its topology context. On the other hand, passive building blocks have no notion of the topology context. These are just generic rendering components. To make them display data, you need to add explicit data binding and data transformation to element.
Limitation: Adding explicit data binding and data transformation to a template's building block is not yet supported.

The following is an example of a template representing a whole ECP list view:

{
  "kind": "template",
  "name": "apm:service-ecpList",
  "target": "apm:service",
  "element": {
    "instanceOf": "ecpList",
    "elements": [
      {
        "instanceOf": "card",
        "props": {
          "style": {
            "border": "solid 1px #464957",
            "borderRadius": "5px",
            "backgroundColor": "rgb(38, 41, 57)",
            "padding": 0
          },
          "elements": [
            {
              "instanceOf": "apm:service-ecpListVisualizationTab"
            }
          ]
        }
      }
    ]
  }
}

The building block used at the top level is ecpList. This renders the structure of an ECP (including relationship map and inspector panel). However, it receives the elements that need to be rendered in the center as part of its configuration.

In this case, there is only one element, a card that provides a colored background. You can configure the card to contain further child elements. In the template, the name apm:service-ecpListVisualizationTab is the child element.

Instead of using a reference to a template, you can embed the element of the referred template to the same effect. Using named templates instead of inlining the respective elements has two advantages. A named template can be:

  • Reused in many other templates with no redundancy
  • Addressed by template extensions

Template Extensions

Every Solution can define its templates to render Entity Centric Pages for the new entity types. But how can you enable users to navigate to your new entities from the ECPs of related entities in other domains?

The navigation to related entities is defined in the Relationship Map template of each entity type. But as a solution developer, you are not allowed to modify the templates of other solutions. However, you are allowed to extend templates of foreign solutions.

A template extension describes modifications to a specific template. For example, extensions can add elements to templates, such as columns to a table or links to a relationship map. When a template is loaded, all its extensions from subscribed solutions are applied (in no particular order).

Parameterized Templates expose explicit extension points (props) that can be set by a template extension (templatePropsExtension). The template itself then contains the logic that modifies itself according to the input from one or more extensions. Using this extension mechanism is safe because:

  • The extension does not need to know the internal structure of the template. Hence, it can be changed at any time without risk.
  • The internal structure of the extended template cannot be modified in an uncontrolled manner.

If the template that you are extending does not expose props, you can use the templateExtension artifact. This requires knowledge of the internal structure of the template. This flavor of template extension allows the addition or removal of elements to/from arrays indicated by a path expression.

Using templateExtension is not recommended because such extensions break when the author of the extended template changes the inner structure.

Forms

In order to create and edit configurations, corresponding Forms need to be generated. Forms are based on the schema of the knowledge type, and the configuration is a JSON object, and consists of different element types, where the type indicates the element type.

Defintions

Name Description
Target object The knowledge object that results from filling in the form. Usually this is a Knowledge Store, but it can be any knowledge object described by a schema.
Target object type A knowledge object that describes the target object.
Object schema The JSON schema of an object, or sub-object that is created or edited. In the case of the target object, this schema is part of the target object type in the Knowledge Store.
Form configuration The JSON artifact that describes a form, which is generated when creating, or editing a target object.
Form element An element within the form configuration. For example, the configuration of a control.

Structure of Forms

A Form consists of header data, and a tree of elements, which follows this syntax:

{
  "kind": "form",
  "id": "namespace:name",
  "@schema": "some-knowledge-type",
  "elements": []
}

The id with the namespace is a unique identifier of the Form, and the schema refers to the schema of the object that the Form produces. This is shown as an inlined JSON schema object, or as a reference in the form of a string that contains the id of a knowledge type. If the elements field is omitted, then the entire Form is generated from the schema of the ZeroConfig algorithm, which follows this syntax:

{
 "kind": "form",
  "id": "dashui:demoTeamExampleE2e",
  "@schema": "dashuiMockSolution:demoTeam",
  "description": "e2e test"
}

Form Elements

An array of elements contains objects with a type that specifies the element type, and additional type-specific properties, such as style. Element types include: form.Control, form.Repeater, form.Dictionary, form.Group, form.Switch, form.Html, form.CodeSnippet, ContainerCard, form.Template, form.Wizard. The common property for form.Control, form.Group, and form.Repeater is @path. The @path specifies the field, or sub-object in the target that the element configures, which must be consistent with the object schema.

Control Element

The form.Control element creates an interactive control for entering Form data. The type of control can be specified by the parameter control, or inferred from the schema of the schema object field specified in the @path. The value of @path indicates the location of the field in the schema of the Group. @Path segments are separated by / characters. Valid values for form.Control include:

Value Detail
checkbox
description: string
defaultValue: boolean
disabled: boolean
label: string
multiselect
disabled: boolean
label: string
searchable: boolean
secondaryHint: string
hint: string
tooltip: string
optionLabelPath: string
optionValuePath: string
number
acceptFloat: boolean
disabled: boolean
secondaryHint: string
hint: string
tooltip: string
password
disabled: boolean
hint: string
secondaryHint: string
tooltip: string
radio
optionDescriptionPath: string
optionLabelPath: string
optionValuePath: string
rowSelect
rows
columns
height: numberals for the grid
hideCheckbox: boolean
multiselect: boolean
select
disabled: boolean
label: string
searchable: boolean
secondaryHint: string
hint: string
tooltip: string
optionLabelPath: string
optionValuePath: string
text
disabled: boolean
readOnly: boolean
secondaryHint: string
hint: string
tooltip: string
textarea
disabled: boolean
readOnly: boolean
secondaryHint: string
hint: string
tooltip: string
rows: number
currency
maximumFractionDigits: number
label: string
required: boolean
disabled: boolean
secondaryHint: string
hint: string
tooltip: string
toggle
disabled: boolean
label: string
showDescription: boolean

The Control element follows this syntax:

{
  "@type": "form.Control",
  "control": "text",
  "@path": "/foo",
  "label": "Example"
}

Repeater Element

The form.Repeater element allows you to populate an array by adding rows, or sub-forms to create array elements. Apart from @path, form.Repeater has the property, itemElements. itemElements is the child form element that is used to input each child element of that array. The Repeater element follows this syntax:

{
  "@type": "form.Repeater",
  "@path": "/foo",
  "itemElements": [
    {
      "@type": "form.Html",
      "text": "Repeater Example",
      "tag": "h2"
    },
    {
      "@type": "form.Control",
      "control": "text",
      "@path": "/text",
      "label": "Text Example"
    }
  ]
}

Dictionary Element

The form.Dictionary element is similar to the form.Repeater element. The only difference is that form.Dictionary has the value object type, instead of array type. The autoGeneratedKey is used for auto-generating the key by the values, which is the default behavior. The itemElements is the child form element that is used to input each child element of the array. The Dictionary element follows this syntax:

{
      "@type": "form.Dictionary",
      "@path": "/repeater",
      "autoGeneratedKey": false,
      "itemElements": [
        {
          "@type": "form.Control",
          "@path": "/...",
          "control": "text",
          "label": "..."
        },
      ]
    }  

Group Element

The form.Group element has two properties, schema and elements. A schema is either an object, or a string identifying the knowledge object. elements include child elements of the group. If there are no elements provided, then the Form will use ZeroConfig automatically. The Group element follows this syntax:

{
  "@type": "form.Group",
  "@schema": {
    "properties": {
      "foo": {
        "type": "string"
      },
      "bar": {
        "type": "string"
      }
    },
    "type": "object"
  },
  "elements": [
    { "@type": "form.Control", "@path": "/foo", "control": "..." },
    { "@type": "form.Control", "@path": "/bar", "control": "..." }
  ]
}
Limitation: form.Group does not support multiple groups with the same @path.

Wizard Element

The form.Wizard element guides you through a complex process using a step-by-step approach. Each step in the Wizard represents a part of the process, and you can choose if one step needs to be concluded before proceeding to the next.

The Wizard element can be configured to have its own Submit or Cancel button that performs the configured operation. By default, these buttons are hidden as the actions they represent are executed by the Container element. However, you can hide the Container options and use the Wizard's action options. This provides the flexibility to either use the default actions provided by the Container, or to use the actions within the Wizard's own interface.

Wizard Actions Detail
steps Step has the following parameters:
name (optional): string
description (optional): string
stepElements (optional): Array<FormElement> FormElement
required (optional): boolean
default: false
size
size (optional): string
COMPACT
NORMAL
blockOnError
blockOnError (optional): boolean
default: true
• If an error occurrs in the current step, then navigation to the previous and next step will be blocked until it's resolved.
cancelLabel
cancelLabel (optional): string
boolean
nextStepLabel
nextStepLabel (optional): string
boolean
previousStepLabel
previousStepLabel (optional): string
boolean
submitLabel
submitLabel (optional): string
boolean
{
    "@type": "form.Wizard",
    "@path": "/wizard",
    "steps": [
      {
        "name": "Step 1",
        "description": "This is a simple example",
        "required": true,
        "elements": [
          {
            "@type": "form.Control",
            "control": "text",
            "@path": "/text",
            "label": "Text Example"
          },
          {
            "@type": "form.Control",
            "control": "text",
            "@path": "/text2",
            "label": "Text Example 2"
          }
        ]
      }
    ]
  }

Switch Element

The form.Switch element is used to select elements depending on the value of an expression. $by contains a JSONata expression that evaluates a string. The result of the expression evaluation is compared with the entries from Cases. Cases is a dictionary that maps the possible results of $by to corresponding Form elements. The default, which is optional, is the Form element chosen if no key in the hashmap matches the result of $by. After evaluating the $by expression, the corresponding element takes the place of the Switch element. The Switch element follows this syntax:

{
  "@type": "form.Switch",
  "$by": "$parent().entity",
  "cases": {
    "Cluster": {
      "@type": "form.Control",
      "@path": "/value",
      "control": "text",
      "label": "New Cluster Name"
    },
    "Namespace": {
      "@type": "form.Control",
      "@path": "/value",
      "control": "text",
      "label": "New Namespace Name"
    },
    "Pod": {
      "@type": "form.Control",
      "@path": "/value",
      "control": "text",
      "label": "New Pod Name"
    }
  },
  "default": {
    "@type": "form.Html",
    "tag": "span",
    "text": "No entity type was selected"
  }
}

HTML Element

The form.Html element contains these properties: tag, elements, text.

  • tag: The HTML element type, such as div or img.
  • elements: Contains new Form elements, which can be omitted.
  • text: Identifies if the HTML element should contain text. This applies only if elements is omitted.

The HTML element follows this syntax:

{
  "@type": "form.Html",
  "text": "Example Headline",
  "tag": "h2"
}
{
  "@type": "form.Html",
  "tag": "div",
  "style": { },
  "elements": [
    {
      "@type": "form.Control",
      "control": "..."
    },
    { }
  ]
}

CodeSnippet Element

The form.CodeSnippet element is used to display code. The form.CodeSnippet element contains these properties: label, maxRows, showActions, showRows, showActionsOnHoverOnly, value.

  • label: A string headline for a CodeSnippet block.
  • maxRows: The number limit that the rows display.
  • showActions: A boolean which controls whether copy, and fold actions of CodeSnippet are displayed or hidden.
  • showRows: A boolean which controls whether row numbers are displayed or hidden.
  • showActionsOnHoverOnly: A boolean which controls whether actions are displayed when hovering, or whether they are always displayed.
  • value: The value shown in the code snippet. This can also be a JSONata expression to display fetched data.

The CodeSnippet element follows this syntax:

{
  "@type": "form.CodeSnippet",
  "value": "Foo\nBar\nBaz\nQux\nFooo\nBarr\nBazz\nQuxx",
  "label": "Display Actions only on Hover",
  "maxRows": "3",
  "showRows": true,
  "showActions": true,
  "showActionsOnHoverOnly": true
}
{
  "@type": "form.CodeSnippet",
  "@fetch": {
    "clusters": {
      "type": "uqe",
      "query": "SINCE now-1h FROM clusterList: entities(k8s:cluster) FETCH id: id, name: clusterList.attributes(k8s.cluster.name)",
      "url": "/api/fso/monitoring/v1/query/execute"
    }
  },
  "$value": "'You can select from a list of Cluster:\n\n' & $reduce($var('clusters').rows.id, function($v, $i) { $v & '\n' & $i }) & '\n\nPlease make sure to select the correct cluster.'",
  "label": "Display Actions only on Hover",
  "showActions": true
}

ContainerCard Element

The ContainerCard element can be used to wrap the form within a Card which has predefined styles, such as padding and background.

Value Detail
elements This defines any FormElement.
title The title of the Card.
style This allows you to customize the style of the Card.

The ContainerCard element follows this syntax:

{
  "@type": "form.ContainerCard",
  "title": "Demo Title",
  "style": {},
  "elements": {
  }
}

Template Element

The template element can be used to provide instructions on how to interact with forms, and uses UIK components without needing to style components. The benefit of using a template is that you can use the entire feature set of v1plus. For example, if you want to navigate to another Form page to create a new entity, then you can use the v1plus action; navigate.settings. A template can either be a string, or an inline template. navigate.settings includes these actions:

Action Detail
settingsId (Required): string ID of the settingsConfig to navigate to.
objectId (Optional): string ID of the object that is edited. This is only required if you want to navigate to an existing object edit page.
params (Optional): Record URL search parameter object that is processed, and attached to the URL as search string.
section (Optional): string only required if a settings page is part of a custom section in the navigation. This defaults to Modules.
create (Optional): boolean flag that must be set to true if the navigation leads to creating a page.

The Template element follows this syntax:

[
  {
    "@type": "form.Template",
    "template": "my:template"
  },
  {
    "@type": "form.Template",
    "template": {
      "element": [
        {
          "@type": "uik.Button",
          "children": "Navigate to Form CREATE",
          "icon": "Navigation.DisclosureRight",
          "onClick": {
            "@action": "navigate.settings",
            "payload": {
              "settingsId": "dashuiplayground:demoSettingsConfig",
              "create": true
            }
          },
          "type": "PRIMARY"
        }
      ]
    }
  }
]

Data Handling for Forms

All Form elements require data handling for populating selection controls, validating input, and controlling the sequence of steps. This is accomplished by the actions: @read, fetch, JSONata expressions, and validations. For each of these actions, a corresponding object is defined. Those blocks complement the scope of the Form element with variables that can be used in JSONata expressions.

  • @read Action: This reads fields from the target object in its current state of completion, and assigns them to variable names in the local scope.
{
  "@type": "...",
  "@read": {
    "$country": "country",
    "$province": "$.province",
    "$state": "$root().state"
  }
}
  • @fetch Action: This allows you to retrieve information from an API, and write it into the scope. @fetch is a dictionary where the variable name is the key, and the value is an object with the following properties.

JSON Example: The example below calls the end point /api/provinces and passes the value of country as the request parameter country and the string name as the request parameter fields.

{
  "@type": "...",
  "@fetch": {
    "provinces": {
      "params": {
        "$country": "$var('country')",
        "fields": "name"
      },
      "url": "/api/provinces",
      "type": "json"
    }
  }
}

UQE Example: If the @fetch block contains a dictionary where the type set is as UQE, then you can use the UQE syntax to fetch data.

{
  "@type": "form.Control",
  "fetch": {
    "pods": {
      "$skip": "$not($exists($var('cluster')))",
      "query": "${\"SINCE now-1h FROM entities(\" & $var('cluster') & \").out.to(k8s:namespace).out.to(k8s:workload).out.to(k8s:pod) FETCH id: id\"}",
      "type": "uqe"
    }
  },
  "$options": "$var('pods').rows.id",
}

Knowledge Store Example: If the @fetch block contains a dictionary where the type is set as objstore, then you can interact with the Knowledge Store API.

{
  "@type": "form.Control",
  "@fetch": {
    "exampleFetch": {
      "type": "objstore",
      "layerType": "LOCALUSER",
      "fullyQualifiedTypeName": "dashui:template",
      "query": {
        "order": "asc"
      }
    }
  },
  "@path": "/example",
  "control": "select",
  "label": "Select an existing dashui:template from your localuser store",
  "$options": "$var('exampleFetch').id"
}

If you want to use the result of the first selection in the next Control, then fetch the level above using $var.

  • JSONata Action: To use JSON in a value, you must flag the key with "$" - $key: "EXPRESSION". The $ signals that the value contains a JSONata expression. If a key is flagged with a $, then the value is a string JSONata expression. The following custom functions can be used inside JSONata expressions:
    • $var('KEY'): To access @read bindings or prior @fetch results.
    • $parent().KEY: To access values written against @path: /foo of the parent element.
    • $root().KEY: To access values written against @path: /foo of the root element.
    • $mode(): To access if the Form object is in create mode or edit mode. This can also be used for disabling a control.
    • $baseURL(): This allows you to access globalThis.document.UR in the Form, and can not be used in @fetch.
    • $format(): This allows you to expose the $format functionality of UIK in the JSONata of FormUI. $format follows this structure: $format(typeName, value, params) => string.
    • Available $format functions include: currency, date, date.long, date.longWithWeekday, date.short, date.shortWithFullYear, dateTime, dateTime.long, dateTime.longWithWeekday, dateTime.short, dateTime.shortWithFullYear, duration, duration.withSeconds, number, number.bitRateBinary, number.bitRateDecimal, number.bytesBinary, number.bytesDecimal, number.long, number.percent, number.short, time, time.long12, time.long24, time.short12, time.short24.
{
  "control": "text",
  "@type": "form.Control",
  "@path": "...",
  "$disabled": "$mode() = 'EDIT' ? true : false "
}
  • Validate Action: The values Control, Group and Repeater are validated against the schema with the Ajv JSON schema validator, a third party library. In the example below, the minLength in items creates the validation.
{
  "properties": {
    "repeaterDemo": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 3
      }
    }
  },
  "type": "object"
}