GUI Server

GUI Server provides user interface for monitoring and controlling Hat system functionality in real time. It provides multi-user environment with authentication and authorization control of available resources.

Running

By installing GUI Server from hat-gui package, executable hat-gui-server becomes available and can be used for starting this component.

usage: hat-gui-server [-h] [--conf PATH]

options:
  -h, --help   show this help message and exit
  --conf PATH  configuration defined by hat-gui://server.yaml (default
               $XDG_CONFIG_HOME/hat/gui.{yaml|yml|toml|json})

Overview

GUI functionality can be defined according to following components:

Functionality is dependent on active connection to Event Server. Adapters, Server and View Manager are created when connection with Event Server is established and destroyed if this connection is closed. If connection with Event Server is closed, GUI will repeatedly try to establish new connection with currently active Event Server. If connection to Monitor Server could not be established or is closed, GUI terminates its process execution.

GUI Server can also run independently of Monitor Server. In this case, GUI Server connects to predefined Event Server address. If this connection could not be established or is broken, GUI Server terminates it’s process execution.

Adapters

Adapters are mutually independent providers of server-side functionality and data exposed to GUI frontends. For providing this functionality and data, adapters rely primarily on their internal state and communication with Event Server. Adapter definitions are dynamically loaded during GUI server startup procedure.

GUI server can be configured to initialize arbitrary number of adapter instances with their custom configurations which will be validated with associated adapter’s optional JSON schema. During adapter instance initialization, each adapter instance is provided with instance of EventerClient, enabling queries and event registration. Each adapter is notified with events sent by Event Server based on its subscriptions.

Server is responsible for creating new instances of AdapterSessions associated with backend-frontend communication session. AdapterSession represents adapter’s interface to single authenticated frontend client. It enables full juggler communication - request/response, server state and server notifications.

Implementation of single adapter is usually split between Adapter implementation and AdapterSession implementation where Adapter encapsulates shared data and AdapterSession encapsulates custom data and functionality specific for each client. Additionally, each AdapterSession is responsible for enforcing fine grained authorization rules in accordance to user authenticated with associated AdapterSession.

Adapters available as part of hat-gui package:

Views

Views are collection of JavaScript code and other frontend resources responsible for graphical representation of adapters state and interaction with user. Each view is represented with content of file system directory.

ViewManager is server side component which is used for loading view’s resources. Each file inside view’s directory (or subdirectory) is identified with unix file path relative to view’s directory. Each file is read from file system and encoded as string based on file extension:

  • .js, .css, .txt

    files are read and encoded as utf-8 encoded strings

  • .json, .yaml, .yml, .toml

    files are read as json, yaml or toml files and encoded as utf-8 json data representation

  • .svg, .xml

    files are read as xml data and encoded as utf-8 json data representing equivalent virtual tree

    Todo

    better definition of transformation between xml and virtual tree data

  • all other files

    files are read as binary data and encoded as base64 strings

Server chooses client’s view depending on authenticated user configuration. This view’s resources and configuration is obtained from ViewManager. Responsibility of ViewManager is to provide current view’s data and configuration as available on file system in the moment when server issued request for these resources. If view directory contains schema.{yaml|yml|json}, it is used as JSON schema for validating view’s configuration.

Views available as part of hat-gui package:

Todo

future improvements:

  • zip archives as view bundles

  • ‘smart’ ViewManager with watching view directory and conf for changes and preloading resources on change

Backend - frontend communication

Request/response

Juggler request/response communication is used executing system and adapter specific actions:

  • system actions

    Currently supported system actions are login and logout, defined by hat-gui://juggler.yaml#/$defs/request. All requests return null on success and raise exception in case of error.

  • adapter specific actions

    Request name is formatted as <adapter>/<action> where <adapter> is name of adapter instance and <action> is one of action names supported by referenced adapter instance type. Structure of request data and response results are defined by specific adapter action.

Because AdapterSessions are created only for authenticated users, adapter specific actions are available only after successful authentication.

Server state

Juggler state is used for transfer of AdapterSession states from backend to frontend. State is single object where keys represent adapter instance names and values contain current associated AdapterSession state. If client is not authenticated, this object is empty.

Server notifications

Juggler notifications enable backend to notify frontend with system and adapter specific notifications:

  • system notifications

    Currently supported system notification is init defined by hat-gui://juggler.yaml#/$defs/notification. Backend can send this notification at any time, informing frontend of changes that should be applied to frontend execution environment.

  • adapter specific notifications

    Notification name is formatted as <adapter>/<notification> where <adapter> is name of adapter instance and <notification> is notification identification supported by referenced adapter instance type. Structure of notification data is defined by specific adapter notification.

Frontend API

Initially, each instance of client is considered unauthenticated and not initialized. Once client receives server’s init notification, it should create new execution environment and initialize view defined as part of init data.

View initialization is based on evaluation of JavaScript code from view’s index.js. This code is evaluated inside environment which contains global constant hat which is also bound to window object. When evaluation is finished, environment should contain global values init, vt, destroy, onNotify and onDisconnected.

If juggler connection to server is broken, last initialized view remains active until new connection is established and new init notification is received. Each time new juggler connection is established, server will send new init notification.

Client bounds juggler connection’s server state to default renderer’s ['remote'] path. Constant hat, available during execution of index.js, references object with properties:

  • conf

    view’s configuration

  • user

    authenticated user identifier

  • roles

    authenticated user roles

  • view

    view’s data

  • login(name, password)

    login method

  • logout()

    logout method

  • send(adapter, name, data)

    method for request/response communication

  • getServerAddresses()

    get GUI server juggler addresses

  • setServerAddresses(addresses)

    set GUI server juggler addresses

  • disconnect()

    close current juggler connection to GUI server

