ovirt-engine本地开发(五): 使用UI Plugin扩展Webadmin(推荐)

就像之前说过的那样,使用GWT开发ovirt-engine的体验非常不好,不过幸运的是,从4.3之后,ovirt提供了一种称作UI Plugin的机制,可以让我们将自己开发的页面嵌入到Webadmin内,4.3中的Dashboard页面就是使用这种机制。对于很多场景,使用UI Plugin我们都可以解决,我们先看一下我简单实现的一些功能。

ovirt 提供了关于UI Plugin的文档相对来说还是挺全面的,包括设计思想和具体API。UI Plugin 就是ovirt提供了让我们将自己的前端页面嵌套进Webadmin的API,这样我们就可以完全按照自己习惯的方式来开发页面,然后再通过这些API集成进Webadmin就可以了,让我们彻底从GWT的泥潭中解放出来。
我自己的建议是: 如果你跟我一样,是个GWT小白的话, 那么如果有可能不用去修改GWT,就一定不要去修改,尽量用UI Plugin + rest api 来解决。
ovirt关于UI Plugin的文档:
https://www.ovirt.org/develop/release-management/features/ux/uiplugins43.html
大家继续看下去之前,一定提前看看ovirt提供的文档。

我在这里跟大家一起实现上图中那个虚机资源监控的功能,功能说明:
1. 在虚机列表上方有一个 资源监控按钮,只有选中一个Up的虚机时按钮才可用
2. 点击这个按钮,弹出的对话框中可以实时看到这个虚机的cpu 内存 网络的使用情况

代码地址: https://github.com/zhangcheng1164/ovirt-uiplugins-vmstatistics
我在代码里尽量详尽的写了注释,大家也可以先整体看一下代码。

好,我们现在开始 ???

Plugin代码结构

我们首先进入 /usr/share/ovirt-engine/ui-plugins/, 如果是本地开发环境,应该是编译输出目录下的 这个目录。
如果是生产环境的话,你会看到这个目录下面会有两个软连接。

[root@center124 ui-plugins]# ll
total 0
lrwxrwxrwx. 1 root root 56 Dec  3  2019 ui-extensions.json -> /usr/share/ovirt-engine-ui-extensions/ui-extensions.json
lrwxrwxrwx. 1 root root 61 Dec  3  2019 ui-extensions-resources -> /usr/share/ovirt-engine-ui-extensions/ui-extensions-resources

这些就是使用UI Plugin实现Dashboard的代码。其中 ui-extensions.json 是UI Plugin的描述符文件。ui-extensions-resources目录是具体的资源目录。

我们首先创建类似的文件和目录。 vm-statistics.json 和 vm-statistics-resources。
其中 vm-statistics.json 如下:

{
    // UI Plugin 的名字,这个名字有id的作用,所以必须唯一
    "name": "vm-statistics",
    // UI Plugin 的启动文件,这个文件主要是调用 UI Plugin API,来实现嵌入我们的页面,
    // 所以在这个文件里面尽量不要有我们具体的页面代码
    "url": "plugin/vm-statistics/start.html",
    // 具体的资源目录
    "resourcePath": "vm-statistics-resources"
}

我们知道JSON是不支持注释的,但有意思的是这个描述符文件是支持注释的,就像上面这样。上面的注释已经描述了各个属性,我们强调一下在UI Plugin中url的使用,比如我们现在的这个plugin叫做 vm-statistics,并指定资源目录为vm-statistics-resources,那么 plugin/vm-statistics/xxx.html 就将匹配到 vm-statistics-resources 目录下面的xxx.html,这个我们后面具体实现页面的时候还会有提及。

