Merge branch 'feature' into 14728-plugins-admin

This commit is contained in:
Jeremy Stretch 2024-01-17 16:27:24 -05:00
commit f746bc2be3
388 changed files with 11593 additions and 10025 deletions

View File

@ -10,7 +10,7 @@ jobs:
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
node-version: ['14.x']
node-version: ['18.x']
services:
redis:
image: redis

View File

@ -1,7 +1,3 @@
# HTML sanitizer
# https://github.com/mozilla/bleach/blob/main/CHANGES
bleach
# The Python web framework on which NetBox is built
# https://docs.djangoproject.com/en/stable/releases/
Django<5.1
@ -108,6 +104,10 @@ mkdocstrings[python-legacy]
# https://github.com/netaddr/netaddr/blob/master/CHANGELOG
netaddr
# Python bindings to the ammonia HTML sanitization library.
# https://github.com/messense/nh3
nh3
# Fork of PIL (Python Imaging Library) for image processing
# https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst
Pillow

View File

@ -21,37 +21,33 @@ WEIGHT = """
"""
DEVICE_LINK = """
{{ value|default:'<span class="badge bg-info">Unnamed device</span>' }}
{{ value|default:'<span class="badge text-bg-info">Unnamed device</span>' }}
"""
DEVICEBAY_STATUS = """
{% if record.installed_device_id %}
<span class="badge bg-{{ record.installed_device.get_status_color }}">
<span class="badge text-bg-{{ record.installed_device.get_status_color }}">
{{ record.installed_device.get_status_display }}
</span>
{% else %}
<span class="badge bg-secondary">Vacant</span>
<span class="badge text-bg-secondary">Vacant</span>
{% endif %}
"""
INTERFACE_IPADDRESSES = """
<div class="table-badge-group">
{% for ip in value.all %}
{% if ip.status != 'active' %}
<a href="{{ ip.get_absolute_url }}" class="table-badge badge bg-{{ ip.get_status_color }}" data-bs-toggle="tooltip" data-bs-placement="left" title="{{ ip.get_status_display }}">{{ ip }}</a>
<a href="{{ ip.get_absolute_url }}" class="badge text-bg-{{ ip.get_status_color }}" data-bs-toggle="tooltip" data-bs-placement="left" title="{{ ip.get_status_display }}">{{ ip }}</a>
{% else %}
<a href="{{ ip.get_absolute_url }}" class="table-badge">{{ ip }}</a>
<a href="{{ ip.get_absolute_url }}">{{ ip }}</a>
{% endif %}
{% endfor %}
</div>
"""
INTERFACE_FHRPGROUPS = """
<div class="table-badge-group">
{% for assignment in value.all %}
<a href="{{ assignment.group.get_absolute_url }}">{{ assignment.group.get_protocol_display }}: {{ assignment.group.group_id }}</a>
{% endfor %}
</div>
"""
INTERFACE_TAGGED_VLANS = """

View File

@ -3353,6 +3353,7 @@ class VirtualChassisEditView(ObjectPermissionRequiredMixin, GetReturnURLMixin, V
formset = VCMemberFormSet(queryset=members_queryset)
return render(request, 'dcim/virtualchassis_edit.html', {
'object': virtual_chassis,
'vc_form': vc_form,
'formset': formset,
'return_url': self.get_return_url(request, virtual_chassis),

View File

@ -18,7 +18,7 @@ __all__ = (
'RoleTable',
)
AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
AVAILABLE_LABEL = mark_safe('<span class="badge text-bg-success">Available</span>')
AGGREGATE_COPY_BUTTON = """
{% copy_content record.pk prefix="aggregate_" %}

View File

