nautobot 2.3.1__py3-none-any.whl → 2.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/core/celery/schedulers.py +18 -0
- nautobot/core/settings.yaml +3 -3
- nautobot/core/tables.py +1 -1
- nautobot/core/templates/home.html +4 -3
- nautobot/core/templatetags/buttons.py +1 -1
- nautobot/core/views/utils.py +3 -3
- nautobot/dcim/factory.py +3 -3
- nautobot/dcim/tables/devices.py +7 -7
- nautobot/dcim/templates/dcim/device.html +12 -0
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +12 -0
- nautobot/dcim/utils.py +9 -6
- nautobot/extras/api/serializers.py +2 -0
- nautobot/extras/filters/__init__.py +14 -2
- nautobot/extras/forms/forms.py +6 -0
- nautobot/extras/forms/mixins.py +2 -2
- nautobot/extras/management/__init__.py +3 -0
- nautobot/extras/migrations/0115_scheduledjob_time_zone.py +23 -0
- nautobot/extras/models/jobs.py +24 -11
- nautobot/extras/tables.py +34 -4
- nautobot/extras/templates/extras/scheduledjob.html +13 -2
- nautobot/extras/tests/test_api.py +17 -18
- nautobot/extras/tests/test_filters.py +57 -1
- nautobot/extras/tests/test_models.py +299 -1
- nautobot/extras/tests/test_views.py +3 -2
- nautobot/extras/views.py +7 -0
- nautobot/ipam/api/views.py +9 -2
- nautobot/ipam/choices.py +17 -0
- nautobot/ipam/factory.py +6 -0
- nautobot/ipam/filters.py +1 -1
- nautobot/ipam/forms.py +5 -3
- nautobot/ipam/migrations/0048_vrf_status.py +23 -0
- nautobot/ipam/migrations/0049_vrf_data_migration.py +25 -0
- nautobot/ipam/models.py +2 -0
- nautobot/ipam/tables.py +3 -2
- nautobot/ipam/templates/ipam/vrf.html +4 -0
- nautobot/ipam/templates/ipam/vrf_edit.html +1 -0
- nautobot/ipam/tests/test_api.py +33 -3
- nautobot/ipam/tests/test_views.py +3 -0
- nautobot/project-static/css/base.css +6 -0
- nautobot/project-static/docs/release-notes/version-2.3.html +163 -33
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +271 -271
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +3 -3
- nautobot/project-static/js/homepage_layout.js +3 -0
- {nautobot-2.3.1.dist-info → nautobot-2.3.2.dist-info}/METADATA +1 -1
- {nautobot-2.3.1.dist-info → nautobot-2.3.2.dist-info}/RECORD +51 -48
- {nautobot-2.3.1.dist-info → nautobot-2.3.2.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.1.dist-info → nautobot-2.3.2.dist-info}/NOTICE +0 -0
- {nautobot-2.3.1.dist-info → nautobot-2.3.2.dist-info}/WHEEL +0 -0
- {nautobot-2.3.1.dist-info → nautobot-2.3.2.dist-info}/entry_points.txt +0 -0
nautobot/ipam/factory.py
CHANGED
|
@@ -84,6 +84,7 @@ class VRFFactory(PrimaryModelFactory):
|
|
|
84
84
|
model = VRF
|
|
85
85
|
exclude = (
|
|
86
86
|
"has_description",
|
|
87
|
+
"has_status",
|
|
87
88
|
"has_tenant",
|
|
88
89
|
)
|
|
89
90
|
|
|
@@ -103,6 +104,11 @@ class VRFFactory(PrimaryModelFactory):
|
|
|
103
104
|
has_description = NautobotBoolIterator()
|
|
104
105
|
description = factory.Maybe("has_description", factory.Faker("text", max_nb_chars=CHARFIELD_MAX_LENGTH), "")
|
|
105
106
|
|
|
107
|
+
has_status = NautobotBoolIterator()
|
|
108
|
+
status = factory.Maybe(
|
|
109
|
+
"has_status", random_instance(lambda: Status.objects.get_for_model(VRF), allow_null=False), None
|
|
110
|
+
)
|
|
111
|
+
|
|
106
112
|
namespace = random_instance(Namespace, allow_null=False)
|
|
107
113
|
|
|
108
114
|
@factory.post_generation
|
nautobot/ipam/filters.py
CHANGED
|
@@ -66,7 +66,7 @@ class NamespaceFilterSet(NautobotFilterSet):
|
|
|
66
66
|
fields = "__all__"
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
class VRFFilterSet(NautobotFilterSet, TenancyModelFilterSetMixin):
|
|
69
|
+
class VRFFilterSet(NautobotFilterSet, StatusModelFilterSetMixin, TenancyModelFilterSetMixin):
|
|
70
70
|
q = SearchFilter(
|
|
71
71
|
filter_predicates={
|
|
72
72
|
"name": "icontains",
|
nautobot/ipam/forms.py
CHANGED
|
@@ -125,6 +125,7 @@ class VRFForm(NautobotModelForm, TenancyForm):
|
|
|
125
125
|
"name",
|
|
126
126
|
"rd",
|
|
127
127
|
"namespace",
|
|
128
|
+
"status",
|
|
128
129
|
"description",
|
|
129
130
|
"import_targets",
|
|
130
131
|
"export_targets",
|
|
@@ -140,10 +141,11 @@ class VRFForm(NautobotModelForm, TenancyForm):
|
|
|
140
141
|
}
|
|
141
142
|
help_texts = {
|
|
142
143
|
"rd": "Route distinguisher unique to this Namespace (as defined in RFC 4364)",
|
|
144
|
+
"status": "Operational status of this VRF",
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
|
|
146
|
-
class VRFBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
|
|
148
|
+
class VRFBulkEditForm(TagsBulkEditFormMixin, StatusModelBulkEditFormMixin, NautobotBulkEditForm):
|
|
147
149
|
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput())
|
|
148
150
|
namespace = DynamicModelChoiceField(queryset=Namespace.objects.all(), required=False)
|
|
149
151
|
tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
|
@@ -162,9 +164,9 @@ class VRFBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
|
|
|
162
164
|
]
|
|
163
165
|
|
|
164
166
|
|
|
165
|
-
class VRFFilterForm(NautobotFilterForm, TenancyFilterForm):
|
|
167
|
+
class VRFFilterForm(NautobotFilterForm, StatusModelFilterFormMixin, TenancyFilterForm):
|
|
166
168
|
model = VRF
|
|
167
|
-
field_order = ["q", "import_targets", "export_targets", "tenant_group", "tenant"]
|
|
169
|
+
field_order = ["q", "import_targets", "export_targets", "status", "tenant_group", "tenant"]
|
|
168
170
|
q = forms.CharField(required=False, label="Search")
|
|
169
171
|
import_targets = DynamicModelMultipleChoiceField(
|
|
170
172
|
queryset=RouteTarget.objects.all(), to_field_name="name", required=False
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated by Django 4.2.15 on 2024-08-26 17:20
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
|
|
6
|
+
import nautobot.extras.models.statuses
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
dependencies = [
|
|
11
|
+
("extras", "0114_computedfield_grouping"),
|
|
12
|
+
("ipam", "0047_alter_ipaddress_role_alter_ipaddress_status_and_more"),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AddField(
|
|
17
|
+
model_name="vrf",
|
|
18
|
+
name="status",
|
|
19
|
+
field=nautobot.extras.models.statuses.StatusField(
|
|
20
|
+
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to="extras.status"
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Generated by Django 4.2.15 on 2024-08-26 18:05
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
from nautobot.extras.management import clear_status_choices, populate_status_choices
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def populate_vrf_status_choices(apps, schema_editor):
|
|
9
|
+
"""Create default Status records for the VRF model."""
|
|
10
|
+
populate_status_choices(apps, schema_editor, models=["ipam.VRF"])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def clear_vrf_status_choices(apps, schema_editor):
|
|
14
|
+
"""Remove default Status records for the VRF model."""
|
|
15
|
+
clear_status_choices(apps, schema_editor, models=["ipam.VRF"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Migration(migrations.Migration):
|
|
19
|
+
dependencies = [
|
|
20
|
+
("ipam", "0048_vrf_status"),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
operations = [
|
|
24
|
+
migrations.RunPython(populate_vrf_status_choices, clear_vrf_status_choices),
|
|
25
|
+
]
|
nautobot/ipam/models.py
CHANGED
|
@@ -97,6 +97,7 @@ def get_default_namespace_pk():
|
|
|
97
97
|
"custom_validators",
|
|
98
98
|
"export_templates",
|
|
99
99
|
"graphql",
|
|
100
|
+
"statuses",
|
|
100
101
|
"webhooks",
|
|
101
102
|
)
|
|
102
103
|
class VRF(PrimaryModel):
|
|
@@ -114,6 +115,7 @@ class VRF(PrimaryModel):
|
|
|
114
115
|
verbose_name="Route distinguisher",
|
|
115
116
|
help_text="Unique route distinguisher (as defined in RFC 4364)",
|
|
116
117
|
)
|
|
118
|
+
status = StatusField(blank=True, null=True)
|
|
117
119
|
namespace = models.ForeignKey(
|
|
118
120
|
"ipam.Namespace",
|
|
119
121
|
on_delete=models.PROTECT,
|
nautobot/ipam/tables.py
CHANGED
|
@@ -217,7 +217,7 @@ class NamespaceTable(BaseTable):
|
|
|
217
217
|
#
|
|
218
218
|
|
|
219
219
|
|
|
220
|
-
class VRFTable(BaseTable):
|
|
220
|
+
class VRFTable(StatusTableMixin, BaseTable):
|
|
221
221
|
pk = ToggleColumn()
|
|
222
222
|
name = tables.LinkColumn()
|
|
223
223
|
# rd = tables.Column(verbose_name="RD")
|
|
@@ -232,6 +232,7 @@ class VRFTable(BaseTable):
|
|
|
232
232
|
"pk",
|
|
233
233
|
"name",
|
|
234
234
|
# "rd",
|
|
235
|
+
"status",
|
|
235
236
|
"namespace",
|
|
236
237
|
"tenant",
|
|
237
238
|
"description",
|
|
@@ -240,7 +241,7 @@ class VRFTable(BaseTable):
|
|
|
240
241
|
"tags",
|
|
241
242
|
)
|
|
242
243
|
# default_columns = ("pk", "name", "rd", "namespace", "tenant", "description")
|
|
243
|
-
default_columns = ("pk", "name", "namespace", "tenant", "description")
|
|
244
|
+
default_columns = ("pk", "name", "status", "namespace", "tenant", "description")
|
|
244
245
|
|
|
245
246
|
|
|
246
247
|
class VRFDeviceAssignmentTable(BaseTable):
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
<td>Tenant</td>
|
|
20
20
|
<td>{{ object.tenant|hyperlinked_object }}</td>
|
|
21
21
|
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<td>Status</td>
|
|
24
|
+
<td>{{ object.status|hyperlinked_object_with_color }}</td>
|
|
25
|
+
</tr>
|
|
22
26
|
<tr>
|
|
23
27
|
<td>Description</td>
|
|
24
28
|
<td>{{ object.description|placeholder }}</td>
|
nautobot/ipam/tests/test_api.py
CHANGED
|
@@ -13,7 +13,7 @@ from nautobot.core.testing import APITestCase, APIViewTestCases, disable_warning
|
|
|
13
13
|
from nautobot.core.testing.api import APITransactionTestCase
|
|
14
14
|
from nautobot.dcim.choices import InterfaceTypeChoices
|
|
15
15
|
from nautobot.dcim.models import Device, DeviceType, Interface, Location, LocationType, Manufacturer
|
|
16
|
-
from nautobot.extras.models import Role, Status
|
|
16
|
+
from nautobot.extras.models import CustomField, Role, Status
|
|
17
17
|
from nautobot.ipam import choices
|
|
18
18
|
from nautobot.ipam.models import (
|
|
19
19
|
IPAddress,
|
|
@@ -84,11 +84,14 @@ class VRFTest(APIViewTestCases.APIViewTestCase):
|
|
|
84
84
|
@classmethod
|
|
85
85
|
def setUpTestData(cls):
|
|
86
86
|
namespace = Namespace.objects.first()
|
|
87
|
+
vrf_statuses = Status.objects.get_for_model(VRF)
|
|
88
|
+
|
|
87
89
|
cls.create_data = [
|
|
88
90
|
{
|
|
89
91
|
"namespace": namespace.pk,
|
|
90
92
|
"name": "VRF 4",
|
|
91
93
|
"rd": "65000:4",
|
|
94
|
+
"status": vrf_statuses.first().pk,
|
|
92
95
|
},
|
|
93
96
|
{
|
|
94
97
|
"namespace": namespace.pk,
|
|
@@ -98,10 +101,12 @@ class VRFTest(APIViewTestCases.APIViewTestCase):
|
|
|
98
101
|
{
|
|
99
102
|
"name": "VRF 6",
|
|
100
103
|
"rd": "65000:6",
|
|
104
|
+
"status": vrf_statuses.last().pk,
|
|
101
105
|
},
|
|
102
106
|
]
|
|
103
107
|
cls.bulk_update_data = {
|
|
104
108
|
"description": "New description",
|
|
109
|
+
"status": vrf_statuses.last().pk,
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
|
|
@@ -332,6 +337,8 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
332
337
|
cls.statuses = Status.objects.get_for_model(Prefix)
|
|
333
338
|
cls.status = cls.statuses[0]
|
|
334
339
|
cls.locations = Location.objects.get_for_model(Prefix)
|
|
340
|
+
cls.custom_field = CustomField.objects.create(key="prefixcf", label="Prefix Custom Field", type="text")
|
|
341
|
+
cls.custom_field.content_types.add(ContentType.objects.get_for_model(Prefix))
|
|
335
342
|
cls.create_data = [
|
|
336
343
|
{
|
|
337
344
|
"prefix": "192.168.4.0/24",
|
|
@@ -346,6 +353,7 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
346
353
|
"rir": rir.pk,
|
|
347
354
|
"type": choices.PrefixTypeChoices.TYPE_NETWORK,
|
|
348
355
|
"namespace": cls.namespace.pk,
|
|
356
|
+
"custom_fields": {"prefixcf": "hello world"},
|
|
349
357
|
},
|
|
350
358
|
{
|
|
351
359
|
"prefix": "192.168.6.0/24",
|
|
@@ -457,12 +465,16 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
457
465
|
"prefix_length": child_prefix_length,
|
|
458
466
|
"status": self.status.pk,
|
|
459
467
|
"description": f"Test Prefix {i + 1}",
|
|
468
|
+
"custom_fields": {"prefixcf": f"value {i+1}"},
|
|
460
469
|
}
|
|
461
470
|
response = self.client.post(url, data, format="json", **self.header)
|
|
462
471
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
463
472
|
self.assertEqual(response.data["prefix"], str(prefixes_to_be_created[i]))
|
|
464
473
|
self.assertEqual(str(response.data["namespace"]["url"]), self.absolute_api_url(prefix.namespace))
|
|
465
474
|
self.assertEqual(response.data["description"], data["description"])
|
|
475
|
+
self.assertIn("custom_fields", response.data)
|
|
476
|
+
self.assertIn("prefixcf", response.data["custom_fields"])
|
|
477
|
+
self.assertEqual(response.data["custom_fields"]["prefixcf"], data["custom_fields"]["prefixcf"])
|
|
466
478
|
|
|
467
479
|
# Try to create one more prefix, and expect a HTTP 204 response.
|
|
468
480
|
# This feels wrong to me (shouldn't it be a 4xx or 5xx?) but it's how the API has historically behaved.
|
|
@@ -501,6 +513,7 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
501
513
|
"prefix_length": child_prefix_length,
|
|
502
514
|
"description": "Test Prefix 1",
|
|
503
515
|
"status": self.status.pk,
|
|
516
|
+
"custom_fields": {"prefixcf": "value 1"},
|
|
504
517
|
},
|
|
505
518
|
{
|
|
506
519
|
"prefix_length": child_prefix_length,
|
|
@@ -537,6 +550,9 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
537
550
|
response = self.client.post(url, data[:4], format="json", **self.header)
|
|
538
551
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
539
552
|
self.assertEqual(len(response.data), 4)
|
|
553
|
+
self.assertIn("custom_fields", response.data[0])
|
|
554
|
+
self.assertIn("prefixcf", response.data[0]["custom_fields"])
|
|
555
|
+
self.assertEqual("value 1", response.data[0]["custom_fields"]["prefixcf"])
|
|
540
556
|
|
|
541
557
|
def test_list_available_ips(self):
|
|
542
558
|
"""
|
|
@@ -571,6 +587,8 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
571
587
|
type=choices.PrefixTypeChoices.TYPE_NETWORK,
|
|
572
588
|
status=self.status,
|
|
573
589
|
)
|
|
590
|
+
cf = CustomField.objects.create(key="ipcf", label="IP Custom Field", type="text")
|
|
591
|
+
cf.content_types.add(ContentType.objects.get_for_model(IPAddress))
|
|
574
592
|
url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
|
|
575
593
|
self.add_permissions("ipam.view_prefix", "ipam.add_ipaddress", "extras.view_status")
|
|
576
594
|
|
|
@@ -579,11 +597,15 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
579
597
|
data = {
|
|
580
598
|
"description": f"Test IP {i}",
|
|
581
599
|
"status": self.status.pk,
|
|
600
|
+
"custom_fields": {"ipcf": f"value {i}"},
|
|
582
601
|
}
|
|
583
602
|
response = self.client.post(url, data, format="json", **self.header)
|
|
584
603
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
585
604
|
self.assertEqual(str(response.data["parent"]["url"]), self.absolute_api_url(prefix))
|
|
586
605
|
self.assertEqual(response.data["description"], data["description"])
|
|
606
|
+
self.assertIn("custom_fields", response.data)
|
|
607
|
+
self.assertIn("ipcf", response.data["custom_fields"])
|
|
608
|
+
self.assertEqual(f"value {i}", response.data["custom_fields"]["ipcf"])
|
|
587
609
|
|
|
588
610
|
# Try to create one more IP
|
|
589
611
|
response = self.client.post(url, {"status": self.status.pk}, format="json", **self.header)
|
|
@@ -601,21 +623,29 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
601
623
|
namespace=self.namespace,
|
|
602
624
|
status=self.status,
|
|
603
625
|
)
|
|
626
|
+
cf = CustomField.objects.create(key="ipcf", label="IP Custom Field", type="text")
|
|
627
|
+
cf.content_types.add(ContentType.objects.get_for_model(IPAddress))
|
|
604
628
|
url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
|
|
605
629
|
self.add_permissions("ipam.view_prefix", "ipam.add_ipaddress", "extras.view_status")
|
|
606
630
|
|
|
607
631
|
# Try to create seven IPs (only six are available)
|
|
608
|
-
data = [
|
|
632
|
+
data = [
|
|
633
|
+
{"description": f"Test IP {i}", "status": self.status.pk, "custom_fields": {"ipcf": str(i)}}
|
|
634
|
+
for i in range(1, 8)
|
|
635
|
+
] # 7 IPs
|
|
609
636
|
response = self.client.post(url, data, format="json", **self.header)
|
|
610
637
|
# This feels wrong to me (shouldn't it be a 4xx or 5xx?) but it's how the API has historically behaved.
|
|
611
638
|
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
|
612
639
|
self.assertIn("detail", response.data)
|
|
613
640
|
|
|
614
641
|
# Create all six available IPs in a single request
|
|
615
|
-
data = [
|
|
642
|
+
data = data[:6]
|
|
616
643
|
response = self.client.post(url, data, format="json", **self.header)
|
|
617
644
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
618
645
|
self.assertEqual(len(response.data), 6)
|
|
646
|
+
self.assertIn("custom_fields", response.data[0])
|
|
647
|
+
self.assertIn("ipcf", response.data[0]["custom_fields"])
|
|
648
|
+
self.assertEqual("1", response.data[0]["custom_fields"]["ipcf"])
|
|
619
649
|
|
|
620
650
|
|
|
621
651
|
class PrefixLocationAssignmentTest(APIViewTestCases.APIViewTestCase):
|
|
@@ -73,6 +73,7 @@ class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
73
73
|
tenants = Tenant.objects.all()[:2]
|
|
74
74
|
namespace = Prefix.objects.first().namespace
|
|
75
75
|
prefixes = Prefix.objects.filter(namespace=namespace)
|
|
76
|
+
vrf_statuses = Status.objects.get_for_model(VRF)
|
|
76
77
|
|
|
77
78
|
cls.form_data = {
|
|
78
79
|
"name": "VRF X",
|
|
@@ -82,9 +83,11 @@ class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
82
83
|
"description": "A new VRF",
|
|
83
84
|
"prefixes": [prefixes[1].id],
|
|
84
85
|
"tags": [t.pk for t in Tag.objects.get_for_model(VRF)],
|
|
86
|
+
"status": vrf_statuses.first().pk,
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
cls.bulk_edit_data = {
|
|
90
|
+
"status": vrf_statuses.first().pk,
|
|
88
91
|
"tenant": tenants[1].pk,
|
|
89
92
|
"description": "New description",
|
|
90
93
|
"namespace": prefixes[0].namespace.id,
|
|
@@ -862,6 +862,12 @@ td span.hover_copy:hover .hover_copy_button {
|
|
|
862
862
|
.collapse-icon {
|
|
863
863
|
float: right;
|
|
864
864
|
cursor: pointer !important;
|
|
865
|
+
transition: transform 0.3s ease;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/* Rotates element 180 degrees */
|
|
869
|
+
.rotated180 {
|
|
870
|
+
transform: rotate(180deg);
|
|
865
871
|
}
|
|
866
872
|
|
|
867
873
|
/* Draggable homepage panels fade in effect on page load */
|