diff --git a/.gitignore b/.gitignore index 3279c76922..fd82f9731c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,7 @@ python/nav/web/static/css python/nav/web/static/js/node_modules python/nav/web/static/js/require_config.dev.js python/nav/web/static/js/coverage + +# Javascript libraries +/node_modules +package-lock.json diff --git a/Makefile b/Makefile index 04c6367cf7..ceebf1e1d7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: dummy clean distclean testclean docclean doc .FORCE +.PHONY: dummy clean distclean testclean docclean doc cssclean sassbuild sasswatch .FORCE dummy: @echo "'make' is no longer used for deployment. See 'doc/intro/install.rst'" @@ -32,4 +32,17 @@ doc/reference/alerttypes.rst: .FORCE echo "UID=$(shell id -u)" >> .env echo "GID=$(shell id -g)" >> .env +cssclean: + -rm -rf python/nav/web/static/css + +sassbuild: cssclean + @if [ -f package-lock.json ]; then \ + (npm ci && npm run build:sass); \ + else \ + (npm install && npm run build:sass); \ + fi + +sasswatch: + -npm run watch:sass + .FORCE: diff --git a/changelog.d/2849.changed.md b/changelog.d/2849.changed.md new file mode 100644 index 0000000000..b53bf04e4a --- /dev/null +++ b/changelog.d/2849.changed.md @@ -0,0 +1 @@ +Switched to building CSS from Sass using webpack instead of deprecated `libsass`. diff --git a/doc/hacking/hacking.rst b/doc/hacking/hacking.rst index b93d84c328..69798f2c91 100644 --- a/doc/hacking/hacking.rst +++ b/doc/hacking/hacking.rst @@ -156,6 +156,7 @@ Directory Description processes. :file:`python/` Python source code. :file:`python/nav/etc/` Example/initial configuration files. +:file:`python/nav/web/sass/` SCSS stylesheets. :file:`python/nav/web/static/` Static media such as CSS stylesheets, images and JavaScript to be served by a webserver. :file:`python/nav/web/templates/` Main/global Django HTML templates. More be located in individual @@ -283,6 +284,24 @@ developing, something browsers love to do! See `config-urlArgs documentation for details. :file:`require_config.dev.js` is listed in the repository :file:`.gitignore` file. +CSS +--- + +NAV uses Sass for styling. If you want to modify the styling, +you can do so by editing the SCSS files in the :file:`python/nav/web/static/scss` +directory. To build the CSS assets, you will need to have Node.js +and npm installed. Once you have these installed, you can run +the following command to build the CSS files:: + + npm install + npm run build:sass + +This will build the CSS assets and place them in the :file:`python/nav/web/static/css` +directory. If you want to watch for changes in the SCSS files and rebuild the +CSS assets automatically, you can run the following command instead:: + + npm run watch:sass + Database diff --git a/doc/hacking/using-docker.rst b/doc/hacking/using-docker.rst index 38bdeb03bc..c6b78958b4 100644 --- a/doc/hacking/using-docker.rst +++ b/doc/hacking/using-docker.rst @@ -187,9 +187,10 @@ nav starts. sass-watcher - This is a process that monitors the :file:`python/nav/web/sass/` subdirectory - for changes, and re-runs ``python setup.py build_sass`` (i.e. rebuilding all - the SASS-based stylesheets) on changes. + This is a process that runs ``npm run watch:sass`` command to monitor and + rebuild all the SASS-based stylesheets whenever changes occur + in the :file:`python/nav/web/sass/` subdirectory. The command continuously + monitors the files and does not exit by itself. The individual logs of these program are typically found inside the ``nav`` container in the :file:`/var/log/supervisor/` directory. The NAV process logs diff --git a/doc/howto/generic-install-from-source.rst b/doc/howto/generic-install-from-source.rst index d3443816fd..9440358fc3 100644 --- a/doc/howto/generic-install-from-source.rst +++ b/doc/howto/generic-install-from-source.rst @@ -5,6 +5,8 @@ This is a generic guide to installing NAV from source code on a \*NIX flavored operating system. The specifics of how to install NAV's dependencies, such as :xref:`PostgreSQL` or :xref:`Graphite` will be entirely up to you and your choice of OS. +Note that building NAV from source will also require Node.js and npm to be installed +in order to manage frontend assets. Dependencies @@ -21,6 +23,11 @@ To build NAV, you need at least the following: * Python >= 3.9.0 * Sphinx >= 1.0 (for building this documentation) +Additionally to build frontend assets (like CSS and JS), you will need: + + * Node.js >= 14.0 + * npm >= 6.0 + Runtime requirements -------------------- @@ -73,6 +80,16 @@ default. Installing NAV ============== +First you need to build the static assets. To do this, you will need to have +Node.js and npm installed. Once you have these installed, you can run +the following command to build the CSS assets:: + + npm install + npm run build:sass + +This will build the CSS assets and place them in the :file:`python/nav/web/static/css` +directory. + To build and install NAV and all its Python dependencies:: pip install -r requirements.txt -c constraints.txt . diff --git a/doc/intro/install.rst b/doc/intro/install.rst index 8cbf854adb..635c2c4120 100644 --- a/doc/intro/install.rst +++ b/doc/intro/install.rst @@ -7,7 +7,8 @@ There are two main options for installing NAV: Either from source code, or from a pre-packaged version. Some of these options will require manually installing and/or configuring 3rd party software that NAV depends on, mainly :xref:`PostgreSQL` -and :xref:`Graphite`. +and :xref:`Graphite`. Building NAV from source code will also require Node.js +and npm to be installed. Installing a pre-packaged version of NAV @@ -96,4 +97,3 @@ For you, we provide two guides: `. 2. :doc:`A step-by-step, detailed guide on installing NAV from source on a Debian GNU/Linux operating system `. - diff --git a/package.json b/package.json new file mode 100644 index 0000000000..6353860a47 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "NAV frontend", + "description": "Network Administration Visualized frontend", + "version": "5", + "private": true, + "dependencies": { + "css-loader": "^6.10.0", + "glob": "^10.3.10", + "mini-css-extract-plugin": "^2.8.0", + "sass": "^1.71.1", + "sass-loader": "^14.1.1", + "webpack": "^5.90.3", + "webpack-cli": "^5.1.4", + "webpack-remove-empty-scripts": "^1.0.4" + }, + "scripts": { + "watch:sass": "webpack --config webpack.config.js --config-name sass --mode development --watch", + "build:sass": "webpack --config webpack.config.js --config-name sass --mode production" + } +} diff --git a/pyproject.toml b/pyproject.toml index ad3cce68ed..a50ef9bceb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61.0", "wheel", "setuptools_scm[toml]>=6.2", "libsass==0.15.1"] +requires = ["setuptools>=61.0", "wheel", "setuptools_scm[toml]>=6.2"] build-backend = "setuptools.build_meta" [project] @@ -63,6 +63,9 @@ include-package-data = true zip-safe = false platforms = ["any"] +[tool.setuptools.package-data] +"nav.web" = ["static/**"] + [tool.setuptools_scm] [tool.setuptools.packages.find] diff --git a/python/nav/web/sass/_fonts.scss b/python/nav/web/sass/_fonts.scss index 047453a398..07c8467a57 100644 --- a/python/nav/web/sass/_fonts.scss +++ b/python/nav/web/sass/_fonts.scss @@ -1,18 +1,20 @@ +@import 'navsettings'; + @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url('/static/fonts/OS300.woff') format('woff'); + src: local('Open Sans Light'), local('OpenSans-Light'), url('#{$assets-url}/fonts/OS300.woff') format('woff'); } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url('/static/fonts/OS400.woff') format('woff'); + src: local('Open Sans'), local('OpenSans'), url('#{$assets-url}/fonts/OS400.woff') format('woff'); } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url('/static/fonts/OS600.woff') format('woff'); + src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url('#{$assets-url}/fonts/OS600.woff') format('woff'); } \ No newline at end of file diff --git a/python/nav/web/sass/_navsettings.scss b/python/nav/web/sass/_navsettings.scss index 60d767f21c..42de255eb2 100644 --- a/python/nav/web/sass/_navsettings.scss +++ b/python/nav/web/sass/_navsettings.scss @@ -23,8 +23,11 @@ $xxlarge-only: "#{$screen} and (min-width:#{lower-bound($xxlarge-range)}) and (m // Relative image path -$image-path: '../../images' !global; -$image-path-partials: '../images' !global; +// Providing variables for all assets paths in this folder so that there will be no hard coding +$assets-url: '../static' !global; +$image-path: '#{$assets-url}/images' !global; +$image-path-partials: '#{$assets-url}/images' !global; +$fa-font-path: '#{$assets-url}/fonts'; // Width of a row. To create a limited viewport use em-calc // Default from Foundation is $row-width: em-calc(1000) !global; diff --git a/python/nav/web/sass/nav/_fonts.scss b/python/nav/web/sass/nav/_fonts.scss index 1ca0275fc2..030faa0192 100644 --- a/python/nav/web/sass/nav/_fonts.scss +++ b/python/nav/web/sass/nav/_fonts.scss @@ -1,18 +1,20 @@ +@import 'settings'; + @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url('/static/fonts/OS300.woff') format('woff'); + src: local('Open Sans Light'), local('OpenSans-Light'), url('#{$assets-url}/fonts/OS300.woff') format('woff'); } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url('/static/fonts/OS400.woff') format('woff'); + src: local('Open Sans'), local('OpenSans'), url('#{$assets-url}/fonts/OS400.woff') format('woff'); } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url('/static/fonts/OS600.woff') format('woff'); + src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url('#{$assets-url}/fonts/OS600.woff') format('woff'); } diff --git a/python/nav/web/sass/nav/_select2.scss b/python/nav/web/sass/nav/_select2.scss index 236d86dcb5..253b63f2b1 100644 --- a/python/nav/web/sass/nav/_select2.scss +++ b/python/nav/web/sass/nav/_select2.scss @@ -2,6 +2,7 @@ Version: 3.4.5 Timestamp: Mon Nov 4 08:22:42 PST 2013 */ @import '../navsettings'; +@import 'settings'; .select2-container { margin: 0 0 1rem 0; diff --git a/python/nav/web/sass/nav/_sensors.scss b/python/nav/web/sass/nav/_sensors.scss index aef207c8d2..426eaa51b0 100644 --- a/python/nav/web/sass/nav/_sensors.scss +++ b/python/nav/web/sass/nav/_sensors.scss @@ -1,5 +1,3 @@ -@import 'navsettings'; - /* * Styles for everything in the environment tab */ diff --git a/python/nav/web/sass/nav/_settings.scss b/python/nav/web/sass/nav/_settings.scss new file mode 100644 index 0000000000..49cc55453d --- /dev/null +++ b/python/nav/web/sass/nav/_settings.scss @@ -0,0 +1,5 @@ +// Assets path variables +// Giving same name to variables as in ../navsettings, and making them default in this folder +$assets-url: '../../static' !default; +$image-path: '#{$assets-url}/images' !default; +$image-path-partials: '#{$assets-url}/images' !default; \ No newline at end of file diff --git a/python/nav/web/sass/nav/geomap.scss b/python/nav/web/sass/nav/geomap.scss index 57f9631aca..0630b0245b 100644 --- a/python/nav/web/sass/nav/geomap.scss +++ b/python/nav/web/sass/nav/geomap.scss @@ -1,3 +1,5 @@ +@import "settings"; + #map { height: 75vh; } @@ -61,51 +63,51 @@ } #time-navigation-prev-jump.enabled:before { - content: url('../../images/geomap/left2.png'); + content: url('#{$assets-url}/images/geomap/left2.png'); } #time-navigation-prev-jump.disabled:before { - content: url('../../images/geomap/left2_grey.png'); + content: url('#{$assets-url}/images/geomap/left2_grey.png'); } #time-navigation-prev.enabled:before { - content: url('../../images/geomap/left1.png'); + content: url('#{$assets-url}/images/geomap/left1.png'); } #time-navigation-prev.disabled:before { - content: url('../../images/geomap/left1_grey.png'); + content: url('#{$assets-url}/images/geomap/left1_grey.png'); } #time-navigation-next.enabled:before { - content: url('../../images/geomap/right1.png'); + content: url('#{$assets-url}/images/geomap/right1.png'); } #time-navigation-next.disabled:before { - content: url('../../images/geomap/right1_grey.png'); + content: url('#{$assets-url}/images/geomap/right1_grey.png'); } #time-navigation-next-jump.enabled:before { - content: url('../../images/geomap/right2.png'); + content: url('#{$assets-url}/images/geomap/right2.png'); } #time-navigation-next-jump.disabled:before { - content: url('../../images/geomap/right2_grey.png'); + content: url('#{$assets-url}/images/geomap/right2_grey.png'); } #time-navigation-last.enabled:before { - content: url('../../images/geomap/right_last.png'); + content: url('#{$assets-url}/images/geomap/right_last.png'); } #time-navigation-last.disabled:before { - content: url('../../images/geomap/right_last_grey.png'); + content: url('#{$assets-url}/images/geomap/right_last_grey.png'); } #time-navigation-up.enabled:before { - content: url('../../images/geomap/up1.png'); + content: url('#{$assets-url}/images/geomap/up1.png'); } #time-navigation-up.disabled:before { - content: url('../../images/geomap/up1_grey.png'); + content: url('#{$assets-url}/images/geomap/up1_grey.png'); } #time-navigation-down.disabled { diff --git a/python/nav/web/sass/nav/info_room.scss b/python/nav/web/sass/nav/info_room.scss index 906bb00c46..d930353ea5 100644 --- a/python/nav/web/sass/nav/info_room.scss +++ b/python/nav/web/sass/nav/info_room.scss @@ -1,4 +1,3 @@ -@import 'sensors'; #netboxes .dataTables_wrapper { display: inline-block; @@ -104,4 +103,4 @@ img.textbottom { padding-top: 0.5rem; padding-bottom: 0.5rem; } -} \ No newline at end of file +} diff --git a/python/nav/web/sass/nav/ipdevinfo.scss b/python/nav/web/sass/nav/ipdevinfo.scss index 2e7b0261f8..66d46ef9ba 100644 --- a/python/nav/web/sass/nav/ipdevinfo.scss +++ b/python/nav/web/sass/nav/ipdevinfo.scss @@ -22,7 +22,7 @@ * */ - @import 'foundation/functions'; + @import '../foundation/functions'; /* Device details table */ diff --git a/python/nav/web/sass/nav/jquery-ui-1.8.21.custom.scss b/python/nav/web/sass/nav/jquery-ui-1.8.21.custom.scss index be376c1053..fdca124a34 100644 --- a/python/nav/web/sass/nav/jquery-ui-1.8.21.custom.scss +++ b/python/nav/web/sass/nav/jquery-ui-1.8.21.custom.scss @@ -8,6 +8,8 @@ * http://docs.jquery.com/UI/Theming/API */ +@import "settings"; + /* Layout helpers ----------------------------------*/ .ui-helper-hidden { display: none; } @@ -28,14 +30,14 @@ ----------------------------------*/ /* states and images */ -.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; width: 16px; height: 16px; background-image: url('#{$assets-url}/images/jqueryui/ui-icons_222222_256x240.png'); } /* Misc visuals ----------------------------------*/ /* Overlays */ -.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #aaaaaa url('#{$assets-url}/images/jqueryui/ui-bg_flat_0_aaaaaa_40x100.png') 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } /*! @@ -56,26 +58,26 @@ /* .ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } */ .ui-widget .ui-widget { font-size: 1em; } .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } -.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url('../../images/jqueryui/ui-bg_flat_75_ffffff_40x100.png') 50% 50% repeat-x; color: #222222; } +.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url('#{$assets-url}/images/jqueryui/ui-bg_flat_75_ffffff_40x100.png') 50% 50% repeat-x; color: #222222; } /*.ui-widget-content a { color: #222222; }*/ -.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url('../../images/jqueryui/ui-bg_highlight-soft_75_cccccc_1x100.png') 50% 50% repeat-x; color: #222222; font-weight: bold; } +.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url('#{$assets-url}/images/jqueryui/ui-bg_highlight-soft_75_cccccc_1x100.png') 50% 50% repeat-x; color: #222222; font-weight: bold; } .ui-widget-header a { color: #222222; } /* Interaction states ----------------------------------*/ -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url('../../images/jqueryui/ui-bg_glass_75_e6e6e6_1x400.png') 50% 50% repeat-x; font-weight: normal; color: #555555; } +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url('#{$assets-url}/images/jqueryui/ui-bg_glass_75_e6e6e6_1x400.png') 50% 50% repeat-x; font-weight: normal; color: #555555; } .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; } -.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url('../../images/jqueryui/ui-bg_glass_75_dadada_1x400.png') 50% 50% repeat-x; font-weight: normal; color: #212121; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url('#{$assets-url}/images/jqueryui/ui-bg_glass_75_dadada_1x400.png') 50% 50% repeat-x; font-weight: normal; color: #212121; } .ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; } -.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url('../../images/jqueryui/ui-bg_glass_65_ffffff_1x400.png') 50% 50% repeat-x; font-weight: normal; color: #212121; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url('#{$assets-url}/images/jqueryui/ui-bg_glass_65_ffffff_1x400.png') 50% 50% repeat-x; font-weight: normal; color: #212121; } .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; } .ui-widget :active { outline: none; } /* Interaction Cues ----------------------------------*/ -.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url('../../images/jqueryui/ui-bg_glass_55_fbf9ee_1x400.png') 50% 50% repeat-x; color: #363636; } +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url('#{$assets-url}/images/jqueryui/ui-bg_glass_55_fbf9ee_1x400.png') 50% 50% repeat-x; color: #363636; } .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } -.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url('../../images/jqueryui/ui-bg_glass_95_fef1ec_1x400.png') 50% 50% repeat-x; color: #cd0a0a; font-weight: normal;} +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url('#{$assets-url}/images/jqueryui/ui-bg_glass_95_fef1ec_1x400.png') 50% 50% repeat-x; color: #cd0a0a; font-weight: normal;} .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } @@ -86,14 +88,13 @@ ----------------------------------*/ /* states and images */ -.ui-icon { width: 16px; height: 16px; background-image: url('../../images/jqueryui/ui-icons_222222_256x240.png'); } -.ui-widget-content .ui-icon {background-image: url('../../images/jqueryui/ui-icons_222222_256x240.png'); } -.ui-widget-header .ui-icon {background-image: url('../../images/jqueryui/ui-icons_222222_256x240.png'); } -.ui-state-default .ui-icon { background-image: url('../../images/jqueryui/ui-icons_888888_256x240.png'); } -.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url('../../images/jqueryui/ui-icons_454545_256x240.png'); } -.ui-state-active .ui-icon {background-image: url('../../images/jqueryui/ui-icons_454545_256x240.png'); } -.ui-state-highlight .ui-icon {background-image: url('../../images/jqueryui/ui-icons_2e83ff_256x240.png'); } -.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url('../../images/jqueryui/ui-icons_cd0a0a_256x240.png'); } +.ui-widget-content .ui-icon {background-image: url('#{$assets-url}/images/jqueryui/ui-icons_222222_256x240.png'); } +.ui-widget-header .ui-icon {background-image: url('#{$assets-url}/images/jqueryui/ui-icons_222222_256x240.png'); } +.ui-state-default .ui-icon { background-image: url('#{$assets-url}/images/jqueryui/ui-icons_888888_256x240.png'); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url('#{$assets-url}/images/jqueryui/ui-icons_454545_256x240.png'); } +.ui-state-active .ui-icon {background-image: url('#{$assets-url}/images/jqueryui/ui-icons_454545_256x240.png'); } +.ui-state-highlight .ui-icon {background-image: url('#{$assets-url}/images/jqueryui/ui-icons_2e83ff_256x240.png'); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url('#{$assets-url}/images/jqueryui/ui-icons_cd0a0a_256x240.png'); } /* positioning */ .ui-icon-carat-1-n { background-position: 0 0; } @@ -283,8 +284,7 @@ .ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } /* Overlays */ -.ui-widget-overlay { background: #aaaaaa url('../../images/jqueryui/ui-bg_flat_0_aaaaaa_40x100.png') 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } -.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url('../../images/jqueryui/ui-bg_flat_0_aaaaaa_40x100.png') 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*! +.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url('#{$assets-url}/images/jqueryui/ui-bg_flat_0_aaaaaa_40x100.png') 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*! * jQuery UI Resizable 1.8.21 * * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) @@ -341,7 +341,7 @@ * * http://docs.jquery.com/UI/Autocomplete#theming */ -.ui-autocomplete { position: absolute; cursor: default; } +.ui-autocomplete { position: absolute; cursor: default; } /* workarounds */ * html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ @@ -397,8 +397,8 @@ .ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ .ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ -.ui-button-icons-only { width: 3.4em; } -button.ui-button-icons-only { width: 3.7em; } +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } /*button text element */ .ui-button .ui-button-text { display: block; line-height: 1.4; } @@ -511,7 +511,7 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad .ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } .ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } .ui-datepicker select.ui-datepicker-month-year {width: 100%;} -.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-month, .ui-datepicker select.ui-datepicker-year { width: 49%;} .ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } .ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } diff --git a/python/nav/web/sass/nav/jquery-ui.scss b/python/nav/web/sass/nav/jquery-ui.scss index deee6287a8..8ae81b5dcb 100644 --- a/python/nav/web/sass/nav/jquery-ui.scss +++ b/python/nav/web/sass/nav/jquery-ui.scss @@ -4,6 +4,8 @@ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ +@import "settings"; + /* Layout helpers ----------------------------------*/ .ui-helper-hidden { @@ -845,7 +847,7 @@ body .ui-tooltip { } .ui-widget-content { border: 1px solid #aaaaaa; - background: #ffffff url("../../images/jqueryui/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x; + background: #ffffff url("#{$assets-url}/images/jqueryui/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x; color: #222222; } .ui-widget-content a { @@ -853,7 +855,7 @@ body .ui-tooltip { } .ui-widget-header { border: 1px solid #aaaaaa; - background: #cccccc url("../../images/jqueryui/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; + background: #cccccc url("#{$assets-url}/images/jqueryui/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; color: #222222; font-weight: bold; } @@ -867,7 +869,7 @@ body .ui-tooltip { .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; - background: #e6e6e6 url("../../images/jqueryui/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; + background: #e6e6e6 url("#{$assets-url}/images/jqueryui/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; font-weight: normal; color: #555555; } @@ -884,7 +886,7 @@ body .ui-tooltip { .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; - background: #dadada url("../../images/jqueryui/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; + background: #dadada url("#{$assets-url}/images/jqueryui/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; font-weight: normal; color: #212121; } @@ -903,7 +905,7 @@ body .ui-tooltip { .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; - background: #ffffff url("../../images/jqueryui/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + background: #ffffff url("#{$assets-url}/images/jqueryui/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; font-weight: normal; color: #212121; } @@ -920,7 +922,7 @@ body .ui-tooltip { .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { border: 1px solid #fcefa1; - background: #fbf9ee url("../../images/jqueryui/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; + background: #fbf9ee url("#{$assets-url}/images/jqueryui/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; color: #363636; } .ui-state-highlight a, @@ -932,7 +934,7 @@ body .ui-tooltip { .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error { border: 1px solid #cd0a0a; - background: #fef1ec url("../../images/jqueryui/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; + background: #fef1ec url("#{$assets-url}/images/jqueryui/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; color: #cd0a0a; } .ui-state-error a, @@ -978,27 +980,27 @@ body .ui-tooltip { } .ui-icon, .ui-widget-content .ui-icon { - background-image: url("../../images/jqueryui/ui-icons_222222_256x240.png"); + background-image: url("#{$assets-url}/images/jqueryui/ui-icons_222222_256x240.png"); } .ui-widget-header .ui-icon { - background-image: url("../../images/jqueryui/ui-icons_222222_256x240.png"); + background-image: url("#{$assets-url}/images/jqueryui/ui-icons_222222_256x240.png"); } .ui-state-default .ui-icon { - background-image: url("../../images/jqueryui/ui-icons_888888_256x240.png"); + background-image: url("#{$assets-url}/images/jqueryui/ui-icons_888888_256x240.png"); } .ui-state-hover .ui-icon, .ui-state-focus .ui-icon { - background-image: url("../../images/jqueryui/ui-icons_454545_256x240.png"); + background-image: url("#{$assets-url}/images/jqueryui/ui-icons_454545_256x240.png"); } .ui-state-active .ui-icon { - background-image: url("../../images/jqueryui/ui-icons_454545_256x240.png"); + background-image: url("#{$assets-url}/images/jqueryui/ui-icons_454545_256x240.png"); } .ui-state-highlight .ui-icon { - background-image: url("../../images/jqueryui/ui-icons_2e83ff_256x240.png"); + background-image: url("#{$assets-url}/images/jqueryui/ui-icons_2e83ff_256x240.png"); } .ui-state-error .ui-icon, .ui-state-error-text .ui-icon { - background-image: url("../../images/jqueryui/ui-icons_cd0a0a_256x240.png"); + background-image: url("#{$assets-url}/images/jqueryui/ui-icons_cd0a0a_256x240.png"); } /* positioning */ @@ -1211,14 +1213,14 @@ body .ui-tooltip { /* Overlays */ .ui-widget-overlay { - background: #aaaaaa url("../../images/jqueryui/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; + background: #aaaaaa url("#{$assets-url}/images/jqueryui/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; opacity: .3; filter: Alpha(Opacity=30); /* support: IE8 */ } .ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; - background: #aaaaaa url("../../images/jqueryui/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; + background: #aaaaaa url("#{$assets-url}/images/jqueryui/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; opacity: .3; filter: Alpha(Opacity=30); /* support: IE8 */ border-radius: 8px; diff --git a/python/nav/web/sass/nav/multi-select.scss b/python/nav/web/sass/nav/multi-select.scss index ea59711d52..3ef3986cb5 100644 --- a/python/nav/web/sass/nav/multi-select.scss +++ b/python/nav/web/sass/nav/multi-select.scss @@ -1,5 +1,7 @@ +@import "settings"; + .ms-container{ - background: transparent url('../../images/multiselect/switch.png') no-repeat 50% 50%; + background: transparent url('#{$assets-url}/images/multiselect/switch.png') no-repeat 50% 50%; width: 100%; } diff --git a/python/nav/web/sass/nav/navlets.scss b/python/nav/web/sass/nav/navlets.scss index f796ff5797..8fedb56e85 100644 --- a/python/nav/web/sass/nav/navlets.scss +++ b/python/nav/web/sass/nav/navlets.scss @@ -135,7 +135,6 @@ $fullscreen-color: #EEE; .SensorWidget { - @import "sensors"; .room-sensor { display: flex; justify-content: center; diff --git a/python/nav/web/sass/nav/netmap.scss b/python/nav/web/sass/nav/netmap.scss index b9fde88e81..0836cd42b2 100644 --- a/python/nav/web/sass/nav/netmap.scss +++ b/python/nav/web/sass/nav/netmap.scss @@ -1,5 +1,5 @@ -@import '../navsettings'; -$image-path: '../../images'; +@import 'settings'; +$image-path: '#{$assets-url}/images'; #graph-view { background: #ffffff; diff --git a/python/nav/web/sass/nav/networkexplorer.scss b/python/nav/web/sass/nav/networkexplorer.scss index be299b2747..3a387d514a 100644 --- a/python/nav/web/sass/nav/networkexplorer.scss +++ b/python/nav/web/sass/nav/networkexplorer.scss @@ -1,3 +1,5 @@ +@import "settings"; + #working { visibility: hidden; } @@ -13,7 +15,7 @@ } #networktree .node { - background: url('../../images/networkexplorer/vertical.gif') repeat-y top left; + background: url('#{$assets-url}/images/networkexplorer/vertical.gif') repeat-y top left; } #networktree #root { @@ -55,12 +57,12 @@ dl, dl dd { dl, dt, dd { padding-left: 1em; - background-image: url('../../images/networkexplorer/vertical.gif'); + background-image: url('#{$assets-url}/images/networkexplorer/vertical.gif'); background-repeat: repeat-y; } .tree-navigators { - background-image: url('../../images/networkexplorer/vertical.gif'); + background-image: url('#{$assets-url}/images/networkexplorer/vertical.gif'); background-repeat: repeat-y; } #tree > dl, dt, dd { diff --git a/python/nav/web/sass/nav/openlayers.scss b/python/nav/web/sass/nav/openlayers.scss index 3c6fd5d828..5228fa9f80 100644 --- a/python/nav/web/sass/nav/openlayers.scss +++ b/python/nav/web/sass/nav/openlayers.scss @@ -1,4 +1,5 @@ -$ol-imagepath: '../../images/openlayers'; +@import "settings"; +$ol-imagepath: '#{$assets-url}/images/openlayers'; div.olMap { z-index: 0; diff --git a/python/nav/web/sass/nav/powersupplies.scss b/python/nav/web/sass/nav/powersupplies.scss index 22deb01622..73cdce4bf0 100644 --- a/python/nav/web/sass/nav/powersupplies.scss +++ b/python/nav/web/sass/nav/powersupplies.scss @@ -1,3 +1,5 @@ +@import "settings"; + .right { float: right; } .left { float: left; } .text-right { text-align: right; } @@ -8,7 +10,7 @@ h4 { text-align: center; } div.infobox { - margin-bottom: 1em; + margin-bottom: 1em; } div#tablecontainer { @@ -22,7 +24,7 @@ div#errors { /* * Styles for the table - */ + */ table#portadmin-interfacecontainer td { height: 30px; @@ -46,7 +48,7 @@ table#portadmin-interfacecontainer tr td.changed { /* - * Styles for information boxes + * Styles for information boxes */ div.saveinfo { position: absolute; @@ -72,13 +74,13 @@ div#summary ul { .success { color: #4F8A10; background-color: #DFF2BF; - background-image:url('/images/lys/green.png'); + background-image:url('#{$assets-url}/images/lys/green.png'); } .error { color: #D8000C; background-color: #FFBABA; - background-image: url('/images/lys/red.png'); + background-image: url('#{$assets-url}/images/lys/red.png'); } table#powersupplycontainer td.warning { diff --git a/requirements/base.txt b/requirements/base.txt index 921dc328fe..ab11756046 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -32,9 +32,6 @@ iso8601 pynetsnmp-2>=0.1.10 -# libsass for compiling scss files to css using distutils/setuptools -libsass==0.15.1 - napalm==3.4.1 git+https://github.com/Uninett/drf-oidc-auth@v4.0#egg=drf-oidc-auth diff --git a/setup.py b/setup.py index c6e74831b0..c6b6bc2c1d 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,5 @@ -from glob import glob from setuptools import setup -from setuptools.command.build import build - - -# Ensure CSS files are built every time build is invoked -build.sub_commands = [('build_sass', None)] + build.sub_commands - setup( - setup_requires=['libsass', 'setuptools_scm'], - sass_manifests={ - 'nav.web': { - 'sass_path': 'sass', - 'css_path': 'static/css', - 'strip_extension': True, - }, - }, + setup_requires=['setuptools_scm'], ) diff --git a/tools/docker/build.sh b/tools/docker/build.sh index 08c5d19aa7..74cea59df3 100755 --- a/tools/docker/build.sh +++ b/tools/docker/build.sh @@ -9,7 +9,7 @@ fi cd /source pip install -vv -e . -python setup.py build_sass +make sassbuild if [[ ! -f "/etc/nav/nav.conf" ]]; then echo "Copying initial NAV config files into this container" diff --git a/tools/docker/sass-watch.sh b/tools/docker/sass-watch.sh index 4837eccf5e..0639f7bd71 100755 --- a/tools/docker/sass-watch.sh +++ b/tools/docker/sass-watch.sh @@ -2,7 +2,4 @@ # ensures CSS files are rebuilt on SASS file changes # cd /source -while inotifywait -e modify -e move -e create -e delete -r --exclude \# /source/python/nav/web/sass -do - /opt/venvs/nav/bin/python setup.py build_sass -done +make sasswatch diff --git a/tox.ini b/tox.ini index 73e1ae57ac..276d0d16b3 100644 --- a/tox.ini +++ b/tox.ini @@ -64,6 +64,7 @@ allowlist_externals = sed mkdir chmod + make usedevelop = true commands_pre = @@ -73,7 +74,7 @@ commands_pre = commands = unit: pytest -o junit_suite_name="{envname} unit tests" --cov-config {toxinidir}/tests/.coveragerc --cov={toxinidir}/python --cov-report=xml:reports/{envname}/coverage.xml --junitxml=reports/{envname}/unit-results.xml --verbose {posargs:tests/unittests} - {integration,functional}: python setup.py build_sass + {integration,functional}: make sassbuild integration: python -m nav.django.manage check {integration,functional}: nav config install {envdir}/etc @@ -92,7 +93,6 @@ commands = [testenv:javascript] usedevelop=True -deps = libsass==0.15.1 allowlist_externals = xvfb-run commands_pre = commands = diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000000..1d15a8c4bf --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,43 @@ +const path = require('path'); +const glob = require('glob'); + +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); + +const stylesHandler = MiniCssExtractPlugin.loader; + +module.exports = [ + { + name: 'sass', + entry: { + nav: path.resolve(__dirname, './python/nav/web/sass/nav.scss'), + ...glob.sync(path.resolve(__dirname, './python/nav/web/sass/nav/**.scss')).filter(v => !path.parse(v).name.startsWith('_')).reduce(function (obj, el) { + obj[`nav/${path.parse(el).name}`] = el; + return obj + }, {}), + 'font-awesome/font-awesome': path.resolve(__dirname, './python/nav/web/sass/font-awesome/font-awesome.scss'), + }, + output: { + path: path.join(__dirname, './python/nav/web/static/css'), + clean: true, + }, + plugins: [ + new MiniCssExtractPlugin(), + new RemoveEmptyScriptsPlugin(), + ], + module: { + rules: [ + { + test: /\.s[ac]ss$/i, + use: [stylesHandler, 'css-loader', 'sass-loader'], + }, + { + test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, + type: 'asset' // webpack will individually pick the best way to include each asset it encounters + }, + ], + }, + // TODO: fix warnings instead of ignoring them + ignoreWarnings: [/./], // Suppress all warnings in the console + }, +];