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 becomes available and can be used for starting this component.

usage: hat-gui [-h] [--conf path]
               [--additional-json-schemas-path [path [path ...]]]
               [--ui-path path]

optional arguments:
  -h, --help            show this help message and exit
  --conf path           configuration defined by hat://gui/main.yaml# (default
                        $XDG_CONFIG_HOME/hat/gui.yaml)
  --additional-json-schemas-path [path [path ...]]
                        additional json schemas paths

development arguments:
  --ui-path path        override web ui directory path

Overview

GUI functionality can be defined according to following components:

folder "Event Server" as EventServer

folder "GUI Backend" {
    component "Event Client" as EventClient

    component Server
    component Session <<Session>> as Session

    component Adapter <<Adapter>> as Adapter
    component "Adapter Event Client" <<AdapterEventClient>> as AdapterEventClient
    component "Adapter Session Client" <<AdapterSessionClient>> as AdapterSessionClient
    component "Adapter Session" <<AdapterSession>> as AdapterSession

    component "View Manager" as ViewManager
}

folder "GUI Frontend" {
    component Client
    component View
}

folder "File system" {
    component "View Dir" as ViewDir
}

EventServer <-> EventClient

Server o-- Session

Session <-> Client

Session o-- AdapterSession

Adapter ..> AdapterSession : create

AdapterSession <-> AdapterSessionClient

Session o- AdapterSessionClient

EventClient <--> AdapterEventClient

AdapterEventClient <-> Adapter

Server -> ViewManager

ViewManager .> ViewDir : get

Session .> View : send

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.

Backend - frontend communication

Communication between backend and frontend is based on juggler protocol. Each connection between server and client (Session) has got unique local data. Structure of local data on both client and server side is defined by JSON schema:

type: object
description: |
    keys represent adapter names
patternProperties:
    ".+":
        description: |
            structure of adapter's local/remote data is defined by
            adapter type

Supported juggler MESSAGE messages sent from client to server are:

definitions:
    login:
        type: object
        required:
            - type
            - name
            - password
        properties:
            type:
                enum:
                    - login
            name:
                type: string
            password:
                type: string
                description: |
                    this property contains SHA-256 password
                    hash encoded as hex string
    logout:
        type: object
        required:
            - type
        properties:
            type:
                enum:
                    - logout
    adapter:
        type: object
        required:
            - type
            - name
            - data
        properties:
            type:
                enum:
                    - adapter
            name:
                type: string
                description: adapter instance name
            data:
                description: |
                    structure of this property is defined by
                    adapter type

Supported juggler MESSAGE messages sent from server to client are:

definitions:
    state:
        type: object
        required:
            - type
            - reason
            - user
            - roles
            - view
            - conf
        properties:
            type:
                enum:
                    - state
            reason:
                enum:
                    - init
                    - login
                    - logout
                    - auth_fail
                    - internal_error
            user:
                type:
                    - string
                    - "null"
            roles:
                type: array
                items:
                    type: string
            view:
                type: object
    adapter:
        type: object
        required:
            - type
            - name
            - data
        properties:
            type:
                enum:
                    - adapter
            name:
                type: string
                description: adapter instance name
            data:
                description: |
                    structure of this property is defined by
                    adapter type

When client establishes new juggler connection with server, initial local data on server side is null. Immediately after connection is established, server sends state message with reason init and initial view.

At any time, client can send login or logout message. Once server receives login or logout message, it should respond with appropriate state message.

If the authentication using credentials provided in login message fails, server sends state message with reason auth_fail and initial view.

If client successfully authenticates, server will create new Session instance which is responsible for further communication with client. It sends a state message with reason login, user set to username, roles containing a list of roles for the user, and a view that is associated with the first role.

Session continuously updates server’s local data according to AdapterSessionClient’s local data. It is also responsible for creating adapter session and bidirectional forwarding of adapter messages between frontend client and adapter client.

