Skip to content

UI

Getting Started

Create a .env file from the template .env.example:

bash
cp .env.example .env

and populate the config values to all the keys.

In the developement environment, the API calls from the UI are proxied by the vite server. For example, the UI running on https://localhost:443 make an API call GET https://localhost:443/api/users which the vite server intercepts and proxies it to the API server running on VITE_API_REDIRECT_URL (ex: http://localhost:3000) as GET http://localhost:3000/users.

Running using docker

  1. Set VITE_API_REDIRECT_URL to http://api:3000
  2. From the project root run: docker composer up ui -d and open https://localhost:443 in the browser. (start the API and its dependencies before starting UI)

Running on host machine

  1. set VITE_API_REDIRECT_URL to the API base url (ex: http://localhost:3000)
  2. Install modules: pnpm install
  3. Start dev server: pnpm dev

Features

Icons

There are multiple ways to include icons:

  • Material Icons are provided by Vuestic. Iconify icon components are auto imported.
    • usage: <va-icon name="dashboard" />
  • Iconify has a lot of third party / community icons
    • usage: <Icon icon="mdi-flask" class="text-2xl" />
    • usage: <i-mdi-flask/>

Iconify icons are installed using

Colors

Vuestic colors: https://ui.vuestic.dev/en/styles/colors

Accent

  • primary
  • secondary
  • success
  • warning
  • danger
  • info

Background

  • backgroundPrimary
  • backgroundSecondary
  • backgroundElement
  • backgroundBorder

Text

  • textPrimary
  • textInverted

Using

html
<template>
  <!-- use javascript object -->
  <div :style="color: {{ colorByStatus }}"></div>

  <!-- use css variables -->
  <span style="color: var(--va-warning)"> </span>

  <!-- using style block -->
  <p class="title">
    Title
  </p>

  <!-- use builtin props -->
  <va-button color="info"></va-button>
</template>

<script setup>
  import { useColors } from "vuestic-ui";
  const colors = useColors()

  const colorByStatus = status == 'FAILED' ? colors.danger : color.primary
</script>

<style scoped>
.title {
  color: var(--va-primary)
}
</style>

Notable Vuestic Classes

CSS: bioloop/ui/node_modules/vuestic-ui/dist/styles/index.css

va-text-text-primary
va-text-text-inverted
va-text-{primary, secondary, warninig, success, danger, info}

va-code-text
va-code-snippet

va-link
va-link-secondary

va-blockquote
va-text-block
va-text-truncate
va-text-highlighted

Configuration

Layered and Hierarchical config system:

  • Config for multiple environment is managed through Env variables specified in .env files.
  • Based on the mode, these environment variables are automatically imported into the code by vite.
  • The values from the environment variables is merged with other static config centrally in config.js. All environment variables are backed by sensible default values.

To introduce new configuration to this project, determine if it is environment-specific or static. If it is static, add both the key and value directly to config.js. Otherwise, add the key to config.js and read the value from an environment variable.

javascript
{
  ...,
  foobar: import.meta.env.FOOBAR || 120,
}

Add the name and value of the environment variable to the .env file. This file is not tracked by the version control system. To keep track of the environment variables required to initialize the project in a new machine, another file called .env.example is maintained. This file contains all the variables defined in .env without the values.

Authentication

Users are authenticated using IU CAS. More on auth module.

By default any page will require user authentication

html
<route lang="yaml">
meta:
  title: Dashboard
</route>

Page requires user authentication + role constrained.

html
<route lang="yaml">
meta:
  title: Dashboard
  requiresRoles: ['operator', 'admin']
</route>

Only users with either operator or admin role can access this page

No authentication, anonymous view

html
<route lang="yaml">
meta:
  title: Dashboard
  requiresAuth: false
</route>

Utility Components

Vue Components developed in house to be reused in the app. Documentation

Coding Conventions

  • Use custom component names as <CustomComponent>

Adding Additional Fonts

  • Search for fonts on https://fontsource.org/
  • Install - npm install @fontsource/audiowide
  • Add import '@fontsource/audiowide'; in main.js
  • Add 'Audiowide' to font-family: in body styles in base.css

Dates and Times

  • All dates, timestamps are returned from API as ISO 8601 strings in UTC time zone
  • datetime module is used to consolidate the various date and time formats to use in the UI.
  • Use browser's local time zone to show date and time whenever possible.

Usage:

javascript
import * as datetime from '@/services/datetime.js'

datetime.date("2023-06-14T01:18:40.501Z") // "Jun 14 2023"
datetime.absolute("2023-06-14T01:18:40.501Z") // "2023-06-13 21:18:40 -04:00"

datetime.fromNow("2023-06-14T01:18:40.501Z") // "2 months ago"
datetime.readableDuration(130*1000) // "2 minutes"
datetime.formatDuration(12000 * 1000) // "3h 20m"

If you have a usecase to display in formats other than above in more than one component, add a function to datetime service and use it.

To set static nav links for a page /page1/page2, add nav attr to route meta config block

html
<route lang="yaml">
meta:
  title: Users
  requiresRoles: ["operator", "admin"]
  nav: [{ label: "Users" }]
</route>

Nav breadcrumb are not reset after leaving a page. So if a page should not show nav breadcrumbs they have to be explicitly disabled.

html
<script setup>
import { useNavStore } from "@/stores/nav";
const nav = useNavStore();
nav.setNavItems([], false);
</script>

To set dynamic nav links for a page /page-dyn-1/page-dyn-2

html
<script setup>
import { useNavStore } from "@/stores/nav";
const nav = useNavStore();

page1Promise = api.getP1()
page2Promise = api.getP2()
Promise.all([page1Promise, page2Promise]).then(results => {
  const page1 = results[0]
  const page2 = results[1]
  nav.setNavItems([
    {
      label: page1.name,
      to: "/page1"
    },
    {
      label: page2.name
    },
  ]);
})
</script>

HTTP API Error Handling and Notifications

API requests are to be made with axios.

Catch the error

javascript
import toast from "@/services/toast";

getRecords()
  .then((res) => {...})
  .catch((err) => {
    if (err?.response?.status == 404)
        toast.info("No datasets");
    else toast.error("Could not fetch datatset");
  })

or let someone else handle the dirty work

javascript
getRecords()
  .then((res) => {...})

Global axios error handler will display a generic error toast based on error class ex: 4xx, 5xx, network errors, etc.