diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 61d93f286..e04b0e602 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -17,7 +17,7 @@ body: What version of NetBox are you currently running? (If you don't have access to the most recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/) before opening a bug report to see if your issue has already been addressed.) - placeholder: v3.0.1 + placeholder: v3.0.2 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 65f452f0b..61a36fb0c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.0.1 + placeholder: v3.0.2 validations: required: true - type: dropdown @@ -30,8 +30,10 @@ body: attributes: label: Proposed functionality description: > - Describe in detail the new feature or behavior you'd like to propose. Include any specific - changes to work flows, data models, or the user interface. + Describe in detail the new feature or behavior you are proposing. Include any specific changes + to work flows, data models, and/or the user interface. The more detail you provide here, the + greater chance your proposal has of being discussed. Feature requests which don't include an + actionable implementation plan will be rejected. validations: required: true - type: textarea diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d734ad2f0..c8e3f47ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,6 +58,9 @@ jobs: - name: Check UI ESLint, TypeScript, and Prettier Compliance run: yarn --cwd netbox/project-static validate + + - name: Validate Static Asset Integrity + run: scripts/verify-bundles.sh - name: Run tests run: coverage run --source="netbox/" netbox/manage.py test netbox/ diff --git a/.gitignore b/.gitignore index b594efe4b..0ce9a20a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,13 @@ *.pyc *.swp -node_modules npm-debug.log* yarn-debug.log* yarn-error.log* -/netbox/project-static/.cache +/netbox/project-static/node_modules /netbox/project-static/docs/* !/netbox/project-static/docs/.info /netbox/netbox/configuration.py /netbox/netbox/ldap_config.py -/netbox/project-static/.cache -/netbox/project-static/node_modules /netbox/reports/* !/netbox/reports/__init__.py /netbox/scripts/* diff --git a/docs/additional-features/prometheus-metrics.md b/docs/additional-features/prometheus-metrics.md index 56365e336..006ff16a4 100644 --- a/docs/additional-features/prometheus-metrics.md +++ b/docs/additional-features/prometheus-metrics.md @@ -26,4 +26,4 @@ For the exhaustive list of exposed metrics, visit the `/metrics` endpoint on you When deploying NetBox in a multiprocess manner (e.g. running multiple Gunicorn workers) the Prometheus client library requires the use of a shared directory to collect metrics from all worker processes. To configure this, first create or designate a local directory to which the worker processes have read and write access, and then configure your WSGI service (e.g. Gunicorn) to define this path as the `prometheus_multiproc_dir` environment variable. !!! warning - If having accurate long-term metrics in a multiprocess environment is crucial to your deployment, it's recommended you use the `uwsgi` library instead of `gunicorn`. The issue lies in the way `gunicorn` tracks worker processes (vs `uwsgi`) which helps manage the metrics files created by the above configurations. If you're using NetBox with gunicorn in a containerized enviroment following the one-process-per-container methodology, then you will likely not need to change to `uwsgi`. More details can be found in [issue #3779](https://github.com/netbox-community/netbox/issues/3779#issuecomment-590547562). + If having accurate long-term metrics in a multiprocess environment is crucial to your deployment, it's recommended you use the `uwsgi` library instead of `gunicorn`. The issue lies in the way `gunicorn` tracks worker processes (vs `uwsgi`) which helps manage the metrics files created by the above configurations. If you're using NetBox with gunicorn in a containerized environment following the one-process-per-container methodology, then you will likely not need to change to `uwsgi`. More details can be found in [issue #3779](https://github.com/netbox-community/netbox/issues/3779#issuecomment-590547562). diff --git a/docs/development/extending-models.md b/docs/development/extending-models.md index 994d2a040..99c448c06 100644 --- a/docs/development/extending-models.md +++ b/docs/development/extending-models.md @@ -34,11 +34,11 @@ class Foo(models.Model): ## 3. Update relevant querysets -If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid extraneous database queries. +If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retrieving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid extraneous database queries. ## 4. Update API serializer -Extend the model's API serializer in `.api.serializers` to include the new field. In most cases, it will not be necessary to also extend the nested serializer, which produces a minimal represenation of the model. +Extend the model's API serializer in `.api.serializers` to include the new field. In most cases, it will not be necessary to also extend the nested serializer, which produces a minimal representation of the model. ## 5. Add field to forms diff --git a/docs/graphql-api/overview.md b/docs/graphql-api/overview.md index f1ce4f455..f024306b0 100644 --- a/docs/graphql-api/overview.md +++ b/docs/graphql-api/overview.md @@ -45,7 +45,7 @@ NetBox provides both a singular and plural query field for each object type: * `$OBJECT`: Returns a single object. Must specify the object's unique ID as `(id: 123)`. * `$OBJECT_list`: Returns a list of objects, optionally filtered by given parameters. -For example, query `device(id:123)` to fetch a specific device (identified by its unique ID), and query `device_list` (with an optional set of fitlers) to fetch all devices. +For example, query `device(id:123)` to fetch a specific device (identified by its unique ID), and query `device_list` (with an optional set of filters) to fetch all devices. For more detail on constructing GraphQL queries, see the [Graphene documentation](https://docs.graphene-python.org/en/latest/). diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index 43b23a649..d20bfaf6b 100644 --- a/docs/installation/3-netbox.md +++ b/docs/installation/3-netbox.md @@ -70,19 +70,22 @@ If `git` is not already installed, install it: Next, clone the **master** branch of the NetBox GitHub repository into the current directory. (This branch always holds the current stable release.) ```no-highlight -sudo git clone -b master https://github.com/netbox-community/netbox.git . +sudo git clone -b master --depth 1 https://github.com/netbox-community/netbox.git . ``` +!!! note + The `git clone` command above utilizes a "shallow clone" to retrieve only the most recent commit. If you need to download the entire history, omit the `--depth 1` argument. + The `git clone` command should generate output similar to the following: ``` Cloning into '.'... -remote: Counting objects: 1994, done. -remote: Compressing objects: 100% (150/150), done. -remote: Total 1994 (delta 80), reused 0 (delta 0), pack-reused 1842 -Receiving objects: 100% (1994/1994), 472.36 KiB | 0 bytes/s, done. -Resolving deltas: 100% (1495/1495), done. -Checking connectivity... done. +remote: Enumerating objects: 996, done. +remote: Counting objects: 100% (996/996), done. +remote: Compressing objects: 100% (935/935), done. +remote: Total 996 (delta 148), reused 386 (delta 34), pack-reused 0 +Receiving objects: 100% (996/996), 4.26 MiB | 9.81 MiB/s, done. +Resolving deltas: 100% (148/148), done. ``` !!! note diff --git a/docs/installation/4-gunicorn.md b/docs/installation/4-gunicorn.md index 7b56754fe..4fc73a58b 100644 --- a/docs/installation/4-gunicorn.md +++ b/docs/installation/4-gunicorn.md @@ -14,7 +14,7 @@ While the provided configuration should suffice for most initial installations, ## systemd Setup -We'll use systemd to control both gunicorn and NetBox's background worker process. First, copy `contrib/netbox.service` and `contrib/netbox-rq.service` to the `/etc/systemd/system/` directory and reload the systemd dameon: +We'll use systemd to control both gunicorn and NetBox's background worker process. First, copy `contrib/netbox.service` and `contrib/netbox-rq.service` to the `/etc/systemd/system/` directory and reload the systemd daemon: ```no-highlight sudo cp -v /opt/netbox/contrib/*.service /etc/systemd/system/ diff --git a/docs/models/dcim/platform.md b/docs/models/dcim/platform.md index 65188fa6e..347abc5b8 100644 --- a/docs/models/dcim/platform.md +++ b/docs/models/dcim/platform.md @@ -4,6 +4,6 @@ A platform defines the type of software running on a device or virtual machine. Platforms may optionally be limited by manufacturer: If a platform is assigned to a particular manufacturer, it can only be assigned to devices with a type belonging to that manufacturer. -The platform model is also used to indicate which [NAPALM](../../additional-features/napalm.md) driver and any associated arguments NetBox should use when connecting to a remote device. The name of the driver along with optional parameters are stored with the platform. +The platform model is also used to indicate which NAPALM driver (if any) and any associated arguments NetBox should use when connecting to a remote device. The name of the driver along with optional parameters are stored with the platform. The assignment of platforms to devices is an optional feature, and may be disregarded if not desired. diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 1587d4b43..379a6877e 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -1,5 +1,26 @@ # NetBox v3.0 +## v3.0.2 (2021-09-08) + +### Bug Fixes + +* [#7131](https://github.com/netbox-community/netbox/issues/7131) - Fix issue where Site fields were hidden when editing a VLAN group +* [#7148](https://github.com/netbox-community/netbox/issues/7148) - Fix issue where static query parameters with multiple values were not queried properly +* [#7153](https://github.com/netbox-community/netbox/issues/7153) - Allow clearing of assigned device type images +* [#7162](https://github.com/netbox-community/netbox/issues/7162) - Ensure consistent treatment of `BASE_PATH` for UI-driven API requests +* [#7164](https://github.com/netbox-community/netbox/issues/7164) - Fix styling of "decommissioned" label for circuits +* [#7169](https://github.com/netbox-community/netbox/issues/7169) - Fix CSV import file upload +* [#7176](https://github.com/netbox-community/netbox/issues/7176) - Fix issue where query parameters were duplicated across different forms of the same type +* [#7179](https://github.com/netbox-community/netbox/issues/7179) - Prevent obscuring "connect" pop-up for interfaces under device view +* [#7188](https://github.com/netbox-community/netbox/issues/7188) - Fix issue where select fields with `null_option` did not render or send the null option +* [#7189](https://github.com/netbox-community/netbox/issues/7189) - Set connection factory for django-redis when Sentinel is in use +* [#7191](https://github.com/netbox-community/netbox/issues/7191) - Fix issue where API-backed multi-select elements cleared selected options when adding new options +* [#7193](https://github.com/netbox-community/netbox/issues/7193) - Fix prefix (flat) template issue when viewing child prefixes with prefixes available +* [#7205](https://github.com/netbox-community/netbox/issues/7205) - Fix issue where selected fields with `null_option` set were not added to applied filters +* [#7209](https://github.com/netbox-community/netbox/issues/7209) - Allow unlimited API results when `MAX_PAGE_SIZE` is disabled + +--- + ## v3.0.1 (2021-09-01) ### Bug Fixes diff --git a/netbox/circuits/choices.py b/netbox/circuits/choices.py index bbf536800..0efa431fa 100644 --- a/netbox/circuits/choices.py +++ b/netbox/circuits/choices.py @@ -29,7 +29,7 @@ class CircuitStatusChoices(ChoiceSet): STATUS_PLANNED: 'info', STATUS_PROVISIONING: 'primary', STATUS_OFFLINE: 'danger', - STATUS_DECOMMISSIONED: 'default', + STATUS_DECOMMISSIONED: 'secondary', } diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 367980ac4..c1f8eccf8 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -23,10 +23,10 @@ from tenancy.forms import TenancyFilterForm, TenancyForm from tenancy.models import Tenant from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, - ColorField, CommentField, CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, - DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, - NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect, StaticSelectMultiple, TagFilterField, - BOOLEAN_WITH_BLANK_CHOICES, + ClearableFileInput, ColorField, CommentField, CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, + CSVTypedChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, + JSONField, NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect, StaticSelectMultiple, + TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import Cluster, ClusterGroup from .choices import * @@ -1271,10 +1271,10 @@ class DeviceTypeForm(BootstrapMixin, CustomFieldModelForm): ) widgets = { 'subdevice_role': StaticSelect(), - 'front_image': forms.ClearableFileInput(attrs={ + 'front_image': ClearableFileInput(attrs={ 'accept': DEVICETYPE_IMAGE_FORMATS }), - 'rear_image': forms.ClearableFileInput(attrs={ + 'rear_image': ClearableFileInput(attrs={ 'accept': DEVICETYPE_IMAGE_FORMATS }) } diff --git a/netbox/extras/migrations/0062_clear_secrets_changelog.py b/netbox/extras/migrations/0062_clear_secrets_changelog.py new file mode 100644 index 000000000..e76fc8d34 --- /dev/null +++ b/netbox/extras/migrations/0062_clear_secrets_changelog.py @@ -0,0 +1,26 @@ +from django.db import migrations + + +def clear_secrets_changelog(apps, schema_editor): + """ + Delete all ObjectChange records referencing a model within the old secrets app (pre-v3.0). + """ + ContentType = apps.get_model('contenttypes', 'ContentType') + ObjectChange = apps.get_model('extras', 'ObjectChange') + + content_type_ids = ContentType.objects.filter(app_label='secrets').values_list('id', flat=True) + ObjectChange.objects.filter(changed_object_type__in=content_type_ids).delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0061_extras_change_logging'), + ] + + operations = [ + migrations.RunPython( + code=clear_secrets_changelog, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index e4bb7d693..5afb6a9c9 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -25,6 +25,15 @@ PREFIX_LINK = """ {{ record.prefix }} """ +PREFIXFLAT_LINK = """ +{% load helpers %} +{% if record.pk %} + {{ record.prefix }} +{% else %} + — +{% endif %} +""" + PREFIX_ROLE_LINK = """ {% if record.role %} {{ record.role }} @@ -281,10 +290,10 @@ class PrefixTable(BaseTable): template_code=PREFIX_LINK, attrs={'td': {'class': 'text-nowrap'}} ) - prefix_flat = tables.Column( - accessor=Accessor('prefix'), - linkify=True, - verbose_name='Prefix (Flat)' + prefix_flat = tables.TemplateColumn( + template_code=PREFIXFLAT_LINK, + attrs={'td': {'class': 'text-nowrap'}}, + verbose_name='Prefix (Flat)', ) depth = tables.Column( accessor=Accessor('_depth'), diff --git a/netbox/netbox/api/pagination.py b/netbox/netbox/api/pagination.py index 77af755ce..e34cb27d0 100644 --- a/netbox/netbox/api/pagination.py +++ b/netbox/netbox/api/pagination.py @@ -34,13 +34,22 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination): return list(queryset[self.offset:]) def get_limit(self, request): - limit = super().get_limit(request) + if self.limit_query_param: + try: + limit = int(request.query_params[self.limit_query_param]) + if limit < 0: + raise ValueError() + # Enforce maximum page size, if defined + if settings.MAX_PAGE_SIZE: + if limit == 0: + return settings.MAX_PAGE_SIZE + else: + return min(limit, settings.MAX_PAGE_SIZE) + return limit + except (KeyError, ValueError): + pass - # Enforce maximum page size - if settings.MAX_PAGE_SIZE: - limit = min(limit, settings.MAX_PAGE_SIZE) - - return limit + return self.default_limit def get_next_link(self): diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index f70be12a0..416536654 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -16,7 +16,7 @@ from django.core.validators import URLValidator # Environment setup # -VERSION = '3.0.1' +VERSION = '3.0.2' # Hostname HOSTNAME = platform.node() @@ -250,6 +250,7 @@ CACHES = { } } if CACHING_REDIS_SENTINELS: + DJANGO_REDIS_CONNECTION_FACTORY = 'django_redis.pool.SentinelConnectionFactory' CACHES['default']['LOCATION'] = f'{CACHING_REDIS_PROTO}://{CACHING_REDIS_SENTINEL_SERVICE}/{CACHING_REDIS_DATABASE}' CACHES['default']['OPTIONS']['CLIENT_CLASS'] = 'django_redis.client.SentinelClient' CACHES['default']['OPTIONS']['SENTINELS'] = CACHING_REDIS_SENTINELS diff --git a/netbox/netbox/views/generic.py b/netbox/netbox/views/generic.py index aafb2f3d8..0d4f89d0f 100644 --- a/netbox/netbox/views/generic.py +++ b/netbox/netbox/views/generic.py @@ -21,8 +21,7 @@ from extras.signals import clear_webhooks from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortTransaction, PermissionsViolation from utilities.forms import ( - BootstrapMixin, BulkRenameForm, ConfirmationForm, CSVDataField, CSVFileField, ImportForm, TableConfigForm, - restrict_form_fields, + BootstrapMixin, BulkRenameForm, ConfirmationForm, CSVDataField, CSVFileField, ImportForm, restrict_form_fields, ) from utilities.permissions import get_permission_for_model from utilities.tables import paginate_table diff --git a/netbox/project-static/dist/config.js b/netbox/project-static/dist/config.js index cf1022589..7cf3ccb30 100644 --- a/netbox/project-static/dist/config.js +++ b/netbox/project-static/dist/config.js @@ -1,5 +1,5 @@ -(()=>{var ko=Object.create;var ue=Object.defineProperty,Ho=Object.defineProperties,Wo=Object.getOwnPropertyDescriptor,Bo=Object.getOwnPropertyDescriptors,Vo=Object.getOwnPropertyNames,yn=Object.getOwnPropertySymbols,Fo=Object.getPrototypeOf,bn=Object.prototype.hasOwnProperty,zo=Object.prototype.propertyIsEnumerable;var Tn=(i,t,e)=>t in i?ue(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,O=(i,t)=>{for(var e in t||(t={}))bn.call(t,e)&&Tn(i,e,t[e]);if(yn)for(var e of yn(t))zo.call(t,e)&&Tn(i,e,t[e]);return i},He=(i,t)=>Ho(i,Bo(t)),An=i=>ue(i,"__esModule",{value:!0});var q=(i,t)=>()=>(t||i((t={exports:{}}).exports,t),t.exports),Uo=(i,t)=>{An(i);for(var e in t)ue(i,e,{get:t[e],enumerable:!0})},Yo=(i,t,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Vo(t))!bn.call(i,n)&&n!=="default"&&ue(i,n,{get:()=>t[n],enumerable:!(e=Wo(t,n))||e.enumerable});return i},Ai=i=>Yo(An(ue(i!=null?ko(Fo(i)):{},"default",i&&i.__esModule&&"default"in i?{get:()=>i.default,enumerable:!0}:{value:i,enumerable:!0})),i);var Si=(i,t,e)=>new Promise((n,r)=>{var o=f=>{try{a(e.next(f))}catch(h){r(h)}},s=f=>{try{a(e.throw(f))}catch(h){r(h)}},a=f=>f.done?n(f.value):Promise.resolve(f.value).then(o,s);a((e=e.apply(i,t)).next())});var sn=q((ro,fi)=>{(function(i,t){typeof define=="function"&&define.amd?define(t):typeof fi=="object"&&fi.exports?fi.exports=t():i.EvEmitter=t()})(typeof window!="undefined"?window:ro,function(){"use strict";function i(){}var t=i.prototype;return t.on=function(e,n){if(!(!e||!n)){var r=this._events=this._events||{},o=r[e]=r[e]||[];return o.indexOf(n)==-1&&o.push(n),this}},t.once=function(e,n){if(!(!e||!n)){this.on(e,n);var r=this._onceEvents=this._onceEvents||{},o=r[e]=r[e]||{};return o[n]=!0,this}},t.off=function(e,n){var r=this._events&&this._events[e];if(!(!r||!r.length)){var o=r.indexOf(n);return o!=-1&&r.splice(o,1),this}},t.emitEvent=function(e,n){var r=this._events&&this._events[e];if(!(!r||!r.length)){r=r.slice(0),n=n||[];for(var o=this._onceEvents&&this._onceEvents[e],s=0;s{(function(i,t){typeof define=="function"&&define.amd?define(t):typeof ui=="object"&&ui.exports?ui.exports=t():i.getSize=t()})(window,function(){"use strict";function t(d){var y=parseFloat(d),E=d.indexOf("%")==-1&&!isNaN(y);return E&&y}function e(){}var n=typeof console=="undefined"?e:function(d){console.error(d)},r=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],o=r.length;function s(){for(var d={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},y=0;y