根据上面的描述符文件,我们首先需要的自然是那个启动文件start.html,我们在start.html里面主要就是调用ovirt提供的UI Plugin的API,我们一步步逐渐完善它,最开始的样子:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<script type='text/javascript'>
    // 获取ovirt提供的API,由于UI Plugin是使用iframe实现的,所以这里的parent指向的是Webadmin所在的上下文。
    var api = parent.pluginApi('vm-statistics');

    function init() {
        // addMainTabActionButton 方法就能够实现在具体的列表页上方增加一个按钮,这里第一个参数VirtualMachine 表明是在虚机列表上方
        // 第二个参数是按钮title,最后一个对象参数是options选项。
        api.addMainTabActionButton(
            'VirtualMachine', 
            '资源监控',
            {
                isEnabled: function() {
                    // 只有选中一条记录,并且选中的虚机是Up状态按钮才可用。
                    return arguments.length == 1 && arguments[0].status === 'Up';
                },
                onClick: function() {
                    alert('测试信息'); 
                },
            });
    };

    // 我们通过register方法来实现初始化和一些事件的绑定
    api.register({
        UiInit: function() {
            init();
        },
    });

    api.ready();
</script>
</body>
</html>

上面代码里的注释基本上说清楚了代码的具体作用,这里关键的地方就是调用api.addMainTabActionButton,从而实现在虚机列表上方新增一个按钮,并且我们为这个按钮增加了一个onClick事件,alert一条消息。 这样之后,我们刷新一下Webadmin的页面,就可以看到这个按钮了。
因为我们是监控某一个具体虚机的资源,所以当按钮可用时,我们首先需要知道当前选中的是哪一个虚机,通过注册 VirtualMachineSelectionChange 事件我们可以监听所有选中修改,从而记住选中的虚机。如下:

    api.register({
        UiInit: function() {
            init();
        },
        // 通过 VirtualMachineSelectionChange 事件,我们可以保存下当前选中的虚机
        VirtualMachineSelectionChange: function() {
            if (arguments.length == 1) {
                // 我们不考虑多选,所以仅仅保存选中一条时的虚机就可以了。
                // selectedVm是我们定义的一个全局变量
                selectedVm = arguments[0];
            } else {
                // Otherwise, forget the entity
                selectedVm = null;
            }
        },
    });

如果我们在onClick事件中打印那个selectedVm全局变量,就会看到我们的确是获得了当前选中的虚机。

将我们的页面嵌入Webadmin

下面我们实现点击按钮,弹出一个对话框。这里有一个关键点,就是我们一开始就说UI Plugin是用来将我们自己的前端页面嵌入Webadmin中的,那自然我们希望的就是这个弹出框中出现的就是我们自己的页面。我们通过showDialog的第三个参数指定我们自己实现的页面。注意url的格式于vm-statistics.json描述符文件中一致。
UI Plugin提供了很多类似于showDialog这种接口,例如 addPrimaryMenuPlace、addDetailPlace,它们都是用来将我们自己实现的页面嵌入到Webadmin中的不同位置。

    // 定义一个唯一的dialog标准,类似于id。
    var dialogToken = 'vm-statistics-dialog';
    // 这个跟 vm-statistics.json描述符文件中的url规则一致,它指向的就是 vm-statistics-resources目录下面的test.html文件
    var dialogUrl = 'plugin/vm-statistics/test.html';

    var openDialog = function() {
        /*
            showDialog方法,第一个参数是Dialog的title,第二个类似于这个Dialog的id,
            第三个参数最关键,它指向的就是我们自己实现的前端页面,我们怎么实现它,打开的Dialog就怎么显示。
            后面的两个参数是 长度和宽度
            最后一个是选项参数,属性名字基本上都非常直观,就不解释了。
        */
        api.showDialog(selectedVm.name + ' 资源监控', dialogToken, dialogUrl, '800px', '860px',
            {
                closeIconVisible: false, 
                closeOnEscKey: false, 
                resizeEnabled: true, 
                buttons:
                [
                    {
                        label: '关闭',
                        onClick: closeDialog
                    }
                ]
            });
    };

    function closeDialog() {
       api.closeDialog(dialogToken);
    };

基本上到此为止,我们就可以实现将自己实现的任意页面嵌入到这个Dialog中来了,但是问题是,我们需要在自己实现的页面中做两个关键的事情:
1. 我们需要将上面保存的selectedVm传递到自己实现的页面中,这样才能知道该显示哪个虚机的资源信息。
2. 我们需要访问ovirt engine的rest api,来进一步获得信息。

使用postMessage来传递信息

我们使用PostMessage和message事件来在启动文件start.html和我们自己的页面文件test.html之前传递信息。
除了上面说的获取选中虚机信息外,我们还使用这个消息机制在关闭Dialog的时候,通知页面文件test.html关闭获取信息的定时器。