When evaluation finishes, environment should contain optional functions:

  • init()

    called immediately after evaluation of index.js finishes

  • vt()

called each time global renderer’s state changes (this function should return virtual tree data)

  • destroy()

    called prior to evaluation of other view’s index.js

  • onNotify(adapter, name, data)

    called each time client receives adapter specific notification from server

  • onDisconnected()

    called if juggler connection is broken

Todo

describe exports and resulting environment in case of js modules

JSON Schemas

Configuration

$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "hat-gui://server.yaml"
title: GUI server
description: GUI server's configuration
type: object
required:
    - gui_name
    - event_server
    - address
    - adapters
    - views
    - users
properties:
    type:
        const: gui
        description: configuration type identification
    version:
        type: string
        description: component version
    log:
        $ref: "hat-json://logging.yaml"
    gui_name:
        type: string
    event_server:
        oneOf:
          - type: object
            required:
                - monitor_component
            properties:
                monitor_component:
                    type: object
                    required:
                        - host
                        - port
                        - gui_group
                        - event_server_group
                    properties:
                        host:
                            type: string
                            default: "127.0.0.1"
                        port:
                            type: integer
                            default: 23010
                        gui_group:
                            type: string
                        event_server_group:
                            type: string
          - type: object
            required:
                - eventer_server
            properties:
                eventer_server:
                    type: object
                    required:
                        - host
                        - port
                    properties:
                        host:
                            type: string
                            default: "127.0.0.1"
                        port:
                            type: integer
                            default: 23012
    address:
        type: object
        required:
            - host
            - port
        properties:
            host:
                type: string
                default: "127.0.0.1"
            port:
                type: integer
                default: 23023
    adapters:
        type: array
        items:
            $ref: "hat-gui://server.yaml#/$defs/adapter"
    views:
        type: array
        items:
            $ref: "hat-gui://server.yaml#/$defs/view"
    users:
        type: array
        items:
            $ref: "hat-gui://server.yaml#/$defs/user"
    initial_view:
        type:
            - string
            - "null"
    client:
        type: object
        properties:
            retry_delay:
                type: number
                default: 5
            ping_delay:
                type: number
                default: 5
            ping_timeout:
                type: number
                default: 5
$defs:
    adapter:
        type: object
        required:
            - name
            - module
        properties:
            name:
                type: string
            module:
                type: string
    view:
        allOf:
          - type: object
            required:
                - name
            properties:
                name:
                    type: string
          - oneOf:
              - type: object
                required:
                    - view_path
                properties:
                    view_path:
                        type: string
              - type: object
                required:
                    - builtin
                properties:
                    builtin:
                        type: string
          - oneOf:
              - type: object
                required:
                    - conf_path
                properties:
                    conf_path:
                        type: string
              - type: object
                required:
                    - conf
    user:
        type: object
        required:
            - name
            - password
            - roles
            - view
        properties:
            name:
                type: string
            password:
                $ref: "hat-gui://server.yaml#/$defs/password"
            roles:
                type: array
                items:
                    type: string
            view:
                type:
                    - string
                    - "null"
    password:
        type: object
        required:
            - hash
            - salt
        properties:
            hash:
                type: string
                description: |
                    SHA256(salt + SHA256(password)) hash encoded as hex string
            salt:
                type: string
                decription: |
                    unique salt used for generating hash encoded as hex string

Juggler

$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "hat-gui://juggler.yaml"
$defs:
    state:
        type: object
    request:
        login:
            type: object
            required:
                - name
                - password
            properties:
                name:
                    type: string
                password:
                    type: string
        logout:
            type: "null"
    notification:
        init:
            type: object
            required:
                - user
                - roles
                - view
                - conf
            properties:
                user:
                    type:
                        - string
                        - "null"
                roles:
                    type: array
                    items:
                        type: string
                view:
                    type:
                        - object
                        - "null"

TypeScript definitions

import type { Renderer } from '@hat-open/renderer';
import type * as u from '@hat-open/util';


export type LogoutAction = (user: string | null) => Promise<void>;

export type LoginFn = (name: string, password: string) => Promise<void>;
export type LogoutFn = () => Promise<void>;
export type SendFn = (adapter: string, name: string, data: u.JData) => Promise<u.JData>;
export type GetServerAddressesFn = () => string[];
export type SetServerAddressesFn = (addresses: string[]) => void;
export type DisconnectFn = () => void;
export type SetLogoutActionFn = (action: LogoutAction | null) => void;

export type InitFn = () => Promise<void>;
export type VtFn = () => u.VNode;
export type DestroyFn = () => Promise<void>;
export type NotifyFn = (adapter: string, name: string, data: u.JData) => Promise<void>;
export type DisconnectedFn = () => Promise<void>;

export type Hat = {
    conf: u.JData;
    user: string | null;
    roles: string[];
    view: Record<string, u.JData>;
    login: LoginFn;
    logout: LogoutFn;
    send: SendFn;
    getServerAddresses: GetServerAddressesFn;
    setServerAddresses: SetServerAddressesFn;
    disconnect: DisconnectFn;
    setLogoutAction: SetLogoutActionFn;
};

export type Env = {
    hat: Hat;
    init: InitFn | undefined;
    vt: VtFn | undefined;
    destroy: DestroyFn | undefined;
    onNotify: NotifyFn | undefined;
    onDisconnected: DisconnectedFn | undefined;
};

export type Util = typeof u;

declare global {
    var hat: Hat;  // eslint-disable-line no-var
    var r: Renderer;  // eslint-disable-line no-var
    // TODO export util types
    var u: Util;  // eslint-disable-line no-var
}