diff --git a/netbox/project-static/bootstrap.scss b/netbox/project-static/bootstrap.scss new file mode 100644 index 000000000..b4629e1e9 --- /dev/null +++ b/netbox/project-static/bootstrap.scss @@ -0,0 +1,34 @@ +// Import the rest of bootstrap. +@import 'bootstrap/scss/utilities'; +@import 'bootstrap/scss/mixins'; +@import 'bootstrap/scss/root'; +@import 'bootstrap/scss/reboot'; +@import 'bootstrap/scss/type'; +@import 'bootstrap/scss/images'; +@import 'bootstrap/scss/containers'; +@import 'bootstrap/scss/grid'; +@import 'bootstrap/scss/tables'; +@import 'bootstrap/scss/forms'; +@import 'bootstrap/scss/buttons'; +@import 'bootstrap/scss/transitions'; +@import 'bootstrap/scss/dropdown'; +@import 'bootstrap/scss/button-group'; +@import 'bootstrap/scss/nav'; +@import 'bootstrap/scss/navbar'; +@import 'bootstrap/scss/card'; +@import 'bootstrap/scss/accordion'; +@import 'bootstrap/scss/breadcrumb'; +@import 'bootstrap/scss/pagination'; +@import 'bootstrap/scss/badge'; +@import 'bootstrap/scss/alert'; +@import 'bootstrap/scss/progress'; +@import 'bootstrap/scss/list-group'; +@import 'bootstrap/scss/close'; +@import 'bootstrap/scss/toasts'; +@import 'bootstrap/scss/modal'; +@import 'bootstrap/scss/tooltip'; +@import 'bootstrap/scss/popover'; +@import 'bootstrap/scss/carousel'; +@import 'bootstrap/scss/spinners'; +@import 'bootstrap/scss/helpers'; +@import 'bootstrap/scss/utilities/api'; diff --git a/netbox/project-static/choices.scss b/netbox/project-static/choices.scss new file mode 100644 index 000000000..23892b13c --- /dev/null +++ b/netbox/project-static/choices.scss @@ -0,0 +1,44 @@ +$choices-font-size-lg: $form-select-font-size-lg; +$choices-font-size-md: $form-select-font-size; +$choices-font-size-sm: $form-select-font-size-sm; +$choices-guttering: $form-select-padding-y; +$choices-border-radius: $form-select-border-radius; +$choices-bg-color: $form-select-bg; +$choices-bg-color-disabled: $form-select-disabled-bg; +$choices-bg-color-dropdown: $form-select-bg; +$choices-text-color: $form-select-color; +$choices-keyline-color: $form-select-border-color; +$choices-primary-color: $primary; +$choices-disabled-color: $form-select-disabled-color; +$choices-highlight-color: $choices-primary-color; +$choices-button-dimension: $form-select-bg-size; + +.choices { + .choices__list--dropdown .choices__item--selectable.is-highlighted[class] { + background-color: $primary; + color: white; + } + + // Floating-input adjusts the z-index of the label. This fixes an issue where if there are two + // floating inputs on top of eachother, the label of an overlapping field is visible inside the + // dropdown's dropdown list. + &.is-open .choices__list--dropdown { + z-index: 10; + } +} + +.choices[data-type*='select-one'] select.choices__input { + display: block !important; + opacity: 0; + pointer-events: none; + position: absolute; + left: 0; + bottom: 0; +} + +select[data-ssid] { + display: block !important; + opacity: 0; + pointer-events: none; + position: absolute; +} diff --git a/netbox/project-static/main.scss b/netbox/project-static/main.scss new file mode 100644 index 000000000..27bd36b6f --- /dev/null +++ b/netbox/project-static/main.scss @@ -0,0 +1,10 @@ +@import './theme.scss'; +@import './bootstrap.scss'; + +@import './materialdesignicons-5.4.55/css/materialdesignicons.min.css'; +@import './node_modules/bootstrap-icons/font/bootstrap-icons.css'; + +@import './select.scss'; +@import './node_modules/flatpickr/dist/flatpickr.min.css'; + +@import './netbox.scss'; diff --git a/netbox/project-static/netbox.scss b/netbox/project-static/netbox.scss new file mode 100644 index 000000000..4e2deddaf --- /dev/null +++ b/netbox/project-static/netbox.scss @@ -0,0 +1,315 @@ +main.login-container { + display: flex; + height: calc(100vh - 4rem); + width: 100vw; + align-items: center; + justify-content: center; + flex-direction: column; + padding-top: 40px; + padding-bottom: 40px; +} + +footer.login-footer { + height: 4rem; + margin-top: auto; + + .container-fluid { + display: flex; + justify-content: flex-end; + padding: $container-padding-x $grid-gutter-width; + } +} + +.form-login { + width: 100%; + max-width: 330px; + padding: 15px; + // margin: auto; +} + +.form-login input[type='text'] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.form-login input[type='password'] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.form-login .form-control { + position: relative; + box-sizing: border-box; + height: auto; + padding: 10px; + font-size: 16px; +} + +li.dropdown-item.dropdown-item-btns { + display: flex; + justify-content: space-between; + align-items: center; +} + +.sidebar { + position: fixed; + top: 0; + bottom: 0; + left: 0; + z-index: 100; /* Behind the navbar */ + // padding: 48px 0 0; /* Height of navbar */ + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1); +} + +@media (max-width: 767.98px) { + .sidebar { + top: 5rem; + } +} + +.sidebar-sticky { + position: relative; + top: 0; + height: calc(100vh - 48px); + padding-top: 0.5rem; + overflow-x: hidden; + overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ +} + +.navbar-brand { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + font-size: 1rem; +} + +.sidebar { + .sidebar-nav-link { + color: $gray-800; + } + .accordion-body { + max-height: calc(100vh - 24rem); + overflow-y: auto; + .nav-item { + .nav-link { + padding: 0.25rem 0.5rem; + font-size: $font-size-base; + } + .nav-link:hover { + background-color: $blue-100; + border-radius: $border-radius; + } + } + } + div.position-sticky { + height: calc(100% - 8rem); + } + div.sidebar-bottom { + padding-left: 0.5rem; + padding-right: 0.5rem; + position: sticky; + height: 8rem; + background-color: $light; + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1); + .nav-link { + padding: 0.5rem 0.25rem; + } + } + a.sidebar-logo { + display: flex; + flex-shrink: 1; + width: 100%; + height: 4rem; + } +} + +.tooltip { + pointer-events: none; +} + +.ws-nowrap { + white-space: nowrap !important; +} + +h1.accordion-item-title, +h2.accordion-item-title, +h3.accordion-item-title, +h4.accordion-item-title, +h5.accordion-item-title, +h6.accordion-item-title { + padding: 0 0.5rem; + font-weight: $font-weight-bold; + text-transform: uppercase; + color: $text-muted; + font-size: $font-size-sm; +} + +#object-type-selector { + button.dropdown-item, + h6.dropdown-header { + font-size: $font-size-sm; + } +} + +.stats-container { + min-height: 50vh; +} + +span.color-label { + width: 5rem; + height: 1rem; + display: block; + box-shadow: $box-shadow-sm; + border-radius: $border-radius; + padding: $badge-padding-y $badge-padding-x; +} + +.card { + box-shadow: $box-shadow-sm; + .card-header { + border-bottom: none; + padding: $card-cap-padding-x; + } + .card-header + .card-body { + padding-top: 0; + } +} + +.form-floating { + position: relative; + + > .input-group > .form-control, + > .input-group > .form-select { + height: $form-floating-height; + padding: $form-floating-padding-y $form-floating-padding-x; + } + + > .input-group > label { + position: absolute; + top: 0; + left: 0; + height: 100%; // allow textareas + padding: $form-floating-padding-y $form-floating-padding-x; + pointer-events: none; + border: $input-border-width solid transparent; // Required for aligning label's text with the input as it affects inner box model + transform-origin: 0 0; + @include transition($form-floating-transition); + } + + > .input-group > .form-control { + &::placeholder { + color: transparent; + } + + &:focus, + &:not(:placeholder-shown) { + padding-top: $form-floating-input-padding-t; + padding-bottom: $form-floating-input-padding-b; + } + // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped + &:-webkit-autofill { + padding-top: $form-floating-input-padding-t; + padding-bottom: $form-floating-input-padding-b; + } + } + + > .input-group > .form-select, + > .choices > .choices__inner, + > .ss-main span.placeholder, // SlimSelect Single + > .ss-main div.ss-values // SlimSelect Multiple + { + padding-top: $form-floating-input-padding-t; + padding-bottom: $form-floating-input-padding-b; + } + + > .input-group > .form-control:focus, + > .input-group > .form-control:not(:placeholder-shown), + > .input-group > .form-select, + > .choices, + > .ss-main { + ~ label { + opacity: $form-floating-label-opacity; + transform: $form-floating-label-transform; + z-index: 4; + } + } + // Duplicated because `:-webkit-autofill` invalidates other selectors when grouped + > .input-group > .form-control:-webkit-autofill { + ~ label { + opacity: $form-floating-label-opacity; + transform: $form-floating-label-transform; + z-index: 4; + } + } +} + +textarea#id_local_context_data, +textarea#id_comments { + font-family: $font-family-monospace; +} + +// Pad all adjacent cards +.card:not(:only-of-type) { + margin-bottom: $spacer; +} + +.stat-btn { + min-width: $spacer * 3; +} + +nav.breadcrumb-container { + padding: $badge-padding-y $badge-padding-x; + border-radius: $border-radius; + background-color: $light; + font-size: $font-size-sm; + width: fit-content; + + ol.breadcrumb { + margin-bottom: 0; + li.breadcrumb-item > a { + text-decoration: none; + } + li.breadcrumb-item > a:hover { + text-decoration: underline; + } + } +} + +div#content-title { + display: flex; + flex-direction: column; + flex: 1 0 auto; +} + +div.paginator > form > div.input-group { + width: fit-content; +} + +button.btn.btn-outline-gray.dropdown-toggle:after { + color: $black; +} + +// Apply bootstrap focus styling to Choices.JS elements. +div.choices.is-focused > div.choices__inner { + border-color: $form-select-focus-border-color; + outline: 0; + @if $enable-shadows { + @include box-shadow($form-select-box-shadow, $form-select-focus-box-shadow); + } @else { + box-shadow: $form-select-focus-box-shadow; + } +} + +div.field-group:not(:first-of-type) { + margin-top: $spacer * 3; + + h1, + h2, + h3, + h4, + h5, + h6 { + margin-bottom: $spacer; + } +} diff --git a/netbox/project-static/rack_elevation.scss b/netbox/project-static/rack_elevation.scss new file mode 100644 index 000000000..5e8155f10 --- /dev/null +++ b/netbox/project-static/rack_elevation.scss @@ -0,0 +1,69 @@ +/* Stylesheet for rendering SVG rack elevations */ +@import './theme.scss'; +* { + font-family: $font-family-sans-serif; + font-size: $font-size-sm; +} +rect { + box-sizing: border-box; +} +text { + text-anchor: middle; + dominant-baseline: middle; +} +.rack { + background-color: $gray-100; + fill: none; + stroke: black; + stroke-width: 2px; +} +.slot { + fill: $gray-200; + stroke: $gray-500; +} +.slot:hover { + fill: $white; +} +.slot + .add-device { + fill: none; +} +.slot:hover + .add-device { + fill: $primary; +} +.add-device:hover { + fill: $primary; +} +.add-device:hover + .slot { + fill: $white; +} +.reserved { + fill: url(#reserved); +} +.reserved:hover { + fill: url(#reserved); +} +.occupied { + fill: url(#occupied); +} +.occupied:hover { + fill: url(#occupied); +} +.blocked { + fill: url(#blocked); +} +.blocked:hover { + fill: url(#blocked); +} +.blocked:hover + .add-device { + fill: none; +} +.unit { + margin: 0; + padding: 5px 0px; + fill: $gray-400; + font-size: $font-size-sm; + font-family: $font-family-sans-serif; +} +.hidden { + visibility: hidden; +} diff --git a/netbox/project-static/select.scss b/netbox/project-static/select.scss new file mode 100644 index 000000000..a017cf5a9 --- /dev/null +++ b/netbox/project-static/select.scss @@ -0,0 +1,78 @@ +$height: $form-floating-height; +$white: $white; +$font-color: $input-color; +$font-placeholder-color: $input-placeholder-color; +$font-disabled-color: $form-select-disabled-color; +$primary-color: $primary; +$border-color: $form-select-border-color; +$search-highlight-color: $yellow; +$border-radius: $form-select-border-radius; +$spacing-l: $input-padding-x; +$spacing-m: $input-padding-x; +$spacing-s: $input-padding-x; + +@import './node_modules/slim-select/src/slim-select/slimselect.scss'; + +.ss-main { + &.is-invalid .ss-single-selected, + &.is-invalid .ss-multi-selected { + border-color: $form-feedback-icon-invalid-color; + } + + &.is-valid .ss-single-selected, + &.is-valid .ss-multi-selected { + border-color: $form-feedback-icon-valid-color; + } + + .ss-single-selected { + span.ss-arrow { + // Inherit the arrow color from the parent (see color selector). + span.arrow-down, + span.arrow-up { + border-color: currentColor; + } + } + span.placeholder > *, + span.placeholder { + line-height: $input-line-height; + } + } + + .ss-multi-selected { + align-items: center; + padding-left: $input-padding-x; + padding-right: $input-padding-x; + .ss-values .ss-disabled { + padding: 4px 0px; + } + .ss-add { + margin: 0 0.75rem; + } + } + + .ss-content { + .ss-list { + .ss-option:last-child { + border-bottom-left-radius: $form-select-border-radius; + border-bottom-right-radius: $form-select-border-radius; + } + } + border-bottom-left-radius: $form-select-border-radius; + border-bottom-right-radius: $form-select-border-radius; + .ss-search { + input[type='search'] { + border: $form-select-border-width solid $form-select-border-color; + &:focus { + border-color: $form-select-focus-border-color; + outline: 0; + @if $enable-shadows { + @include box-shadow($form-select-box-shadow, $form-select-focus-box-shadow); + } @else { + // Avoid using mixin so we can pass custom focus shadow properly + box-shadow: $form-select-focus-box-shadow; + } + } + } + } + } +} diff --git a/netbox/project-static/theme.scss b/netbox/project-static/theme.scss new file mode 100644 index 000000000..bfc12ce17 --- /dev/null +++ b/netbox/project-static/theme.scss @@ -0,0 +1,26 @@ +@import 'bootstrap/scss/functions'; + +// Override built-in variables/add new variables. +// $blue: #1685fc; +// $primary: #1685fc; +$green: #47e5bc; +$orange: #f9a620; +$yellow: #ffd449; +$red: #ff5964; +$alt: #13293d; + +$card-cap-bg: none; + +// On import, any variables marked `!default` will be overridden by the above. +@import 'bootstrap/scss/variables'; + +$theme-color-addons: ( + 'alt': $alt, + 'gray': $gray-400, +); + +// Merge/modify bootstrap variables. +$theme-colors: map-merge($theme-colors, $theme-color-addons); +$card-cap-color: $gray-800; + +$breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='currentColor'/%3E%3C/svg%3E");