Includes:
- compositor.app, compositor.session, compositor.session.app, compositor.audit.log models
- /web/compositor/{token,apps,launch,audit} HTTP controllers with JWT (HS256)
- CompositorService OWL service (reusable via useService('compositor'))
- CompositorCanvas OWL component (reusable canvas wrapper)
- Security: compositor_group_user, compositor_group_manager groups + ir.access.csv rules
- 5 demo apps pre-loaded (gnome-calculator, xterm, gtk4-demo, glxgears, featherpad)
- Audit log capturing JWT issuance + app launch events
- Stub mode: launching apps returns a stub SurfaceHandle when proxy isn't connected
- Tested against Odoo 20 + PostgreSQL 17
Author: 1bspace.com
Parent company: 1B Global
License: Proprietary (1B Space internal use)
141 lines
8.3 KiB
XML
141 lines
8.3 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
<data>
|
|
<!-- ─── Session List View ─────────────────────────────────────── -->
|
|
<record id="view_compositor_session_list" model="ir.ui.view">
|
|
<field name="name">compositor.session.list</field>
|
|
<field name="model">compositor.session</field>
|
|
<field name="arch" type="xml">
|
|
<list string="Compositor Sessions">
|
|
<field name="compositor_session_id"/>
|
|
<field name="user_id"/>
|
|
<field name="state" widget="badge"
|
|
decoration-success="state == 'active'"
|
|
decoration-warning="state == 'disconnected'"
|
|
decoration-danger="state == 'closed'"
|
|
decoration-info="state == 'pending'"/>
|
|
<field name="running_app_count" string="Apps"/>
|
|
<field name="started_at"/>
|
|
<field name="last_activity"/>
|
|
<field name="duration_seconds" widget="float_time" string="Duration"/>
|
|
</list>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- ─── Session Form View ─────────────────────────────────────── -->
|
|
<record id="view_compositor_session_form" model="ir.ui.view">
|
|
<field name="name">compositor.session.form</field>
|
|
<field name="model">compositor.session</field>
|
|
<field name="arch" type="xml">
|
|
<form string="Compositor Session">
|
|
<header>
|
|
<button name="action_close" string="Close Session" type="object"
|
|
class="btn-danger"
|
|
confirm="This will terminate all running apps. Continue?"
|
|
invisible="state == 'closed'"/>
|
|
<button name="action_reconnect" string="Mark Reconnected" type="object"
|
|
class="btn-secondary"
|
|
invisible="state not in ['disconnected', 'closed']"/>
|
|
<field name="state" widget="statusbar"
|
|
statusbar_visible="pending,active,disconnected,closed"/>
|
|
</header>
|
|
<sheet>
|
|
<div class="oe_button_box" name="button_box">
|
|
<button name="action_view_app_instances" type="object"
|
|
class="oe_stat_button" icon="fa-desktop">
|
|
<field name="running_app_count" widget="statinfo"
|
|
string="Running Apps"/>
|
|
</button>
|
|
<button name="action_view_app_instances" type="object"
|
|
class="oe_stat_button" icon="fa-list">
|
|
<field name="app_count" widget="statinfo" string="Total Apps"/>
|
|
</button>
|
|
</div>
|
|
<group>
|
|
<group>
|
|
<field name="compositor_session_id" readonly="1"/>
|
|
<field name="user_id" readonly="1"/>
|
|
<field name="company_id" groups="base.group_multi_company"/>
|
|
</group>
|
|
<group>
|
|
<field name="proxy_url" readonly="1"/>
|
|
<field name="wayland_display" readonly="1"/>
|
|
<field name="proxy_pid" readonly="1"/>
|
|
</group>
|
|
</group>
|
|
<group>
|
|
<group string="Timing">
|
|
<field name="started_at" readonly="1"/>
|
|
<field name="last_activity" readonly="1"/>
|
|
<field name="ended_at" readonly="1"/>
|
|
<field name="duration_seconds" widget="float_time" readonly="1"/>
|
|
</group>
|
|
<group string="JWT">
|
|
<field name="last_jwt_issued_at" readonly="1"/>
|
|
<field name="last_jwt_expires_at" readonly="1"/>
|
|
</group>
|
|
</group>
|
|
<notebook>
|
|
<page string="App Instances" name="app_instances">
|
|
<field name="app_instance_ids" context="{'default_session_id': id}">
|
|
<list>
|
|
<field name="app_id"/>
|
|
<field name="title"/>
|
|
<field name="state" widget="badge"
|
|
decoration-success="state == 'running'"
|
|
decoration-info="state == 'launching'"
|
|
decoration-danger="state == 'crashed'"
|
|
decoration-muted="state == 'terminated'"/>
|
|
<field name="pid"/>
|
|
<field name="started_at"/>
|
|
<field name="ended_at"/>
|
|
<button name="action_terminate" type="object" string="Terminate"
|
|
class="btn-danger btn-sm" icon="fa-stop"
|
|
invisible="state not in ['launching', 'running']"/>
|
|
<button name="action_force_kill" type="object" string="Force Kill"
|
|
class="btn-danger btn-sm" icon="fa-times"
|
|
invisible="state not in ['launching', 'running']"/>
|
|
</list>
|
|
</field>
|
|
</page>
|
|
<page string="Audit Log" name="audit_log">
|
|
<field name="message_ids" context="{'default_session_id': id}"/>
|
|
</page>
|
|
</notebook>
|
|
</sheet>
|
|
<div class="oe_chatter">
|
|
<field name="message_follower_ids"/>
|
|
<field name="message_ids"/>
|
|
</div>
|
|
</form>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- ─── Session Search View ───────────────────────────────────── -->
|
|
<record id="view_compositor_session_search" model="ir.ui.view">
|
|
<field name="name">compositor.session.search</field>
|
|
<field name="model">compositor.session</field>
|
|
<field name="arch" type="xml">
|
|
<search string="Compositor Sessions">
|
|
<field name="compositor_session_id"/>
|
|
<field name="user_id"/>
|
|
<filter name="active" string="Active" domain="[('state', '=', 'active')]"/>
|
|
<filter name="disconnected" string="Disconnected" domain="[('state', '=', 'disconnected')]"/>
|
|
<filter name="my_sessions" string="My Sessions" domain="[('user_id', '=', uid)]"/>
|
|
<separator/>
|
|
<filter name="group_user" string="User" context="{'group_by': 'user_id'}"/>
|
|
<filter name="group_state" string="State" context="{'group_by': 'state'}"/>
|
|
</search>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- ─── Action ────────────────────────────────────────────────── -->
|
|
<record id="action_compositor_session" model="ir.actions.act_window">
|
|
<field name="name">Sessions</field>
|
|
<field name="res_model">compositor.session</field>
|
|
<field name="view_mode">list,form</field>
|
|
<field name="search_view_id" ref="view_compositor_session_search"/>
|
|
</record>
|
|
</data>
|
|
</odoo>
|