@ -18,7 +18,7 @@ __all__ = (
'VLANVirtualMachinesTable',
)
AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
AVAILABLE_LABEL = mark_safe('<span class="badge text-bg-success">Available</span>')
VLAN_LINK = """
{% if record.pk %}

View File

@ -79,8 +79,7 @@ def get_model_buttons(app_label, model_name, actions=('add', 'import')):
link=f'{app_label}:{model_name}_add',
title='Add',
icon_class='mdi mdi-plus-thick',
permissions=[f'{app_label}.add_{model_name}'],
color=ButtonColorChoices.GREEN
permissions=[f'{app_label}.add_{model_name}']
)
)
if 'import' in actions:
@ -89,8 +88,7 @@ def get_model_buttons(app_label, model_name, actions=('add', 'import')):
link=f'{app_label}:{model_name}_import',
title='Import',
icon_class='mdi mdi-upload',
permissions=[f'{app_label}.add_{model_name}'],
color=ButtonColorChoices.CYAN
permissions=[f'{app_label}.add_{model_name}']
)
)

View File

@ -386,15 +386,13 @@ ADMIN_MENU = Menu(
link=f'users:netboxuser_add',
title='Add',
icon_class='mdi mdi-plus-thick',
permissions=[f'auth.add_user'],
color=ButtonColorChoices.GREEN
permissions=[f'auth.add_user']
),
MenuItemButton(
link=f'users:netboxuser_import',
title='Import',
icon_class='mdi mdi-upload',
permissions=[f'auth.add_user'],
color=ButtonColorChoices.CYAN
permissions=[f'auth.add_user']
)
)
),
@ -409,15 +407,13 @@ ADMIN_MENU = Menu(
link=f'users:netboxgroup_add',
title='Add',
icon_class='mdi mdi-plus-thick',
permissions=[f'auth.add_group'],
color=ButtonColorChoices.GREEN
permissions=[f'auth.add_group']
),
MenuItemButton(
link=f'users:netboxgroup_import',
title='Import',
icon_class='mdi mdi-upload',
permissions=[f'auth.add_group'],
color=ButtonColorChoices.CYAN
permissions=[f'auth.add_group']
)
)
),

View File

@ -161,8 +161,11 @@ class ToggleColumn(tables.CheckBoxColumn):
visible = kwargs.pop('visible', False)
if 'attrs' not in kwargs:
kwargs['attrs'] = {
'th': {
'class': 'w-1',
},
'td': {
'class': 'min-width',
'class': 'w-1',
},
'input': {
'class': 'form-check-input'
@ -322,7 +325,7 @@ class ChoiceFieldColumn(tables.Column):
except AttributeError:
bg_color = self.DEFAULT_BG_COLOR
return mark_safe(f'<span class="badge bg-{bg_color}">{value}</span>')
return mark_safe(f'<span class="badge text-bg-{bg_color}">{value}</span>')
def value(self, value):
return value

View File

@ -1,6 +1,6 @@
SEARCH_RESULT_ATTRS = """
{% for name, value in record.display_attrs.items %}
<span class="badge bg-secondary"
<span class="badge text-bg-secondary"
{% if value|length > 40 %} data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{ value }}"{% endif %}
>
{{ name|bettertitle }}:

View File