大体流程是:
内容前端页面test.html –> postMessage(‘getSelectedVm’) –> 启动文件start.html
启动文件start.html接收到请求信息后,发送 保存下来的selectedVm
启动文件start.html –> postMessage(selectedVm) –> 内容前端页面
这样我们自己的前端页面就获得了这个选中的虚机信息

关闭Dialog时:
启动文件start.html –> postMessage(‘close’) –> 内容前端页面 test.html(test.html清空定时器)

首先来看启动文件start.html,我们通过注册MessageRecevied事件来接收发送来的信息。如下:

    // 我们通过register方法来实现初始化和一些事件的绑定
    api.register({
        UiInit: function() {
            init();
        },
        // 通过 VirtualMachineSelectionChange 事件,我们可以保存下当前选中的虚机
        VirtualMachineSelectionChange: function() {
            if (arguments.length == 1) {
                // 我们不考虑多选,所以仅仅保存选中一条时的虚机就可以了。
                // selectedVm是我们定义的一个全局变量
                selectedVm = arguments[0];
            } else {
                // Otherwise, forget the entity
                selectedVm = null;
            }
        },
        // 通过注册MessageReceived 事件,我们可以接收到发送到启动文件的message信息。
        MessageReceived: function(data, _sourceWindow) {
            if (data === 'getSelectedVm') {
                // 这个sourceWindow是一个全局变量,它指向我们自己实现的内容页面,我们之所以保存它,
                // 是为了在关闭这个Dialog的时候,向它发送信息,让它关闭获取虚机信息的定时器。
                sourceWindow = _sourceWindow;
                sourceWindow.postMessage(selectedVm, '*');
            }
        },
    });

在自己的前端页面test.html中有类似这样的代码:

        //向start.json请求选中的虚机
        parent.postMessage('getSelectedVm', '*');

        // 定义message监听
        window.addEventListener("message", function(event) {
            // 在开始时获取选中的虚机信息
            if (event.data !== 'close') {
                selectedVm = event.data;

                getStatistic(function(cpuHistory, memoryHistory, networkHistory) {
                    initChart('cpu_container', cpuHistory);            
                    initChart('memory_container', memoryHistory);            
                    initChart('network_container', networkHistory);            
                });

                inter = setInterval(() => {
                    getStatistic(function(cpuHistory, memoryHistory, networkHistory) {
                        reloadChart(cpuChart, cpuHistory);
                        reloadChart(memoryChart, memoryHistory);
                        reloadChart(networkChart, networkHistory);
                    });
                }, 1000 * 30);
            } else { // 当Dialog关闭时,接收信息,清空定时器。
                if(inter) {
                    clearInterval(inter);
                }
            }
        }, false);

这样我们就实现了信息的双向传递。

如何请求rest api

现在来解决第二个问题,在自己实现的前端页面中调用ovirt engine的rest api。 这里主要就是如何获取SSO Token。如下:

            // 使用于start.html中相同的方式获得UI Plugin的API
            var api = parent.pluginApi('vm-statistics');
            // 通过ssoToken() 获得认证token
            var ssoToken = api.ssoToken();
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function()
            {   
                if (xhr.readyState === 4 && xhr.status === 200)
                {   
                  // .......
                }   
            }   

            xhr.open('GET', '/ovirt-engine/api/vms/' + selectedVm.id + '/statistics', true);
            xhr.withCredentials = true;
            // 保留这些头信息
            xhr.setRequestHeader('Accept', 'application/json');
            xhr.setRequestHeader('Filter', 'false'); 
            xhr.setRequestHeader('Prefer', 'persistent-auth');
            xhr.setRequestHeader('Authorization', 'Bearer ' + ssoToken); 
            xhr.send(null);

这样我们就可以访问ovirt engine的rest api了,拥有了这个能力,我们基本上就可以做任何想做的事情了。

到此为止,我们在内容页面上既可以获得到选中的虚机,又可以访问rest api了,后面的具体业务,我们就可以按照自己习惯的方式来做了。
我们这样就算是彻底从GWT的泥潭中解脱出来了

2 条回复 A 作者 M 管理员 E
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论