mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-02 23:19:34 +01:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be903a64a2 | ||
|
|
0d7bac433e | ||
|
|
b1cfbbc472 | ||
|
|
6dd311f600 | ||
|
|
85d250014f | ||
|
|
552c81509a | ||
|
|
ed7a0a32cc | ||
|
|
a544b55e9e | ||
|
|
53e1ab5fc5 | ||
|
|
2c1a9ae455 | ||
|
|
1afa476a19 |
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -26,7 +26,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox Version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.7.7
|
||||
placeholder: v3.7.8
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@@ -14,7 +14,7 @@ body:
|
||||
attributes:
|
||||
label: NetBox version
|
||||
description: What version of NetBox are you currently running?
|
||||
placeholder: v3.7.7
|
||||
placeholder: v3.7.8
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# NetBox v3.7
|
||||
|
||||
## v3.7.8 (2024-05-06)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#12127](https://github.com/netbox-community/netbox/issues/12127) - Enable adding new cables directly from navigation menu
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#15877](https://github.com/netbox-community/netbox/issues/15877) - Account for virtual chassis membership when assigning related interfaces via bulk edit
|
||||
* [#15917](https://github.com/netbox-community/netbox/issues/15917) - Fix pagination through search results within dropdown fields
|
||||
* [#15925](https://github.com/netbox-community/netbox/issues/15925) - Fix SVG rendering of cable traces to circuit terminations
|
||||
* [#15948](https://github.com/netbox-community/netbox/issues/15948) - Fix cable trace SVG generation for cables with multiple terminations at both ends
|
||||
* [#15960](https://github.com/netbox-community/netbox/issues/15960) - Replace CSV export formatting for several many-to-many fields
|
||||
* [#15961](https://github.com/netbox-community/netbox/issues/15961) - Fix secret toggle button for IKE policies
|
||||
|
||||
---
|
||||
|
||||
## v3.7.7 (2024-05-01)
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -1411,9 +1411,9 @@ class InterfaceBulkEditForm(
|
||||
device = Device.objects.filter(pk=self.initial['device']).first()
|
||||
|
||||
# Restrict parent/bridge/LAG interface assignment by device
|
||||
self.fields['parent'].widget.add_query_param('device_id', device.pk)
|
||||
self.fields['bridge'].widget.add_query_param('device_id', device.pk)
|
||||
self.fields['lag'].widget.add_query_param('device_id', device.pk)
|
||||
self.fields['parent'].widget.add_query_param('virtual_chassis_member_id', device.pk)
|
||||
self.fields['bridge'].widget.add_query_param('virtual_chassis_member_id', device.pk)
|
||||
self.fields['lag'].widget.add_query_param('virtual_chassis_member_id', device.pk)
|
||||
|
||||
# Limit VLAN choices by device
|
||||
self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk)
|
||||
|
||||
@@ -17,7 +17,7 @@ PADDING = 10
|
||||
LINE_HEIGHT = 20
|
||||
FANOUT_HEIGHT = 35
|
||||
FANOUT_LEG_HEIGHT = 15
|
||||
CABLE_HEIGHT = 4 * LINE_HEIGHT + FANOUT_HEIGHT + FANOUT_LEG_HEIGHT
|
||||
CABLE_HEIGHT = 5 * LINE_HEIGHT + FANOUT_HEIGHT + FANOUT_LEG_HEIGHT
|
||||
|
||||
|
||||
class Node(Hyperlink):
|
||||
@@ -223,7 +223,7 @@ class CableTraceSVG:
|
||||
nodes_height = 0
|
||||
nodes = []
|
||||
# Sort them by name to make renders more readable
|
||||
for i, term in enumerate(sorted(terminations, key=lambda x: x.name)):
|
||||
for i, term in enumerate(sorted(terminations, key=lambda x: str(x))):
|
||||
node = Node(
|
||||
position=(offset_x + i * width, self.cursor),
|
||||
width=width,
|
||||
@@ -266,7 +266,7 @@ class CableTraceSVG:
|
||||
Draw the far-end objects and its terminations and return all created nodes
|
||||
"""
|
||||
# Make sure elements are sorted by name for readability
|
||||
objects = sorted(obj_list, key=lambda x: x.name)
|
||||
objects = sorted(obj_list, key=lambda x: str(x))
|
||||
width = self.width / len(objects)
|
||||
|
||||
# Max-height of created terminations
|
||||
@@ -361,7 +361,8 @@ class CableTraceSVG:
|
||||
# Connector (a Cable or WirelessLink)
|
||||
if links:
|
||||
|
||||
parent_object_nodes, far_terminations = self.draw_far_objects(set(end.parent_object for end in far_ends), far_ends)
|
||||
obj_list = {end.parent_object for end in far_ends}
|
||||
parent_object_nodes, far_terminations = self.draw_far_objects(obj_list, far_ends)
|
||||
for cable in links:
|
||||
# Fill in labels and description with all available data
|
||||
description = [
|
||||
@@ -404,7 +405,17 @@ class CableTraceSVG:
|
||||
end = far[0].top_center
|
||||
text_offset = 0
|
||||
|
||||
if len(near) > 1:
|
||||
if len(near) > 1 and len(far) > 1:
|
||||
start_center = sum([pos.bottom_center[0] for pos in near]) / len(near)
|
||||
end_center = sum([pos.bottom_center[0] for pos in far]) / len(far)
|
||||
center_x = (start_center + end_center) / 2
|
||||
|
||||
start = (center_x, start[1] + FANOUT_HEIGHT + FANOUT_LEG_HEIGHT)
|
||||
end = (center_x, end[1] - FANOUT_HEIGHT - FANOUT_LEG_HEIGHT)
|
||||
text_offset -= (FANOUT_HEIGHT + FANOUT_LEG_HEIGHT)
|
||||
self.draw_fanin(start, near, color)
|
||||
self.draw_fanout(end, far, color)
|
||||
elif len(near) > 1:
|
||||
# Handle Fan-In - change start position to be directly below start
|
||||
start = (end[0], start[1] + FANOUT_HEIGHT + FANOUT_LEG_HEIGHT)
|
||||
self.draw_fanin(start, near, color)
|
||||
|
||||
@@ -618,7 +618,7 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
|
||||
verbose_name=_('VRF'),
|
||||
linkify=True
|
||||
)
|
||||
inventory_items = tables.ManyToManyColumn(
|
||||
inventory_items = columns.ManyToManyColumn(
|
||||
linkify_item=True,
|
||||
verbose_name=_('Inventory Items'),
|
||||
)
|
||||
|
||||
@@ -394,6 +394,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 2)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 2
|
||||
cable2.delete()
|
||||
path1 = self.assertPathExists(
|
||||
@@ -450,6 +453,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 2)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 2
|
||||
cable2.delete()
|
||||
path1 = self.assertPathExists(
|
||||
@@ -558,6 +564,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 4)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 3
|
||||
cable3.delete()
|
||||
|
||||
@@ -673,6 +682,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 4)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 3
|
||||
cable3.delete()
|
||||
|
||||
@@ -804,6 +816,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 4)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 3
|
||||
cable3.delete()
|
||||
|
||||
@@ -931,6 +946,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 4)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 5
|
||||
cable5.delete()
|
||||
|
||||
@@ -1034,6 +1052,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 4)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 3
|
||||
cable3.delete()
|
||||
|
||||
@@ -1093,6 +1114,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 3)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 1
|
||||
cable1.delete()
|
||||
|
||||
@@ -1135,6 +1159,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 1)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
def test_210_interface_to_circuittermination(self):
|
||||
"""
|
||||
[IF1] --C1-- [CT1]
|
||||
@@ -1156,6 +1183,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 1)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 1
|
||||
cable1.delete()
|
||||
self.assertEqual(CablePath.objects.count(), 0)
|
||||
@@ -1212,6 +1242,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 2)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 2
|
||||
cable2.delete()
|
||||
path1 = self.assertPathExists(
|
||||
@@ -1277,6 +1310,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 2)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 2
|
||||
cable2.delete()
|
||||
path1 = self.assertPathExists(
|
||||
@@ -1314,6 +1350,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 1)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 1
|
||||
cable1.delete()
|
||||
self.assertEqual(CablePath.objects.count(), 0)
|
||||
@@ -1342,6 +1381,9 @@ class CablePathTestCase(TestCase):
|
||||
self.assertEqual(CablePath.objects.count(), 1)
|
||||
self.assertTrue(CablePath.objects.first().is_complete)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 1
|
||||
cable1.delete()
|
||||
self.assertEqual(CablePath.objects.count(), 0)
|
||||
@@ -1439,6 +1481,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 4)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cables 3-4
|
||||
cable3.delete()
|
||||
cable4.delete()
|
||||
@@ -1495,6 +1540,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 2)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 2
|
||||
cable2.delete()
|
||||
path1 = self.assertPathExists(
|
||||
@@ -1578,6 +1626,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 2)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 2
|
||||
cable2.delete()
|
||||
|
||||
@@ -1697,6 +1748,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 4)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
# Delete cable 3
|
||||
cable3.delete()
|
||||
|
||||
@@ -1784,6 +1838,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 2)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
def test_220_interface_to_interface_duplex_via_multiple_front_and_rear_ports(self):
|
||||
"""
|
||||
[IF1] --C1-- [FP1] [RP1] --C2-- [RP2] [FP2] --C3-- [IF2]
|
||||
@@ -1877,6 +1934,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 3)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
def test_221_non_symmetric_paths(self):
|
||||
"""
|
||||
[IF1] --C1-- [FP1] [RP1] --C2-- [RP2] [FP2] --C3-- -------------------------------------- [IF2]
|
||||
@@ -1997,6 +2057,9 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
self.assertEqual(CablePath.objects.count(), 3)
|
||||
|
||||
# Test SVG generation
|
||||
CableTraceSVG(interface1).render()
|
||||
|
||||
def test_301_create_path_via_existing_cable(self):
|
||||
"""
|
||||
[IF1] --C1-- [FP1] [RP1] --C2-- [RP2] [FP2] --C3-- [IF2]
|
||||
|
||||
@@ -3166,12 +3166,6 @@ class CableListView(generic.ObjectListView):
|
||||
filterset = filtersets.CableFilterSet
|
||||
filterset_form = forms.CableFilterForm
|
||||
table = tables.CableTable
|
||||
actions = {
|
||||
'import': {'add'},
|
||||
'export': {'view'},
|
||||
'bulk_edit': {'change'},
|
||||
'bulk_delete': {'delete'},
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Cable)
|
||||
|
||||
@@ -378,7 +378,7 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
|
||||
orderable=False,
|
||||
verbose_name=_('NAT (Inside)')
|
||||
)
|
||||
nat_outside = tables.ManyToManyColumn(
|
||||
nat_outside = columns.ManyToManyColumn(
|
||||
linkify_item=True,
|
||||
orderable=False,
|
||||
verbose_name=_('NAT (Outside)')
|
||||
|
||||
@@ -102,7 +102,7 @@ CONNECTIONS_MENU = Menu(
|
||||
MenuGroup(
|
||||
label=_('Connections'),
|
||||
items=(
|
||||
get_model_item('dcim', 'cable', _('Cables'), actions=['import']),
|
||||
get_model_item('dcim', 'cable', _('Cables')),
|
||||
get_model_item('wireless', 'wirelesslink', _('Wireless Links')),
|
||||
MenuItem(
|
||||
link='dcim:interface_connections_list',
|
||||
|
||||
@@ -28,7 +28,7 @@ from netbox.plugins import PluginConfig
|
||||
# Environment setup
|
||||
#
|
||||
|
||||
VERSION = '3.7.7'
|
||||
VERSION = '3.7.8'
|
||||
|
||||
# Hostname
|
||||
HOSTNAME = platform.node()
|
||||
|
||||
18
netbox/project-static/dist/netbox.js
vendored
18
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
2
netbox/project-static/dist/netbox.js.map
vendored
2
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -60,18 +60,17 @@ function handleSecretToggle(state: StateManager<SecretState>, button: HTMLButton
|
||||
toggleSecretButton(hidden, button);
|
||||
}
|
||||
|
||||
function toggleCallback(event: MouseEvent) {
|
||||
handleSecretToggle(secretState, event.currentTarget as HTMLButtonElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize secret toggle button.
|
||||
*/
|
||||
export function initSecretToggle(): void {
|
||||
hideSecret();
|
||||
for (const button of getElements<HTMLButtonElement>('button.toggle-secret')) {
|
||||
button.addEventListener(
|
||||
'click',
|
||||
event => {
|
||||
handleSecretToggle(secretState, event.currentTarget as HTMLButtonElement);
|
||||
},
|
||||
false,
|
||||
);
|
||||
button.removeEventListener('click', toggleCallback);
|
||||
button.addEventListener('click', toggleCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,10 @@ export class APISelect {
|
||||
*/
|
||||
private queryUrl: string = '';
|
||||
|
||||
/**
|
||||
* Interal state variable used to remember search key entered by user for "Filter" search box
|
||||
*/
|
||||
private searchKey: Nullable<string> = null;
|
||||
/**
|
||||
* Scroll position of options is at the bottom of the list, or not. Used to determine if
|
||||
* additional options should be fetched from the API.
|
||||
@@ -359,30 +363,41 @@ export class APISelect {
|
||||
this.slim.enable();
|
||||
}
|
||||
|
||||
private setSearchKey(event: Event) {
|
||||
const { value: q } = event.target as HTMLInputElement;
|
||||
this.searchKey = q
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listeners to this element and its dependencies so that when dependencies change
|
||||
* this element's options are updated.
|
||||
*/
|
||||
private addEventListeners(): void {
|
||||
// Create a debounced function to fetch options based on the search input value.
|
||||
const fetcher = debounce((event: Event) => this.handleSearch(event), 300, false);
|
||||
const fetcher = debounce((action:ApplyMethod, url: Nullable<string>) => this.handleSearch(action, url), 300, false);
|
||||
|
||||
// Query the API when the input value changes or a value is pasted.
|
||||
this.slim.slim.search.input.addEventListener('keyup', event => {
|
||||
// Only search when necessary keys are pressed.
|
||||
if (!event.key.match(/^(Arrow|Enter|Tab).*/)) {
|
||||
return fetcher(event);
|
||||
this.setSearchKey(event);
|
||||
return fetcher('replace', null);
|
||||
}
|
||||
});
|
||||
this.slim.slim.search.input.addEventListener('paste', event => fetcher(event));
|
||||
this.slim.slim.search.input.addEventListener('paste', event => {
|
||||
this.setSearchKey(event);
|
||||
return fetcher('replace', null);;
|
||||
});
|
||||
|
||||
// Watch every scroll event to determine if the scroll position is at bottom.
|
||||
this.slim.slim.list.addEventListener('scroll', () => this.handleScroll());
|
||||
|
||||
// When the scroll position is at bottom, fetch additional options.
|
||||
this.base.addEventListener(`netbox.select.atbottom.${this.name}`, () =>
|
||||
this.fetchOptions(this.more, 'merge'),
|
||||
);
|
||||
this.base.addEventListener(`netbox.select.atbottom.${this.name}`, () => {
|
||||
if (this.more!=null) {
|
||||
return fetcher('merge', this.more, )
|
||||
}
|
||||
});
|
||||
|
||||
// When the base select element is disabled or enabled, properly disable/enable this instance.
|
||||
this.base.addEventListener(`netbox.select.disabled.${this.name}`, event =>
|
||||
@@ -551,6 +566,14 @@ export class APISelect {
|
||||
}
|
||||
}
|
||||
|
||||
private getUrl() {
|
||||
var url = this.queryUrl
|
||||
if (this.searchKey!=null) {
|
||||
url = queryString.stringifyUrl({ url: this.queryUrl, query: { q : this.searchKey } })
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the NetBox API for this element's options.
|
||||
*/
|
||||
@@ -559,21 +582,25 @@ export class APISelect {
|
||||
this.resetOptions();
|
||||
return;
|
||||
}
|
||||
await this.fetchOptions(this.queryUrl, action);
|
||||
const url = this.getUrl()
|
||||
await this.fetchOptions(url, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the API for a specific search pattern and add the results to the available options.
|
||||
*/
|
||||
private async handleSearch(event: Event) {
|
||||
const { value: q } = event.target as HTMLInputElement;
|
||||
const url = queryString.stringifyUrl({ url: this.queryUrl, query: { q } });
|
||||
if (!url.includes(`{{`)) {
|
||||
await this.fetchOptions(url, 'merge');
|
||||
this.slim.data.search(q);
|
||||
this.slim.render();
|
||||
private async handleSearch(action: ApplyMethod = 'merge', url: Nullable<string> ) {
|
||||
if (url==null) {
|
||||
url = this.getUrl()
|
||||
}
|
||||
return;
|
||||
if (url.includes(`{{`)) {
|
||||
return
|
||||
}
|
||||
await this.fetchOptions(url, action);
|
||||
if (this.searchKey!=null) {
|
||||
this.slim.data.search(this.searchKey);
|
||||
}
|
||||
this.slim.render();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -586,13 +613,11 @@ export class APISelect {
|
||||
Math.floor(this.slim.slim.list.scrollTop) + this.slim.slim.list.offsetHeight ===
|
||||
this.slim.slim.list.scrollHeight;
|
||||
|
||||
if (this.atBottom && !atBottom) {
|
||||
this.atBottom = false;
|
||||
this.atBottom = atBottom
|
||||
|
||||
if (this.atBottom) {
|
||||
this.base.dispatchEvent(this.bottomEvent);
|
||||
} else if (!this.atBottom && atBottom) {
|
||||
this.atBottom = true;
|
||||
this.base.dispatchEvent(this.bottomEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -994,7 +1019,9 @@ export class APISelect {
|
||||
['btn', 'btn-sm', 'btn-ghost-dark'],
|
||||
[createElement('i', null, ['mdi', 'mdi-reload'])],
|
||||
);
|
||||
refreshButton.addEventListener('click', () => this.loadData());
|
||||
// calling this.loadData() will prevent first page of returned items
|
||||
// with non-null search key inplace not selectable
|
||||
refreshButton.addEventListener('click', () => this.handleSearch('replace', null));
|
||||
refreshButton.type = 'button';
|
||||
this.slim.slim.search.container.appendChild(refreshButton);
|
||||
}
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -63,7 +63,7 @@ class IKEPolicyTable(NetBoxTable):
|
||||
mode = tables.Column(
|
||||
verbose_name=_('Mode')
|
||||
)
|
||||
proposals = tables.ManyToManyColumn(
|
||||
proposals = columns.ManyToManyColumn(
|
||||
linkify_item=True,
|
||||
verbose_name=_('Proposals')
|
||||
)
|
||||
@@ -129,7 +129,7 @@ class IPSecPolicyTable(NetBoxTable):
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
)
|
||||
proposals = tables.ManyToManyColumn(
|
||||
proposals = columns.ManyToManyColumn(
|
||||
linkify_item=True,
|
||||
verbose_name=_('Proposals')
|
||||
)
|
||||
|
||||
@@ -91,7 +91,7 @@ class TunnelTerminationTable(TenancyColumnsMixin, NetBoxTable):
|
||||
verbose_name=_('Tunnel interface'),
|
||||
linkify=True
|
||||
)
|
||||
ip_addresses = tables.ManyToManyColumn(
|
||||
ip_addresses = columns.ManyToManyColumn(
|
||||
accessor=tables.A('termination__ip_addresses'),
|
||||
orderable=False,
|
||||
linkify_item=True,
|
||||
|
||||
@@ -19,10 +19,10 @@ drf-spectacular-sidecar==2024.5.1
|
||||
feedparser==6.0.11
|
||||
graphene-django==3.0.0
|
||||
gunicorn==22.0.0
|
||||
Jinja2==3.1.3
|
||||
Jinja2==3.1.4
|
||||
Markdown==3.6
|
||||
mkdocs-material==9.5.20
|
||||
mkdocstrings[python-legacy]==0.25.0
|
||||
mkdocs-material==9.5.21
|
||||
mkdocstrings[python-legacy]==0.25.1
|
||||
netaddr==1.2.1
|
||||
Pillow==10.3.0
|
||||
psycopg[binary,pool]==3.1.18
|
||||
|
||||
Reference in New Issue
Block a user