Back to Consul

Significant patterns

ui/packages/consul-ui/docs/significant-patterns.mdx

1.22.76.2 KB
Original Source

Significant patterns

Ember specific

The Consul UI tries to follow typical conventional ember but differs significantly in several places:

Routing

We use a declarative configuration format for our routing rather than Ember's DSL. The format is closely modeled on Ember's DSL and if you need to generate Ember's DSL from this for some reason you can use one of our Debug Utilities to do this.

All Route based configuration including paths, queryParams, simple redirects and non-default templateNames are configured in this configuration format. Please note redirects and template names use relative slash separated paths to avoid copypasta and for future potential reuse purposes.

Routes

We use a specific BaseRoute as a parent Route for all our Routes. This contains project wide functionality for several important additions. If you use the normal ember generate route route-name generator this inheritance is automatically added for you. If you don't use the generator please ensure you use:

js
import Route from 'consul-ui/routing/route';
export default class NameOfRoute extends Route {}

Query parameters should be added to Routes, not Controllers. If a controller has no query parameters configured they are copied over from the Route. Preferably we don't use Controllers, but this doesn't mean you shouldn't if you need to.

Routlets

We use have a concept of 'routlets', the combination of a <Route /> and <Outlet /> components that we use within our route templates to achieve several different pieces of functionality.

Every route template should be wrapped in a <Route @name={{routeName}}> component and ever {{outlet}} should be wrapped in <Outlet @name={{routeName}}> component. The routeName variable is made available in every single route template and it equal to the routeName of the current template e.g. dc.services.index

DataSources

In order to support our live updating long-polling blocking queries we use 'DataSources' which come in two flavours. A service backed approach for use within javascript:

js
class RouteName extends Route {
  @service('datasource/service') data;
  async model(params) {
    return this.data.source(uri => uri`/${params.nspace}/${params.dc}/services`)
  }
}

This method returns an Ember proxy that lets you access the data as if it was 'just the data', but is also reactive/auto updates when the data in the backend updates, for example:

hbs

{{@model.Name}}

And a component based approach for use within templates.

hbs
<DataSource @src={{
  uri '/${nspace}/${dc}/services'
  (hash
    nspace="default"
    dc="dc"
  )
  }}
  @onchange=""
/>

See the relavant component/service documentation for more detail.

Components

You could group our components into two different groups:

  1. UI Components - generic components not necessarily specific to the product.
  2. Consul Components - Components that are specific to Consul/the product. These are mostly 'glorified partials'.

Mostly the CSS for a component lives in the component folder itself, but if it makes sense for it not to live here, thats fine.

We currently use a mix of named/block slots and contextual components and its fair to say that we use more named/block slots, but both are fine depending on your use case.

Significant Addons

  • ember-data - model layer
  • ember-stargate - wormhole/portal/put this DOM over there functionality
  • ember-can - user abilities and permissions
  • ember-composable-helpers, ember-string-fns, ember-truth-helpers - helpers x lots
  • ember-changeset and ember-changeset-validations - form validation
  • ember-cli-flash - notifications

Please see our package.json file dependencies.

Consul specific

Namespace support

Whilst Consul namespaces are a CE feature of Consul, we almost always build things 'pretending' that namespaces are always enabled. Then there is code within the data layer of the application that removes any namespace related query parameters.

Namespaces are a little more complex to think about than other things in Consul as we have the idea of 'the default' namespace, and then additionally an ACL token can have 'a default' namespace (not the same as 'the default' namespace) which is a little like a namespace that is assigned to the token, or where the token originated from. The Consul backend can then use the tokens default/origin/assigned namespace to figure out whether and how to filter any results by namespace before sending those results to the UI.

When you are requesting data from the API you should always include a namespace (if applicable to the API endpoint), but you should never default the namespace value to 'the default' namespace - default. Conversly, when receiving data from an API response, there are some places where we do default a namespace to 'the default' namespace default. The easiest way to think about this is: If its on the way out, don't add a default ever, if its on the way in you may need to default any potentially empty Namespace parameters to default (due to using Consul without namespaces enabled), there is a NspaceAbility and/or an env feature flag to help you to discover this.

We've made various decisions in the UI to make it hard to omit a namespace when you shouldn't, for example using our DataSource component requires you to include a defined namespace parameter (even if the value for that is ''), and the route parameter nspace value will always default to ''. It also reminds you to be continually thinking about namespaces whilst adding new features.

Lastly, we always refer to namespaces in code as the word nspace or nspaces. Both ns and namespace have other purposes in Ember and Ember Data (in various places one is significant but not the other and vice versa). Always using the term nspace means we avoid any strange bugs or clashes/overwrites with anything in Ember or Ember Data core. The only place where we use ns or Namespace is to construct API requests/responses. Sometimes when using this term in CSS or on plain javascript objects, this may seem unnecessary but it just helps smooth over writing code around namespaces as you don't have the split second decision of whether you write ns or namespace - just use nspace and everything will be fine :)