@ -73,12 +73,10 @@ async function bundleScripts() {
async function bundleStyles() {
try {
const entryPoints = {
'netbox-external': 'styles/_external.scss',
'netbox-light': 'styles/_light.scss',
'netbox-dark': 'styles/_dark.scss',
'netbox-print': 'styles/_print.scss',
rack_elevation: 'styles/_rack_elevation.scss',
cable_trace: 'styles/_cable_trace.scss',
'netbox-external': 'styles/external.scss',
'netbox': 'styles/netbox.scss',
rack_elevation: 'styles/svg/rack_elevation.scss',
cable_trace: 'styles/svg/cable_trace.scss',
graphiql: 'netbox-graphiql/graphiql.scss',
};
const pluginOptions = { outputStyle: 'compressed' };
@ -102,8 +100,7 @@ async function bundleStyles() {
});
if (result.errors.length === 0) {
for (const [targetName, sourceName] of Object.entries(entryPoints)) {
const source = sourceName.split('/')[1];
console.log(`✅ Bundled source file '${source}' to '${targetName}.css'`);
console.log(`✅ Bundled source file '${sourceName}' to '${targetName}.css'`);
}
}
} catch (err) {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
netbox/project-static/dist/netbox.css vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

4108
netbox/project-static/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -23,8 +23,7 @@
},
"dependencies": {
"@mdi/font": "^7.0.96",
"@popperjs/core": "^2.11.6",
"bootstrap": "~5.0.2",
"@tabler/core": "1.0.0-beta20",
"clipboard": "^2.0.11",
"color2k": "^2.0.0",
"dayjs": "^1.11.5",
@ -35,13 +34,13 @@
"just-debounce-it": "^3.1.1",
"query-string": "^7.1.1",
"sass": "^1.55.0",
"simplebar": "^5.3.9",
"slim-select": "^1.27.1"
"slim-select": "^1.27.1",
"typeface-inter": "^3.18.1",
"typeface-roboto-mono": "^1.1.13"
},
"devDependencies": {
"@types/bootstrap": "^5.0.17",
"@types/bootstrap": "5.2.10",
"@types/cookie": "^0.5.1",
"@types/masonry-layout": "^4.2.5",
"@typescript-eslint/eslint-plugin": "^5.39.0",
"@typescript-eslint/parser": "^5.39.0",
"esbuild": "^0.13.15",

View File

@ -1,10 +1,6 @@
import { getElements, isTruthy } from './util';
const COLOR_MODE_KEY = 'netbox-color-mode';
const TEXT_WHEN_DARK = 'Light Mode';
const TEXT_WHEN_LIGHT = 'Dark Mode';
const ICON_WHEN_DARK = 'mdi-lightbulb-on';
const ICON_WHEN_LIGHT = 'mdi-lightbulb';
/**
* Determine if a value is a supported color mode string value.
@ -24,23 +20,11 @@ function storeColorMode(mode: ColorMode): void {
}
function updateElements(targetMode: ColorMode): void {
document.documentElement.setAttribute(`data-${COLOR_MODE_KEY}`, targetMode);
for (const text of getElements<HTMLSpanElement>('span.color-mode-text')) {
if (targetMode === 'light') {
text.innerText = TEXT_WHEN_LIGHT;
} else if (targetMode === 'dark') {
text.innerText = TEXT_WHEN_DARK;
}
}
for (const icon of getElements<HTMLSpanElement>('i.color-mode-icon', 'span.color-mode-icon')) {
if (targetMode === 'light') {
icon.classList.remove(ICON_WHEN_DARK);
icon.classList.add(ICON_WHEN_LIGHT);
} else if (targetMode === 'dark') {
icon.classList.remove(ICON_WHEN_LIGHT);
icon.classList.add(ICON_WHEN_DARK);
}
const body = document.querySelector('body');
if (body && targetMode == 'dark') {
body.setAttribute('data-bs-theme', 'dark');
} else if (body) {
body.setAttribute('data-bs-theme', 'light');
}
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
@ -57,9 +41,8 @@ function updateElements(targetMode: ColorMode): void {
* @param mode Target color mode.
*/
export function setColorMode(mode: ColorMode): void {
for (const func of [storeColorMode, updateElements]) {
func(mode);
}
storeColorMode(mode);
updateElements(mode);
}
/**

View File

@ -1,5 +1,4 @@
import '@popperjs/core';
import 'bootstrap';
import 'htmx.org';
import 'simplebar';
import './netbox';

View File

@ -5,7 +5,7 @@ import { Toast } from 'bootstrap';
*/
export function initMessages(): void {
const elements = document.querySelectorAll<HTMLDivElement>(
'body > div#django-messages > div.django-message.toast',
'body > div#django-messages > div.toast',
);
for (const element of elements) {
if (element !== null) {

View File

@ -204,23 +204,25 @@ class SideNav {
* @param link Active nav link
* @param action Expand or Collapse
*/
private activateLink(link: HTMLAnchorElement, action: 'expand' | 'collapse'): void {
// Find the closest .collapse element, which should contain `link`.
const collapse = link.closest('.collapse') as Nullable<HTMLDivElement>;
if (isElement(collapse)) {
// Find the closest `.nav-link`, which should be adjacent to the `.collapse` element.
const groupLink = collapse.parentElement?.querySelector('.nav-link');
if (isElement(groupLink)) {
groupLink.classList.add('active');
private activateLink(link: HTMLDivElement, action: 'expand' | 'collapse'): void {
// Find the closest .dropdown-menu element, which should contain `link`.
const dropdownMenu = link.closest('.dropdown-menu') as Nullable<HTMLDivElement>;
if (isElement(dropdownMenu)) {
// Find the closest `.nav-link`, which should be adjacent to the `.dropdown-menu` element.
const groupItem = dropdownMenu.parentElement;
const groupLink = dropdownMenu.parentElement?.querySelector('.nav-link');
if (isElement(groupLink) && isElement(groupItem)) {
switch (action) {
case 'expand':
groupLink.setAttribute('aria-expanded', 'true');
collapse.classList.add('show');
groupItem.classList.add('active');
dropdownMenu.classList.add('show');
link.classList.add('active');
break;
case 'collapse':
groupLink.setAttribute('aria-expanded', 'false');
collapse.classList.remove('show');
groupItem.classList.remove('active');
dropdownMenu.classList.remove('show');
link.classList.remove('active');
break;
}
@ -232,13 +234,16 @@ class SideNav {
* Find any nav links with `href` attributes matching the current path, to determine which nav
* link should be considered active.
*/
private *getActiveLinks(): Generator<HTMLAnchorElement> {
for (const link of this.base.querySelectorAll<HTMLAnchorElement>(
'.navbar-nav .nav .nav-item a.nav-link',
private *getActiveLinks(): Generator<HTMLDivElement> {
for (const menuitem of this.base.querySelectorAll<HTMLDivElement>(
'ul.navbar-nav .nav-item .dropdown-item',
)) {
const link = menuitem.querySelector<HTMLAnchorElement>('a')
if (link) {
const href = new RegExp(link.href, 'gi');
if (window.location.href.match(href)) {
yield link;
yield menuitem;
}
}
}
}
@ -309,7 +314,7 @@ class SideNav {
}
export function initSideNav(): void {
for (const sidenav of getElements<HTMLDivElement>('.sidenav')) {
for (const sidenav of getElements<HTMLDivElement>('.navbar')) {
new SideNav(sidenav);
}
}

View File

@ -1,2 +0,0 @@
@import './theme-light.scss';
@import './cable-trace.scss';

View File

@ -1,10 +0,0 @@
// Entry for netbox-dark.css stylesheet.
html[data-netbox-color-mode='dark'] {
// Imports are scoped under the body when its data-netbox-color-mode attribute is set to 'dark'.
@import './theme-dark.scss';
@import './bootstrap.scss';
@import './select.scss';
@import './flatpickr-dark.scss';
@import './netbox.scss';
}

View File

@ -1,5 +0,0 @@
// Entry for all 3rd party library imports that do not rely on Bootstrap or NetBox styles.
@import '../node_modules/@mdi/font/css/materialdesignicons.min.css';
@import '../node_modules/flatpickr/dist/flatpickr.css';
@import '../node_modules/simplebar/dist/simplebar.css';
@import 'gridstack/dist/gridstack.min.css';

View File

@ -1,6 +0,0 @@
// Entry for netbox-light.css stylesheet.
@import './theme-light.scss';
@import './bootstrap.scss';
@import './select.scss';
@import './netbox.scss';

View File

@ -1,18 +0,0 @@
// Entry for netbox-print.css. Force light-mode theming when printing.
@media print {
// Force black and white background/foreground colors when printing.
:root {
--nbx-body-bg: #fff !important;
--nbx-body-color: #000 !important;
}
html,
html[data-netbox-color-mode='dark'],
html[data-netbox-color-mode='light'] {
@import './theme-light';
@import './bootstrap';
@import './select';
@import './netbox';
}
}

View File

@ -1,2 +0,0 @@
@import './theme-light.scss';
@import './rack-elevation.scss';

View File

@ -0,0 +1,20 @@
// Global variables
// Set base fonts
$font-family-base: 'Inter';
// See https://github.com/tabler/tabler/issues/1812
$font-family-monospace: 'Roboto Mono';
// Set the navigation sidebar width
$sidebar-width: 18rem;
// Reduce the default button padding
$btn-padding-x: 0.5rem;
$btn-padding-y: 0.25rem;
// Reduce the default table cell padding
$table-cell-padding-x: 0.5rem;
$table-cell-padding-y: 0.5rem;
// Fix Tabler bug #1694 in 1.0.0-beta20
$hover-bg: rgba(var(--tblr-secondary-rgb), 0.08);

View File

@ -1,35 +0,0 @@
// Import the rest of bootstrap.
@import '../node_modules/bootstrap/scss/utilities';
@import './extensions';
@import '../node_modules/bootstrap/scss/mixins';
@import '../node_modules/bootstrap/scss/root';
@import '../node_modules/bootstrap/scss/reboot';
@import '../node_modules/bootstrap/scss/type';
@import '../node_modules/bootstrap/scss/images';
@import '../node_modules/bootstrap/scss/containers';
@import '../node_modules/bootstrap/scss/grid';
@import '../node_modules/bootstrap/scss/tables';
@import '../node_modules/bootstrap/scss/forms';
@import '../node_modules/bootstrap/scss/buttons';
@import '../node_modules/bootstrap/scss/transitions';
@import '../node_modules/bootstrap/scss/dropdown';
@import '../node_modules/bootstrap/scss/button-group';
@import '../node_modules/bootstrap/scss/nav';
@import '../node_modules/bootstrap/scss/navbar';
@import '../node_modules/bootstrap/scss/card';
@import '../node_modules/bootstrap/scss/accordion';
@import '../node_modules/bootstrap/scss/breadcrumb';
@import '../node_modules/bootstrap/scss/pagination';
@import '../node_modules/bootstrap/scss/badge';
@import '../node_modules/bootstrap/scss/alert';
@import '../node_modules/bootstrap/scss/progress';
@import '../node_modules/bootstrap/scss/list-group';
@import '../node_modules/bootstrap/scss/close';
@import '../node_modules/bootstrap/scss/toasts';
@import '../node_modules/bootstrap/scss/modal';
@import '../node_modules/bootstrap/scss/tooltip';
@import '../node_modules/bootstrap/scss/popover';
@import '../node_modules/bootstrap/scss/carousel';
@import '../node_modules/bootstrap/scss/spinners';
@import '../node_modules/bootstrap/scss/helpers';
@import '../node_modules/bootstrap/scss/utilities/api';

View File

@ -0,0 +1,40 @@
// Serialized data from change records
pre.change-data {
padding-right: 0;
padding-left: 0;
// Display each line individually for highlighting
> span {
display: block;
padding-right: $spacer;
padding-left: $spacer;
&.added {
background-color: $green;
}
&.removed {
background-color: $red;
}
}
}
// Change data diff w/added & removed data
pre.change-diff {
border-color: transparent;
&.change-added {
background-color: $green;
}
&.change-removed {
background-color: $red;
}
}
// <pre> elements displayed with a border
pre.block {
padding: $spacer;
border: 1px solid $border-color;
border-radius: $border-radius;
}

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