Compare commits

...

96 Commits

Author SHA1 Message Date
Jeremy Stretch
6726403de9 Merge pull request #2821 from digitalocean/develop
Release v2.5.4
2019-01-29 16:38:54 -05:00
Jeremy Stretch
f6d18d243e Release v2.5.4 2019-01-29 16:32:15 -05:00
Jeremy Stretch
8bd9b258a8 Fixes #2816: Handle exception when deleting a device with connected components 2019-01-29 14:27:47 -05:00
Jeremy Stretch
0256448dd8 Closes #2810: Include description fields in interface connections export 2019-01-29 14:08:15 -05:00
Jeremy Stretch
dc70fdbe03 Force string formatting of integer values 2019-01-29 13:57:19 -05:00
Jeremy Stretch
ce1a2875bc Closes #2808: Loosen version pinning for Django to allow patch releases 2019-01-29 13:48:21 -05:00
fouram
b4c9ec27e0 Updated optional-settings.md - Webhook links (#2818)
Corrected (../miscellaneous/webhooks/) to (../additional-features/webhooks/) in two place
2019-01-29 13:38:05 -05:00
Jeremy Stretch
8977ded7b6 Changelog for #2802 2019-01-29 13:36:09 -05:00
ChrisPortman
afb4c636fe Fix #2802 (#2803) 2019-01-29 13:29:22 -05:00
John Anderson
ff40a13f29 fixed select2 paging offset calculation to index at 1 2019-01-29 00:48:41 -05:00
Jeremy Stretch
61a48320af Add pagination widget to console/power/interface connections views 2019-01-23 14:26:17 -05:00
Jeremy Stretch
d53249060d Closes #2807: Include device site/rack assignment in cable trace view 2019-01-23 13:57:26 -05:00
Jeremy Stretch
22eebbbc71 Fixes #2795: Fix duplicate display of pagination controls on child prefix/IP tables 2019-01-23 13:38:16 -05:00
Jeremy Stretch
4227c6b806 Add select2-bootstrap for consistent widget styling 2019-01-23 10:18:44 -05:00
Jeremy Stretch
14695037da Strip return characters when removing line breaks 2019-01-18 09:24:32 -05:00
Jeremy Stretch
0d717cdc82 Changelog for #2798 2019-01-18 09:23:53 -05:00
Jeremy Stretch
ae8c5ae7b8 Merge pull request #2799 from bgilmore/develop
Fixes #2798: URL encode physical_address in links
2019-01-18 09:18:00 -05:00
Brandon Gilmore
fb0ed3db2f Fixes #2798: URL encode physical_address in links 2019-01-17 17:41:46 -08:00
Jeremy Stretch
c69fad7429 Closes #2733: Enable bulk assignment of MAC addresses to interfaces 2019-01-17 16:20:14 -05:00
Jeremy Stretch
92a2f529e3 Closes #2766: Extend users admin table to include superuser and active fields 2019-01-17 16:14:06 -05:00
Jeremy Stretch
bd74e2f30b Closes #2782: Add is_pool field for prefix filtering 2019-01-17 16:02:16 -05:00
Jeremy Stretch
a950c95416 Merge pull request #2738 from candlerb/candlerb/graphviz-exception
Show exception description on failure to render graphviz
2019-01-16 16:02:11 -05:00
John Anderson
dc9e9fd08f select2 changelog items 2019-01-15 20:01:02 -05:00
John Anderson
1abbaf99dc Merge pull request #2772 from digitalocean/select2-ui
Select2 UI
2019-01-15 16:57:03 -08:00
John Anderson
af6bb53a01 removed debug log 2019-01-15 16:36:51 -05:00
John Anderson
2d7e5a57e7 handle disabled-indicator 2019-01-15 16:33:44 -05:00
Jeremy Stretch
b6737aff59 Remove invalid kwarg (conflict w/ #2779) 2019-01-15 15:47:47 -05:00
Jeremy Stretch
c5f2cbf9fa Syntax fix 2019-01-15 15:44:21 -05:00
John Anderson
d6d8b078b9 Merge branch 'develop' into select2-ui 2019-01-15 10:26:39 -08:00
Jeremy Stretch
1d7a7e2d1d Fixes #2779: Include "none" option when filter IP addresses by role 2019-01-15 11:26:41 -05:00
Jeremy Stretch
4a290f3834 Fixes #2783: Fix AttributeError exception when attempting to delete region(s) 2019-01-15 10:44:11 -05:00
Jeremy Stretch
9e492cbb4d Post-release version bump 2019-01-11 11:35:06 -05:00
Jeremy Stretch
e17d79e10f Merge pull request #2778 from digitalocean/develop
Release v2.5.3
2019-01-11 11:32:49 -05:00
Jeremy Stretch
28a2981a4f Release v2.5.3 2019-01-11 11:27:59 -05:00
Jeremy Stretch
d356e288a2 Expanded the bug report template 2019-01-11 10:30:35 -05:00
Jeremy Stretch
dd5f37391f Fixes #2777: Fix cable validation to handle duplicate connections on import 2019-01-11 10:17:06 -05:00
John Anderson
17d6584ef4 removed deprecated code 2019-01-10 21:23:22 -05:00
John Anderson
ad4fb3ce8b IPAM Select2 forms and changelog 2019-01-10 21:19:13 -05:00
John Anderson
5f1f8ee73b Circuits Select2 forms 2019-01-10 17:50:06 -05:00
John Anderson
60224be272 Merge branch 'develop' of github.com:digitalocean/netbox into select2-ui 2019-01-10 17:32:38 -05:00
John Anderson
6dcd48fef1 Virtulization Select2 forms 2019-01-10 17:32:23 -05:00
John Anderson
951e7a68e9 Secrets Select2 forms 2019-01-09 23:46:45 -05:00
John Anderson
86bafbb760 Tenancy Select2 forms 2019-01-09 23:33:08 -05:00
John Anderson
a6564c49e2 DCIM removed annotates from querysets 2019-01-09 13:44:39 -05:00
Jeremy Stretch
c89735cd4e Disable wrapping of report labels 2019-01-09 11:47:34 -05:00
John Anderson
f3216abebf Merge branch 'develop' of github.com:digitalocean/netbox into select2-ui 2019-01-09 11:25:56 -05:00
Jeremy Stretch
5676bd15dd Closes #2682: Add DAC and AOC cable types 2019-01-09 09:28:44 -05:00
John Anderson
bf8d57c7d1 DCIM filter forms select2 2019-01-08 15:35:34 -08:00
Jeremy Stretch
0d415d94a5 Fixes #2757: Always treat first/last IPs within a /31 or /127 as usable 2019-01-08 12:04:32 -05:00
Jeremy Stretch
73a1d6a7ba Fixes #2762: Add missing DCIM field values to API _choices endpoint 2019-01-08 11:51:13 -05:00
John Anderson
72d5c6fd1b #2590 changelog 2019-01-05 17:46:23 -05:00
John Anderson
6d5d9c8af3 implemented #2590 - improved color picker with actual colors 2019-01-05 17:43:42 -05:00
John Anderson
c27cea981c select2 for all create/edit forms in dcim 2019-01-04 14:41:36 -05:00
Jeremy Stretch
f7f6704fc1 Preserve filtering/ordering parameters when modifying per_page count 2019-01-04 13:17:24 -05:00
John Anderson
fca97f9768 Merge branch 'develop' of github.com:digitalocean/netbox into select2-ui 2019-01-04 12:44:41 -05:00
John Anderson
7a5a73ce34 fixed whitespace 2019-01-04 12:44:31 -05:00
Jeremy Stretch
170e01b549 Closes #1983: Enable regular expressions when bulk renaming device components 2019-01-04 12:30:38 -05:00
Jeremy Stretch
99dc46a89e Fixes #2742: Preserve cluster assignment when editing a device 2019-01-04 11:46:53 -05:00
Jeremy Stretch
848aa0b098 Closes #1870: Add per-page toggle to object lists 2019-01-04 11:07:55 -05:00
John Anderson
81a0889568 initial static select2 fields 2019-01-03 23:02:05 -05:00
Jeremy Stretch
0a820d9c98 Closes #1871: Enable filtering sites by parent region 2019-01-03 16:59:49 -05:00
Jeremy Stretch
209a9f0ffc Closes #1630: Enable bulk editing of prefix/IP mask length 2019-01-03 16:21:21 -05:00
Jeremy Stretch
3101a86381 Changelog updates; import cleanup 2019-01-03 15:30:12 -05:00
Jeremy Stretch
27ca0d0930 Merge pull request #2737 from TakeMeNL/feature/2726
Closes #2726 - Search for Cable Labels
2019-01-03 15:27:21 -05:00
Jeremy Stretch
6ca045e1a9 Merge pull request #2694 from DanSheps/2693-fiber-cable-colors
Closes #2693 - Adds additional colors for cables & roles
2019-01-03 15:24:36 -05:00
dansheps
0c86693dc4 Closes #2693
* Revert CSS Background Hack
2019-01-03 13:20:29 -06:00
John Anderson
5285b6926f updated filter-for attr handling 2019-01-03 03:00:27 -05:00
John Anderson
f3cfc17a52 fix processing of initial tags 2019-01-03 02:25:12 -05:00
Brian Candler
c58166137c Show exception description on failure to render graphviz
This means that problems give a more specific reason.  In the event
that dot is not found, the error is now:

There was an error generating the requested graph: failed to execute ['dot',
'-Tpng'], make sure the Graphviz executables are on your systems' PATH
2019-01-01 11:41:54 +00:00
John Anderson
28a02e9943 initial select2 support 2018-12-30 02:35:18 -05:00
TakeMeNL
c6d9206dd1 Added ability to search for cables in global search 2018-12-29 22:22:12 +01:00
Jeremy Stretch
d144d3a584 Post-release version bump 2018-12-21 11:48:12 -05:00
Jeremy Stretch
8cf8710130 Merge pull request #2725 from digitalocean/develop
Release v2.5.2
2018-12-21 11:46:31 -05:00
Jeremy Stretch
3705e37678 Release v2.5.2 2018-12-21 11:44:30 -05:00
Jeremy Stretch
ebe5193348 Fixes #2724: Limit rear port choices to current device when editing a front port 2018-12-21 11:09:44 -05:00
Jeremy Stretch
a3097d254e Fixes #2721: Detect loops when tracing front/rear ports 2018-12-21 10:54:20 -05:00
Jeremy Stretch
38276d9539 Fixes #2723: Correct permission evaluation when bulk deleting tags 2018-12-21 09:11:07 -05:00
Jeremy Stretch
91a2168952 Fixes #2717: Fix bulk deletion of tags 2018-12-21 09:08:00 -05:00
Jeremy Stretch
4a10b4ece0 Fixes #2704: Fix form select widget population on parent with null value 2018-12-20 15:49:35 -05:00
Jeremy Stretch
853b1fad15 Fixes #2712: Preserve list filtering after editing objects in bulk 2018-12-20 15:33:53 -05:00
Jeremy Stretch
7acbeb55bc Minor tweaks 2018-12-20 09:54:59 -05:00
Jeremy Stretch
8498e0088b Merge pull request #2667 from Jemikwa/develop
#2656 Updating LDAP documentation
2018-12-20 09:53:21 -05:00
Jeremy Stretch
aae10f7d71 Tweaked 200GE and 400GE interface type labels 2018-12-20 09:18:18 -05:00
Jeremy Stretch
6b19a2b101 Fixes #2709: Update example report for compatibility with v2.5 2018-12-19 16:34:35 -05:00
Jeremy Stretch
b44a76e6bd Closes #2537: Added AUTH_LDAP_MIRROR_GROUPS setting to LDAP docs 2018-12-19 16:24:41 -05:00
Jeremy Stretch
7f71fc1d42 Closes #2561: Add 200G and 400G interface types 2018-12-19 16:13:04 -05:00
Jeremy Stretch
ba9fe408bc #2675: Added InventoryItem search form field for 'discovered' 2018-12-19 14:15:22 -05:00
Jeremy Stretch
40cb576e11 Fixes #2673: Fix exception on LLDP neighbors view for device with a circuit connected 2018-12-19 14:04:22 -05:00
Jeremy Stretch
2f1db2fdf3 Fixes #2691: Cable trace should follow circuits 2018-12-19 12:48:20 -05:00
Jeremy Stretch
f4a22e5af3 Introduced fgcolor template filter to render ideal foreground color for any background color 2018-12-19 12:17:40 -05:00
Jeremy Stretch
aca57ec281 Fixes #2698: Remove pagination restriction on bulk component creation for devices/VMs 2018-12-19 10:59:12 -05:00
Jeremy Stretch
68cb8b6895 Closes #2701: Enable filtering of prefixes by exact prefix value 2018-12-19 10:02:18 -05:00
Jeremy Stretch
82e8c0152e Fixes #2707: Correct permission evaluation for circuit termination cabling 2018-12-19 09:36:45 -05:00
dansheps
f499f2dd66 Closes #2693 2018-12-14 11:51:20 -06:00
Jeremy Stretch
d4a9318826 Post-release version bump 2018-12-13 15:24:13 -05:00
Jemikwa
064dd9bef2 Updating LDAP documentation
Adding information on service restarts and logging LDAP queries for troubleshooting.
2018-12-11 11:45:45 -06:00
124 changed files with 16753 additions and 2819 deletions

View File

@@ -17,15 +17,20 @@ about: Report a reproducible bug in the current release of NetBox
-->
### Environment
* Python version: <!-- Example: 3.5.4 -->
* NetBox version: <!-- Example: 2.3.6 -->
* NetBox version: <!-- Example: 2.5.2 -->
<!--
Describe in detail the steps that someone else can take to reproduce this
bug using the current stable release of NetBox (or the current beta release
where applicable).
Describe in detail the exact steps that someone else can take to reproduce
this bug using the current stable release of NetBox (or the current beta
release where applicable). Begin with the creation of any necessary
database objects and call out every operation being performed explicitly.
If reporting a bug in the REST API, be sure to reconstruct the raw HTTP
request(s) being made: Don't rely on a wrapper like pynetbox.
-->
### Steps to Reproduce
1.
2.
3.
<!-- What did you expect to happen? -->
### Expected Behavior

2
.gitignore vendored
View File

@@ -10,3 +10,5 @@
fabfile.py
*.swp
gunicorn_config.py
.DS_Store
.vscode

File diff suppressed because it is too large Load Diff

View File

@@ -44,7 +44,7 @@ class DeviceConnectionsReport(Report):
# Check that every console port for every active device has a connection defined.
for console_port in ConsolePort.objects.select_related('device').filter(device__status=DEVICE_STATUS_ACTIVE):
if console_port.cs_port is None:
if console_port.connected_endpoint is None:
self.log_failure(
console_port.device,
"No console connection defined for {}".format(console_port.name)
@@ -63,7 +63,7 @@ class DeviceConnectionsReport(Report):
for device in Device.objects.filter(status=DEVICE_STATUS_ACTIVE):
connected_ports = 0
for power_port in PowerPort.objects.filter(device=device):
if power_port.power_outlet is not None:
if power_port.connected_endpoint is not None:
connected_ports += 1
if power_port.connection_status == CONNECTION_STATUS_PLANNED:
self.log_warning(

View File

@@ -251,7 +251,7 @@ The time zone NetBox will use when dealing with dates and times. It is recommend
Default: False
Enable this option to run the webhook backend. See the docs section on the webhook backend [here](../miscellaneous/webhooks/) for more information on setup and use.
Enable this option to run the webhook backend. See the docs section on the webhook backend [here](../additional-features/webhooks/) for more information on setup and use.
---
@@ -274,7 +274,7 @@ SHORT_DATETIME_FORMAT = 'Y-m-d H:i' # 2016-06-27 13:23
## Redis Connection Settings
[Redis](https://redis.io/) is a key-value store which functions as a very lightweight database. It is required when enabling NetBox [webhooks](../miscellaneous/webhooks/). A Redis connection is configured using a dictionary similar to the following:
[Redis](https://redis.io/) is a key-value store which functions as a very lightweight database. It is required when enabling NetBox [webhooks](../additional-features/webhooks/). A Redis connection is configured using a dictionary similar to the following:
```
REDIS = {

View File

@@ -95,6 +95,9 @@ AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
# Define a group required to login.
AUTH_LDAP_REQUIRE_GROUP = "CN=NETBOX_USERS,DC=example,DC=com"
# Mirror LDAP group assignments.
AUTH_LDAP_MIRROR_GROUPS = True
# Define special user types using groups. Exercise great caution when assigning superuser status.
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": "cn=active,ou=groups,dc=example,dc=com",
@@ -113,3 +116,21 @@ AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
* `is_active` - All users must be mapped to at least this group to enable authentication. Without this, users cannot log in.
* `is_staff` - Users mapped to this group are enabled for access to the administration tools; this is the equivalent of checking the "staff status" box on a manually created user. This doesn't grant any specific permissions.
* `is_superuser` - Users mapped to this group will be granted superuser status. Superusers are implicitly granted all permissions.
# Troubleshooting LDAP
`supervisorctl restart netbox` restarts the Netbox service, and initiates any changes made to `ldap_config.py`. If there are syntax errors present, the NetBox process will not spawn an instance, and errors should be logged to `/var/log/supervisor/`.
For troubleshooting LDAP user/group queries, add the following lines to the start of `ldap_config.py` after `import ldap`.
```python
import logging, logging.handlers
logfile = "/opt/netbox/logs/django-ldap-debug.log"
my_logger = logging.getLogger('django_auth_ldap')
my_logger.setLevel(logging.DEBUG)
handler = logging.handlers.RotatingFileHandler(
logfile, maxBytes=1024 * 500, backupCount=5)
my_logger.addHandler(handler)
```
Ensure the file and path specified in logfile exist and are writable and executable by the application service account. Restart the netbox service and attempt to log into the site to trigger log entries to this file.

View File

@@ -1,5 +1,4 @@
from django import forms
from django.db.models import Count
from taggit.forms import TagField
from dcim.models import Site
@@ -7,8 +6,8 @@ from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEdit
from tenancy.forms import TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
AnnotatedMultipleChoiceField, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, FilterChoiceField,
SmallTextarea, SlugField,
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
)
from .constants import CIRCUIT_STATUS_CHOICES
from .models import Circuit, CircuitTermination, CircuitType, Provider
@@ -107,7 +106,11 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
)
site = FilterChoiceField(
queryset=Site.objects.all(),
to_field_name='slug'
to_field_name='slug',
widget=APISelect(
api_url="/api/dcim/sites/",
value_field="slug",
)
)
asn = forms.IntegerField(
required=False,
@@ -161,6 +164,16 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
'install_date': "Format: YYYY-MM-DD",
'commit_rate': "Committed rate",
}
widgets = {
'provider': APISelect(
api_url="/api/circuits/providers/"
),
'type': APISelect(
api_url="/api/circuits/circuit-types/"
),
'status': StaticSelect2(),
}
class CircuitCSVForm(forms.ModelForm):
@@ -209,20 +222,30 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
)
type = forms.ModelChoiceField(
queryset=CircuitType.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/circuits/circuit-types/"
)
)
provider = forms.ModelChoiceField(
queryset=Provider.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/circuits/providers/"
)
)
status = forms.ChoiceField(
choices=add_blank_choice(CIRCUIT_STATUS_CHOICES),
required=False,
initial=''
initial='',
widget=StaticSelect2()
)
tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/tenancy/tenants/"
)
)
commit_rate = forms.IntegerField(
required=False,
@@ -249,35 +272,43 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
label='Search'
)
type = FilterChoiceField(
queryset=CircuitType.objects.annotate(
filter_count=Count('circuits')
),
to_field_name='slug'
queryset=CircuitType.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/circuits/circuit-types/",
value_field="slug",
)
)
provider = FilterChoiceField(
queryset=Provider.objects.annotate(
filter_count=Count('circuits')
),
to_field_name='slug'
queryset=Provider.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/circuits/providers/",
value_field="slug",
)
)
status = AnnotatedMultipleChoiceField(
status = forms.MultipleChoiceField(
choices=CIRCUIT_STATUS_CHOICES,
annotate=Circuit.objects.all(),
annotate_field='status',
required=False
required=False,
widget=StaticSelect2Multiple()
)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(
filter_count=Count('circuits')
),
queryset=Tenant.objects.all(),
to_field_name='slug',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
)
site = FilterChoiceField(
queryset=Site.objects.annotate(
filter_count=Count('circuit_terminations')
),
to_field_name='slug'
queryset=Site.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
)
)
commit_rate = forms.IntegerField(
required=False,
@@ -304,4 +335,7 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
}
widgets = {
'term_side': forms.HiddenInput(),
'site': APISelect(
api_url="/api/dcim/sites/"
)
}

View File

@@ -35,13 +35,18 @@ from .exceptions import MissingFilterException
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
fields = (
(Cable, ['length_unit']),
(Device, ['face', 'status']),
(Cable, ['length_unit', 'status', 'type']),
(ConsolePort, ['connection_status']),
(Interface, ['connection_status', 'form_factor', 'mode']),
(Device, ['face', 'status']),
(DeviceType, ['subdevice_role']),
(FrontPort, ['type']),
(FrontPortTemplate, ['type']),
(Interface, ['form_factor', 'mode']),
(InterfaceTemplate, ['form_factor']),
(PowerPort, ['connection_status']),
(Rack, ['outer_unit', 'status', 'type', 'width']),
(RearPort, ['type']),
(RearPortTemplate, ['type']),
(Site, ['status']),
)
@@ -60,7 +65,7 @@ class CableTraceMixin(object):
# Initialize the path array
path = []
for near_end, cable, far_end in obj.trace():
for near_end, cable, far_end in obj.trace(follow_circuits=True):
# Serialize each object
serializer_a = get_serializer_for_model(near_end, prefix='Nested')

View File

@@ -82,6 +82,9 @@ IFACE_FF_100GE_CFP2 = 1510
IFACE_FF_100GE_CFP4 = 1520
IFACE_FF_100GE_CPAK = 1550
IFACE_FF_100GE_QSFP28 = 1600
IFACE_FF_200GE_CFP2 = 1650
IFACE_FF_200GE_QSFP56 = 1700
IFACE_FF_400GE_QSFP_DD = 1750
# Wireless
IFACE_FF_80211A = 2600
IFACE_FF_80211G = 2610
@@ -153,9 +156,12 @@ IFACE_FF_CHOICES = [
[IFACE_FF_40GE_QSFP_PLUS, 'QSFP+ (40GE)'],
[IFACE_FF_100GE_CFP, 'CFP (100GE)'],
[IFACE_FF_100GE_CFP2, 'CFP2 (100GE)'],
[IFACE_FF_200GE_CFP2, 'CFP2 (200GE)'],
[IFACE_FF_100GE_CFP4, 'CFP4 (100GE)'],
[IFACE_FF_100GE_CPAK, 'Cisco CPAK (100GE)'],
[IFACE_FF_100GE_QSFP28, 'QSFP28 (100GE)'],
[IFACE_FF_200GE_QSFP56, 'QSFP56 (200GE)'],
[IFACE_FF_400GE_QSFP_DD, 'QSFP-DD (400GE)'],
]
],
[
@@ -333,11 +339,14 @@ CABLE_TYPE_CAT5E = 1510
CABLE_TYPE_CAT6 = 1600
CABLE_TYPE_CAT6A = 1610
CABLE_TYPE_CAT7 = 1700
CABLE_TYPE_DAC_ACTIVE = 1800
CABLE_TYPE_DAC_PASSIVE = 1810
CABLE_TYPE_MMF_OM1 = 3010
CABLE_TYPE_MMF_OM2 = 3020
CABLE_TYPE_MMF_OM3 = 3030
CABLE_TYPE_MMF_OM4 = 3040
CABLE_TYPE_SMF = 3500
CABLE_TYPE_AOC = 3800
CABLE_TYPE_POWER = 5000
CABLE_TYPE_CHOICES = (
(
@@ -348,6 +357,8 @@ CABLE_TYPE_CHOICES = (
(CABLE_TYPE_CAT6, 'CAT6'),
(CABLE_TYPE_CAT6A, 'CAT6a'),
(CABLE_TYPE_CAT7, 'CAT7'),
(CABLE_TYPE_DAC_ACTIVE, 'Direct Attach Copper (Active)'),
(CABLE_TYPE_DAC_PASSIVE, 'Direct Attach Copper (Passive)'),
),
),
(
@@ -357,6 +368,7 @@ CABLE_TYPE_CHOICES = (
(CABLE_TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
(CABLE_TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
(CABLE_TYPE_SMF, 'Singlemode Fiber'),
(CABLE_TYPE_AOC, 'Active Optical Cabling (AOC)'),
),
),
(CABLE_TYPE_POWER, 'Power'),

View File

@@ -0,0 +1,5 @@
class LoopDetected(Exception):
"""
A loop has been detected while tracing a cable path.
"""
pass

View File

@@ -62,14 +62,14 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
choices=SITE_STATUS_CHOICES,
null_value=None
)
region_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
region_id = django_filters.NumberFilter(
method='filter_region',
field_name='pk',
label='Region (ID)',
)
region = django_filters.ModelMultipleChoiceFilter(
field_name='region__slug',
queryset=Region.objects.all(),
to_field_name='slug',
region = django_filters.CharFilter(
method='filter_region',
field_name='slug',
label='Region (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
@@ -108,6 +108,16 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
pass
return queryset.filter(qs_filter)
def filter_region(self, queryset, name, value):
try:
region = Region.objects.get(**{name: value})
except ObjectDoesNotExist:
return queryset.none()
return queryset.filter(
Q(region=region) |
Q(region__in=region.get_descendants())
)
class RackGroupFilter(django_filters.FilterSet):
q = django_filters.CharFilter(
@@ -750,6 +760,10 @@ class InterfaceFilter(django_filters.FilterSet):
"""
Not using DeviceComponentFilterSet for Interfaces because we need to check for VirtualChassis membership.
"""
q = django_filters.CharFilter(
method='search',
label='Search',
)
device = django_filters.CharFilter(
method='filter_device',
field_name='name',
@@ -796,6 +810,13 @@ class InterfaceFilter(django_filters.FilterSet):
model = Interface
fields = ['name', 'connection_status', 'form_factor', 'enabled', 'mtu', 'mgmt_only']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value)
).distinct()
def filter_device(self, queryset, name, value):
try:
device = Device.objects.get(**{name: value})

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@ from utilities.managers import NaturalOrderingManager
from utilities.models import ChangeLoggedModel
from utilities.utils import serialize_object, to_meters
from .constants import *
from .exceptions import LoopDetected
from .fields import ASNField, MACAddressField
from .managers import DeviceComponentManager, InterfaceManager
@@ -53,7 +54,11 @@ class ComponentModel(models.Model):
"""
Log an ObjectChange including the parent Device/VM.
"""
parent = self.device if self.device is not None else getattr(self, 'virtual_machine', None)
try:
parent = getattr(self, 'device', None) or getattr(self, 'virtual_machine', None)
except ObjectDoesNotExist:
# The parent device/VM has already been deleted
parent = None
ObjectChange(
user=user,
request_id=request_id,
@@ -88,7 +93,7 @@ class CableTermination(models.Model):
class Meta:
abstract = True
def trace(self, position=1, follow_circuits=False):
def trace(self, position=1, follow_circuits=False, cable_history=None):
"""
Return a list representing a complete cable path, with each individual segment represented as a three-tuple:
[
@@ -133,6 +138,13 @@ class CableTermination(models.Model):
if not self.cable:
return [(self, None, None)]
# Record cable history to detect loops
if cable_history is None:
cable_history = []
elif self.cable in cable_history:
raise LoopDetected()
cable_history.append(self.cable)
far_end = self.cable.termination_b if self.cable.termination_a == self else self.cable.termination_a
path = [(self, self.cable, far_end)]
@@ -140,7 +152,11 @@ class CableTermination(models.Model):
if peer_port is None:
return path
next_segment = peer_port.trace(position)
try:
next_segment = peer_port.trace(position, follow_circuits, cable_history)
except LoopDetected:
return path
if next_segment is None:
return path + [(peer_port, None, None)]
@@ -189,6 +205,13 @@ class Region(MPTTModel, ChangeLoggedModel):
self.parent.name if self.parent else None,
)
@property
def site_count(self):
return Site.objects.filter(
Q(region=self) |
Q(region__in=self.get_descendants())
).count()
#
# Sites
@@ -2539,52 +2562,55 @@ class Cable(ChangeLoggedModel):
def clean(self):
# Check that termination types are compatible
type_a = self.termination_a_type.model
type_b = self.termination_b_type.model
if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
raise ValidationError("Incompatible termination types: {} and {}".format(
self.termination_a_type, self.termination_b_type
))
if self.termination_a and self.termination_b:
# A termination point cannot be connected to itself
if self.termination_a == self.termination_b:
raise ValidationError("Cannot connect {} to itself".format(self.termination_a_type))
type_a = self.termination_a_type.model
type_b = self.termination_b_type.model
# A front port cannot be connected to its corresponding rear port
if (
type_a in ['frontport', 'rearport'] and
type_b in ['frontport', 'rearport'] and
(
getattr(self.termination_a, 'rear_port', None) == self.termination_b or
getattr(self.termination_b, 'rear_port', None) == self.termination_a
)
):
raise ValidationError("A front port cannot be connected to it corresponding rear port")
# Check that termination types are compatible
if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
raise ValidationError("Incompatible termination types: {} and {}".format(
self.termination_a_type, self.termination_b_type
))
# Check for an existing Cable connected to either termination object
if self.termination_a.cable not in (None, self):
raise ValidationError("{} already has a cable attached (#{})".format(
self.termination_a, self.termination_a.cable_id
))
if self.termination_b.cable not in (None, self):
raise ValidationError("{} already has a cable attached (#{})".format(
self.termination_b, self.termination_b.cable_id
))
# A termination point cannot be connected to itself
if self.termination_a == self.termination_b:
raise ValidationError("Cannot connect {} to itself".format(self.termination_a_type))
# Virtual interfaces cannot be connected
endpoint_a, endpoint_b, _ = self.get_path_endpoints()
if (
(
isinstance(endpoint_a, Interface) and
endpoint_a.form_factor == IFACE_FF_VIRTUAL
) or
(
isinstance(endpoint_b, Interface) and
endpoint_b.form_factor == IFACE_FF_VIRTUAL
)
):
raise ValidationError("Cannot connect to a virtual interface")
# A front port cannot be connected to its corresponding rear port
if (
type_a in ['frontport', 'rearport'] and
type_b in ['frontport', 'rearport'] and
(
getattr(self.termination_a, 'rear_port', None) == self.termination_b or
getattr(self.termination_b, 'rear_port', None) == self.termination_a
)
):
raise ValidationError("A front port cannot be connected to it corresponding rear port")
# Check for an existing Cable connected to either termination object
if self.termination_a.cable not in (None, self):
raise ValidationError("{} already has a cable attached (#{})".format(
self.termination_a, self.termination_a.cable_id
))
if self.termination_b.cable not in (None, self):
raise ValidationError("{} already has a cable attached (#{})".format(
self.termination_b, self.termination_b.cable_id
))
# Virtual interfaces cannot be connected
endpoint_a, endpoint_b, _ = self.get_path_endpoints()
if (
(
isinstance(endpoint_a, Interface) and
endpoint_a.form_factor == IFACE_FF_VIRTUAL
) or
(
isinstance(endpoint_b, Interface) and
endpoint_b.form_factor == IFACE_FF_VIRTUAL
)
):
raise ValidationError("Cannot connect to a virtual interface")
# Validate length and length_unit
if self.length is not None and self.length_unit is None:

View File

@@ -29,7 +29,8 @@ SITE_REGION_LINK = """
"""
COLOR_LABEL = """
<label class="label" style="background-color: #{{ record.color }}">{{ record }}</label>
{% load helpers %}
<label class="label" style="color: {{ record.color|fgcolor }}; background-color: #{{ record.color }}">{{ record }}</label>
"""
DEVICE_LINK = """

View File

@@ -1,3 +1,5 @@
import re
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.paginator import EmptyPage, PageNotAnInteger
@@ -50,7 +52,16 @@ class BulkRenameView(GetReturnURLMixin, View):
if form.is_valid():
for obj in selected_objects:
obj.new_name = obj.name.replace(form.cleaned_data['find'], form.cleaned_data['replace'])
find = form.cleaned_data['find']
replace = form.cleaned_data['replace']
if form.cleaned_data['use_regex']:
try:
obj.new_name = re.sub(find, replace, obj.name)
# Catch regex group reference errors
except re.error:
obj.new_name = obj.name
else:
obj.new_name = obj.name.replace(find, replace)
if '_apply' in request.POST:
for obj in selected_objects:
@@ -124,7 +135,7 @@ class BulkDisconnectView(GetReturnURLMixin, View):
#
class RegionListView(ObjectListView):
queryset = Region.objects.annotate(site_count=Count('sites'))
queryset = Region.objects.all()
filter = filters.RegionFilter
filter_form = forms.RegionFilterForm
table = tables.RegionTable
@@ -151,7 +162,7 @@ class RegionBulkImportView(PermissionRequiredMixin, BulkImportView):
class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_region'
queryset = Region.objects.annotate(site_count=Count('sites'))
queryset = Region.objects.all()
filter = filters.RegionFilter
table = tables.RegionTable
default_return_url = 'dcim:region_list'
@@ -1530,6 +1541,7 @@ class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateV
form = forms.DeviceBulkAddComponentForm
model = ConsolePort
model_form = forms.ConsolePortForm
filter = filters.DeviceFilter
table = tables.DeviceTable
default_return_url = 'dcim:device_list'
@@ -1541,6 +1553,7 @@ class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentC
form = forms.DeviceBulkAddComponentForm
model = ConsoleServerPort
model_form = forms.ConsoleServerPortForm
filter = filters.DeviceFilter
table = tables.DeviceTable
default_return_url = 'dcim:device_list'
@@ -1552,6 +1565,7 @@ class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateVie
form = forms.DeviceBulkAddComponentForm
model = PowerPort
model_form = forms.PowerPortForm
filter = filters.DeviceFilter
table = tables.DeviceTable
default_return_url = 'dcim:device_list'
@@ -1563,6 +1577,7 @@ class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateV
form = forms.DeviceBulkAddComponentForm
model = PowerOutlet
model_form = forms.PowerOutletForm
filter = filters.DeviceFilter
table = tables.DeviceTable
default_return_url = 'dcim:device_list'
@@ -1574,6 +1589,7 @@ class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateVie
form = forms.DeviceBulkAddInterfaceForm
model = Interface
model_form = forms.InterfaceForm
filter = filters.DeviceFilter
table = tables.DeviceTable
default_return_url = 'dcim:device_list'
@@ -1585,6 +1601,7 @@ class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateVie
form = forms.DeviceBulkAddComponentForm
model = DeviceBay
model_form = forms.DeviceBayForm
filter = filters.DeviceFilter
table = tables.DeviceTable
default_return_url = 'dcim:device_list'
@@ -1765,14 +1782,20 @@ class InterfaceConnectionsListView(ObjectListView):
def queryset_to_csv(self):
csv_data = [
# Headers
','.join(['device_a', 'interface_a', 'device_b', 'interface_b', 'connection_status'])
','.join([
'device_a', 'interface_a', 'interface_a_description',
'device_b', 'interface_b', 'interface_b_description',
'connection_status'
])
]
for obj in self.queryset:
csv = csv_format([
obj.connected_endpoint.device.identifier if obj.connected_endpoint else None,
obj.connected_endpoint.name if obj.connected_endpoint else None,
obj.connected_endpoint.description if obj.connected_endpoint else None,
obj.device.identifier,
obj.name,
obj.description,
obj.get_connection_status_display(),
])
csv_data.append(csv)

View File

@@ -99,10 +99,9 @@ class TopologyMapViewSet(ModelViewSet):
try:
data = tmap.render(img_format=img_format)
except Exception:
except Exception as e:
return HttpResponse(
"There was an error generating the requested graph. Ensure that the GraphViz executables have been "
"installed correctly."
"There was an error generating the requested graph: %s" % e
)
response = HttpResponse(data, content_type='image/{}'.format(img_format))

View File

@@ -7,19 +7,19 @@ urlpatterns = [
# Tags
url(r'^tags/$', views.TagListView.as_view(), name='tag_list'),
url(r'^tags/delete/$', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
url(r'^tags/(?P<slug>[\w-]+)/$', views.TagView.as_view(), name='tag'),
url(r'^tags/(?P<slug>[\w-]+)/edit/$', views.TagEditView.as_view(), name='tag_edit'),
url(r'^tags/(?P<slug>[\w-]+)/delete/$', views.TagDeleteView.as_view(), name='tag_delete'),
url(r'^tags/delete/$', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'),
# Config contexts
url(r'^config-contexts/$', views.ConfigContextListView.as_view(), name='configcontext_list'),
url(r'^config-contexts/add/$', views.ConfigContextCreateView.as_view(), name='configcontext_add'),
url(r'^config-contexts/edit/$', views.ConfigContextBulkEditView.as_view(), name='configcontext_bulk_edit'),
url(r'^config-contexts/delete/$', views.ConfigContextBulkDeleteView.as_view(), name='configcontext_bulk_delete'),
url(r'^config-contexts/(?P<pk>\d+)/$', views.ConfigContextView.as_view(), name='configcontext'),
url(r'^config-contexts/(?P<pk>\d+)/edit/$', views.ConfigContextEditView.as_view(), name='configcontext_edit'),
url(r'^config-contexts/(?P<pk>\d+)/delete/$', views.ConfigContextDeleteView.as_view(), name='configcontext_delete'),
url(r'^config-contexts/delete/$', views.ConfigContextBulkDeleteView.as_view(), name='configcontext_bulk_delete'),
# Image attachments
url(r'^image-attachments/(?P<pk>\d+)/edit/$', views.ImageAttachmentEditView.as_view(), name='imageattachment_edit'),

View File

@@ -82,7 +82,7 @@ class TagDeleteView(PermissionRequiredMixin, ObjectDeleteView):
class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'circuits.delete_circuittype'
permission_required = 'taggit.delete_tag'
queryset = Tag.objects.annotate(
items=Count('taggit_taggeditem_items')
).order_by(

View File

@@ -112,6 +112,10 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search',
label='Search',
)
prefix = django_filters.CharFilter(
method='filter_prefix',
label='Prefix',
)
within = django_filters.CharFilter(
method='search_within',
label='Within prefix',
@@ -197,6 +201,15 @@ class PrefixFilter(CustomFieldFilterSet, django_filters.FilterSet):
pass
return queryset.filter(qs_filter)
def filter_prefix(self, queryset, name, value):
if not value.strip():
return queryset
try:
query = str(netaddr.IPNetwork(value).cidr)
return queryset.filter(prefix=query)
except ValidationError:
return queryset.none()
def search_within(self, queryset, name, value):
value = value.strip()
if not value:

View File

@@ -1,7 +1,6 @@
from django import forms
from django.core.exceptions import MultipleObjectsReturned
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db.models import Count
from taggit.forms import TagField
from dcim.models import Site, Rack, Device, Interface
@@ -9,9 +8,9 @@ from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEdit
from tenancy.forms import TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
AnnotatedMultipleChoiceField, APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, ReturnURLForm,
SlugField, add_blank_choice,
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, SlugField,
StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
)
from virtualization.models import VirtualMachine
from .constants import (
@@ -77,7 +76,10 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm
)
tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/tenancy/tenants/"
)
)
enforce_unique = forms.NullBooleanField(
required=False,
@@ -102,11 +104,14 @@ class VRFFilterForm(BootstrapMixin, CustomFieldFilterForm):
label='Search'
)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(
filter_count=Count('vrfs')
),
queryset=Tenant.objects.all(),
to_field_name='slug',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
)
@@ -139,12 +144,8 @@ class RIRFilterForm(BootstrapMixin, forms.Form):
is_private = forms.NullBooleanField(
required=False,
label='Private',
widget=forms.Select(
choices=[
('', '---------'),
('True', 'Yes'),
('False', 'No'),
]
widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
@@ -168,6 +169,11 @@ class AggregateForm(BootstrapMixin, CustomFieldForm):
'rir': "Regional Internet Registry responsible for this prefix",
'date_added': "Format: YYYY-MM-DD",
}
widgets = {
'rir': APISelect(
api_url="/api/ipam/rirs/"
)
}
class AggregateCSVForm(forms.ModelForm):
@@ -193,7 +199,10 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
rir = forms.ModelChoiceField(
queryset=RIR.objects.all(),
required=False,
label='RIR'
label='RIR',
widget=APISelect(
api_url="/api/ipam/rirs/"
)
)
date_added = forms.DateField(
required=False
@@ -218,12 +227,17 @@ class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
family = forms.ChoiceField(
required=False,
choices=IP_FAMILY_CHOICES,
label='Address family'
label='Address family',
widget=StaticSelect2()
)
rir = FilterChoiceField(
queryset=RIR.objects.annotate(filter_count=Count('aggregates')),
queryset=RIR.objects.all(),
to_field_name='slug',
label='RIR'
label='RIR',
widget=APISelectMultiple(
api_url="/api/ipam/rirs/",
value_field="slug",
)
)
@@ -261,9 +275,13 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
queryset=Site.objects.all(),
required=False,
label='Site',
widget=forms.Select(
widget=APISelect(
api_url="/api/dcim/sites/",
filter_for={
'vlan_group': 'site_id',
'vlan': 'site_id',
},
attrs={
'filter-for': 'vlan_group',
'nullable': 'true',
}
)
@@ -276,9 +294,11 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
required=False,
label='VLAN group',
widget=APISelect(
api_url='/api/ipam/vlan-groups/?site_id={{site}}',
api_url='/api/ipam/vlan-groups/',
filter_for={
'vlan': 'group_id'
},
attrs={
'filter-for': 'vlan',
'nullable': 'true',
}
)
@@ -292,7 +312,7 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
required=False,
label='VLAN',
widget=APISelect(
api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}',
api_url='/api/ipam/vlans/',
display_field='display_name'
)
)
@@ -304,6 +324,15 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm):
'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'description', 'tenant_group', 'tenant',
'tags',
]
widgets = {
'vrf': APISelect(
api_url="/api/ipam/vrfs/"
),
'status': StaticSelect2(),
'role': APISelect(
api_url="/api/ipam/roles/"
)
}
def __init__(self, *args, **kwargs):
@@ -415,24 +444,42 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
)
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/dcim/sites/"
)
)
vrf = forms.ModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label='VRF'
label='VRF',
widget=APISelect(
api_url="/api/ipam/vrfs/"
)
)
prefix_length = forms.IntegerField(
min_value=1,
max_value=127,
required=False
)
tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/tenancy/tenants/"
)
)
status = forms.ChoiceField(
choices=add_blank_choice(PREFIX_STATUS_CHOICES),
required=False
required=False,
widget=StaticSelect2()
)
role = forms.ModelChoiceField(
queryset=Role.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/ipam/roles/"
)
)
is_pool = forms.NullBooleanField(
required=False,
@@ -468,47 +515,67 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
family = forms.ChoiceField(
required=False,
choices=IP_FAMILY_CHOICES,
label='Address family'
label='Address family',
widget=StaticSelect2()
)
mask_length = forms.ChoiceField(
required=False,
choices=PREFIX_MASK_LENGTH_CHOICES,
label='Mask length'
label='Mask length',
widget=StaticSelect2()
)
vrf = FilterChoiceField(
queryset=VRF.objects.annotate(
filter_count=Count('prefixes')
),
queryset=VRF.objects.all(),
to_field_name='rd',
label='VRF',
null_label='-- Global --'
null_label='-- Global --',
widget=APISelectMultiple(
api_url="/api/ipam/vrfs/",
value_field="slug",
null_option=True,
)
)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(
filter_count=Count('prefixes')
),
queryset=Tenant.objects.all(),
to_field_name='slug',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
)
status = AnnotatedMultipleChoiceField(
status = forms.MultipleChoiceField(
choices=PREFIX_STATUS_CHOICES,
annotate=Prefix.objects.all(),
annotate_field='status',
required=False
required=False,
widget=StaticSelect2Multiple()
)
site = FilterChoiceField(
queryset=Site.objects.annotate(
filter_count=Count('prefixes')
),
queryset=Site.objects.all(),
to_field_name='slug',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
null_option=True,
)
)
role = FilterChoiceField(
queryset=Role.objects.annotate(
filter_count=Count('prefixes')
),
queryset=Role.objects.all(),
to_field_name='slug',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/ipam/roles/",
value_field="slug",
null_option=True,
)
)
is_pool = forms.NullBooleanField(
required=False,
label='Is a pool',
widget=StaticSelect2(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
expand = forms.BooleanField(
required=False,
@@ -529,9 +596,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
queryset=Site.objects.all(),
required=False,
label='Site',
widget=forms.Select(
attrs={
'filter-for': 'nat_rack'
widget=APISelect(
api_url="/api/dcim/sites/",
filter_for={
'nat_rack': 'site_id',
'nat_device': 'site_id'
}
)
)
@@ -543,10 +612,12 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
required=False,
label='Rack',
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{nat_site}}',
api_url='/api/dcim/racks/',
display_field='display_name',
filter_for={
'nat_device': 'rack_id'
},
attrs={
'filter-for': 'nat_device',
'nullable': 'true'
}
)
@@ -560,9 +631,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
required=False,
label='Device',
widget=APISelect(
api_url='/api/dcim/devices/?site_id={{nat_site}}&rack_id={{nat_rack}}',
api_url='/api/dcim/devices/',
display_field='display_name',
attrs={'filter-for': 'nat_inside'}
filter_for={
'nat_inside': 'device_id'
}
)
)
nat_inside = ChainedModelChoiceField(
@@ -573,20 +646,10 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
required=False,
label='IP Address',
widget=APISelect(
api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}',
api_url='/api/ipam/ip-addresses/',
display_field='address'
)
)
livesearch = forms.CharField(
required=False,
label='Search',
widget=Livesearch(
query_key='q',
query_url='ipam-api:ipaddress-list',
field_to_update='nat_inside',
obj_label='address'
)
)
primary_for_parent = forms.BooleanField(
required=False,
label='Make this the primary IP for the device/VM'
@@ -601,6 +664,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site',
'nat_rack', 'nat_inside', 'tenant_group', 'tenant', 'tags',
]
widgets = {
'status': StaticSelect2(),
'role': StaticSelect2(),
'vrf': APISelect(
api_url="/api/ipam/vrfs/"
)
}
def __init__(self, *args, **kwargs):
@@ -680,6 +750,13 @@ class IPAddressBulkAddForm(BootstrapMixin, TenancyForm, CustomFieldForm):
fields = [
'address', 'vrf', 'status', 'role', 'description', 'tenant_group', 'tenant',
]
widgets = {
'status': StaticSelect2(),
'role': StaticSelect2(),
'vrf': APISelect(
api_url="/api/ipam/vrfs/"
)
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -817,19 +894,32 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
vrf = forms.ModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label='VRF'
label='VRF',
widget=APISelect(
api_url="/api/ipam/vrfs/"
)
)
mask_length = forms.IntegerField(
min_value=1,
max_value=128,
required=False
)
tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/tenancy/tenants/"
)
)
status = forms.ChoiceField(
choices=add_blank_choice(IPADDRESS_STATUS_CHOICES),
required=False
required=False,
widget=StaticSelect2()
)
role = forms.ChoiceField(
choices=add_blank_choice(IPADDRESS_ROLE_CHOICES),
required=False
required=False,
widget=StaticSelect2()
)
description = forms.CharField(
max_length=100, required=False
@@ -846,7 +936,10 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
queryset=VRF.objects.all(),
required=False,
label='VRF',
empty_label='Global'
empty_label='Global',
widget=APISelect(
api_url="/api/ipam/vrfs/"
)
)
address = forms.CharField(
label='IP Address'
@@ -871,39 +964,45 @@ class IPAddressFilterForm(BootstrapMixin, CustomFieldFilterForm):
family = forms.ChoiceField(
required=False,
choices=IP_FAMILY_CHOICES,
label='Address family'
label='Address family',
widget=StaticSelect2()
)
mask_length = forms.ChoiceField(
required=False,
choices=IPADDRESS_MASK_LENGTH_CHOICES,
label='Mask length'
label='Mask length',
widget=StaticSelect2()
)
vrf = FilterChoiceField(
queryset=VRF.objects.annotate(
filter_count=Count('ip_addresses')
),
queryset=VRF.objects.all(),
to_field_name='rd',
label='VRF',
null_label='-- Global --'
null_label='-- Global --',
widget=APISelectMultiple(
api_url="/api/ipam/vrfs/",
value_field="slug",
null_option=True,
)
)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(
filter_count=Count('ip_addresses')
),
queryset=Tenant.objects.all(),
to_field_name='slug',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
)
status = AnnotatedMultipleChoiceField(
status = forms.MultipleChoiceField(
choices=IPADDRESS_STATUS_CHOICES,
annotate=IPAddress.objects.all(),
annotate_field='status',
required=False
required=False,
widget=StaticSelect2Multiple()
)
role = AnnotatedMultipleChoiceField(
role = forms.MultipleChoiceField(
choices=IPADDRESS_ROLE_CHOICES,
annotate=IPAddress.objects.all(),
annotate_field='role',
required=False
required=False,
widget=StaticSelect2Multiple()
)
@@ -919,6 +1018,11 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm):
fields = [
'site', 'name', 'slug',
]
widgets = {
'site': APISelect(
api_url="/api/dcim/sites/"
)
}
class VLANGroupCSVForm(forms.ModelForm):
@@ -943,11 +1047,14 @@ class VLANGroupCSVForm(forms.ModelForm):
class VLANGroupFilterForm(BootstrapMixin, forms.Form):
site = FilterChoiceField(
queryset=Site.objects.annotate(
filter_count=Count('vlan_groups')
),
queryset=Site.objects.all(),
to_field_name='slug',
null_label='-- Global --'
null_label='-- Global --',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
null_option=True,
)
)
@@ -959,9 +1066,12 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
required=False,
widget=forms.Select(
widget=APISelect(
api_url="/api/dcim/sites/",
filter_for={
'group': 'site_id'
},
attrs={
'filter-for': 'group',
'nullable': 'true',
}
)
@@ -974,7 +1084,7 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
required=False,
label='Group',
widget=APISelect(
api_url='/api/ipam/vlan-groups/?site_id={{site}}',
api_url='/api/ipam/vlan-groups/',
)
)
tags = TagField(required=False)
@@ -992,6 +1102,12 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
'status': "Operational status of this VLAN",
'role': "The primary function of this VLAN",
}
widgets = {
'status': StaticSelect2(),
'role': APISelect(
api_url="/api/ipam/roles/"
)
}
class VLANCSVForm(forms.ModelForm):
@@ -1067,23 +1183,36 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
)
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/dcim/sites/"
)
)
group = forms.ModelChoiceField(
queryset=VLANGroup.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/ipam/vlan-groups/"
)
)
tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/tenancy/tenants/"
)
)
status = forms.ChoiceField(
choices=add_blank_choice(VLAN_STATUS_CHOICES),
required=False
required=False,
widget=StaticSelect2()
)
role = forms.ModelChoiceField(
queryset=Role.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/ipam/roles/"
)
)
description = forms.CharField(
max_length=100,
@@ -1103,38 +1232,48 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
label='Search'
)
site = FilterChoiceField(
queryset=Site.objects.annotate(
filter_count=Count('vlans')
),
queryset=Site.objects.all(),
to_field_name='slug',
null_label='-- Global --'
null_label='-- Global --',
widget=APISelectMultiple(
api_url="/api/dcim/sites/",
value_field="slug",
null_option=True,
)
)
group_id = FilterChoiceField(
queryset=VLANGroup.objects.annotate(
filter_count=Count('vlans')
),
queryset=VLANGroup.objects.all(),
label='VLAN group',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/ipam/vlan-groups/",
null_option=True,
)
)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(
filter_count=Count('vlans')
),
queryset=Tenant.objects.all(),
to_field_name='slug',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/tenancy/tenants/",
value_field="slug",
null_option=True,
)
)
status = AnnotatedMultipleChoiceField(
status = forms.MultipleChoiceField(
choices=VLAN_STATUS_CHOICES,
annotate=VLAN.objects.all(),
annotate_field='status',
required=False
required=False,
widget=StaticSelect2Multiple()
)
role = FilterChoiceField(
queryset=Role.objects.annotate(
filter_count=Count('vlans')
),
queryset=Role.objects.all(),
to_field_name='slug',
null_label='-- None --'
null_label='-- None --',
widget=APISelectMultiple(
api_url="/api/ipam/roles/",
value_field="slug",
null_option=True,
)
)
@@ -1156,6 +1295,10 @@ class ServiceForm(BootstrapMixin, CustomFieldForm):
'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
"reachable via all IPs assigned to the device.",
}
widgets = {
'protocol': StaticSelect2(),
'ipaddresses': StaticSelect2Multiple(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1182,10 +1325,11 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm):
)
protocol = forms.ChoiceField(
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
required=False
required=False,
widget=StaticSelect2Multiple()
)
port = forms.IntegerField(
required=False
required=False,
)
@@ -1196,7 +1340,8 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
)
protocol = forms.ChoiceField(
choices=add_blank_choice(IP_PROTOCOL_CHOICES),
required=False
required=False,
widget=StaticSelect2()
)
port = forms.IntegerField(
validators=[
@@ -1212,5 +1357,5 @@ class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
class Meta:
nullable_fields = [
'site', 'group', 'tenant', 'role', 'description',
'site', 'tenant', 'role', 'description',
]

View File

@@ -385,6 +385,15 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
self.description,
)
def _set_prefix_length(self, value):
"""
Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly,
e.g. for bulk editing.
"""
if self.prefix is not None:
self.prefix.prefixlen = value
prefix_length = property(fset=_set_prefix_length)
def get_status_class(self):
return STATUS_CHOICE_CLASSES[self.status]
@@ -429,12 +438,23 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()])
available_ips = prefix - child_ips
# Remove unusable IPs from non-pool prefixes
if not self.is_pool:
available_ips -= netaddr.IPSet([
netaddr.IPAddress(self.prefix.first),
netaddr.IPAddress(self.prefix.last),
])
# All IP addresses within a pool are considered usable
if self.is_pool:
return available_ips
# All IP addresses within a point-to-point prefix (IPv4 /31 or IPv6 /127) are considered usable
if (
self.family == 4 and self.prefix.prefixlen == 31 # RFC 3021
) or (
self.family == 6 and self.prefix.prefixlen == 127 # RFC 6164
):
return available_ips
# Omit first and last IP address from the available set
available_ips -= netaddr.IPSet([
netaddr.IPAddress(self.prefix.first),
netaddr.IPAddress(self.prefix.last),
])
return available_ips
@@ -630,6 +650,15 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
self.description,
)
def _set_mask_length(self, value):
"""
Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly,
e.g. for bulk editing.
"""
if self.address is not None:
self.address.prefixlen = value
mask_length = property(fset=_set_mask_length)
@property
def device(self):
if self.interface:

View File

@@ -15,6 +15,7 @@ OBJ_TYPE_CHOICES = (
('devicetype', 'Device types'),
('device', 'Devices'),
('virtualchassis', 'Virtual Chassis'),
('cable', 'Cables'),
)),
('IPAM', (
('vrf', 'VRFs'),

View File

@@ -22,7 +22,7 @@ except ImportError:
)
VERSION = '2.5.1'
VERSION = '2.5.4'
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -246,6 +246,14 @@ LOGIN_URL = '/{}login/'.format(BASE_PATH)
# Secrets
SECRETS_MIN_PUBKEY_SIZE = 2048
# Pagination
PER_PAGE_DEFAULTS = [
25, 50, 100, 250, 500, 1000
]
if PAGINATE_COUNT not in PER_PAGE_DEFAULTS:
PER_PAGE_DEFAULTS.append(PAGINATE_COUNT)
PER_PAGE_DEFAULTS = sorted(PER_PAGE_DEFAULTS)
# Django filters
FILTERS_NULL_CHOICE_LABEL = 'None'
FILTERS_NULL_CHOICE_VALUE = 'null'

View File

@@ -11,13 +11,13 @@ from circuits.filters import CircuitFilter, ProviderFilter
from circuits.models import Circuit, Provider
from circuits.tables import CircuitTable, ProviderTable
from dcim.filters import (
DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter
CableFilter, DeviceFilter, DeviceTypeFilter, RackFilter, RackGroupFilter, SiteFilter, VirtualChassisFilter
)
from dcim.models import (
Cable, ConsolePort, Device, DeviceType, Interface, PowerPort, Rack, RackGroup, Site, VirtualChassis
)
from dcim.tables import (
DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable
CableTable, DeviceDetailTable, DeviceTypeTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable
)
from extras.models import ObjectChange, ReportResult, TopologyMap
from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter
@@ -88,6 +88,12 @@ SEARCH_TYPES = OrderedDict((
'table': VirtualChassisTable,
'url': 'dcim:virtualchassis_list',
}),
('cable', {
'queryset': Cable.objects.all(),
'filter': CableFilter,
'table': CableTable,
'url': 'dcim:cable_list',
}),
# IPAM
('vrf', {
'queryset': VRF.objects.select_related('tenant'),

View File

@@ -120,6 +120,117 @@ input[name="pk"] {
margin-top: 0;
}
/* Color Selections */
.color-selection-aa1409 {
background-color: #aa1409;
color: #ffffff;
}
.color-selection-f44336 {
background-color: #f44336;
color: #ffffff;
}
.color-selection-e91e63 {
background-color: #e91e63;
color: #ffffff;
}
.color-selection-ffe4e1 {
background-color: #ffe4e1;
color: #000000;
}
.color-selection-ff66ff {
background-color: #ff66ff;
color: #ffffff;
}
.color-selection-9c27b0 {
background-color: #9c27b0;
color: #ffffff;
}
.color-selection-673ab7 {
background-color: #673ab7;
color: #ffffff;
}
.color-selection-3f51b5 {
background-color: #3f51b5;
color: #ffffff;
}
.color-selection-2196f3 {
background-color: #2196f3;
color: #ffffff;
}
.color-selection-03a9f4 {
background-color: #03a9f4;
color: #ffffff;
}
.color-selection-00bcd4 {
background-color: #00bcd4;
color: #ffffff;
}
.color-selection-009688 {
background-color: #009688;
color: #ffffff;
}
.color-selection-00ffff {
background-color: #00ffff;
color: #ffffff;
}
.color-selection-2f6a31 {
background-color: #2f6a31;
color: #ffffff;
}
.color-selection-4caf50 {
background-color: #4caf50;
color: #ffffff;
}
.color-selection-8bc34a {
background-color: #8bc34a;
color: #ffffff;
}
.color-selection-cddc39 {
background-color: #cddc39;
color: #000000;
}
.color-selection-ffeb3b {
background-color: #ffeb3b;
color: #000000;
}
.color-selection-ffc107 {
background-color: #ffc107;
color: #000000;
}
.color-selection-ff9800 {
background-color: #ff9800;
color: #ffffff;
}
.color-selection-ff5722 {
background-color: #ff5722;
color: #ffffff;
}
.color-selection-795548 {
background-color: #795548;
color: #ffffff;
}
.color-selection-c0c0c0 {
background-color: #c0c0c0;
color: #000000;
}
.color-selection-9e9e9e {
background-color: #9e9e9e;
color: #ffffff;
}
.color-selection-607d8b {
background-color: #607d8b;
color: #ffffff;
}
.color-selection-111111 {
background-color: #111111;
color: #ffffff;
}
.color-selection-ffffff {
background-color: #ffffff;
color: #000000;
}
/* Tables */
th.pk, td.pk {
padding-bottom: 6px;
@@ -140,6 +251,9 @@ table.attr-table td:nth-child(1) {
div.paginator {
margin-bottom: 20px;
}
div.paginator form {
margin-bottom: 6px;
}
nav ul.pagination {
margin-top: 0;
margin-bottom: 8px !important;

View File

@@ -1,5 +1,10 @@
$(document).ready(function() {
// Pagination
$('select#per_page').change(function() {
this.form.submit();
});
// "Toggle" checkbox for object lists (PK column)
$('input:checkbox.toggle').click(function() {
$(this).closest('table').find('input:checkbox[name=pk]').prop('checked', $(this).prop('checked'));
@@ -62,135 +67,223 @@ $(document).ready(function() {
form.submit();
});
// API select widget
$('select[filter-for]').change(function() {
// Resolve child field by ID specified in parent
var child_names = $(this).attr('filter-for');
var parent = this;
// allow more than one child
$.each(child_names.split(" "), function(_, child_name){
var child_field = $('#id_' + child_name);
var child_selected = child_field.val();
// Wipe out any existing options within the child field and create a default option
child_field.empty();
if (!child_field.attr('multiple')) {
child_field.append($("<option></option>").attr("value", "").text("---------"));
// Parse URLs which may contain variable refrences to other field values
function parseURL(url) {
var filter_regex = /\{\{([a-z_]+)\}\}/g;
var match;
var rendered_url = url;
var filter_field;
while (match = filter_regex.exec(url)) {
filter_field = $('#id_' + match[1]);
var custom_attr = $('option:selected', filter_field).attr('api-value');
if (custom_attr) {
rendered_url = rendered_url.replace(match[0], custom_attr);
} else if (filter_field.val()) {
rendered_url = rendered_url.replace(match[0], filter_field.val());
} else if (filter_field.attr('nullable') == 'true') {
rendered_url = rendered_url.replace(match[0], 'null');
}
}
return rendered_url
}
if ($(parent).val() || $(parent).attr('nullable') == 'true') {
var api_url = child_field.attr('api-url') + '&limit=0&brief=1';
var disabled_indicator = child_field.attr('disabled-indicator');
var initial_value = child_field.attr('initial');
var display_field = child_field.attr('display-field') || 'name';
// Determine the filter fields needed to make an API call
var filter_regex = /\{\{([a-z_]+)\}\}/g;
var match;
var rendered_url = api_url;
var filter_field;
while (match = filter_regex.exec(api_url)) {
filter_field = $('#id_' + match[1]);
var custom_attr = $('option:selected', filter_field).attr('api-value');
if (custom_attr) {
rendered_url = rendered_url.replace(match[0], custom_attr);
} else if (filter_field.val()) {
rendered_url = rendered_url.replace(match[0], filter_field.val());
} else if (filter_field.attr('nullable') == 'true') {
rendered_url = rendered_url.replace(match[0], '0');
}
}
// Account for any conditional URL append strings
$.each(child_field[0].attributes, function(index, attr){
if (attr.name.includes("data-url-conditional-append-")){
var conditional = attr.name.split("data-url-conditional-append-")[1].split("__");
var field = $("#id_" + conditional[0]);
var field_value = conditional[1];
if ($('option:selected', field).attr('api-value') === field_value){
rendered_url = rendered_url + attr.value;
}
}
})
// If all URL variables have been replaced, make the API call
if (rendered_url.search('{{') < 0) {
console.log(child_name + ": Fetching " + rendered_url);
$.ajax({
url: rendered_url,
dataType: 'json',
success: function(response, status) {
$.each(response.results, function(index, choice) {
var option = $("<option></option>").attr("value", choice.id).text(choice[display_field]);
if (disabled_indicator && choice[disabled_indicator] && choice.id != initial_value) {
option.attr("disabled", "disabled");
} else if (choice.id == child_selected) {
option.attr("selected", "selected");
}
child_field.append(option);
});
}
});
}
}
// Trigger change event in case the child field is the parent of another field
child_field.change();
});
// Assign color picker selection classes
function colorPickerClassCopy(data, container) {
if (data.element) {
$(container).addClass($(data.element).attr("class"));
}
return data.text;
}
// Color Picker
$('.netbox-select2-color-picker').select2({
allowClear: true,
placeholder: "---------",
theme: "bootstrap",
templateResult: colorPickerClassCopy,
templateSelection: colorPickerClassCopy
});
// Auto-complete tags
function split_tags(val) {
return val.split(/,\s*/);
}
$("#id_tags")
.on("keydown", function(event) {
if (event.keyCode === $.ui.keyCode.TAB &&
$(this).autocomplete("instance").menu.active) {
event.preventDefault();
}
})
.autocomplete({
source: function(request, response) {
$.ajax({
type: 'GET',
url: netbox_api_path + 'extras/tags/',
data: 'q=' + split_tags(request.term).pop(),
success: function(data) {
var choices = [];
$.each(data.results, function (index, choice) {
choices.push(choice.name);
});
response(choices);
// Static choice selection
$('.netbox-select2-static').select2({
allowClear: true,
placeholder: "---------",
theme: "bootstrap"
});
// API backed selection
// Includes live search and chained fields
// The `multiple` setting may be controled via a data-* attribute
$('.netbox-select2-api').select2({
allowClear: true,
placeholder: "---------",
theme: "bootstrap",
ajax: {
delay: 500,
url: function(params) {
var element = this[0];
var url = parseURL(element.getAttribute("data-url"));
if (url.includes("{{")) {
// URL is not fully rendered yet, abort the request
return false;
}
});
},
search: function() {
// Need 3 or more characters to begin searching
var term = split_tags(this.value).pop();
if (term.length < 3) {
return false;
}
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
var terms = split_tags(this.value);
// remove the current input
terms.pop();
// add the selected item
terms.push(ui.item.value);
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join(", ");
return false;
return url;
},
data: function(params) {
var element = this[0];
// Paging. Note that `params.page` indexes at 1
var offset = (params.page - 1) * 50 || 0;
// Base query params
var parameters = {
q: params.term,
brief: 1,
limit: 50,
offset: offset,
};
// filter-for fields from a chain
var attr_name = "data-filter-for-" + $(element).attr("name");
var form = $(element).closest('form');
var filter_for_elements = form.find("select[" + attr_name + "]");
filter_for_elements.each(function(index, filter_for_element) {
var param_name = $(filter_for_element).attr(attr_name);
var value = $(filter_for_element).val();
if (param_name && value) {
parameters[param_name] = value;
}
});
// Conditional query params
$.each(element.attributes, function(index, attr){
if (attr.name.includes("data-conditional-query-param-")){
var conditional = attr.name.split("data-conditional-query-param-")[1].split("__");
var field = $("#id_" + conditional[0]);
var field_value = conditional[1];
if ($('option:selected', field).attr('api-value') === field_value){
var _val = attr.value.split("=");
parameters[_val[0]] = _val[1];
}
}
});
// Additional query params
$.each(element.attributes, function(index, attr){
if (attr.name.includes("data-additional-query-param-")){
var param_name = attr.name.split("data-additional-query-param-")[1]
parameters[param_name] = attr.value;
}
});
// This will handle params with multiple values (i.e. for list filter forms)
return $.param(parameters, true);
},
processResults: function (data) {
var element = this.$element[0];
var results = $.map(data.results, function (obj) {
obj.text = obj[element.getAttribute('display-field')] || obj.name;
obj.id = obj[element.getAttribute('value-field')] || obj.id;
if(element.getAttribute('disabled-indicator') && obj[element.getAttribute('disabled-indicator')]) {
// The disabled-indicator equated to true, so we disable this option
obj.disabled = true;
}
return obj;
});
// Handle the null option
if (element.getAttribute('data-null-option')) {
var null_option = $(element).children()[0]
results.unshift({
id: null_option.value,
text: null_option.text
});
}
// Check if there are more results to page
var page = data.next !== null;
return {
results: results,
pagination: {
more: page
}
};
}
}
});
});
// API backed tags
var tags = $('#id_tags');
if (tags.length > 0 && tags.val().length > 0){
tags = $('#id_tags').val().split(/,\s*/);
} else {
tags = [];
}
tag_objs = $.map(tags, function (tag) {
return {
id: tag,
text: tag,
selected: true
}
});
// Replace the django issued text input with a select element
$('#id_tags').replaceWith('<select name="tags" id="id_tags" class="form-control"></select>');
$('#id_tags').select2({
tags: true,
data: tag_objs,
multiple: true,
allowClear: true,
placeholder: "Tags",
ajax: {
delay: 250,
url: "/api/extras/tags/",
data: function(params) {
// Paging. Note that `params.page` indexes at 1
var offset = (params.page - 1) * 50 || 0;
var parameters = {
q: params.term,
brief: 1,
limit: 50,
offset: offset,
};
return parameters;
},
processResults: function (data) {
var results = $.map(data.results, function (obj) {
return {
id: obj.name,
text: obj.name
}
});
// Check if there are more results to page
var page = data.next !== null;
return {
results: results,
pagination: {
more: page
}
};
}
}
});
$('#id_tags').closest('form').submit(function(event){
// django-taggit can only accept a single comma seperated string value
var value = $('#id_tags').val();
if (value.length > 0){
var final_tags = value.join(', ');
$('#id_tags').val(null).trigger('change');
var option = new Option(final_tags, final_tags, true, true);
$('#id_tags').append(option).trigger('change');
}
});
});

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,123 @@
Select2
=======
[![Build Status][travis-ci-image]][travis-ci-status]
Select2 is a jQuery-based replacement for select boxes. It supports searching,
remote data sets, and pagination of results.
To get started, checkout examples and documentation at
https://select2.org/
Use cases
---------
* Enhancing native selects with search.
* Enhancing native selects with a better multi-select interface.
* Loading data from JavaScript: easily load items via AJAX and have them
searchable.
* Nesting optgroups: native selects only support one level of nesting. Select2
does not have this restriction.
* Tagging: ability to add new items on the fly.
* Working with large, remote datasets: ability to partially load a dataset based
on the search term.
* Paging of large datasets: easy support for loading more pages when the results
are scrolled to the end.
* Templating: support for custom rendering of results and selections.
Browser compatibility
---------------------
* IE 8+
* Chrome 8+
* Firefox 10+
* Safari 3+
* Opera 10.6+
Select2 is automatically tested on the following browsers.
[![Sauce Labs Test Status][saucelabs-matrix]][saucelabs-status]
Usage
-----
You can source Select2 directly from a CDN like [JSDliver][jsdelivr] or
[CDNJS][cdnjs], [download it from this GitHub repo][releases], or use one of
the integrations below.
Integrations
------------
Third party developers have created plugins for platforms which allow Select2 to be integrated more natively and quickly. For many platforms, additional plugins are not required because Select2 acts as a standard `<select>` box.
Plugins
* [Django]
- [django-autocomplete-light]
- [django-easy-select2]
- [django-select2]
* [Meteor] - [meteor-select2]
* [Ruby on Rails][ruby-on-rails] - [select2-rails]
* [Wicket] - [wicketstuff-select2]
* [Yii 2][yii2] - [yii2-widget-select2]
Themes
- [Bootstrap 3][bootstrap3] - [select2-bootstrap-theme]
- [Flat UI][flat-ui] - [select2-flat-theme]
- [Metro UI][metro-ui] - [select2-metro]
Missing an integration? Modify this `README` and make a pull request back here to Select2 on GitHub.
Internationalization (i18n)
---------------------------
Select2 supports multiple languages by simply including the right language JS
file (`dist/js/i18n/it.js`, `dist/js/i18n/nl.js`, etc.) after
`dist/js/select2.js`.
Missing a language? Just copy `src/js/select2/i18n/en.js`, translate it, and
make a pull request back to Select2 here on GitHub.
Documentation
-------------
The documentation for Select2 is available
[through GitHub Pages][documentation] and is located within this repository
in the [`docs` folder][documentation-folder].
Community
---------
You can find out about the different ways to get in touch with the Select2
community at the [Select2 community page][community].
Copyright and license
---------------------
The license is available within the repository in the [LICENSE][license] file.
[cdnjs]: http://www.cdnjs.com/libraries/select2
[community]: https://select2.org/getting-help
[documentation]: https://select2.org
[documentation-folder]: https://github.com/select2/select2/tree/master/docs
[freenode]: https://freenode.net/
[jsdelivr]: http://www.jsdelivr.com/#!select2
[license]: LICENSE.md
[releases]: https://github.com/select2/select2/releases
[saucelabs-matrix]: https://saucelabs.com/browser-matrix/select2.svg
[saucelabs-status]: https://saucelabs.com/u/select2
[travis-ci-image]: https://img.shields.io/travis/select2/select2/master.svg
[travis-ci-status]: https://travis-ci.org/select2/select2
[bootstrap3]: https://getbootstrap.com/
[django]: https://www.djangoproject.com/
[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
[django-easy-select2]: https://github.com/asyncee/django-easy-select2
[django-select2]: https://github.com/applegrew/django-select2
[flat-ui]: http://designmodo.github.io/Flat-UI/
[meteor]: https://www.meteor.com/
[meteor-select2]: https://github.com/nate-strauser/meteor-select2
[metro-ui]: http://metroui.org.ua/
[select2-metro]: http://metroui.org.ua/select2.html
[ruby-on-rails]: http://rubyonrails.org/
[select2-bootstrap-theme]: https://github.com/select2/select2-bootstrap-theme
[select2-flat-theme]: https://github.com/techhysahil/select2-Flat_Theme
[select2-rails]: https://github.com/argerim/select2-rails
[vue.js]: http://vuejs.org/
[select2-vue]: http://vuejs.org/examples/select2.html
[wicket]: https://wicket.apache.org/
[wicketstuff-select2]: https://github.com/wicketstuff/core/tree/master/select2-parent
[yii2]: http://www.yiiframework.com/
[yii2-widget-select2]: https://github.com/kartik-v/yii2-widget-select2

View File

@@ -0,0 +1,484 @@
.select2-container {
box-sizing: border-box;
display: inline-block;
margin: 0;
position: relative;
vertical-align: middle; }
.select2-container .select2-selection--single {
box-sizing: border-box;
cursor: pointer;
display: block;
height: 28px;
user-select: none;
-webkit-user-select: none; }
.select2-container .select2-selection--single .select2-selection__rendered {
display: block;
padding-left: 8px;
padding-right: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
.select2-container .select2-selection--single .select2-selection__clear {
position: relative; }
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
padding-right: 8px;
padding-left: 20px; }
.select2-container .select2-selection--multiple {
box-sizing: border-box;
cursor: pointer;
display: block;
min-height: 32px;
user-select: none;
-webkit-user-select: none; }
.select2-container .select2-selection--multiple .select2-selection__rendered {
display: inline-block;
overflow: hidden;
padding-left: 8px;
text-overflow: ellipsis;
white-space: nowrap; }
.select2-container .select2-search--inline {
float: left; }
.select2-container .select2-search--inline .select2-search__field {
box-sizing: border-box;
border: none;
font-size: 100%;
margin-top: 5px;
padding: 0; }
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none; }
.select2-dropdown {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
box-sizing: border-box;
display: block;
position: absolute;
left: -100000px;
width: 100%;
z-index: 1051; }
.select2-results {
display: block; }
.select2-results__options {
list-style: none;
margin: 0;
padding: 0; }
.select2-results__option {
padding: 6px;
user-select: none;
-webkit-user-select: none; }
.select2-results__option[aria-selected] {
cursor: pointer; }
.select2-container--open .select2-dropdown {
left: 0; }
.select2-container--open .select2-dropdown--above {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--open .select2-dropdown--below {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-search--dropdown {
display: block;
padding: 4px; }
.select2-search--dropdown .select2-search__field {
padding: 4px;
width: 100%;
box-sizing: border-box; }
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none; }
.select2-search--dropdown.select2-search--hide {
display: none; }
.select2-close-mask {
border: 0;
margin: 0;
padding: 0;
display: block;
position: fixed;
left: 0;
top: 0;
min-height: 100%;
min-width: 100%;
height: auto;
width: auto;
opacity: 0;
z-index: 99;
background-color: #fff;
filter: alpha(opacity=0); }
.select2-hidden-accessible {
border: 0 !important;
clip: rect(0 0 0 0) !important;
height: 1px !important;
margin: -1px !important;
overflow: hidden !important;
padding: 0 !important;
position: absolute !important;
width: 1px !important; }
.select2-container--default .select2-selection--single {
background-color: #fff;
border: 1px solid #aaa;
border-radius: 4px; }
.select2-container--default .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px; }
.select2-container--default .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold; }
.select2-container--default .select2-selection--single .select2-selection__placeholder {
color: #999; }
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px; }
.select2-container--default .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0; }
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left; }
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 1px;
right: auto; }
.select2-container--default.select2-container--disabled .select2-selection--single {
background-color: #eee;
cursor: default; }
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
display: none; }
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px; }
.select2-container--default .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text; }
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 5px;
width: 100%; }
.select2-container--default .select2-selection--multiple .select2-selection__rendered li {
list-style: none; }
.select2-container--default .select2-selection--multiple .select2-selection__placeholder {
color: #999;
margin-top: 5px;
float: left; }
.select2-container--default .select2-selection--multiple .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-top: 5px;
margin-right: 10px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
float: right; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto; }
.select2-container--default.select2-container--focus .select2-selection--multiple {
border: solid black 1px;
outline: 0; }
.select2-container--default.select2-container--disabled .select2-selection--multiple {
background-color: #eee;
cursor: default; }
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
display: none; }
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--default .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa; }
.select2-container--default .select2-search--inline .select2-search__field {
background: transparent;
border: none;
outline: 0;
box-shadow: none;
-webkit-appearance: textfield; }
.select2-container--default .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto; }
.select2-container--default .select2-results__option[role=group] {
padding: 0; }
.select2-container--default .select2-results__option[aria-disabled=true] {
color: #999; }
.select2-container--default .select2-results__option[aria-selected=true] {
background-color: #ddd; }
.select2-container--default .select2-results__option .select2-results__option {
padding-left: 1em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
margin-left: -1em;
padding-left: 2em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -2em;
padding-left: 3em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -3em;
padding-left: 4em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -4em;
padding-left: 5em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -5em;
padding-left: 6em; }
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #5897fb;
color: white; }
.select2-container--default .select2-results__group {
cursor: default;
display: block;
padding: 6px; }
.select2-container--classic .select2-selection--single {
background-color: #f7f7f7;
border: 1px solid #aaa;
border-radius: 4px;
outline: 0;
background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
.select2-container--classic .select2-selection--single:focus {
border: 1px solid #5897fb; }
.select2-container--classic .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px; }
.select2-container--classic .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-right: 10px; }
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
color: #999; }
.select2-container--classic .select2-selection--single .select2-selection__arrow {
background-color: #ddd;
border: none;
border-left: 1px solid #aaa;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0; }
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left; }
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
border: none;
border-right: 1px solid #aaa;
border-radius: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
left: 1px;
right: auto; }
.select2-container--classic.select2-container--open .select2-selection--single {
border: 1px solid #5897fb; }
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
background: transparent;
border: none; }
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px; }
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
.select2-container--classic .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text;
outline: 0; }
.select2-container--classic .select2-selection--multiple:focus {
border: 1px solid #5897fb; }
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
list-style: none;
margin: 0;
padding: 0 5px; }
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
display: none; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
color: #888;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #555; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
float: right; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto; }
.select2-container--classic.select2-container--open .select2-selection--multiple {
border: 1px solid #5897fb; }
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--classic .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa;
outline: 0; }
.select2-container--classic .select2-search--inline .select2-search__field {
outline: 0;
box-shadow: none; }
.select2-container--classic .select2-dropdown {
background-color: white;
border: 1px solid transparent; }
.select2-container--classic .select2-dropdown--above {
border-bottom: none; }
.select2-container--classic .select2-dropdown--below {
border-top: none; }
.select2-container--classic .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto; }
.select2-container--classic .select2-results__option[role=group] {
padding: 0; }
.select2-container--classic .select2-results__option[aria-disabled=true] {
color: grey; }
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
background-color: #3875d7;
color: white; }
.select2-container--classic .select2-results__group {
cursor: default;
display: block;
padding: 6px; }
.select2-container--classic.select2-container--open .select2-dropdown {
border-color: #5897fb; }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/af",[],function(){return{errorLoading:function(){return"Die resultate kon nie gelaai word nie."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Verwyders asseblief "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Voer asseblief "+t+" of meer karakters";return n},loadingMore:function(){return"Meer resultate word gelaai…"},maximumSelected:function(e){var t="Kies asseblief net "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"Geen resultate gevind"},searching:function(){return"Besig…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ar",[],function(){return{errorLoading:function(){return"لا يمكن تحميل النتائج"},inputTooLong:function(e){var t=e.input.length-e.maximum;return"الرجاء حذف "+t+" عناصر"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"الرجاء إضافة "+t+" عناصر"},loadingMore:function(){return"جاري تحميل نتائج إضافية..."},maximumSelected:function(e){return"تستطيع إختيار "+e.maximum+" بنود فقط"},noResults:function(){return"لم يتم العثور على أي نتائج"},searching:function(){return"جاري البحث…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/az",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum;return t+" simvol silin"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(e){return"Sadəcə "+e.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bg",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Моля въведете с "+t+" по-малко символ";return t>1&&(n+="a"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Моля въведете още "+t+" символ";return t>1&&(n+="a"),n},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(e){var t="Можете да направите до "+e.maximum+" ";return e.maximum>1?t+="избора":t+="избор",t},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/bs",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspijelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Si us plau, elimina "+t+" car";return t==1?n+="àcter":n+="àcters",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Si us plau, introdueix "+t+" car";return t==1?n+="àcter":n+="àcters",n},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var t="Només es pot seleccionar "+e.maximum+" element";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/cs",[],function(){function e(e,t){switch(e){case 2:return t?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadejte o jeden znak méně.":n<=4?"Prosím, zadejte o "+e(n,!0)+" znaky méně.":"Prosím, zadejte o "+n+" znaků méně."},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadejte ještě jeden znak.":n<=4?"Prosím, zadejte ještě další "+e(n,!0)+" znaky.":"Prosím, zadejte ještě dalších "+n+" znaků."},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(t){var n=t.maximum;return n==1?"Můžete zvolit jen jednu položku.":n<=4?"Můžete zvolit maximálně "+e(n,!1)+" položky.":"Můžete zvolit maximálně "+n+" položek."},noResults:function(){return"Nenalezeny žádné položky."},searching:function(){return"Vyhledávání…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Angiv venligst "+t+" tegn mindre"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Angiv venligst "+t+" tegn mere"},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var t="Du kan kun vælge "+e.maximum+" emne";return e.maximum!=1&&(t+="r"),t},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/de",[],function(){return{errorLoading:function(){return"Die Ergebnisse konnten nicht geladen werden."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Bitte "+t+" Zeichen weniger eingeben"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Bitte "+t+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var t="Sie können nur "+e.maximum+" Eintr";return e.maximum===1?t+="ag":t+="äge",t+=" auswählen",t},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/dsb",[],function(){var e=["znamuško","znamušce","znamuška","znamuškow"],t=["zapisk","zapiska","zapiski","zapiskow"],n=function(t,n){if(t===1)return n[0];if(t===2)return n[1];if(t>2&&t<=4)return n[2];if(t>=5)return n[3]};return{errorLoading:function(){return"Wuslědki njejsu se dali zacytaś."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Pšosym lašuj "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Pšosym zapódaj nanejmjenjej "+r+" "+n(r,e)},loadingMore:function(){return"Dalšne wuslědki se zacytaju…"},maximumSelected:function(e){return"Móžoš jano "+e.maximum+" "+n(e.maximum,t)+"wubraś."},noResults:function(){return"Žedne wuslědki namakane"},searching:function(){return"Pyta se…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Παρακαλώ διαγράψτε "+t+" χαρακτήρ";return t==1&&(n+="α"),t!=1&&(n+="ες"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Παρακαλώ συμπληρώστε "+t+" ή περισσότερους χαρακτήρες";return n},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(e){var t="Μπορείτε να επιλέξετε μόνο "+e.maximum+" επιλογ";return e.maximum==1&&(t+="ή"),e.maximum!=1&&(t+="ές"),t},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Please delete "+t+" character";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Please enter "+t+" or more characters";return n},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var t="You can only select "+e.maximum+" item";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"No se pudieron cargar los resultados"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor, elimine "+t+" car";return t==1?n+="ácter":n+="acteres",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Por favor, introduzca "+t+" car";return t==1?n+="ácter":n+="acteres",n},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var t="Sólo puede seleccionar "+e.maximum+" elemento";return e.maximum!=1&&(t+="s"),t},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" vähem",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Sisesta "+t+" täht";return t!=1&&(n+="e"),n+=" rohkem",n},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var t="Saad vaid "+e.maximum+" tulemus";return e.maximum==1?t+="e":t+="t",t+=" valida",t},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gutxiago",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return t==1?n+="karaktere bat":n+=t+" karaktere",n+=" gehiago",n},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return e.maximum===1?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="لطفاً "+t+" کاراکتر را حذف نمایید";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لطفاً تعداد "+t+" کاراکتر یا بیشتر وارد نمایید";return n},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(e){var t="شما تنها می‌توانید "+e.maximum+" آیتم را انتخاب نمایید";return t},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fi",[],function(){return{errorLoading:function(){return"Tuloksia ei saatu ladattua."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Ole hyvä ja anna "+t+" merkkiä vähemmän"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Ole hyvä ja anna "+t+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(e){return"Voit valita ainoastaan "+e.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){return"Haetaan…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Supprimez "+t+" caractère"+(t>1)?"s":""},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Saisissez au moins "+t+" caractère"+(t>1)?"s":""},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){return"Vous pouvez seulement sélectionner "+e.maximum+" élément"+(e.maximum>1)?"s":""},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/gl",[],function(){return{errorLoading:function(){return"Non foi posíbel cargar os resultados."},inputTooLong:function(e){var t=e.input.length-e.maximum;return t===1?"Elimine un carácter":"Elimine "+t+" caracteres"},inputTooShort:function(e){var t=e.minimum-e.input.length;return t===1?"Engada un carácter":"Engada "+t+" caracteres"},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){return e.maximum===1?"Só pode seleccionar un elemento":"Só pode seleccionar "+e.maximum+" elementos"},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="נא למחוק ";return t===1?n+="תו אחד":n+=t+" תווים",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="נא להכניס ";return t===1?n+="תו אחד":n+=t+" תווים",n+=" או יותר",n},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(e){var t="באפשרותך לבחור עד ";return e.maximum===1?t+="פריט אחד":t+=e.maximum+" פריטים",t},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" अक्षर को हटा दें";return t>1&&(n=t+" अक्षरों को हटा दें "),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="कृपया "+t+" या अधिक अक्षर दर्ज करें";return n},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(e){var t="आप केवल "+e.maximum+" आइटम का चयन कर सकते हैं";return t},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hr",[],function(){function e(e){var t=" "+e+" znak";return e%10<5&&e%10>0&&(e%100<5||e%100>19)?e%10>1&&(t+="a"):t+="ova",t}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Unesite "+e(n)},inputTooShort:function(t){var n=t.minimum-t.input.length;return"Unesite još "+e(n)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(e){return"Maksimalan broj odabranih stavki je "+e.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hsb",[],function(){var e=["znamješko","znamješce","znamješka","znamješkow"],t=["zapisk","zapiskaj","zapiski","zapiskow"],n=function(t,n){if(t===1)return n[0];if(t===2)return n[1];if(t>2&&t<=4)return n[2];if(t>=5)return n[3]};return{errorLoading:function(){return"Wuslědki njedachu so začitać."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Prošu zhašej "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Prošu zapodaj znajmjeńša "+r+" "+n(r,e)},loadingMore:function(){return"Dalše wuslědki so začitaja…"},maximumSelected:function(e){return"Móžeš jenož "+e.maximum+" "+n(e.maximum,t)+"wubrać"},noResults:function(){return"Žane wuslědki namakane"},searching:function(){return"Pyta so…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hu",[],function(){return{errorLoading:function(){return"Az eredmények betöltése nem sikerült."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Túl hosszú. "+t+" karakterrel több, mint kellene."},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Túl rövid. Még "+t+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/hy",[],function(){return{errorLoading:function(){return"Արդյունքները հնարավոր չէ բեռնել։"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Խնդրում ենք հեռացնել "+t+" նշան";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Խնդրում ենք մուտքագրել "+t+" կամ ավել նշաններ";return n},loadingMore:function(){return"Բեռնվում են նոր արդյունքներ․․․"},maximumSelected:function(e){var t="Դուք կարող եք ընտրել առավելագույնը "+e.maximum+" կետ";return t},noResults:function(){return"Արդյունքներ չեն գտնվել"},searching:function(){return"Որոնում․․․"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Hapuskan "+t+" huruf"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Masukkan "+t+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(e){return"Anda hanya dapat memilih "+e.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/is",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vinsamlegast styttið texta um "+t+" staf";return t<=1?n:n+"i"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vinsamlegast skrifið "+t+" staf";return t>1&&(n+="i"),n+=" í viðbót",n},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(e){return"Þú getur aðeins valið "+e.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Per favore cancella "+t+" caratter";return t!==1?n+="i":n+="e",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Per favore inserisci "+t+" o più caratteri";return n},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var t="Puoi selezionare solo "+e.maximum+" element";return e.maximum!==1?t+="i":t+="o",t},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" 文字を削除してください";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="少なくとも "+t+" 文字を入力してください";return n},loadingMore:function(){return"読み込み中…"},maximumSelected:function(e){var t=e.maximum+" 件しか選択できません";return t},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="សូមលុបចេញ "+t+" អក្សរ";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="សូមបញ្ចូល"+t+" អក្សរ រឺ ច្រើនជាងនេះ";return n},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(e){var t="អ្នកអាចជ្រើសរើសបានតែ "+e.maximum+" ជម្រើសប៉ុណ្ណោះ";return t},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="너무 깁니다. "+t+" 글자 지워주세요.";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="너무 짧습니다. "+t+" 글자 더 입력해주세요.";return n},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(e){var t="최대 "+e.maximum+"개까지만 선택 가능합니다.";return t},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lt",[],function(){function e(e,t,n,r){return e%10===1&&(e%100<11||e%100>19)?t:e%10>=2&&e%10<=9&&(e%100<11||e%100>19)?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Pašalinkite "+n+" simbol";return r+=e(n,"į","ius","ių"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Įrašykite dar "+n+" simbol";return r+=e(n,"į","ius","ių"),r},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(t){var n="Jūs galite pasirinkti tik "+t.maximum+" element";return n+=e(t.maximum,"ą","us","ų"),n},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/lv",[],function(){function e(e,t,n,r){return e===11?t:e%10===1?n:r}return{inputTooLong:function(t){var n=t.input.length-t.maximum,r="Lūdzu ievadiet par "+n;return r+=" simbol"+e(n,"iem","u","iem"),r+" mazāk"},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Lūdzu ievadiet vēl "+n;return r+=" simbol"+e(n,"us","u","us"),r},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(t){var n="Jūs varat izvēlēties ne vairāk kā "+t.maximum;return n+=" element"+e(t.maximum,"us","u","us"),n},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/mk",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Ве молиме внесете "+e.maximum+" помалку карактер";return e.maximum!==1&&(n+="и"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Ве молиме внесете уште "+e.maximum+" карактер";return e.maximum!==1&&(n+="и"),n},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(e){var t="Можете да изберете само "+e.maximum+" ставк";return e.maximum===1?t+="а":t+="и",t},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Sila hapuskan "+t+" aksara"},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Sila masukkan "+t+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(e){return"Anda hanya boleh memilih "+e.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Vennligst fjern "+t+" tegn"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vennligst skriv inn "+t+" tegn til";return n+" tegn til"},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Gelieve "+t+" karakters te verwijderen";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Gelieve "+t+" of meer karakters in te voeren";return n},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var t=e.maximum==1?"kan":"kunnen",n="Er "+t+" maar "+e.maximum+" item";return e.maximum!=1&&(n+="s"),n+=" worden geselecteerd",n},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pl",[],function(){var e=["znak","znaki","znaków"],t=["element","elementy","elementów"],n=function(t,n){if(t===1)return n[0];if(t>1&&t<=4)return n[1];if(t>=5)return n[2]};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(t){var r=t.input.length-t.maximum;return"Usuń "+r+" "+n(r,e)},inputTooShort:function(t){var r=t.minimum-t.input.length;return"Podaj przynajmniej "+r+" "+n(r,e)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(e){return"Możesz zaznaczyć tylko "+e.maximum+" "+n(e.maximum,t)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ps",[],function(){return{errorLoading:function(){return"پايلي نه سي ترلاسه کېدای"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="د مهربانۍ لمخي "+t+" توری ړنګ کړئ";return t!=1&&(n=n.replace("توری","توري")),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="لږ تر لږه "+t+" يا ډېر توري وليکئ";return n},loadingMore:function(){return"نوري پايلي ترلاسه کيږي..."},maximumSelected:function(e){var t="تاسو يوازي "+e.maximum+" قلم په نښه کولای سی";return e.maximum!=1&&(t=t.replace("قلم","قلمونه")),t},noResults:function(){return"پايلي و نه موندل سوې"},searching:function(){return"لټول کيږي..."}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Apague "+t+" caracter";return t!=1&&(n+="es"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Digite "+t+" ou mais caracteres";return n},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var t="Você só pode selecionar "+e.maximum+" ite";return e.maximum==1?t+="m":t+="ns",t},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Por favor apague "+t+" ";return n+=t!=1?"caracteres":"caractere",n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Introduza "+t+" ou mais caracteres";return n},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var t="Apenas pode seleccionar "+e.maximum+" ";return t+=e.maximum!=1?"itens":"item",t},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ro",[],function(){return{errorLoading:function(){return"Rezultatele nu au putut fi incărcate."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vă rugăm să ștergeți"+t+" caracter";return t!==1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vă rugăm să introduceți "+t+" sau mai multe caractere";return n},loadingMore:function(){return"Se încarcă mai multe rezultate…"},maximumSelected:function(e){var t="Aveți voie să selectați cel mult "+e.maximum;return t+=" element",e.maximum!==1&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/ru",[],function(){function e(e,t,n,r){return e%10<5&&e%10>0&&e%100<5||e%100>20?e%10>1?n:t:r}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Пожалуйста, введите на "+n+" символ";return r+=e(n,"","a","ов"),r+=" меньше",r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Пожалуйста, введите еще хотя бы "+n+" символ";return r+=e(n,"","a","ов"),r},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(t){var n="Вы можете выбрать не более "+t.maximum+" элемент";return n+=e(t.maximum,"","a","ов"),n},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{errorLoading:function(){return"Výsledky sa nepodarilo načítať."},inputTooLong:function(t){var n=t.input.length-t.maximum;return n==1?"Prosím, zadajte o jeden znak menej":n>=2&&n<=4?"Prosím, zadajte o "+e[n](!0)+" znaky menej":"Prosím, zadajte o "+n+" znakov menej"},inputTooShort:function(t){var n=t.minimum-t.input.length;return n==1?"Prosím, zadajte ešte jeden znak":n<=4?"Prosím, zadajte ešte ďalšie "+e[n](!0)+" znaky":"Prosím, zadajte ešte ďalších "+n+" znakov"},loadingMore:function(){return"Načítanie ďalších výsledkov…"},maximumSelected:function(t){return t.maximum==1?"Môžete zvoliť len jednu položku":t.maximum>=2&&t.maximum<=4?"Môžete zvoliť najviac "+e[t.maximum](!1)+" položky":"Môžete zvoliť najviac "+t.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sl",[],function(){return{errorLoading:function(){return"Zadetkov iskanja ni bilo mogoče naložiti."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Prosim zbrišite "+t+" znak";return t==2?n+="a":t!=1&&(n+="e"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Prosim vpišite še "+t+" znak";return t==2?n+="a":t!=1&&(n+="e"),n},loadingMore:function(){return"Nalagam več zadetkov…"},maximumSelected:function(e){var t="Označite lahko največ "+e.maximum+" predmet";return e.maximum==2?t+="a":e.maximum!=1&&(t+="e"),t},noResults:function(){return"Ni zadetkov."},searching:function(){return"Iščem…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr-Cyrl",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Обришите "+n+" симбол";return r+=e(n,"","а","а"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Укуцајте бар још "+n+" симбол";return r+=e(n,"","а","а"),r},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(t){var n="Можете изабрати само "+t.maximum+" ставк";return n+=e(t.maximum,"у","е","и"),n},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sr",[],function(){function e(e,t,n,r){return e%10==1&&e%100!=11?t:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?n:r}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(t){var n=t.input.length-t.maximum,r="Obrišite "+n+" simbol";return r+=e(n,"","a","a"),r},inputTooShort:function(t){var n=t.minimum-t.input.length,r="Ukucajte bar još "+n+" simbol";return r+=e(n,"","a","a"),r},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(t){var n="Možete izabrati samo "+t.maximum+" stavk";return n+=e(t.maximum,"u","e","i"),n},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vänligen sudda ut "+t+" tecken";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vänligen skriv in "+t+" eller fler tecken";return n},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(e){var t="Du kan max välja "+e.maximum+" element";return t},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/th",[],function(){return{errorLoading:function(){return"ไม่สามารถค้นข้อมูลได้"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="โปรดลบออก "+t+" ตัวอักษร";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="โปรดพิมพ์เพิ่มอีก "+t+" ตัวอักษร";return n},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(e){var t="คุณสามารถเลือกได้ไม่เกิน "+e.maximum+" รายการ";return t},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/tr",[],function(){return{errorLoading:function(){return"Sonuç yüklenemedi"},inputTooLong:function(e){var t=e.input.length-e.maximum,n=t+" karakter daha girmelisiniz";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="En az "+t+" karakter daha girmelisiniz";return n},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(e){var t="Sadece "+e.maximum+" seçim yapabilirsiniz";return t},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/uk",[],function(){function e(e,t,n,r){return e%100>10&&e%100<15?r:e%10===1?t:e%10>1&&e%10<5?n:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(t){var n=t.input.length-t.maximum;return"Будь ласка, видаліть "+n+" "+e(t.maximum,"літеру","літери","літер")},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Будь ласка, введіть "+t+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(t){return"Ви можете вибрати лише "+t.maximum+" "+e(t.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/vi",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vui lòng nhập ít hơn "+t+" ký tự";return t!=1&&(n+="s"),n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Vui lòng nhập nhiều hơn "+t+" ký tự";return n},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(e){var t="Chỉ có thể chọn được "+e.maximum+" lựa chọn";return t},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(e){var t=e.input.length-e.maximum,n="请删除"+t+"个字符";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="请再输入至少"+t+"个字符";return n},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(e){var t="最多只能选择"+e.maximum+"个项目";return t},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"}}}),{define:e.define,require:e.require}})();

View File

@@ -0,0 +1,3 @@
/*! Select2 4.0.5 | https://github.com/select2/select2/blob/master/LICENSE.md */
(function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;return e.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="請刪掉"+t+"個字元";return n},inputTooShort:function(e){var t=e.minimum-e.input.length,n="請再輸入"+t+"個字元";return n},loadingMore:function(){return"載入中…"},maximumSelected:function(e){var t="你只能選擇最多"+e.maximum+"項";return t},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"}}}),{define:e.define,require:e.require}})();

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,14 @@
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from django import forms
from django.db.models import Count
from taggit.forms import TagField
from dcim.models import Device
from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldForm
from utilities.forms import BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField
from utilities.forms import (
APISelect, APISelectMultiple, BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField,
StaticSelect2Multiple
)
from .models import Secret, SecretRole, UserKey
@@ -42,6 +44,10 @@ class SecretRoleForm(BootstrapMixin, forms.ModelForm):
fields = [
'name', 'slug', 'users', 'groups',
]
widgets = {
'users': StaticSelect2Multiple(),
'groups': StaticSelect2Multiple(),
}
class SecretRoleCSVForm(forms.ModelForm):
@@ -85,6 +91,11 @@ class SecretForm(BootstrapMixin, CustomFieldForm):
fields = [
'role', 'name', 'plaintext', 'plaintext2', 'tags',
]
widgets = {
'role': APISelect(
api_url="/api/secrets/secret-roles/"
)
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -143,7 +154,10 @@ class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
)
role = forms.ModelChoiceField(
queryset=SecretRole.objects.all(),
required=False
required=False,
widget=APISelect(
api_url="/api/secrets/secret-roles/"
)
)
name = forms.CharField(
max_length=100,
@@ -163,10 +177,12 @@ class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
label='Search'
)
role = FilterChoiceField(
queryset=SecretRole.objects.annotate(
filter_count=Count('secrets')
),
to_field_name='slug'
queryset=SecretRole.objects.all(),
to_field_name='slug',
widget=APISelectMultiple(
api_url="/api/secrets/secret-roles/",
value_field="slug",
)
)

View File

@@ -7,6 +7,8 @@
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'font-awesome-4.7.0/css/font-awesome.min.css' %}">
<link rel="stylesheet" href="{% static 'jquery-ui-1.12.1/jquery-ui.css' %}">
<link rel="stylesheet" href="{% static 'select2-4.0.5/css/select2.min.css' %}">
<link rel="stylesheet" href="{% static 'select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/base.css' %}?v{{ settings.VERSION }}">
<link rel="icon" type="image/png" href="{% static 'img/netbox.ico' %}" />
<meta charset="UTF-8">
@@ -66,6 +68,7 @@
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
<script src="{% static 'jquery-ui-1.12.1/jquery-ui.min.js' %}"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
<script src="{% static 'select2-4.0.5/js/select2.min.js' %}"></script>
<script src="{% static 'js/forms.js' %}?v{{ settings.VERSION }}"></script>
<script type="text/javascript">
var netbox_api_path = "/{{ settings.BASE_PATH }}api/";

View File

@@ -53,7 +53,7 @@
<i class="fa fa-angle-right"></i> {{ termination.connected_endpoint }}
{% endif %}
{% else %}
{% if perms.circuits.add_cable %}
{% if perms.dcim.add_cable %}
<div class="pull-right">
<a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk %}?return_url={{ circuit.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i> Connect

View File

@@ -107,14 +107,14 @@
</ul>
<div class="tab-content">
<div class="tab-pane active" id="search">
{% render_field form.livesearch %}
&nbsp;
</div>
<div class="tab-pane" id="select">
{% render_field form.termination_b_site %}
{% render_field form.termination_b_rack %}
{% render_field form.termination_b_device %}
</div>
</div>
{% render_field form.termination_b_device %}
{% render_field form.termination_b_type %}
{% render_field form.termination_b_id %}
</div>

View File

@@ -29,8 +29,8 @@
{% if cable.label %}<code>{{ cable.label }}</code>{% else %}Cable #{{ cable.pk }}{% endif %}
</a>
</h4>
{{ cable.get_status_display }}<br />
{{ cable.get_type_display|default:"" }}
<p><span class="label label-{% if cable.status %}success{% else %}info{% endif %}">{{ cable.get_status_display }}</span></p>
<p>{{ cable.get_type_display|default:"" }}</p>
{% if cable.length %}- {{ cable.length }}{{ cable.get_length_unit_display }}{% endif %}
<span class="label color-block center-block" style="background-color: #{{ cable.color }}">&nbsp;</span>
{% else %}

View File

@@ -9,6 +9,7 @@
<div class="row">
<div class="col-md-9">
{% include 'responsive_table.html' %}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
</div>
<div class="col-md-3">
{% include 'inc/search_panel.html' %}

View File

@@ -20,88 +20,3 @@
</div>
</div>
{% endblock %}
{% block javascript %}
<script type="text/javascript">
$(document).ready(function() {
var site_list = $('#id_site');
var rack_group_list = $('#id_rack_group_id');
var rack_list = $('#id_rack_id');
var manufacturer_list = $('#id_manufacturer_id');
var model_list = $('#id_device_type_id');
// Update device type options based on selected manufacturer
manufacturer_list.change(function() {
var selected_manufacturers = $(this).val();
if (selected_manufacturers) {
model_list.empty();
$.ajax({
url: netbox_api_path + 'dcim/device-types/?limit=500&manufacturer_id=' + selected_manufacturers.join('&manufacturer_id='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, device_type) {
var option = $("<option></option>").attr("value", device_type.id).text(device_type.model + " (" + device_type.instance_count + ")");
model_list.append(option);
});
}
});
}
});
// Update rack group and rack options based on selected site
site_list.change(function() {
var selected_sites = $(this).val();
if (selected_sites) {
// Update rack group options
rack_group_list.empty();
$.ajax({
url: netbox_api_path + 'dcim/rack-groups/?limit=500&site=' + selected_sites.join('&site='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, group) {
var option = $("<option></option>").attr("value", group.id).text(group.name);
rack_group_list.append(option);
});
}
});
// Update rack options
rack_list.empty();
rack_list.append($("<option></option>").attr("value", "0").text("None"));
$.ajax({
url: netbox_api_path + 'dcim/racks/?limit=500&site=' + selected_sites.join('&site='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, rack) {
var option = $("<option></option>").attr("value", rack.id).text(rack.display_name);
rack_list.append(option);
});
}
});
}
});
// Update rack options based on selected rack group
rack_group_list.change(function() {
var selected_rack_groups = $(this).val();
if (selected_rack_groups) {
rack_list.empty();
$.ajax({
url: netbox_api_path + 'dcim/racks/?limit=500&group_id=' + selected_rack_groups.join('&group_id='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, rack) {
var option = $("<option></option>").attr("value", rack.id).text(rack.display_name);
rack_list.append(option);
});
}
});
}
});
});
</script>
{% endblock %}

View File

@@ -22,13 +22,20 @@
{% for iface in interfaces %}
<tr id="{{ iface.name }}">
<td>{{ iface }}</td>
{% if iface.connected_endpoint %}
{% if iface.connected_endpoint.device %}
<td class="configured_device" data="{{ iface.connected_endpoint.device }}">
<a href="{% url 'dcim:device' pk=iface.connected_endpoint.device.pk %}">{{ iface.connected_endpoint.device }}</a>
</td>
<td class="configured_interface" data="{{ iface.connected_endpoint }}">
<span title="{{ iface.connected_endpoint.get_form_factor_display }}">{{ iface.connected_endpoint }}</span>
</td>
{% elif iface.connected_endpoint.circuit %}
{% with circuit=iface.connected_endpoint.circuit %}
<td colspan="2">
<i class="fa fa-fw fa-globe" title="Circuit"></i>
<a href="{{ circuit.get_absolute_url }}">{{ circuit.provider }} {{ circuit }}</a>
</td>
{% endwith %}
{% else %}
<td colspan="2">None</td>
{% endif %}

View File

@@ -73,53 +73,58 @@ $(document).ready(function() {
url: "{% url 'dcim-api:device-napalm' pk=device.pk %}?method=get_facts&method=get_environment",
dataType: 'json',
success: function(json) {
$('#hostname').html(json['get_facts']['hostname']);
$('#fqdn').html(json['get_facts']['fqdn']);
$('#vendor').html(json['get_facts']['vendor']);
$('#model').html(json['get_facts']['model']);
$('#serial_number').html(json['get_facts']['serial_number']);
$('#os_version').html(json['get_facts']['os_version']);
// Calculate uptime
var uptime = json['get_facts']['uptime'];
console.log(uptime);
var uptime_days = Math.floor(uptime / 86400);
var uptime_hours = Math.floor(uptime % 86400 / 3600);
var uptime_minutes = Math.floor(uptime % 3600 / 60);
$('#uptime').html(uptime_days + "d " + uptime_hours + "h " + uptime_minutes + "m");
$.each(json['get_environment']['cpu'], function(name, obj) {
var row="<tr><td>" + name + "</td><td>" + obj['%usage'] + "%</td></tr>";
$("#cpu").after(row)
});
$('#memory').after("<tr><td>Used</td><td>" + json['get_environment']['memory']['used_ram'] + "</td></tr>");
$('#memory').after("<tr><td>Available</td><td>" + json['get_environment']['memory']['available_ram'] + "</td></tr>");
$.each(json['get_environment']['temperature'], function(name, obj) {
var style = "success";
if (obj['is_alert']) {
style = "warning";
} else if (obj['is_critical']) {
style = "danger";
}
var row="<tr class=\"" + style +"\"><td>" + name + "</td><td>" + obj['temperature'] + "°C</td></tr>";
$("#temperature").after(row)
});
$.each(json['get_environment']['fans'], function(name, obj) {
var row;
if (obj['status']) {
row="<tr class=\"success\"><td>" + name + "</td><td><i class=\"fa fa-check text-success\"></i></td></tr>";
} else {
row="<tr class=\"error\"><td>" + name + "</td><td><i class=\"fa fa-close text-error\"></i></td></tr>";
}
$("#fans").after(row)
});
$.each(json['get_environment']['power'], function(name, obj) {
var row;
if (obj['status']) {
row="<tr class=\"success\"><td>" + name + "</td><td><i class=\"fa fa-check text-success\"></i></td></tr>";
} else {
row="<tr class=\"danger\"><td>" + name + "</td><td><i class=\"fa fa-close text-danger\"></i></td></tr>";
}
$("#power").after(row)
});
if (!json['get_facts']['error']) {
$('#hostname').html(json['get_facts']['hostname']);
$('#fqdn').html(json['get_facts']['fqdn']);
$('#vendor').html(json['get_facts']['vendor']);
$('#model').html(json['get_facts']['model']);
$('#serial_number').html(json['get_facts']['serial_number']);
$('#os_version').html(json['get_facts']['os_version']);
// Calculate uptime
var uptime = json['get_facts']['uptime'];
console.log(uptime);
var uptime_days = Math.floor(uptime / 86400);
var uptime_hours = Math.floor(uptime % 86400 / 3600);
var uptime_minutes = Math.floor(uptime % 3600 / 60);
$('#uptime').html(uptime_days + "d " + uptime_hours + "h " + uptime_minutes + "m");
}
if (!json['get_environment']['error']) {
$.each(json['get_environment']['cpu'], function(name, obj) {
var row="<tr><td>" + name + "</td><td>" + obj['%usage'] + "%</td></tr>";
$("#cpu").after(row)
});
$('#memory').after("<tr><td>Used</td><td>" + json['get_environment']['memory']['used_ram'] + "</td></tr>");
$('#memory').after("<tr><td>Available</td><td>" + json['get_environment']['memory']['available_ram'] + "</td></tr>");
$.each(json['get_environment']['temperature'], function(name, obj) {
var style = "success";
if (obj['is_alert']) {
style = "warning";
} else if (obj['is_critical']) {
style = "danger";
}
var row="<tr class=\"" + style +"\"><td>" + name + "</td><td>" + obj['temperature'] + "°C</td></tr>";
$("#temperature").after(row)
});
$.each(json['get_environment']['fans'], function(name, obj) {
var row;
if (obj['status']) {
row="<tr class=\"success\"><td>" + name + "</td><td><i class=\"fa fa-check text-success\"></i></td></tr>";
} else {
row="<tr class=\"error\"><td>" + name + "</td><td><i class=\"fa fa-close text-error\"></i></td></tr>";
}
$("#fans").after(row)
});
$.each(json['get_environment']['power'], function(name, obj) {
var row;
if (obj['status']) {
row="<tr class=\"success\"><td>" + name + "</td><td><i class=\"fa fa-check text-success\"></i></td></tr>";
} else {
row="<tr class=\"danger\"><td>" + name + "</td><td><i class=\"fa fa-close text-danger\"></i></td></tr>";
}
$("#power").after(row)
});
}
},
error: function(xhr) {
alert(xhr.responseText);

View File

@@ -3,7 +3,13 @@
<div class="panel panel-default">
<div class="panel-heading text-center">
{% if end.device %}
<strong><a href="{{ end.device.get_absolute_url }}">{{ end.device }}</a></strong>
<strong><a href="{{ end.device.get_absolute_url }}">{{ end.device }}</a></strong><br/>
<small>
<a href="{{ end.device.site.get_absolute_url }}">{{ end.device.site }}</a>
{% if end.device.rack %}
/ <a href="{{ end.device.rack.get_absolute_url }}">{{ end.device.rack }}</a>
{% endif %}
</small>
{% else %}
<strong><a href="{{ end.circuit.provider.get_absolute_url }}">{{ end.circuit.provider }}</a></strong>
{% endif %}

View File

@@ -1,29 +0,0 @@
<script type="text/javascript">
$(document).ready(function() {
var site_list = $('#id_site');
var rack_group_list = $('#id_group_id');
// Update rack group and rack options based on selected site
site_list.change(function() {
var selected_sites = $(this).val();
if (selected_sites) {
// Update rack group options
rack_group_list.empty();
$.ajax({
url: netbox_api_path + 'dcim/rack-groups/?limit=500&site=' + selected_sites.join('&site='),
dataType: 'json',
success: function (response, status) {
$.each(response["results"], function (index, group) {
var option = $("<option></option>").attr("value", group.id).text(group.name);
rack_group_list.append(option);
});
}
});
}
});
});
</script>

Some files were not shown because too many files have changed in this diff Show More