After a client logs out, server sends state message with reason logout and user set to null.

If a server-side error causes sending a state message with initial view, reason is set to internal_error.

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 associdated adapter’s optional JSON schema. During adapter instance initialization, each adapter instance is provided with instance of AdapterEventClient. AdapterEventClient provides proxy interface to active Event client connection which enables communication with Event Server in accordance with adapter’s event type subscription.

Adapter is responsible for creating new instances of AdapterSessions associated with backend-frontend communication session. During initialization of AdapterSession, adapter is provided with appropriate instance of AdapterSessionClient which can be used by Adapter or AdapterSession for communication with individual GUI frontends. AdapterSessionClient enables full juggler communication (exchange of local/remote state and asynchronous message communication) appropriate for associated AdapterSession.

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 Session instance. Additionally, each AdapterSession is responsible for enforcing fine grained authorization rules in accordance to user authenticated with associated AdapterSessionClient.

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

    files are read as json or yaml 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 and configuration of first role associated with user. 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.

Todo

future improvements:

  • zip archives as view bundles

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

Once client receives new state message, it will evaluate JavaScript code from view’s index.js. This code is evaluated inside environment which contains global constant hat. When evaluation is finished, environment should contain global values init, vt and destroy.

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

  • conf

    view’s configuration

  • reason

    enum representing a reason for showing this view

    • init

      initial view after connection establishment

    • login

      user has successfully logged in

    • logout

      user has successfully logged out

    • auth_fail

      authentication attempt was unsuccessful, wrong credentials were provided

    • internal_error

      server experienced internal error

  • user

    authenticated user identifier

  • view

    view’s data

  • conn

    object representing connection with server with properties:

    • onMessage

      property which references function called each time new adapter message is received (callback receives arguments adapter and msg which reference content of adapter message)

    • login(name, password)

      login method

    • logout()

      logout method

    • send(adapter, msg)

      method for sending adapter messages

When evaluation finishes, environment should contain:

  • init

    optional initialization function which is called immediately after evaluation of index.js finishes (this function has no input arguments and its return value is ignored)

  • vt

    function called each time global renderer’s state changes (this function has no input arguments and it should return virtual tree data)

  • destroy

    optional function called prior to evaluation of other view’s index.js (this function has no input arguments and its return value is ignored)

Views available as part of hat-gui package:

Python implementation

class hat.gui.common.Adapter

Bases: abc.ABC

Adapter interface

Adapters are implemented as python modules which are dynamically imported. Each adapter instance has configuration which must include module - python module identifier. It is expected that this module implements:

  • json_schema_id (Optional[str]): JSON schema id

  • event_type_prefix (Optional[hat.event.common.EventType]):

    event type prefix

  • create (Callable[[json.Data,AdapterEventClient],Adapter]):

    coroutine responsible for creating adapter

If module defines JSON schema id, it will be used for aditional validation of module’s configuration.

Event type prefix is used for filtering events that can be obtained by calling AdapterEventClient.receive(). It can not contain subscription wildchars. If it is None, adapter will not receive any event notifications.

create coroutine is called with adapter instance configuration and adapter event client.

abstract property closed

closed future

Type

asyncio.Future

abstract async async_close()

Async close

abstract async create_session(client)

Create new adapter session

Parameters

client (AdapterSessionClient) – adapter session client

Returns

AdapterSession

class hat.gui.common.AdapterSession

Bases: abc.ABC

Adapter’s single client session

abstract property closed

closed future

Type

asyncio.Future

abstract async async_close()

Async close

class hat.gui.common.AdapterSessionClient

Bases: abc.ABC

Adapter’s session client represents single juggler connection

property user

user identifier

Type

str

property roles

user roles

Type

List[str]

property local_data

json serializable local data

Type

json.Data

property remote_data

json serializable remote data

Type

json.Data

register_change_cb(cb)

Register remote data change callback

Parameters

