使用Horizon构建Dashboard
一、写在前面
继上篇博文,在这篇博文中举例说明扩展一个dashboard 以及 panel。类似这样的博文在网上较多,这里仅仅是回忆下,好久没有跟进OpenStack Horizon版本的代码,基于目前OpenStack rocky版本。
邮箱地址:jpzhang.ht@gmail.com
个人博客:https://jianpengzhang.github.io/
CSDN博客:http://blog.csdn.net/u011521019
Horizon 原文阅读地址:
https://docs.openstack.org/horizon/latest/contributor/tutorials/dashboard.html
# 二、新建Dashboard & Panel
按照惯例,这里简单介绍下Horizon的目录结构,至于如何构建一个Horizon的开发环境这里不在详述,这里是基于devstack部署的all in one 简单环境。
#### Horizon设计和架构的核心价值:
核心支持:对所有核心OpenStack项目提供开箱即用的支持; 可扩展性:任何开发者都能增加组件;
易于管理:架构和代码易于管理,浏览方便; 视图一致:各组件的界面和交互模式保持一致;
可兼容性:API向后兼容; 易于使用:界面用户友好;
#### Horizon 结构:
Horizon提供一个模块化的基于Web的图形界面,采用了Django框架。Horizon目录结构主要分为两个:Horizon、opendtack_dashboard,Horizon模块的实现充分利用了很多Django框架提供的一些高级特性。如果把horizon目录中的各种实现当作积木,那么搭好的小房屋就放在openstack_dashboard的目录下。目录源码结构如下:
|
|
使用Horizon构建dashboard
介绍如何使用Horizon中的各种组件来构建示例dashboard和带有tab的panel,该tab包含一个包含后端数据的表。【参考官方实例】
例如,我们将创建一个新的“My Dashboard”的Dashboard,其中“My Panel”的Panel具有“Instances Tab” 标签。该tab标签卡有一个表,其中包含Nova实例(instances)API提取的数据。
创建 Dashboard
快速方式
Horizon提供自定义管理命令,创建典型的Dashboard基础结构。在Horizon根目录中运行以下命令。它生成了需要的大部分样板代码:
12345678910$ mkdir openstack_dashboard/dashboards/mydashboard$ tox -e manage -- startdash mydashboard \--target openstack_dashboard/dashboards/mydashboard$ mkdir openstack_dashboard/dashboards/mydashboard/mypanel$ tox -e manage -- startpanel mypanel \--dashboard=openstack_dashboard.dashboards.mydashboard \--target=openstack_dashboard/dashboards/mydashboard/mypanel你会注意到目录mydashboard会自动填充与dashboard相关的文件,mypanel目录会自动填充与面板相关的文件。
目录结构
如果使用tree mydashboard命令列出openstack_dashboard/ dashboards中的mydashboard目录,你将看到如下所示的目录结构:12345678910111213.├── dashboard.py├── __init__.py├── mypanel│ ├── __init__.py│ ├── panel.py.tmpl│ ├── templates│ │ └── mypanel│ ├── tests.py.tmpl│ ├── urls.py.tmpl│ └── views.py└── templates└── mydashboard不处理静态目录或tests.py文件,保持原样,有了其他文件和目录,我们可以继续添加我们自己的dashboard。
定义dashboard
打开dashboard.py文件,会注意到已自动生成以下代码:12345678910111213from django.utils.translation import ugettext_lazy as _import horizonclass Mydashboard(horizon.Dashboard):name = _("My dashboard")slug = "mydashboard"panels = () # Add your panels here.default_panel = '' # Specify the slug of the dashboard's default panel.horizon.register(Mydashboard)
如果你希望dashboard名称是其他名称,则可以更改dashboard.py文件中的name属性。 例如,你可以将其更改为“My Dashboard”
dashboard类通常包含name属性(dashboard的显示名称),slug属性(可由其他组件引用的内部名称),panels列表,默认panel等。
创建 panel
我们将创建一个panel并将其命名为My Panel。
目录结构
如上所述,openstack_dashboard/dashboards/mydashboard下的mypanel目录应如下所示:
123456.├── __init__.py├── panel.py├── templates│ └── mypanel└── views.py上面引用的panel.py文件具有特殊含义。在dashboard中,dashboard类的panels属性中列出的任何模块名称都将通过查找相应目录中的panel.py文件自动发现。
打开panel.py文件,自动生成的代码:1234567891011from django.utils.translation import ugettext_lazy as _import horizonfrom openstack_dashboard.dashboards.mydashboard import dashboardclass Mypanel(horizon.Panel):name = _("My panel")slug = "mypanel"dashboard.Mydashboard.register(Mypanel)如果你希望Panel名称是其他名称,则可以更改panel.py文件中的name属性。 例如,可以将其更改为”My panel”。
再次打开dashboard.py文件,在Mydashboard类上面插入以下代码。此代码定义了Mygroup类并添加了一个名为mypanel的面板:12345class Mydashboard(horizon.Dashboard):name = _("My Dashboard")slug = "mydashboard"panels = (Mygroup,) # Add your panels here.default_panel = 'mypanel' # Specify the slug of the default panel.完成的dashboard.py文件应如下所示:
1234567891011121314151617from django.utils.translation import ugettext_lazy as _import horizonclass Mygroup(horizon.PanelGroup):slug = "mygroup"name = _("My Group")panels = ('mypanel',)class Mydashboard(horizon.Dashboard):name = _("Mydashboard")slug = "mydashboard"panels = (Mygroup, ) # Add your panels here.default_panel = 'mygroup' # Specify the slug of the dashboard's default panel.horizon.register(Mydashboard)
Tables, Tabs, and Views
将从table开始,将其与tabs相结合,然后从各个部分构建我们的视图。
定义table
Horizon提供了一个SelfHandlingForm DataTable类,它简化了大多数向最终用户显示数据的形式。我们只是在这里简单举例,但它具有大量的功能。在mypanel目录下创建一个tables.py文件并添加以下代码:
123456789101112131415from django.utils.translation import ugettext_lazy as _from horizon import tablesclass InstancesTable(tables.DataTable):name = tables.Column("name", verbose_name=_("Name"))status = tables.Column("status", verbose_name=_("Status"))zone = tables.Column('availability_zone',verbose_name=_("Availability Zone"))image_name = tables.Column('image_name', verbose_name=_("Image Name"))class Meta(object):name = "instances"verbose_name = _("Instances")创建了一个tables.DataTable子类,并定义了我们想要检索显示的四列数据。这些列中的每一列显示的数据为Instances对象的属性,即定义的第一个属性值,verbose_name参数表示该列在table表格thead中的中文名字。
最后,我们添加了一个Meta类,描述instances表的元对象,即表示数据来自instances数据表。定义table 动作
Horizon提供了三种类型的基本操作类,可以在表的数据上使用:- Action:表示可以对此表的数据执行的操作。
- LinkAction:表示操作只是一个链接而不是POST表单。
- FilterAction:表示表的过滤器操作的基类。
还有其他操作是基本Action类的扩展:
- BatchAction:对一个或多个对象执行批处理操作的表操作。此操作不应要求基于每个对象的用户输入。
- DeleteAction:用于对表数据执行删除操作的表操作。
- FixedFilterAction:带固定按钮的过滤器操作。
表中添加一个过滤器操作,显示包含在过滤器字段中输入的字符串的行。编辑tables.py文件:
|
|
上面指定的操作将默认filter_type为“query”。这意味着过滤器将使用客户端表排序。
将该操作添加到表的表操作中:
定义tabs
上面定义了一个表,可以显示数据。我们可以直接从这里看到一个view视图,但在里,我们还将使用horizon的TabGroup类,来定义一个tab,通过一个tab来显示视图。
在mypanel目录下创建一个tabs.py文件,创建一个包含一个tab的tab group:12345678910111213141516171819202122232425262728293031323334353637383940from django.utils.translation import ugettext_lazy as _from horizon import exceptionsfrom horizon import tabsfrom openstack_dashboard import apifrom openstack_dashboard.dashboards.mydashboard.mypanel import tablesclass InstanceTab(tabs.TableTab):name = _("Instances Tab")slug = "instances_tab"table_classes = (tables.InstancesTable,)template_name = ("horizon/common/_detail_table.html")preload = Falsedef has_more_data(self, table):return self._has_moredef get_instances_data(self):try:marker = self.request.GET.get(tables.InstancesTable._meta.pagination_param, None)instances, self._has_more = api.nova.server_list(self.request,search_opts={'marker': marker, 'paginate': True})return instancesexcept Exception:self._has_more = Falseerror_message = _('Unable to get instances')exceptions.handle(self.request, error_message)return []class MypanelTabs(tabs.TabGroup):slug = "mypanel_tabs"tabs = (InstanceTab,)sticky = True这个tab标签变得有点复杂。该tab用于处理上面定义的数据表(及其所有相关特性),并且还使用preload属性指定默认情况下不应加载此tab,相反,当有人单击它时,它将通过AJAX加载,在大多数情况下节省了API调用的时间。
此外,表的显示由一个可重用的模板horizon/common/_detail_table.html处理。添加了一些简单的分页代码来处理大量实例列表。
最后,介绍了horizon中错误处理的机制,即是异常捕捉。horizon.exceptions.handle()函数是一种集中式错误处理机制,它可以处理来自API的异常的所有错误以及不一致。在视图中将它们添加上去
打开views.py文件,自动生成的代码如下所示:12345678910from horizon import viewsclass IndexView(views.APIView):# A very simple class-based view...template_name = 'mydashboard/mypanel/index.html'def get_data(self, request, context, *args, **kwargs):# Add data to the context here...return context在本例中:导入正确的包后,已完成的views.py文件现在如下所示:
12345678910111213from horizon import tabsfrom openstack_dashboard.dashboards.mydashboard.mypanel \import tabs as mydashboard_tabsclass IndexView(tabs.TabbedTableView):tab_group_class = mydashboard_tabs.MypanelTabstemplate_name = 'mydashboard/mypanel/index.html'def get_data(self, request, context, *args, **kwargs):# Add data to the context here...return context
创建 URLs
自动生成的urls.py文件如下:
创建 template
在mydashboard/mypanel/templates/mypanel目录中创建/打开index.html文件:
这里提供了自定义页面标题,并呈现了视图提供的tab组。
接下去将它集成到我们的OpenStack Dashboard站点中。
启用并显示dashboard
为了使My Dashboard与Project或Admin等现有dashboard一起显示,需要在openstack_dashboard/enabled下创建一个名为_50_mydashboard.py的文件,并添加以下内容:
运行
|
|