一、环境
[root@localhost horizon]# cat /etc/redhat-release
CentOS Linux release 7.8.2003 (Core)
[root@localhost horizon]# python -V
Python 2.7.5
[root@localhost horizon]# pip -V
pip 20.2 from /usr/lib/python2.7/site-packages/pip (python 2.7)
[root@localhost horizon]# python3 -V
Python 3.7.7
[root@localhost horizon]# pip3 -V
pip 19.2.3 from /usr/local/python3/lib/python3.7/site-packages/pip (python 3.7)
[root@localhost horizon]# tox --version
3.18.0 imported from /usr/lib/python2.7/site-packages/tox/__init__.pyc
[root@localhost horizon]# pip list | grep horizon
horizon 16.2.0
二、步骤
本文假设已用源码方式部署成功本地运行的horizon环境,部署过程参考:
https://blog.csdn.net/zhujisoft/article/details/107726414
1、创建面板的典型结构
在horizon目录下
$ 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
输出
[root@localhost horizon]# tox -e manage -- startpanel mypanel \
> --dashboard=openstack_dashboard.dashboards.mydashboard \
> --target=openstack_dashboard/dashboards/mydashboard/mypanel
/root/horizon/.tox/venv/lib/python3.7/site-packages/setuptools/depends.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
manage develop-inst-noop: /root/horizon
manage installed: appdirs==1.4.3,asn1crypto==0.24.0,astroid==2.1.0,attrs==19.1.0,Babel==2.7.0,bandit==1.6.2,certifi==2019.6.16,cffi==1.12.3,chardet==3.0.4,cliff==2.16.0,cmd2==0.8.9,coverage==4.5.4,cryptography==2.7,debtcollector==1.22.0,decorator==4.4.0,Django==2.0.13,django-appconf==1.0.3,django-babel==0.6.2,django-compressor==2.3,django-debreach==1.5.2,django-pyscss==2.0.2,doc8==0.8.0,docutils==0.15.2,dogpile.cache==0.7.1,extras==1.0.0,fasteners==0.14.1,fixtures==3.0.0,flake8==2.6.2,flake8-import-order==0.12,futurist==1.9.0,gitdb2==2.0.5,GitPython==3.0.2,hacking==1.1.0,-e git+https://git.openstack.org/openstack/horizon@d83d58af1e247ca39f90fdb22d0005698540f520#egg=horizon,idna==2.8,iso8601==0.1.12,isort==5.2.0,jmespath==0.9.4,jsonpatch==1.24,jsonpointer==2.0,jsonschema==3.0.2,keystoneauth1==3.17.3,lazy-object-proxy==1.5.1,linecache2==1.0.0,mccabe==0.6.1,mock==3.0.5,monotonic==1.5,mox3==0.28.0,msgpack==0.6.1,munch==2.3.2,netaddr==0.7.19,netifaces==0.10.9,nodeenv==1.3.3,openstacksdk==0.36.4,os-client-config==1.33.0,os-service-types==1.7.0,osc-lib==1.14.1,oslo.concurrency==3.30.0,oslo.config==6.11.2,oslo.context==2.23.1,oslo.i18n==3.24.0,oslo.log==3.44.3,oslo.policy==2.3.4,oslo.serialization==2.29.2,oslo.upgradecheck==0.3.2,oslo.utils==3.41.6,osprofiler==2.8.2,pbr==5.4.3,Pint==0.9,prettytable==0.7.2,pycodestyle==2.6.0,pycparser==2.19,pyflakes==1.2.3,pyinotify==0.9.6,pylint==2.2.2,pymongo==3.9.0,pyOpenSSL==19.0.0,pyparsing==2.4.2,pyperclip==1.7.0,pyrsistent==0.15.4,pyScss==1.3.7,python-cinderclient==5.0.1,python-dateutil==2.8.0,python-glanceclient==2.17.1,python-keystoneclient==3.21.0,python-memcached==1.59,python-mimeparse==1.6.0,python-neutronclient==6.14.1,python-novaclient==15.1.1,python-swiftclient==3.8.1,pytz==2019.2,PyYAML==5.1.2,rcssmin==1.0.6,requests==2.22.0,requestsexceptions==1.4.0,restructuredtext-lint==1.3.0,rfc3986==1.3.2,rjsmin==1.1.0,selenium==3.141.0,semantic-version==2.8.2,simplejson==3.16.0,six==1.12.0,smmap2==2.0.5,stevedore==1.31.0,testscenarios==0.5.0,testtools==2.3.0,traceback2==1.4.0,unittest2==1.1.0,urllib3==1.25.3,warlock==1.3.3,wcwidth==0.1.7,WebOb==1.8.5,wrapt==1.11.2,XStatic==1.0.2,XStatic-Angular==1.5.8.0,XStatic-Angular-Bootstrap==2.2.0.0,XStatic-Angular-FileUpload==12.0.4.0,XStatic-Angular-Gettext==2.3.8.0,XStatic-Angular-lrdragndrop==1.0.2.4,XStatic-Angular-Schema-Form==0.8.13.0,XStatic-Bootstrap-Datepicker==1.3.1.0,XStatic-Bootstrap-SCSS==3.3.7.1,XStatic-bootswatch==3.3.7.0,XStatic-D3==3.5.17.0,XStatic-Font-Awesome==4.7.0.0,XStatic-Hogan==2.0.0.2,XStatic-Jasmine==2.4.1.2,XStatic-jQuery==1.12.4.1,XStatic-JQuery-Migrate==1.2.1.1,XStatic-jquery-ui==1.12.1.1,XStatic-JQuery.quicksearch==2.0.3.1,XStatic-JQuery.TableSorter==2.14.5.1,XStatic-JSEncrypt==2.3.1.1,XStatic-mdi==1.6.50.2,XStatic-objectpath==1.2.1.0,XStatic-Rickshaw==1.5.0.0,XStatic-roboto-fontface==0.5.0.0,XStatic-smart-table==1.4.13.2,XStatic-Spin==1.2.5.2,XStatic-term.js==0.0.7.0,XStatic-tv4==1.2.7.0,xvfbwrapper==0.2.9
manage run-test-pre: PYTHONHASHSEED='2063410502'
manage run-test: commands[0] | /root/horizon/.tox/venv/bin/python /root/horizon/manage.py startpanel mypanel --dashboard=openstack_dashboard.dashboards.mydashboard --target=openstack_dashboard/dashboards/mydashboard/mypanel
/root/horizon/.tox/venv/lib/python3.7/site-packages/django/db/models/fields/__init__.py:155: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working
if isinstance(choices, collections.Iterator):
/root/horizon/.tox/venv/lib/python3.7/site-packages/django/db/models/sql/query.py:9: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working
from collections import Counter, Iterator, Mapping, OrderedDict, namedtuple
/root/horizon/.tox/venv/lib/python3.7/site-packages/django/core/paginator.py:130: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working
class Page(collections.Sequence):
DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop workingDeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()FutureWarning: Possible nested set at position 329_____________________________________________________________________________________________________ summary _____________________________________________________________________________________________________
manage: commands succeeded
congratulations :)
查看目录结构
[root@localhost horizon]# tree openstack_dashboard/dashboards/mydashboard
openstack_dashboard/dashboards/mydashboard
├── dashboard.py
├── __init__.py
├── mypanel
│ ├── __init__.py
│ ├── panel.py
│ ├── templates
│ │ └── mypanel
│ │ └── index.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── static
│ └── mydashboard
│ ├── js
│ │ └── mydashboard.js
│ └── scss
│ └── mydashboard.scss
└── templates
└── mydashboard
└── base.html
结构中定义了mydashboard和mypanel,代码的层级结构和图形界面的对应关系如下:
2、增加代码
mydashboard/dashboard.py
from django.utils.translation import ugettext_lazy as _
import horizon
class Mygroup(horizon.PanelGroup):
slug = "mygroup"
name = _("My Group")
panels = ('mypanel',)
class Mydashboard(horizon.Dashboard):
name = _("My Dashboard")
slug = "mydashboard"
panels = (Mygroup,) # Add your panels here.
default_panel = 'mypanel' # Specify the slug of the default panel.
horizon.register(Mydashboard)
mydashboard/mypanel/tables.py
from django.utils.translation import ugettext_lazy as _
from horizon import tables
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class 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")
table_actions = (MyFilterAction,)
mydashboard/mypanel/tabs.py
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.mydashboard.mypanel import tables
class InstanceTab(tabs.TableTab):
name = _("Instances Tab")
slug = "instances_tab"
table_classes = (tables.InstancesTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def 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 instances
except Exception:
self._has_more = False
error_message = _('Unable to get instances')
exceptions.handle(self.request, error_message)
return []
class MypanelTabs(tabs.TabGroup):
slug = "mypanel_tabs"
tabs = (InstanceTab,)
sticky = True
mydashboard/mypanel/views.py
from horizon import tabs
from openstack_dashboard.dashboards.mydashboard.mypanel \
import tabs as mydashboard_tabs
class IndexView(tabs.TabbedTableView):
tab_group_class = mydashboard_tabs.MypanelTabs
template_name = 'mydashboard/mypanel/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
mydashboard/mypanel/urls.py
from django.conf.urls import url
from openstack_dashboard.dashboards.mydashboard.mypanel import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
]
mydashboard/mypanel/templates/mypanel/index.html
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "My Panel" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("My Panel") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}
#启用mydashboard
horizon/openstack_dashboard/enabled/_50_mydashboard.py
# The name of the dashboard to be added to HORIZON['dashboards']. Required.
DASHBOARD = 'mydashboard'
# If set to True, this dashboard will not be added to the settings.
DISABLED = False
# A list of applications to be added to INSTALLED_APPS.
ADD_INSTALLED_APPS = [
'openstack_dashboard.dashboards.mydashboard',
]
#本地设置
horizon/openstack_dashboard/local/local_settings.py
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = ['*', ]
# https://docs.djangoproject.com/en/1.11/topics/http/sessions/.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '192.168.44.91:11211',
},
}
OPENSTACK_HOST = "192.168.44.91"
OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST
3、运行
tox -e runserver 0:9000
访问localhost:9000
三、参考文档
https://docs.openstack.org/horizon/rocky/contributor/tutorials/dashboard.html