cb (Callable[[],None]) – change callback

Returns

util.RegisterCallbackHandle

set_local_data(data)

Set local data

Parameters

data (json.Data) – json serializable local data

async send(msg)

Send message

Parameters

msg (json.Data) – json serializable message

async receive()

Receive message

Returns

json serializable message

Return type

json.Data

class hat.gui.common.AdapterEventClient

Bases: abc.ABC

Adapters interface to event client

Received event notifications include only those that start with event_type_prefix as defined by adapter implementation.

async receive()

See hat.event.client.Client.receive()

register(events)

See hat.event.client.Client.register()

async register_with_response(events)

See hat.event.client.Client.register_with_response()

async query(data)

See hat.event.client.Client.query()

class hat.gui.view.View(name, conf, data)

Bases: tuple

Create new instance of View(name, conf, data)

conf

json.Data

data

Dict[str,json.Data]

name

str

async hat.gui.view.create_view_manager(conf, json_schema_repo)

Create view manager

Parameters
  • conf (json.Data) – configuration defined by hat://gui/main.yaml#/definitions/views

  • json_schema_repo (json.SchemaRepository) – json schema repository used for view configuration validation

Returns

ViewManager

class hat.gui.view.ViewManager

Bases: object

View manager

property closed

closed future

Type

asyncio.Future

async async_close()

Async close

async get(name)

Get view

Parameters

name (str) – view name

Returns

View

async hat.gui.server.create(conf, path, adapters, views)

Create server

Parameters
  • conf (json.Data) – configuration defined by hat://gui/main.yaml#/definitions/server

  • path (pathlib.Path) – web ui directory path

  • adapters (Dict[str,common.Adapter]) – adapters

  • views (hat.gui.view.ViewManager) – view manager

Returns

Server

class hat.gui.server.Server

Bases: object

property closed

closed future

Type

asyncio.Future

async async_close()

Async close

async hat.gui.server.create_session(conn, user, roles, adapters)

Create client session

adapters contain only adapters associated with roles.

Parameters
  • conn (juggler.Connection) – juggler connection

  • user (str) – user identifier

  • roles (List[str]) – user roles

  • adapters (Dict[str,common.Adapter]) – adapters

Returns

Session

class hat.gui.server.Session

Bases: object

Client session

property closed

closed future

Type

asyncio.Future

async async_close()

Async close

add_adapter_message(name, msg)

Add adapter message

Parameters
  • name (str) – adapter name

  • msg (json.Data) – message

Virtual tree XML parser

hat.gui.vt.parse(file)

Parse XML document into virtual tree

Each element is recursively parsed into a list with the following structure, starting from the root of a document:

  • First item is a valid CSS selector string, consisting of element tag name; and optionally id and class attributes if present.

  • If the element has attributes other than id or class, they are stored as a second item. The item is a dictionary which has an attrs key, whose value is another dictionary, with key-value pairs representing attribute names and their values, respectively.

  • All other items are element content. Each item is either a recursively parsed element child (a list), or text (a string).

Resulting structure is JSON serializable.

Namespace prefix declaration attributes (xmlns:*) are ignored.

Example usage:

import io
import hat.gui.vt

xml = '''\
    <html>
        <body>
            <div id="first" class="c1 c2">
                Banana
            </div>
            Orange
            <br/>
            <span id="second" style="color:green">
                Watermelon
            </span>
        </body>
    </html>
'''
stripped = ''.join(line.lstrip() for line in xml.split('\n'))
stream = io.StringIO(stripped)
parsed = hat.gui.vt.parse(stream)

Output:

['html',
    ['body',
        ['div#first.c1.c2',
            "Banana"
        ],
        "Orange",
        ['br'],
        ['span#second',
            {'attrs':
                {'style': "color:green"}
            },
            "Watermelon"
        ]
    ]
]
Parameters

file – file stream

Returns

json serializable virtual tree

Return type

hat.json.Data