Skip to main content
Big news! Interval has been acquired by Meter. Learn more →

Pages

While actions are the bread and butter of building with Interval, Interval's Pages API enables more sophisticated UIs and structure, such as dashboards, list and resource views, and hierarchical navigation.

info

The Pages API is available on SDK versions v0.35.0 and higher.

With the Pages API you can:

  • Define top-level navigation within your organization's Interval dashboard.
  • Group related actions to provide better structure and easier navigation.
  • Create screens with text, data, and list views to be used as a launching point for other actions.

The Pages API introduces a second type of screen, called a Page. While actions are designed for collecting input and displaying output (analogous to PUT/POST/DELETE in HTTP), pages are just for displaying content (analogous to GET in HTTP). For example, you might create a Users screen in your app, where the root Users screen that shows a list of users is a page, and the Create user and Update user forms are actions.

Together, pages and actions - along with linking APIs for navigating between the two - are the primary building blocks for developing apps with Interval.

Defining pages

Pages are defined using Page class instances. Pages will create a hierarchy entry for the page and provide a space for nesting related actions or other pages beneath it in the hierarchy.

The Page constructor accepts an object of the following shape:

  • name: The page's name. Required.
  • handler: The page's layout handler. Optional.
  • unlisted: An optional boolean property that hides the page from auto-generated navigation if set to true. Defaults to false.
  • routes: An object containing the actions and sub-pages nested within the page, just like Interval's top-level routes property. Optional.
tip

See Writing actions for more information about how routes are defined, and Loading routes from the filesystem for more information about how that relates to Pages specifically.

import { Page } from "@interval/sdk";

export default new Page({
name: "Users",
routes: {
create: {
name: "Create user",
handler: async () => {
// Usual action code, just nested within a route
},
},
},
});

In addition to being defined by the filesystem or in the Interval or Page constructors, routes can be added or removed dynamically via the add() and remove() methods. This works the same as adding and removing actions, and the add() and remove() methods are also available on Page to manage sub-pages or nested actions within a page.

Customizing page layout

The default appearance of a Page is a grid or list view of the actions defined within it, similar to the root Dashboard screen:

interval.com

For more control over output - such as displaying text, metadata, and list views - you can define an asynchronous handler() function for the Page which allows for customizing the page's layout.

A Page's handler is conceptually similar to an Action handler, but it can't await input; it must return an instance of the Layout class.

Layout supports the following properties in its class constructor:

  • title: The page's title and primary heading. Type string | Promise<string> | (() => string | Promise<string>). Optional. If not defined, it will use the name property from its parent Page.
  • description: The page's description. Type string | Promise<string> | (() => string | Promise<string>). Optional.
  • menuItems: An array of objects defining links to other actions and populate in the page as a menu of buttons.
  • children: An array of io.display components to render as the page's body. These should not be awaited. Optional.

In the following example, the default actions list is replaced with a list of users:

import { Page, Layout, io } from "@interval/sdk";
import { fetchUsers } from "../db/users";

export default new Page({
name: "Users",
routes: {
create: {
name: "Create user",
handler: async () => {
// Usual action code, just nested within a route
},
},
},
handler: async () => {
const betaUsers = await fetchUsers();

return new Layout({
title: "Beta users",
description: "These users have signed up for the beta.",
menuItems: [
{
label: "Create user",
route: "users/create",
},
],
children: [
io.display.metadata("", {
layout: "card",
data: [
{
label: "Total users",
value: betaUsers.length,
},
{
label: "Active users",
value: betaUsers.filter(u => u.isActive).length,
},
],
}),
io.display.table("Users", {
data: betaUsers,
}),
],
});
},
});

Loading routes from the filesystem

Loading actions and pages from the filesystem can be done automatically with the routesDirectory property:

import { Interval } from "@interval/sdk";
import path from "path";

const interval = new Interval({
endpoint: "wss://<YOUR INTERVAL SERVER URL>/websocket",
apiKey: "<YOUR API KEY>", // get an API key from the Keys page in your Interval dashboard
routesDirectory: path.resolve(__dirname, "routes"),
});

When routesDirectory is defined, Interval will recursively walk directories and detect files with default exports of Actions or Pages and add them to your routes based on their files' paths.

tip

Relative path resolution is based on your Node.js process's current working directory. In order to load starting from a path relative to the Interval constructor, be sure to provide an explicit path, for instance by using the __dirname variable.

A Page is automatically created for each directory, and files inside the directory that export an Action or Page will be nested within that Page. The directory's auto-generated Page can be customized by an index.ts (or index.js) file, which should export a Page as its default export.

If necessary, routes can also be defined inline using the routes property. The two tabs below provide an identical route structure, one using routesDirectory and the other using inline routes.

src/interval.ts
import { Interval } from "@interval/sdk";
import path from "path";

const interval = new Interval({
endpoint: "wss://<YOUR INTERVAL SERVER URL>/websocket",
apiKey: "<YOUR API KEY>", // get an API key from the Keys page in your Interval dashboard
routesDirectory: path.resolve(__dirname, "routes"),
});

interval.listen();
src/routes/hello_world.ts
import { Action, io } from "@interval/sdk";

export default new Action(async () => {
const name = await io.input.text("Your name");
return `Hello, ${name}`;
});
src/routes/users/index.ts
import { Page, Layout, io } from "@interval/sdk";

export default new Page({
name: "Users",
handler: async () => {
return new Layout({
title: "Beta users",
description: "These users have signed up for the beta.",
menuItems: [
{
label: "Create user",
route: "users/create",
},
{
label: "View metrics",
route: "hello_world",
},
],
children: [
io.display.metdata("", {
layout: "card",
data: [
{
label: "Total users",
value: 2,
},
{
label: "Active users",
value: 1,
},
],
}),
io.display.table("Users", {
data: [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bartholomew", email: "bart@example.com" },
],
}),
],
});
},
});
src/routes/users/create.ts
import { Action, io } from "@interval/sdk";

export default new Action({
name: "Create user",
handler: async () => {
// Usual action code, just nested within a route
},
});
info

Standalone object or function exports are not supported for file-based actions; always use the Action and Page class wrappers.

Was this section useful?