Sfoglia il codice sorgente

#6372: Implement layout improvements

checktheroads 4 anni fa
parent
commit
eb0d5c996e

+ 1 - 1
netbox/project-static/_external.scss

@@ -1,4 +1,4 @@
 // Entry for all 3rd party library imports that do not rely on Bootstrap or NetBox styles.
 // Entry for all 3rd party library imports that do not rely on Bootstrap or NetBox styles.
-
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
 @import '@mdi/font/css/materialdesignicons.min.css';
 @import '@mdi/font/css/materialdesignicons.min.css';
 @import 'flatpickr/dist/flatpickr.css';
 @import 'flatpickr/dist/flatpickr.css';

File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/netbox-dark.css


File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/netbox-dark.css.map


File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/netbox-external.css


File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/netbox-external.css.map


File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/netbox-light.css


File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/netbox-light.css.map


File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/netbox.js


File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/netbox.js.map


+ 1 - 1
netbox/project-static/dist/rack_elevation.css

@@ -1,2 +1,2 @@
-*{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:.875rem}rect{box-sizing:border-box}text{text-anchor:middle;dominant-baseline:middle}svg .rack{background-color:#f3f4f6;fill:none;stroke:#111827;stroke-width:2px}svg .slot{fill:#f3f4f6;stroke:#6b7280}svg .slot:hover{fill:#f9fafb}svg .slot+.add-device{fill:none}svg .slot .add-device:hover,svg .slot:hover+.add-device{fill:#3b82f6}svg .slot .add-device:hover+.slot{fill:#fff}svg .slot.reserved:hover[class],svg .slot.reserved[class]{fill:url(#reserved)}svg .slot.occupied:hover[class],svg .slot.occupied[class]{fill:url(#occupied)}svg .slot.blocked:hover[class],svg .slot.blocked[class]{fill:url(#blocked)}svg .slot.blocked:hover+.add-device{fill:none}svg .unit{margin:0;padding:5px 0;fill:#9ca3af;font-size:.875rem;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}svg .hidden{visibility:hidden}svg[data-netbox-color-mode=dark] .rack{background-color:#1f2937}svg[data-netbox-color-mode=dark] .slot{fill:#374151;stroke:#9ca3af}svg[data-netbox-color-mode=dark] .slot:hover{fill:#4b5563}svg[data-netbox-color-mode=dark] .slot+.add-device{fill:none}svg[data-netbox-color-mode=dark] .add-device:hover,svg[data-netbox-color-mode=dark] .slot:hover+.add-device{fill:#93c5fd}svg[data-netbox-color-mode=dark] .add-device:hover+.slot{fill:#000}
+*{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:.875rem}rect{box-sizing:border-box}text{text-anchor:middle;dominant-baseline:middle}svg .rack{background-color:#f3f4f6;fill:none;stroke:#111827;stroke-width:2px}svg .slot{fill:#f3f4f6;stroke:#6b7280}svg .slot:hover{fill:#f9fafb}svg .slot+.add-device{fill:none}svg .slot .add-device:hover,svg .slot:hover+.add-device{fill:#3b82f6}svg .slot .add-device:hover+.slot{fill:#fff}svg .slot.reserved:hover[class],svg .slot.reserved[class]{fill:url(#reserved)}svg .slot.occupied:hover[class],svg .slot.occupied[class]{fill:url(#occupied)}svg .slot.blocked:hover[class],svg .slot.blocked[class]{fill:url(#blocked)}svg .slot.blocked:hover+.add-device{fill:none}svg .unit{margin:0;padding:5px 0;fill:#9ca3af;font-size:.875rem;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}svg .hidden{visibility:hidden}svg[data-netbox-color-mode=dark] .rack{background-color:#1f2937}svg[data-netbox-color-mode=dark] .slot{fill:#374151;stroke:#9ca3af}svg[data-netbox-color-mode=dark] .slot:hover{fill:#4b5563}svg[data-netbox-color-mode=dark] .slot+.add-device{fill:none}svg[data-netbox-color-mode=dark] .add-device:hover,svg[data-netbox-color-mode=dark] .slot:hover+.add-device{fill:#93c5fd}svg[data-netbox-color-mode=dark] .add-device:hover+.slot{fill:#000}
 /*# sourceMappingURL=/static/rack_elevation.css.map */
 /*# sourceMappingURL=/static/rack_elevation.css.map */

File diff suppressed because it is too large
+ 0 - 0
netbox/project-static/dist/rack_elevation.css.map


+ 43 - 13
netbox/project-static/netbox.scss

@@ -98,6 +98,21 @@ table td > .progress {
   min-width: 6rem;
   min-width: 6rem;
 }
 }
 
 
+span.profile-button .dropdown-menu {
+  transition: opacity 0.2s ease-in-out;
+  display: block !important;
+  right: 0;
+  left: auto;
+  margin-top: 0.5rem;
+  box-shadow: $box-shadow;
+  &:not(.show) {
+    opacity: 0;
+  }
+  &.show {
+    opacity: 1;
+  }
+}
+
 div#advanced-search-content div.card div.card-body div.col:not(:last-child) {
 div#advanced-search-content div.card div.card-body div.col:not(:last-child) {
   margin-right: 1rem;
   margin-right: 1rem;
 }
 }
