tables.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. import django_tables2 as tables
  2. from django_tables2.utils import Accessor
  3. from dcim.models import Interface
  4. from tenancy.tables import COL_TENANT
  5. from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
  6. from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
  7. RIR_UTILIZATION = """
  8. <div class="progress">
  9. {% if record.stats.total %}
  10. <div class="progress-bar" role="progressbar" style="width: {{ record.stats.percentages.active }}%;">
  11. <span class="sr-only">{{ record.stats.percentages.active }}%</span>
  12. </div>
  13. <div class="progress-bar progress-bar-info" role="progressbar" style="width: {{ record.stats.percentages.reserved }}%;">
  14. <span class="sr-only">{{ record.stats.percentages.reserved }}%</span>
  15. </div>
  16. <div class="progress-bar progress-bar-danger" role="progressbar" style="width: {{ record.stats.percentages.deprecated }}%;">
  17. <span class="sr-only">{{ record.stats.percentages.deprecated }}%</span>
  18. </div>
  19. <div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ record.stats.percentages.available }}%;">
  20. <span class="sr-only">{{ record.stats.percentages.available }}%</span>
  21. </div>
  22. {% endif %}
  23. </div>
  24. """
  25. RIR_ACTIONS = """
  26. <a href="{% url 'ipam:rir_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Changelog">
  27. <i class="fa fa-history"></i>
  28. </a>
  29. {% if perms.ipam.change_rir %}
  30. <a href="{% url 'ipam:rir_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
  31. {% endif %}
  32. """
  33. UTILIZATION_GRAPH = """
  34. {% load helpers %}
  35. {% if record.pk %}{% utilization_graph record.get_utilization %}{% else %}&mdash;{% endif %}
  36. """
  37. ROLE_PREFIX_COUNT = """
  38. <a href="{% url 'ipam:prefix_list' %}?role={{ record.slug }}">{{ value }}</a>
  39. """
  40. ROLE_VLAN_COUNT = """
  41. <a href="{% url 'ipam:vlan_list' %}?role={{ record.slug }}">{{ value }}</a>
  42. """
  43. ROLE_ACTIONS = """
  44. <a href="{% url 'ipam:role_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Changelog">
  45. <i class="fa fa-history"></i>
  46. </a>
  47. {% if perms.ipam.change_role %}
  48. <a href="{% url 'ipam:role_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
  49. {% endif %}
  50. """
  51. PREFIX_LINK = """
  52. {% if record.has_children %}
  53. <span class="text-nowrap" style="padding-left: {{ record.depth }}0px "><i class="fa fa-caret-right"></i></a>
  54. {% else %}
  55. <span class="text-nowrap" style="padding-left: {{ record.depth }}9px">
  56. {% endif %}
  57. <a href="{% if record.pk %}{% url 'ipam:prefix' pk=record.pk %}{% else %}{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if parent.vrf %}&vrf={{ parent.vrf.pk }}{% endif %}{% if parent.site %}&site={{ parent.site.pk }}{% endif %}{% if parent.tenant %}&tenant_group={{ parent.tenant.group.pk }}&tenant={{ parent.tenant.pk }}{% endif %}{% endif %}">{{ record.prefix }}</a>
  58. </span>
  59. """
  60. PREFIX_ROLE_LINK = """
  61. {% if record.role %}
  62. <a href="{% url 'ipam:prefix_list' %}?role={{ record.role.slug }}">{{ record.role }}</a>
  63. {% else %}
  64. &mdash;
  65. {% endif %}
  66. """
  67. IPADDRESS_LINK = """
  68. {% if record.pk %}
  69. <a href="{{ record.get_absolute_url }}">{{ record.address }}</a>
  70. {% elif perms.ipam.add_ipaddress %}
  71. <a href="{% url 'ipam:ipaddress_add' %}?address={{ record.1 }}{% if prefix.vrf %}&vrf={{ prefix.vrf.pk }}{% endif %}{% if prefix.tenant %}&tenant={{ prefix.tenant.pk }}{% endif %}" class="btn btn-xs btn-success">{% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available</a>
  72. {% else %}
  73. {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available
  74. {% endif %}
  75. """
  76. IPADDRESS_ASSIGN_LINK = """
  77. <a href="{% url 'ipam:ipaddress_edit' pk=record.pk %}?interface={{ request.GET.interface }}&return_url={{ request.GET.return_url }}">{{ record }}</a>
  78. """
  79. IPADDRESS_PARENT = """
  80. {% if record.interface %}
  81. <a href="{{ record.interface.parent.get_absolute_url }}">{{ record.interface.parent }}</a>
  82. {% else %}
  83. &mdash;
  84. {% endif %}
  85. """
  86. VRF_LINK = """
  87. {% if record.vrf %}
  88. <a href="{{ record.vrf.get_absolute_url }}">{{ record.vrf }}</a>
  89. {% elif prefix.vrf %}
  90. {{ prefix.vrf }}
  91. {% else %}
  92. Global
  93. {% endif %}
  94. """
  95. STATUS_LABEL = """
  96. {% if record.pk %}
  97. <span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span>
  98. {% else %}
  99. <span class="label label-success">Available</span>
  100. {% endif %}
  101. """
  102. VLAN_LINK = """
  103. {% if record.pk %}
  104. <a href="{{ record.get_absolute_url }}">{{ record.vid }}</a>
  105. {% elif perms.ipam.add_vlan %}
  106. <a href="{% url 'ipam:vlan_add' %}?vid={{ record.vid }}&group={{ vlan_group.pk }}{% if vlan_group.site %}&site={{ vlan_group.site.pk }}{% endif %}" class="btn btn-xs btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>
  107. {% else %}
  108. {{ record.available }} VLAN{{ record.available|pluralize }} available
  109. {% endif %}
  110. """
  111. VLAN_PREFIXES = """
  112. {% for prefix in record.prefixes.all %}
  113. <a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a>{% if not forloop.last %}<br />{% endif %}
  114. {% empty %}
  115. &mdash;
  116. {% endfor %}
  117. """
  118. VLAN_ROLE_LINK = """
  119. {% if record.role %}
  120. <a href="{% url 'ipam:vlan_list' %}?role={{ record.role.slug }}">{{ record.role }}</a>
  121. {% else %}
  122. &mdash;
  123. {% endif %}
  124. """
  125. VLANGROUP_ACTIONS = """
  126. <a href="{% url 'ipam:vlangroup_changelog' pk=record.pk %}" class="btn btn-default btn-xs" title="Changelog">
  127. <i class="fa fa-history"></i>
  128. </a>
  129. {% with next_vid=record.get_next_available_vid %}
  130. {% if next_vid and perms.ipam.add_vlan %}
  131. <a href="{% url 'ipam:vlan_add' %}?site={{ record.site_id }}&group={{ record.pk }}&vid={{ next_vid }}" title="Add VLAN" class="btn btn-xs btn-success">
  132. <i class="glyphicon glyphicon-plus" aria-hidden="true"></i>
  133. </a>
  134. {% endif %}
  135. {% endwith %}
  136. {% if perms.ipam.change_vlangroup %}
  137. <a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
  138. {% endif %}
  139. """
  140. VLAN_MEMBER_UNTAGGED = """
  141. {% if record.untagged_vlan_id == vlan.pk %}
  142. <i class="glyphicon glyphicon-ok">
  143. {% endif %}
  144. """
  145. VLAN_MEMBER_ACTIONS = """
  146. {% if perms.dcim.change_interface %}
  147. <a href="{% if record.device %}{% url 'dcim:interface_edit' pk=record.pk %}{% else %}{% url 'virtualization:interface_edit' pk=record.pk %}{% endif %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil"></i></a>
  148. {% endif %}
  149. """
  150. TENANT_LINK = """
  151. {% if record.tenant %}
  152. <a href="{% url 'tenancy:tenant' slug=record.tenant.slug %}" title="{{ record.tenant.description }}">{{ record.tenant }}</a>
  153. {% elif record.vrf.tenant %}
  154. <a href="{% url 'tenancy:tenant' slug=record.vrf.tenant.slug %}" title="{{ record.vrf.tenant.description }}">{{ record.vrf.tenant }}</a>*
  155. {% else %}
  156. &mdash;
  157. {% endif %}
  158. """
  159. #
  160. # VRFs
  161. #
  162. class VRFTable(BaseTable):
  163. pk = ToggleColumn()
  164. name = tables.LinkColumn()
  165. rd = tables.Column(verbose_name='RD')
  166. tenant = tables.TemplateColumn(template_code=COL_TENANT)
  167. class Meta(BaseTable.Meta):
  168. model = VRF
  169. fields = ('pk', 'name', 'rd', 'tenant', 'description')
  170. #
  171. # RIRs
  172. #
  173. class RIRTable(BaseTable):
  174. pk = ToggleColumn()
  175. name = tables.LinkColumn(verbose_name='Name')
  176. is_private = BooleanColumn(verbose_name='Private')
  177. aggregate_count = tables.Column(verbose_name='Aggregates')
  178. actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name='')
  179. class Meta(BaseTable.Meta):
  180. model = RIR
  181. fields = ('pk', 'name', 'is_private', 'aggregate_count', 'actions')
  182. class RIRDetailTable(RIRTable):
  183. stats_total = tables.Column(
  184. accessor='stats.total',
  185. verbose_name='Total',
  186. footer=lambda table: sum(r.stats['total'] for r in table.data)
  187. )
  188. stats_active = tables.Column(
  189. accessor='stats.active',
  190. verbose_name='Active',
  191. footer=lambda table: sum(r.stats['active'] for r in table.data)
  192. )
  193. stats_reserved = tables.Column(
  194. accessor='stats.reserved',
  195. verbose_name='Reserved',
  196. footer=lambda table: sum(r.stats['reserved'] for r in table.data)
  197. )
  198. stats_deprecated = tables.Column(
  199. accessor='stats.deprecated',
  200. verbose_name='Deprecated',
  201. footer=lambda table: sum(r.stats['deprecated'] for r in table.data)
  202. )
  203. stats_available = tables.Column(
  204. accessor='stats.available',
  205. verbose_name='Available',
  206. footer=lambda table: sum(r.stats['available'] for r in table.data)
  207. )
  208. utilization = tables.TemplateColumn(
  209. template_code=RIR_UTILIZATION,
  210. verbose_name='Utilization'
  211. )
  212. class Meta(RIRTable.Meta):
  213. fields = (
  214. 'pk', 'name', 'is_private', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved',
  215. 'stats_deprecated', 'stats_available', 'utilization', 'actions',
  216. )
  217. #
  218. # Aggregates
  219. #
  220. class AggregateTable(BaseTable):
  221. pk = ToggleColumn()
  222. prefix = tables.LinkColumn(verbose_name='Aggregate')
  223. date_added = tables.DateColumn(format="Y-m-d", verbose_name='Added')
  224. class Meta(BaseTable.Meta):
  225. model = Aggregate
  226. fields = ('pk', 'prefix', 'rir', 'date_added', 'description')
  227. class AggregateDetailTable(AggregateTable):
  228. child_count = tables.Column(verbose_name='Prefixes')
  229. utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
  230. class Meta(AggregateTable.Meta):
  231. fields = ('pk', 'prefix', 'rir', 'child_count', 'utilization', 'date_added', 'description')
  232. #
  233. # Roles
  234. #
  235. class RoleTable(BaseTable):
  236. pk = ToggleColumn()
  237. prefix_count = tables.TemplateColumn(
  238. accessor=Accessor('prefixes.count'),
  239. template_code=ROLE_PREFIX_COUNT,
  240. orderable=False,
  241. verbose_name='Prefixes'
  242. )
  243. vlan_count = tables.TemplateColumn(
  244. accessor=Accessor('vlans.count'),
  245. template_code=ROLE_VLAN_COUNT,
  246. orderable=False,
  247. verbose_name='VLANs'
  248. )
  249. actions = tables.TemplateColumn(template_code=ROLE_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name='')
  250. class Meta(BaseTable.Meta):
  251. model = Role
  252. fields = ('pk', 'name', 'prefix_count', 'vlan_count', 'slug', 'actions')
  253. #
  254. # Prefixes
  255. #
  256. class PrefixTable(BaseTable):
  257. pk = ToggleColumn()
  258. prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
  259. status = tables.TemplateColumn(STATUS_LABEL)
  260. vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
  261. tenant = tables.TemplateColumn(template_code=TENANT_LINK)
  262. site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
  263. vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
  264. role = tables.TemplateColumn(PREFIX_ROLE_LINK)
  265. class Meta(BaseTable.Meta):
  266. model = Prefix
  267. fields = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
  268. row_attrs = {
  269. 'class': lambda record: 'success' if not record.pk else '',
  270. }
  271. class PrefixDetailTable(PrefixTable):
  272. utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False)
  273. class Meta(PrefixTable.Meta):
  274. fields = ('pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description')
  275. #
  276. # IPAddresses
  277. #
  278. class IPAddressTable(BaseTable):
  279. pk = ToggleColumn()
  280. address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address')
  281. vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
  282. status = tables.TemplateColumn(STATUS_LABEL)
  283. tenant = tables.TemplateColumn(template_code=TENANT_LINK)
  284. parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
  285. interface = tables.Column(orderable=False)
  286. class Meta(BaseTable.Meta):
  287. model = IPAddress
  288. fields = (
  289. 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'dns_name', 'description',
  290. )
  291. row_attrs = {
  292. 'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
  293. }
  294. class IPAddressDetailTable(IPAddressTable):
  295. nat_inside = tables.LinkColumn(
  296. 'ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False, verbose_name='NAT (Inside)'
  297. )
  298. class Meta(IPAddressTable.Meta):
  299. fields = (
  300. 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'parent', 'interface', 'dns_name',
  301. 'description',
  302. )
  303. class IPAddressAssignTable(BaseTable):
  304. address = tables.TemplateColumn(IPADDRESS_ASSIGN_LINK, verbose_name='IP Address')
  305. status = tables.TemplateColumn(STATUS_LABEL)
  306. parent = tables.TemplateColumn(IPADDRESS_PARENT, orderable=False)
  307. interface = tables.Column(orderable=False)
  308. class Meta(BaseTable.Meta):
  309. model = IPAddress
  310. fields = ('address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'description')
  311. orderable = False
  312. class InterfaceIPAddressTable(BaseTable):
  313. """
  314. List IP addresses assigned to a specific Interface.
  315. """
  316. address = tables.TemplateColumn(IPADDRESS_ASSIGN_LINK, verbose_name='IP Address')
  317. vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
  318. status = tables.TemplateColumn(STATUS_LABEL)
  319. tenant = tables.TemplateColumn(template_code=TENANT_LINK)
  320. class Meta(BaseTable.Meta):
  321. model = IPAddress
  322. fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
  323. #
  324. # VLAN groups
  325. #
  326. class VLANGroupTable(BaseTable):
  327. pk = ToggleColumn()
  328. name = tables.LinkColumn(verbose_name='Name')
  329. site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
  330. vlan_count = tables.Column(verbose_name='VLANs')
  331. slug = tables.Column(verbose_name='Slug')
  332. actions = tables.TemplateColumn(template_code=VLANGROUP_ACTIONS, attrs={'td': {'class': 'text-right noprint'}},
  333. verbose_name='')
  334. class Meta(BaseTable.Meta):
  335. model = VLANGroup
  336. fields = ('pk', 'name', 'site', 'vlan_count', 'slug', 'actions')
  337. #
  338. # VLANs
  339. #
  340. class VLANTable(BaseTable):
  341. pk = ToggleColumn()
  342. vid = tables.TemplateColumn(VLAN_LINK, verbose_name='ID')
  343. site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
  344. group = tables.LinkColumn('ipam:vlangroup_vlans', args=[Accessor('group.pk')], verbose_name='Group')
  345. tenant = tables.TemplateColumn(template_code=COL_TENANT)
  346. status = tables.TemplateColumn(STATUS_LABEL)
  347. role = tables.TemplateColumn(VLAN_ROLE_LINK)
  348. class Meta(BaseTable.Meta):
  349. model = VLAN
  350. fields = ('pk', 'vid', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
  351. row_attrs = {
  352. 'class': lambda record: 'success' if not isinstance(record, VLAN) else '',
  353. }
  354. class VLANDetailTable(VLANTable):
  355. prefixes = tables.TemplateColumn(VLAN_PREFIXES, orderable=False, verbose_name='Prefixes')
  356. class Meta(VLANTable.Meta):
  357. fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
  358. class VLANMemberTable(BaseTable):
  359. parent = tables.LinkColumn(order_by=['device', 'virtual_machine'])
  360. name = tables.LinkColumn(verbose_name='Interface')
  361. untagged = tables.TemplateColumn(
  362. template_code=VLAN_MEMBER_UNTAGGED,
  363. orderable=False
  364. )
  365. actions = tables.TemplateColumn(
  366. template_code=VLAN_MEMBER_ACTIONS,
  367. attrs={'td': {'class': 'text-right noprint'}},
  368. verbose_name=''
  369. )
  370. class Meta(BaseTable.Meta):
  371. model = Interface
  372. fields = ('parent', 'name', 'untagged', 'actions')
  373. class InterfaceVLANTable(BaseTable):
  374. """
  375. List VLANs assigned to a specific Interface.
  376. """
  377. vid = tables.LinkColumn('ipam:vlan', args=[Accessor('pk')], verbose_name='ID')
  378. tagged = BooleanColumn()
  379. site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
  380. group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
  381. tenant = tables.TemplateColumn(template_code=COL_TENANT)
  382. status = tables.TemplateColumn(STATUS_LABEL)
  383. role = tables.TemplateColumn(VLAN_ROLE_LINK)
  384. class Meta(BaseTable.Meta):
  385. model = VLAN
  386. fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
  387. def __init__(self, interface, *args, **kwargs):
  388. self.interface = interface
  389. super().__init__(*args, **kwargs)
  390. #
  391. # Services
  392. #
  393. class ServiceTable(BaseTable):
  394. pk = ToggleColumn()
  395. name = tables.LinkColumn(
  396. viewname='ipam:service',
  397. args=[Accessor('pk')]
  398. )
  399. class Meta(BaseTable.Meta):
  400. model = Service
  401. fields = ('pk', 'name', 'parent', 'protocol', 'port', 'description')