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.
When connecting to Event Server, GUI will use client name
gui/<name>
where <name> represents configured component’s name.
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
andlogout
, defined byhat-gui://juggler.yaml#/$defs/request
. All requests returnnull
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 byhat-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
GUI events¶
In addition to events registered by Adapters, Server registers events representing current state of authenticated Clients. These events have event type:
gui/<name>/clients
where <name>
represents configured Server’s name.
Payload for clients events is defined by
hat-gui://events.yaml#/$defs/events/clients
.
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:
- 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"
name:
type: string
description: component name
event_server:
allOf:
- type: object
properties:
require_operational:
type: boolean
- 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"
Events¶
$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "hat-gui://events.yaml"
definitions:
events:
clients:
type: array
items:
type: object
required:
- remote
- user
properties:
remote:
type: string
user:
type: string
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
}