@@ -362,6 +377,12 @@ div.content-container {
   min-height: 100vh;
   min-height: 100vh;
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
+  overflow: hidden;
+
+  @include media-breakpoint-up(md) {
+    margin-left: $sidebar-width;
+  }
+
   div.content {
   div.content {
     flex: 1;
     flex: 1;
   }
   }
@@ -383,27 +404,36 @@ div.content-container {
   z-index: 100; /* Behind the navbar */
   z-index: 100; /* Behind the navbar */
   border-right: 1px solid $border-color;
   border-right: 1px solid $border-color;
   background-color: var(--nbx-sidebar-bg);
   background-color: var(--nbx-sidebar-bg);
+  max-height: 100%;
+  width: 100%;
+
+  @include media-breakpoint-up(md) {
+    width: 100%;
+    max-width: $sidebar-width;
+  }
 
 
   @media (max-width: map.get($grid-breakpoints, 'md')) {
   @media (max-width: map.get($grid-breakpoints, 'md')) {
     top: 8.125rem;
     top: 8.125rem;
   }
   }
 
 
   div.accordion-item {
   div.accordion-item {
-    div.accordion-collapse {
-      &.collapse.show,
-      &.collapsing {
-        background-color: $accordion-body-active-bg;
-      }
-    }
-    & > a.accordion-button.nav-link {
-      &:hover {
-        color: $accordion-button-active-color;
-        background-color: $accordion-button-active-bg;
-      }
-      &:focus {
-        border-color: unset;
+    border: unset;
+
+    & > a.accordion-button {
+      &:not(.collapsed) {
         box-shadow: unset;
         box-shadow: unset;
       }
       }
+      &.nav-link {
+        border-radius: $border-radius;
+        &:hover {
+          color: $accordion-button-active-color;
+          background-color: $accordion-button-active-bg;
+        }
+        &:focus {
+          border-color: unset;
+          box-shadow: unset;
+        }
+      }
     }
     }
   }
   }
 
 

+ 1 - 0
netbox/project-static/package.json

@@ -20,6 +20,7 @@
     "parcel-bundler": "1.12.3",
     "parcel-bundler": "1.12.3",
     "query-string": "^6.14.1",
     "query-string": "^6.14.1",
     "sass": "^1.32.8",
     "sass": "^1.32.8",
+    "simplebar": "^5.3.4",
     "slim-select": "^1.27.0"
     "slim-select": "^1.27.0"
   },
   },
   "devDependencies": {
   "devDependencies": {

+ 0 - 3
netbox/project-static/src/colorMode.ts

@@ -1,8 +1,5 @@
 import { getElements, isTruthy } from './util';
 import { getElements, isTruthy } from './util';
 
 
-type ColorMode = 'light' | 'dark';
-type ColorModePreference = ColorMode | 'none';
-
 const COLOR_MODE_KEY = 'netbox-color-mode';
 const COLOR_MODE_KEY = 'netbox-color-mode';
 const TEXT_WHEN_DARK = 'Light Mode';
 const TEXT_WHEN_DARK = 'Light Mode';
 const TEXT_WHEN_LIGHT = 'Dark Mode';
 const TEXT_WHEN_LIGHT = 'Dark Mode';

+ 11 - 0
netbox/project-static/src/global.d.ts

@@ -169,3 +169,14 @@ interface ObjectWithGroup extends APIObjectBase {
 declare const messages: string[];
 declare const messages: string[];
 
 
 type FormControls = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
 type FormControls = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
+
+type ColorMode = 'light' | 'dark';
+type ColorModePreference = ColorMode | 'none';
+type ConfigContextFormat = 'json' | 'yaml';
+
+type UserPreferences = {
+  ui: {
+    colorMode: ColorMode;
+    showRackImages: boolean;
+  };
+};

+ 1 - 0
netbox/project-static/src/index.ts

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

+ 2 - 0
netbox/project-static/src/netbox.ts

@@ -9,6 +9,7 @@ import { initClipboard } from './clipboard';
 import { initDateSelector } from './dateSelector';
 import { initDateSelector } from './dateSelector';
 import { initTableConfig } from './tableConfig';
 import { initTableConfig } from './tableConfig';
 import { initInterfaceTable } from './tables';
 import { initInterfaceTable } from './tables';
+import { initSideNav } from './sidenav';
 
 
 function init() {
 function init() {
   for (const init of [
   for (const init of [
@@ -23,6 +24,7 @@ function init() {
     initClipboard,
     initClipboard,
     initTableConfig,
     initTableConfig,
     initInterfaceTable,
     initInterfaceTable,
+    initSideNav,
   ]) {
   ]) {
     init();
     init();
   }
   }

+ 22 - 0
netbox/project-static/src/sidenav.ts

@@ -0,0 +1,22 @@
+import { getElement, getElements } from './util';
+
+const breakpoints = {
+  sm: 540,
+  md: 720,
+  lg: 960,
+  xl: 1140,
+};
+
+function toggleBodyPosition(position: HTMLBodyElement['style']['position']): void {
+  for (const element of getElements('body')) {
+    element.style.position = position;
+  }
+}
+
+export function initSideNav() {
+  const element = getElement<HTMLAnchorElement>('sidebarMenu');
+  if (element !== null && document.body.clientWidth < breakpoints.lg) {
+    element.addEventListener('shown.bs.collapse', () => toggleBodyPosition('fixed'));
+    element.addEventListener('hidden.bs.collapse', () => toggleBodyPosition('relative'));
+  }
+}

+ 5 - 3
netbox/project-static/theme-base.scss

@@ -235,11 +235,13 @@ $theme-color-addons: (
   'pink-900': $pink-900,
   'pink-900': $pink-900,
 );
 );
 
 
-$font-family-sans-serif: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
-  Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
-  'Segoe UI Symbol', 'Noto Color Emoji';
+$font-family-sans-serif: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
+  'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
+  'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
 $font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
 $font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
   'Courier New', monospace;
   'Courier New', monospace;
 
 
 $accordion-padding-y: 0.8125rem;
 $accordion-padding-y: 0.8125rem;
 $accordion-padding-x: 0.8125rem;
 $accordion-padding-x: 0.8125rem;
+
+$sidebar-width: 280px;

+ 32 - 0
netbox/project-static/yarn.lock

@@ -1070,6 +1070,11 @@
   resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
   resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
   integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
   integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
 
 
+"@juggle/resize-observer@^3.3.1":
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0"
+  integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==
+
 "@mdi/font@^5.9.55":
 "@mdi/font@^5.9.55":
   version "5.9.55"
   version "5.9.55"
   resolved "https://registry.yarnpkg.com/@mdi/font/-/font-5.9.55.tgz#41acd50b88073ded7095fc3029d8712b6e12f38e"
   resolved "https://registry.yarnpkg.com/@mdi/font/-/font-5.9.55.tgz#41acd50b88073ded7095fc3029d8712b6e12f38e"
@@ -2022,6 +2027,11 @@ camelcase@^5.0.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
 
+can-use-dom@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/can-use-dom/-/can-use-dom-0.1.0.tgz#22cc4a34a0abc43950f42c6411024a3f6366b45a"
+  integrity sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo=
+
 caniuse-api@^3.0.0:
 caniuse-api@^3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
   resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
@@ -2388,6 +2398,11 @@ core-js@^2.4.0, core-js@^2.5.0:
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
   integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
   integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
 
 
+core-js@^3.0.1:
+  version "3.15.1"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.1.tgz#6c08ab88abdf56545045ccf5fd81f47f407e7f1a"
+  integrity sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg==
+
 core-util-is@1.0.2, core-util-is@~1.0.0:
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -4820,6 +4835,11 @@ lodash.sortby@^4.7.0:
   resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
   resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
   integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
   integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
 
 
+lodash.throttle@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
+  integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
+
 lodash.toarray@^4.4.0:
 lodash.toarray@^4.4.0:
   version "4.4.0"
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
   resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
@@ -6957,6 +6977,18 @@ simple-swizzle@^0.2.2:
   dependencies:
   dependencies:
     is-arrayish "^0.3.1"
     is-arrayish "^0.3.1"
 
 
+simplebar@^5.3.4:
+  version "5.3.4"
+  resolved "https://registry.yarnpkg.com/simplebar/-/simplebar-5.3.4.tgz#7de8d4a07ed3c6612644f4dbc04a8427fdf038ef"
+  integrity sha512-2mCaVdiroCKmXuD+Qfy+QSE32m5BMuZ4ssHvRD1QEPYH95Re/kox7j/Wy0Hje8Uo7LY7O6JK3XSNJmesGlsP8Q==
+  dependencies:
+    "@juggle/resize-observer" "^3.3.1"
+    can-use-dom "^0.1.0"
+    core-js "^3.0.1"
+    lodash.debounce "^4.0.8"
+    lodash.memoize "^4.1.2"
+    lodash.throttle "^4.1.1"
+
 slash@^3.0.0:
 slash@^3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"

+ 96 - 99
netbox/templates/base/layout.html

@@ -6,126 +6,123 @@
 
 
 {% block layout %}
 {% block layout %}
   <div class="container-fluid">
   <div class="container-fluid">
-    <div class="row">
-      <main class="col-md-9 ms-sm-auto col-lg-10 px-0">
+    <main class="ms-sm-auto px-0">
+    {# Sidebar #}
+      <nav id="sidebar-menu" class="d-md-block sidebar collapse px-0" data-simplebar>
+
+        {# Sidebar content #}
+        <div class="position-sticky pt-3">
+
+          {# Logo #}
+          <a class="p-1 sidebar-logo d-none d-md-flex justify-content-center" href="{% url 'home' %}">
+            <img src="{% static 'netbox_logo.svg' %}" alt="NetBox logo" />
+          </a>
+
+          {# Search bar #}
+          <ul class="nav flex-column px-4">
+            <div class="d-block d-md-none mx-1 my-3 search-container">
+              {% search_options %}
+            </div>
+            <div class="d-flex d-md-none mx-1 my-3 justify-content-end">
+              {% include 'inc/profile_button.html' %}
+            </div>
+            {% nav %}
+          </ul>
 
 
-        {# Sidebar #}
-        <nav id="sidebar-menu" class="col-md-3 col-lg-2 d-md-block sidebar collapse px-0">
+        </div>
 
 
-          {# Sidebar content #}
-          <div class="position-sticky pt-3">
+        {# Sidebar footer #}
+        <div class="d-flex flex-column container-fluid mt-auto justify-content-end sidebar-bottom">
+          <nav class="nav justify-content-between mb-2 mt-4 px-2">
 
 
-            {# Logo #}
-            <a class="p-1 sidebar-logo d-none d-md-flex justify-content-center" href="{% url 'home' %}">
-              <img src="{% static 'netbox_logo.svg' %}" alt="NetBox logo" />
+            {# Documentation #}
+            <a type="button" target="_blank" class="nav-link" href="https://netbox.readthedocs.io/">
+              <i title="Docs" class="mdi mdi-book-open-variant text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
             </a>
             </a>
 
 
-            {# Search bar #}
-            <ul class="nav flex-column">
-              <div class="d-block d-md-none mx-1 my-3 search-container">
-                {% search_options %}
-              </div>
-              <div class="d-flex d-md-none mx-1 my-3 justify-content-end">
-                {% include 'inc/profile_button.html' %}
-              </div>
-              {% nav %}
-            </ul>
-
-          </div>
+            {# API docs #}
+            <a class="nav-link" href="{% url 'api_docs' %}" target="_blank">
+              <i title="API" data-bs-placement="top" data-bs-toggle="tooltip" class="mdi mdi-code-braces text-primary"></i>
+            </a>
 
 
-          {# Sidebar footer #}
-          <div class="d-flex flex-column container-fluid mt-auto justify-content-end sidebar-bottom">
-            <nav class="nav justify-content-between mb-2 mt-4 px-2">
+            {# GitHub #}
+            <a class="nav-link" href="https://github.com/netbox-community/netbox" target="_blank">
+              <i title="Source Code" data-bs-placement="top" data-bs-toggle="tooltip" class="mdi mdi-code-tags text-primary"></i>
+            </a>
 
 
-              {# Documentation #}
-              <a type="button" target="_blank" class="nav-link" href="https://netbox.readthedocs.io/">
-                <i title="Docs" class="mdi mdi-book-open-variant text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
-              </a>
+            {# GitHub wiki #}
+            <a target="_blank" class="nav-link" href="https://github.com/netbox-community/netbox/wiki">
+              <i title="Get Help" data-bs-placement="top" data-bs-toggle="tooltip" class="mdi mdi-lifebuoy text-primary"></i>
+            </a>
+          </nav>
+        </div>
 
 
-              {# API docs #}
-              <a class="nav-link" href="{% url 'api_docs' %}" target="_blank">
-                <i title="API" data-bs-placement="top" data-bs-toggle="tooltip" class="mdi mdi-code-braces text-primary"></i>
-              </a>
+      </nav>
 
 
-              {# GitHub #}
-              <a class="nav-link" href="https://github.com/netbox-community/netbox" target="_blank">
-                <i title="Source Code" data-bs-placement="top" data-bs-toggle="tooltip" class="mdi mdi-code-tags text-primary"></i>
-              </a>
+      {# Body #}
+      <div class="content-container">
 
 
-              {# GitHub wiki #}
-              <a target="_blank" class="nav-link" href="https://github.com/netbox-community/netbox/wiki">
-                <i title="Get Help" data-bs-placement="top" data-bs-toggle="tooltip" class="mdi mdi-lifebuoy text-primary"></i>
+        {# Top bar #}
+        <nav class="navbar navbar-light sticky-top flex-md-nowrap p-3 search container-fluid">
+          <div class="d-md-none w-100 d-flex justify-content-between align-items-center my-3">
+              <a class="px-2 sidebar-logo d-block d-md-none" href="{% url 'home' %}">
+                <img src="{% static 'netbox_logo.svg' %}" alt="NetBox logo" />
               </a>
               </a>
-            </nav>
-          </div>
-
+              <button
+                type="button"
+                aria-expanded="false"
+                data-bs-toggle="collapse"
+                aria-controls="sidebar-menu"
+                data-bs-target="#sidebar-menu"
+                aria-label="Toggle Navigation"
+                class="navbar-toggler position-relative collapsed"
+              >
+                <span class="navbar-toggler-icon"></span>
+              </button>
+            </div>
+            <div class="d-none d-md-flex w-100 search-container">
+              {% search_options %}
+              {% include 'inc/profile_button.html' %}
+            </div>
         </nav>
         </nav>
 
 
-        {# Body #}
-        <div class="content-container">
-
-          {# Top bar #}
-          <nav class="navbar navbar-light sticky-top flex-md-nowrap p-3 search container-fluid">
-            <div class="d-md-none w-100 d-flex justify-content-between align-items-center my-3">
-                <a class="px-2 sidebar-logo d-block d-md-none" href="{% url 'home' %}">
-                  <img src="{% static 'netbox_logo.svg' %}" alt="NetBox logo" />
-                </a>
-                <button
-                  type="button"
-                  aria-expanded="false"
-                  data-bs-toggle="collapse"
-                  aria-controls="sidebar-menu"
-                  data-bs-target="#sidebar-menu"
-                  aria-label="Toggle Navigation"
-                  class="navbar-toggler position-relative collapsed"
-                >
-                  <span class="navbar-toggler-icon"></span>
-                </button>
-              </div>
-              <div class="d-none d-md-flex w-100 search-container">
-                {% search_options %}
-                {% include 'inc/profile_button.html' %}
-              </div>
-          </nav>
-
-          {# Page header #}
-          {% block header %}
-            <div class="title-container px-3 py-3">
+        {# Page header #}
+        {% block header %}
+          <div class="title-container px-3 py-3">
 
 
-              {# Title #}
-              <div id="content-title">
-                {# Center-align title in object-edit views #}
-                <h1 class="h2 w-100{% if form or vc_form %} text-center{% endif %}">{% block title %}{% endblock title %}</h1>
-              </div>
+            {# Title #}
+            <div id="content-title">
+              {# Center-align title in object-edit views #}
+              <h1 class="h2 w-100{% if form or vc_form %} text-center{% endif %}">{% block title %}{% endblock title %}</h1>
+            </div>
 
 
-              {# Controls #}
-              {% block controls %}{% endblock controls %}
+            {# Controls #}
+            {% block controls %}{% endblock controls %}
 
 
-            </div>
-          {% endblock header %}
+          </div>
+        {% endblock header %}
 
 
-          {# Page content #}
-          <div id="content" class="container-fluid content px-0 m-0">
-            {% block tabs %}{% endblock %}
-            <div class="px-3">
-              {% block content %}{% endblock %}
-            </div>
+        {# Page content #}
+        <div id="content" class="container-fluid content px-0 m-0">
+          {% block tabs %}{% endblock %}
+          <div class="px-3">
+            {% block content %}{% endblock %}
           </div>
           </div>
+        </div>
 
 
-          {# Page footer #}
-          <footer class="footer container-fluid pb-3 pt-4 px-0">
-            <div class="row align-items-center justify-content-end mx-0">
-            <div class="col-auto d-none d-md-block"></div>
-              <div class="col text-center small text-muted">
-                <span class="fw-light d-block d-md-inline">{% now 'Y-m-d H:i:s T' %}</span>
-                <span class="ms-md-3 d-block d-md-inline">{{ settings.HOSTNAME }} (v{{ settings.VERSION }})</span>
-              </div>
+        {# Page footer #}
+        <footer class="footer container-fluid pb-3 pt-4 px-0">
+          <div class="row align-items-center justify-content-end mx-0">
+          <div class="col-auto d-none d-md-block"></div>
+            <div class="col text-center small text-muted">
+              <span class="fw-light d-block d-md-inline">{% now 'Y-m-d H:i:s T' %}</span>
+              <span class="ms-md-3 d-block d-md-inline">{{ settings.HOSTNAME }} (v{{ settings.VERSION }})</span>
             </div>
             </div>
-          </footer>
+          </div>
+        </footer>
 
 
-        </div>
+      </div>
 
 
-      </main>
-    </div>
+    </main>
   </div>
   </div>
 {% endblock layout %}
 {% endblock layout %}

+ 3 - 3
netbox/templates/inc/profile_button.html

@@ -1,5 +1,5 @@
 {% if request.user.is_authenticated %}
 {% if request.user.is_authenticated %}
-<span class="dropdown ms-0 ms-md-3">
+<span class="dropdown ms-0 ms-md-3 profile-button">
   <button
   <button
     type="button"
     type="button"
     aria-expanded="false"
     aria-expanded="false"
@@ -25,12 +25,12 @@
     </li>
     </li>
     <li>
     <li>
       <a class="dropdown-item" href="{% url 'user:profile' %}">
       <a class="dropdown-item" href="{% url 'user:profile' %}">
-        <i class="mdi mdi-account"></i> Profile
+        <i class="mdi mdi-account"></i> Profile & Settings
       </a>
       </a>
     </li>
     </li>
     <li><hr class="dropdown-divider" /></li>
     <li><hr class="dropdown-divider" /></li>
     <li>
     <li>
-      <a class="dropdown-item" href="{% url 'logout' %}">
+      <a class="dropdown-item text-danger" href="{% url 'logout' %}">
         <i class="mdi mdi-logout-variant"></i> Log Out
         <i class="mdi mdi-logout-variant"></i> Log Out
       </a>
       </a>
     </li>
     </li>

+ 1 - 0
netbox/utilities/templates/navigation/nav_items.html

@@ -11,6 +11,7 @@
         data-bs-target="#{{ menu.label|lower }}"
         data-bs-target="#{{ menu.label|lower }}"
         class="d-flex justify-content-between align-items-center accordion-button nav-link collapsed">
         class="d-flex justify-content-between align-items-center accordion-button nav-link collapsed">
           <span class="fw-bold sidebar-nav-link">
           <span class="fw-bold sidebar-nav-link">
+            <i class="mdi mdi-{{ menu.icon }} me-1"></i>
             {{ menu.label }}
             {{ menu.label }}
           </span>
           </span>
         </a>
         </a>

+ 8 - 0
netbox/utilities/templatetags/nav.py

@@ -33,11 +33,13 @@ class Menu:
     """A top level menu group. Example: Organization, Devices, IPAM."""
     """A top level menu group. Example: Organization, Devices, IPAM."""
 
 
     label: str
     label: str
+    icon: str
     groups: Sequence[MenuGroup]
     groups: Sequence[MenuGroup]
 
 
 
 
 ORGANIZATION_MENU = Menu(
 ORGANIZATION_MENU = Menu(
     label="Organization",
     label="Organization",
+    icon="domain",
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
             label="Sites",
             label="Sites",
@@ -83,6 +85,7 @@ ORGANIZATION_MENU = Menu(
 
 
 DEVICES_MENU = Menu(
 DEVICES_MENU = Menu(
     label="Devices",
     label="Devices",
+    icon="server",
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
             label="Devices",
             label="Devices",
@@ -150,6 +153,7 @@ DEVICES_MENU = Menu(
 
 
 IPAM_MENU = Menu(
 IPAM_MENU = Menu(
     label="IPAM",
     label="IPAM",
+    icon="counter",
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
             label="IP Addresses",
             label="IP Addresses",
@@ -204,6 +208,7 @@ IPAM_MENU = Menu(
 
 
 VIRTUALIZATION_MENU = Menu(
 VIRTUALIZATION_MENU = Menu(
     label="Virtualization",
     label="Virtualization",
+    icon="monitor",
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
             label="Virtual Machines",
             label="Virtual Machines",
@@ -231,6 +236,7 @@ VIRTUALIZATION_MENU = Menu(
 
 
 CIRCUITS_MENU = Menu(
 CIRCUITS_MENU = Menu(
     label="Circuits",
     label="Circuits",
+    icon="transit-connection-variant",
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
             label="Circuits",
             label="Circuits",
@@ -256,6 +262,7 @@ CIRCUITS_MENU = Menu(
 
 
 POWER_MENU = Menu(
 POWER_MENU = Menu(
     label="Power",
     label="Power",
+    icon="flash",
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
             label="Power",
             label="Power",
@@ -271,6 +278,7 @@ POWER_MENU = Menu(
 
 
 OTHER_MENU = Menu(
 OTHER_MENU = Menu(
     label="Other",
     label="Other",
+    icon="notification-clear-all",
     groups=(
     groups=(
         MenuGroup(
         MenuGroup(
             label="Logging",
             label="Logging",

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