]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Rework] WebUI: Replace Glyphicons with FontAwesome SVG icons
authorCopilot <198982749+Copilot@users.noreply.github.com>
Fri, 24 Oct 2025 11:40:42 +0000 (14:40 +0300)
committerAlexander Moisseev <moiseev@mezonplus.ru>
Fri, 24 Oct 2025 12:29:19 +0000 (15:29 +0300)
The implementation uses a global MutationObserver watching document.body that:

-  Detects when new fooicon elements are added to the DOM
-  Detects when class attributes change on existing fooicon elements (e.g., sort icon cycling)
-  Automatically processes and replaces icons

Benefits:

✓ Sharp SVG rendering at all zoom levels (vs blurry webfonts)
✓ No font loading issues or CORS problems
✓ Reduced code footprint

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: moisseev <2275981+moisseev@users.noreply.github.com>
interface/css/FooTable.FontAwesome.css [new file with mode: 0644]
interface/css/FooTable.Glyphicons.css [deleted file]
interface/css/font-glyphicons.css [deleted file]
interface/fonts/glyphicons-halflings-regular.ttf [deleted file]
interface/fonts/glyphicons-halflings-regular.woff [deleted file]
interface/fonts/glyphicons-halflings-regular.woff2 [deleted file]
interface/index.html
interface/js/app/footable-fontawesome.js [new file with mode: 0644]
interface/js/app/rspamd.js
interface/js/main.js

diff --git a/interface/css/FooTable.FontAwesome.css b/interface/css/FooTable.FontAwesome.css
new file mode 100644 (file)
index 0000000..aacebfd
--- /dev/null
@@ -0,0 +1,28 @@
+/* Hide the old Glyphicons pseudo-elements */
+.fooicon::before,
+.fooicon::after {
+    content: none !important;
+    display: none;
+}
+
+/* Critical: allow clicks to pass through SVG to parent span */
+.fooicon svg {
+    pointer-events: none;
+}
+
+/* Smaller toggle icon and icons in footable header */
+.footable-header .fooicon svg,
+.footable-toggle.fooicon svg {
+    font-size: 12px;
+}
+
+/* Stop span rotation for loader - we animate the SVG instead */
+.fooicon-loader {
+    animation: none !important;
+    transform: none !important;
+}
+
+/* Make spinner icon larger to match original size */
+.fooicon-loader.fooicon svg {
+    font-size: 36px;
+}
diff --git a/interface/css/FooTable.Glyphicons.css b/interface/css/FooTable.Glyphicons.css
deleted file mode 100644 (file)
index c68f66e..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Glyphicons Icons - We're not actually using Glyphicons classes but instead provide a simple mapping
-   from Glyphicons to FooTable class names. */
-.fooicon {
-    position: relative;
-    top: 1px;
-    display: inline-block;
-    /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
-    font-family: "Glyphicons Halflings" !important;
-    font-style: normal;
-    font-weight: 400;
-    line-height: 1;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-}
-.fooicon::before,
-.fooicon::after {
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-}
-.fooicon-loader::before {
-    content: "\e030";
-}
-.fooicon-plus::before {
-    content: "\2b";
-}
-.fooicon-minus::before {
-    content: "\2212";
-}
-.fooicon-search::before {
-    content: "\e003";
-}
-.fooicon-remove::before {
-    content: "\e014";
-}
-.fooicon-sort::before {
-    content: "\e150";
-}
-.fooicon-sort-asc::before {
-    content: "\e155";
-}
-.fooicon-sort-desc::before {
-    content: "\e156";
-}
-.fooicon-pencil::before {
-    content: "\270f";
-}
-.fooicon-trash::before {
-    content: "\e020";
-}
-.fooicon-eye-close::before {
-    content: "\e106";
-}
-.fooicon-flash::before {
-    content: "\e162";
-}
-.fooicon-cog::before {
-    content: "\e019";
-}
-.fooicon-stats::before {
-    content: "\e185";
-}
diff --git a/interface/css/font-glyphicons.css b/interface/css/font-glyphicons.css
deleted file mode 100644 (file)
index ecb237b..0000000
+++ /dev/null
@@ -1,805 +0,0 @@
-/* https://gist.github.com/itchief/e3aa0888c959ee367cd69f5d83ff06f9 */
-
-@font-face {
-    font-family: "Glyphicons Halflings";
-    src: url("../fonts/glyphicons-halflings-regular.eot");
-    src: url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"), url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"), url("../fonts/glyphicons-halflings-regular.woff") format("woff"), url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"), url("../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg");
-}
-.glyphicon {
-    position: relative;
-    top: 1px;
-    display: inline-block;
-    font-family: "Glyphicons Halflings"; /* stylelint-disable-line font-family-no-missing-generic-family-keyword */
-    font-style: normal;
-    font-weight: 400;
-    line-height: 1;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-}
-.glyphicon-asterisk::before {
-    content: "\002a";
-}
-.glyphicon-plus::before {
-    content: "\002b";
-}
-.glyphicon-euro::before,
-.glyphicon-eur::before {
-    content: "\20ac";
-}
-.glyphicon-minus::before {
-    content: "\2212";
-}
-.glyphicon-cloud::before {
-    content: "\2601";
-}
-.glyphicon-envelope::before {
-    content: "\2709";
-}
-.glyphicon-pencil::before {
-    content: "\270f";
-}
-.glyphicon-glass::before {
-    content: "\e001";
-}
-.glyphicon-music::before {
-    content: "\e002";
-}
-.glyphicon-search::before {
-    content: "\e003";
-}
-.glyphicon-heart::before {
-    content: "\e005";
-}
-.glyphicon-star::before {
-    content: "\e006";
-}
-.glyphicon-star-empty::before {
-    content: "\e007";
-}
-.glyphicon-user::before {
-    content: "\e008";
-}
-.glyphicon-film::before {
-    content: "\e009";
-}
-.glyphicon-th-large::before {
-    content: "\e010";
-}
-.glyphicon-th::before {
-    content: "\e011";
-}
-.glyphicon-th-list::before {
-    content: "\e012";
-}
-.glyphicon-ok::before {
-    content: "\e013";
-}
-.glyphicon-remove::before {
-    content: "\e014";
-}
-.glyphicon-zoom-in::before {
-    content: "\e015";
-}
-.glyphicon-zoom-out::before {
-    content: "\e016";
-}
-.glyphicon-off::before {
-    content: "\e017";
-}
-.glyphicon-signal::before {
-    content: "\e018";
-}
-.glyphicon-cog::before {
-    content: "\e019";
-}
-.glyphicon-trash::before {
-    content: "\e020";
-}
-.glyphicon-home::before {
-    content: "\e021";
-}
-.glyphicon-file::before {
-    content: "\e022";
-}
-.glyphicon-time::before {
-    content: "\e023";
-}
-.glyphicon-road::before {
-    content: "\e024";
-}
-.glyphicon-download-alt::before {
-    content: "\e025";
-}
-.glyphicon-download::before {
-    content: "\e026";
-}
-.glyphicon-upload::before {
-    content: "\e027";
-}
-.glyphicon-inbox::before {
-    content: "\e028";
-}
-.glyphicon-play-circle::before {
-    content: "\e029";
-}
-.glyphicon-repeat::before {
-    content: "\e030";
-}
-.glyphicon-refresh::before {
-    content: "\e031";
-}
-.glyphicon-list-alt::before {
-    content: "\e032";
-}
-.glyphicon-lock::before {
-    content: "\e033";
-}
-.glyphicon-flag::before {
-    content: "\e034";
-}
-.glyphicon-headphones::before {
-    content: "\e035";
-}
-.glyphicon-volume-off::before {
-    content: "\e036";
-}
-.glyphicon-volume-down::before {
-    content: "\e037";
-}
-.glyphicon-volume-up::before {
-    content: "\e038";
-}
-.glyphicon-qrcode::before {
-    content: "\e039";
-}
-.glyphicon-barcode::before {
-    content: "\e040";
-}
-.glyphicon-tag::before {
-    content: "\e041";
-}
-.glyphicon-tags::before {
-    content: "\e042";
-}
-.glyphicon-book::before {
-    content: "\e043";
-}
-.glyphicon-bookmark::before {
-    content: "\e044";
-}
-.glyphicon-print::before {
-    content: "\e045";
-}
-.glyphicon-camera::before {
-    content: "\e046";
-}
-.glyphicon-font::before {
-    content: "\e047";
-}
-.glyphicon-bold::before {
-    content: "\e048";
-}
-.glyphicon-italic::before {
-    content: "\e049";
-}
-.glyphicon-text-height::before {
-    content: "\e050";
-}
-.glyphicon-text-width::before {
-    content: "\e051";
-}
-.glyphicon-align-left::before {
-    content: "\e052";
-}
-.glyphicon-align-center::before {
-    content: "\e053";
-}
-.glyphicon-align-right::before {
-    content: "\e054";
-}
-.glyphicon-align-justify::before {
-    content: "\e055";
-}
-.glyphicon-list::before {
-    content: "\e056";
-}
-.glyphicon-indent-left::before {
-    content: "\e057";
-}
-.glyphicon-indent-right::before {
-    content: "\e058";
-}
-.glyphicon-facetime-video::before {
-    content: "\e059";
-}
-.glyphicon-picture::before {
-    content: "\e060";
-}
-.glyphicon-map-marker::before {
-    content: "\e062";
-}
-.glyphicon-adjust::before {
-    content: "\e063";
-}
-.glyphicon-tint::before {
-    content: "\e064";
-}
-.glyphicon-edit::before {
-    content: "\e065";
-}
-.glyphicon-share::before {
-    content: "\e066";
-}
-.glyphicon-check::before {
-    content: "\e067";
-}
-.glyphicon-move::before {
-    content: "\e068";
-}
-.glyphicon-step-backward::before {
-    content: "\e069";
-}
-.glyphicon-fast-backward::before {
-    content: "\e070";
-}
-.glyphicon-backward::before {
-    content: "\e071";
-}
-.glyphicon-play::before {
-    content: "\e072";
-}
-.glyphicon-pause::before {
-    content: "\e073";
-}
-.glyphicon-stop::before {
-    content: "\e074";
-}
-.glyphicon-forward::before {
-    content: "\e075";
-}
-.glyphicon-fast-forward::before {
-    content: "\e076";
-}
-.glyphicon-step-forward::before {
-    content: "\e077";
-}
-.glyphicon-eject::before {
-    content: "\e078";
-}
-.glyphicon-chevron-left::before {
-    content: "\e079";
-}
-.glyphicon-chevron-right::before {
-    content: "\e080";
-}
-.glyphicon-plus-sign::before {
-    content: "\e081";
-}
-.glyphicon-minus-sign::before {
-    content: "\e082";
-}
-.glyphicon-remove-sign::before {
-    content: "\e083";
-}
-.glyphicon-ok-sign::before {
-    content: "\e084";
-}
-.glyphicon-question-sign::before {
-    content: "\e085";
-}
-.glyphicon-info-sign::before {
-    content: "\e086";
-}
-.glyphicon-screenshot::before {
-    content: "\e087";
-}
-.glyphicon-remove-circle::before {
-    content: "\e088";
-}
-.glyphicon-ok-circle::before {
-    content: "\e089";
-}
-.glyphicon-ban-circle::before {
-    content: "\e090";
-}
-.glyphicon-arrow-left::before {
-    content: "\e091";
-}
-.glyphicon-arrow-right::before {
-    content: "\e092";
-}
-.glyphicon-arrow-up::before {
-    content: "\e093";
-}
-.glyphicon-arrow-down::before {
-    content: "\e094";
-}
-.glyphicon-share-alt::before {
-    content: "\e095";
-}
-.glyphicon-resize-full::before {
-    content: "\e096";
-}
-.glyphicon-resize-small::before {
-    content: "\e097";
-}
-.glyphicon-exclamation-sign::before {
-    content: "\e101";
-}
-.glyphicon-gift::before {
-    content: "\e102";
-}
-.glyphicon-leaf::before {
-    content: "\e103";
-}
-.glyphicon-fire::before {
-    content: "\e104";
-}
-.glyphicon-eye-open::before {
-    content: "\e105";
-}
-.glyphicon-eye-close::before {
-    content: "\e106";
-}
-.glyphicon-warning-sign::before {
-    content: "\e107";
-}
-.glyphicon-plane::before {
-    content: "\e108";
-}
-.glyphicon-calendar::before {
-    content: "\e109";
-}
-.glyphicon-random::before {
-    content: "\e110";
-}
-.glyphicon-comment::before {
-    content: "\e111";
-}
-.glyphicon-magnet::before {
-    content: "\e112";
-}
-.glyphicon-chevron-up::before {
-    content: "\e113";
-}
-.glyphicon-chevron-down::before {
-    content: "\e114";
-}
-.glyphicon-retweet::before {
-    content: "\e115";
-}
-.glyphicon-shopping-cart::before {
-    content: "\e116";
-}
-.glyphicon-folder-close::before {
-    content: "\e117";
-}
-.glyphicon-folder-open::before {
-    content: "\e118";
-}
-.glyphicon-resize-vertical::before {
-    content: "\e119";
-}
-.glyphicon-resize-horizontal::before {
-    content: "\e120";
-}
-.glyphicon-hdd::before {
-    content: "\e121";
-}
-.glyphicon-bullhorn::before {
-    content: "\e122";
-}
-.glyphicon-bell::before {
-    content: "\e123";
-}
-.glyphicon-certificate::before {
-    content: "\e124";
-}
-.glyphicon-thumbs-up::before {
-    content: "\e125";
-}
-.glyphicon-thumbs-down::before {
-    content: "\e126";
-}
-.glyphicon-hand-right::before {
-    content: "\e127";
-}
-.glyphicon-hand-left::before {
-    content: "\e128";
-}
-.glyphicon-hand-up::before {
-    content: "\e129";
-}
-.glyphicon-hand-down::before {
-    content: "\e130";
-}
-.glyphicon-circle-arrow-right::before {
-    content: "\e131";
-}
-.glyphicon-circle-arrow-left::before {
-    content: "\e132";
-}
-.glyphicon-circle-arrow-up::before {
-    content: "\e133";
-}
-.glyphicon-circle-arrow-down::before {
-    content: "\e134";
-}
-.glyphicon-globe::before {
-    content: "\e135";
-}
-.glyphicon-wrench::before {
-    content: "\e136";
-}
-.glyphicon-tasks::before {
-    content: "\e137";
-}
-.glyphicon-filter::before {
-    content: "\e138";
-}
-.glyphicon-briefcase::before {
-    content: "\e139";
-}
-.glyphicon-fullscreen::before {
-    content: "\e140";
-}
-.glyphicon-dashboard::before {
-    content: "\e141";
-}
-.glyphicon-paperclip::before {
-    content: "\e142";
-}
-.glyphicon-heart-empty::before {
-    content: "\e143";
-}
-.glyphicon-link::before {
-    content: "\e144";
-}
-.glyphicon-phone::before {
-    content: "\e145";
-}
-.glyphicon-pushpin::before {
-    content: "\e146";
-}
-.glyphicon-usd::before {
-    content: "\e148";
-}
-.glyphicon-gbp::before {
-    content: "\e149";
-}
-.glyphicon-sort::before {
-    content: "\e150";
-}
-.glyphicon-sort-by-alphabet::before {
-    content: "\e151";
-}
-.glyphicon-sort-by-alphabet-alt::before {
-    content: "\e152";
-}
-.glyphicon-sort-by-order::before {
-    content: "\e153";
-}
-.glyphicon-sort-by-order-alt::before {
-    content: "\e154";
-}
-.glyphicon-sort-by-attributes::before {
-    content: "\e155";
-}
-.glyphicon-sort-by-attributes-alt::before {
-    content: "\e156";
-}
-.glyphicon-unchecked::before {
-    content: "\e157";
-}
-.glyphicon-expand::before {
-    content: "\e158";
-}
-.glyphicon-collapse-down::before {
-    content: "\e159";
-}
-.glyphicon-collapse-up::before {
-    content: "\e160";
-}
-.glyphicon-log-in::before {
-    content: "\e161";
-}
-.glyphicon-flash::before {
-    content: "\e162";
-}
-.glyphicon-log-out::before {
-    content: "\e163";
-}
-.glyphicon-new-window::before {
-    content: "\e164";
-}
-.glyphicon-record::before {
-    content: "\e165";
-}
-.glyphicon-save::before {
-    content: "\e166";
-}
-.glyphicon-open::before {
-    content: "\e167";
-}
-.glyphicon-saved::before {
-    content: "\e168";
-}
-.glyphicon-import::before {
-    content: "\e169";
-}
-.glyphicon-export::before {
-    content: "\e170";
-}
-.glyphicon-send::before {
-    content: "\e171";
-}
-.glyphicon-floppy-disk::before {
-    content: "\e172";
-}
-.glyphicon-floppy-saved::before {
-    content: "\e173";
-}
-.glyphicon-floppy-remove::before {
-    content: "\e174";
-}
-.glyphicon-floppy-save::before {
-    content: "\e175";
-}
-.glyphicon-floppy-open::before {
-    content: "\e176";
-}
-.glyphicon-credit-card::before {
-    content: "\e177";
-}
-.glyphicon-transfer::before {
-    content: "\e178";
-}
-.glyphicon-cutlery::before {
-    content: "\e179";
-}
-.glyphicon-header::before {
-    content: "\e180";
-}
-.glyphicon-compressed::before {
-    content: "\e181";
-}
-.glyphicon-earphone::before {
-    content: "\e182";
-}
-.glyphicon-phone-alt::before {
-    content: "\e183";
-}
-.glyphicon-tower::before {
-    content: "\e184";
-}
-.glyphicon-stats::before {
-    content: "\e185";
-}
-.glyphicon-sd-video::before {
-    content: "\e186";
-}
-.glyphicon-hd-video::before {
-    content: "\e187";
-}
-.glyphicon-subtitles::before {
-    content: "\e188";
-}
-.glyphicon-sound-stereo::before {
-    content: "\e189";
-}
-.glyphicon-sound-dolby::before {
-    content: "\e190";
-}
-.glyphicon-sound-5-1::before {
-    content: "\e191";
-}
-.glyphicon-sound-6-1::before {
-    content: "\e192";
-}
-.glyphicon-sound-7-1::before {
-    content: "\e193";
-}
-.glyphicon-copyright-mark::before {
-    content: "\e194";
-}
-.glyphicon-registration-mark::before {
-    content: "\e195";
-}
-.glyphicon-cloud-download::before {
-    content: "\e197";
-}
-.glyphicon-cloud-upload::before {
-    content: "\e198";
-}
-.glyphicon-tree-conifer::before {
-    content: "\e199";
-}
-.glyphicon-tree-deciduous::before {
-    content: "\e200";
-}
-.glyphicon-cd::before {
-    content: "\e201";
-}
-.glyphicon-save-file::before {
-    content: "\e202";
-}
-.glyphicon-open-file::before {
-    content: "\e203";
-}
-.glyphicon-level-up::before {
-    content: "\e204";
-}
-.glyphicon-copy::before {
-    content: "\e205";
-}
-.glyphicon-paste::before {
-    content: "\e206";
-}
-.glyphicon-alert::before {
-    content: "\e209";
-}
-.glyphicon-equalizer::before {
-    content: "\e210";
-}
-.glyphicon-king::before {
-    content: "\e211";
-}
-.glyphicon-queen::before {
-    content: "\e212";
-}
-.glyphicon-pawn::before {
-    content: "\e213";
-}
-.glyphicon-bishop::before {
-    content: "\e214";
-}
-.glyphicon-knight::before {
-    content: "\e215";
-}
-.glyphicon-baby-formula::before {
-    content: "\e216";
-}
-.glyphicon-tent::before {
-    content: "\26fa";
-}
-.glyphicon-blackboard::before {
-    content: "\e218";
-}
-.glyphicon-bed::before {
-    content: "\e219";
-}
-.glyphicon-apple::before {
-    content: "\f8ff";
-}
-.glyphicon-erase::before {
-    content: "\e221";
-}
-.glyphicon-hourglass::before {
-    content: "\231b";
-}
-.glyphicon-lamp::before {
-    content: "\e223";
-}
-.glyphicon-duplicate::before {
-    content: "\e224";
-}
-.glyphicon-piggy-bank::before {
-    content: "\e225";
-}
-.glyphicon-scissors::before {
-    content: "\e226";
-}
-.glyphicon-bitcoin::before {
-    content: "\e227";
-}
-.glyphicon-btc::before {
-    content: "\e227";
-}
-.glyphicon-xbt::before {
-    content: "\e227";
-}
-.glyphicon-yen::before {
-    content: "\00a5";
-}
-.glyphicon-jpy::before {
-    content: "\00a5";
-}
-.glyphicon-ruble::before {
-    content: "\20bd";
-}
-.glyphicon-rub::before {
-    content: "\20bd";
-}
-.glyphicon-scale::before {
-    content: "\e230";
-}
-.glyphicon-ice-lolly::before {
-    content: "\e231";
-}
-.glyphicon-ice-lolly-tasted::before {
-    content: "\e232";
-}
-.glyphicon-education::before {
-    content: "\e233";
-}
-.glyphicon-option-horizontal::before {
-    content: "\e234";
-}
-.glyphicon-option-vertical::before {
-    content: "\e235";
-}
-.glyphicon-menu-hamburger::before {
-    content: "\e236";
-}
-.glyphicon-modal-window::before {
-    content: "\e237";
-}
-.glyphicon-oil::before {
-    content: "\e238";
-}
-.glyphicon-grain::before {
-    content: "\e239";
-}
-.glyphicon-sunglasses::before {
-    content: "\e240";
-}
-.glyphicon-text-size::before {
-    content: "\e241";
-}
-.glyphicon-text-color::before {
-    content: "\e242";
-}
-.glyphicon-text-background::before {
-    content: "\e243";
-}
-.glyphicon-object-align-top::before {
-    content: "\e244";
-}
-.glyphicon-object-align-bottom::before {
-    content: "\e245";
-}
-.glyphicon-object-align-horizontal::before {
-    content: "\e246";
-}
-.glyphicon-object-align-left::before {
-    content: "\e247";
-}
-.glyphicon-object-align-vertical::before {
-    content: "\e248";
-}
-.glyphicon-object-align-right::before {
-    content: "\e249";
-}
-.glyphicon-triangle-right::before {
-    content: "\e250";
-}
-.glyphicon-triangle-left::before {
-    content: "\e251";
-}
-.glyphicon-triangle-bottom::before {
-    content: "\e252";
-}
-.glyphicon-triangle-top::before {
-    content: "\e253";
-}
-.glyphicon-console::before {
-    content: "\e254";
-}
-.glyphicon-superscript::before {
-    content: "\e255";
-}
-.glyphicon-subscript::before {
-    content: "\e256";
-}
-.glyphicon-menu-left::before {
-    content: "\e257";
-}
-.glyphicon-menu-right::before {
-    content: "\e258";
-}
-.glyphicon-menu-down::before {
-    content: "\e259";
-}
-.glyphicon-menu-up::before {
-    content: "\e260";
-}
diff --git a/interface/fonts/glyphicons-halflings-regular.ttf b/interface/fonts/glyphicons-halflings-regular.ttf
deleted file mode 100644 (file)
index 1413fc6..0000000
Binary files a/interface/fonts/glyphicons-halflings-regular.ttf and /dev/null differ
diff --git a/interface/fonts/glyphicons-halflings-regular.woff b/interface/fonts/glyphicons-halflings-regular.woff
deleted file mode 100644 (file)
index 9e61285..0000000
Binary files a/interface/fonts/glyphicons-halflings-regular.woff and /dev/null differ
diff --git a/interface/fonts/glyphicons-halflings-regular.woff2 b/interface/fonts/glyphicons-halflings-regular.woff2
deleted file mode 100644 (file)
index 64539b5..0000000
Binary files a/interface/fonts/glyphicons-halflings-regular.woff2 and /dev/null differ
index 1cd49203e15d4a8f47263ae5fb30523dbcb9c762..c50d17fc6367b8ad8786d494f621f26d3e857f6c 100644 (file)
@@ -15,9 +15,8 @@
        <meta name="msapplication-TileColor" content="#2b5797">
 
        <link href="./css/bootstrap.min.css" rel="stylesheet">
-       <link rel="stylesheet" type="text/css" href="./css/font-glyphicons.css">
        <link rel="stylesheet" type="text/css" href="./css/footable.standalone.min.css"/>
-       <link rel="stylesheet" type="text/css" href="./css/FooTable.Glyphicons.css"/>
+       <link rel="stylesheet" type="text/css" href="./css/FooTable.FontAwesome.css"/>
        <link rel="stylesheet" type="text/css" href="./css/svg-with-js.min.css">
        <link rel="stylesheet" type="text/css" href="./css/d3evolution.css">
        <link rel="stylesheet" type="text/css" href="./css/d3pie.css">
diff --git a/interface/js/app/footable-fontawesome.js b/interface/js/app/footable-fontawesome.js
new file mode 100644 (file)
index 0000000..109409f
--- /dev/null
@@ -0,0 +1,156 @@
+/**
+ * Replace FooTable fooicon elements with FontAwesome SVG icons
+ */
+define(["jquery", "fontawesome"], ($, FontAwesome) => {
+    "use strict";
+
+    // Icon mapping from FooTable classes to FontAwesome icon definitions
+    // Each entry: [prefix, iconName, additionalClasses (optional, space-separated string)]
+    const iconMap = {
+        "fooicon-loader": ["fas", "spinner", "fa-spin"],
+        "fooicon-plus": ["fas", "plus"],
+        "fooicon-minus": ["fas", "minus"],
+        "fooicon-search": ["fas", "search"],
+        "fooicon-remove": ["fas", "times"],
+        "fooicon-sort": ["fas", "arrows-up-down"],
+        "fooicon-sort-asc": ["fas", "arrow-down-short-wide"],
+        "fooicon-sort-desc": ["fas", "arrow-up-wide-short"],
+    };
+
+    let observer = null;
+
+    /**
+     * Process a single fooicon element and replace with SVG
+     */
+    function processIcon(element) {
+        const $el = $(element);
+
+        // Find which fooicon-* class this element has
+        const classList = element.className.split(/\s+/);
+        const fooClass = classList.find((cls) => cls.startsWith("fooicon-") && iconMap[cls]);
+
+        if (!fooClass) return;
+
+        // Check if already processed with this icon
+        const currentIcon = $el.data("fa-current-icon");
+        if (currentIcon === fooClass) return;
+
+        const iconDef = iconMap[fooClass];
+        const [prefix, iconName, additionalClasses] = iconDef;
+
+        try {
+            // Create FontAwesome SVG icon
+            const iconObj = FontAwesome.icon({prefix, iconName});
+
+            // Create jQuery object for SVG
+            const $svg = $(iconObj.node[0]);
+
+            // Ensure clicks pass through SVG to parent span
+            $svg.css("pointer-events", "none");
+
+            // Add optional additional classes (e.g., fa-spin)
+            if (additionalClasses) {
+                $svg.addClass(additionalClasses);
+            }
+
+            // Replace element content with SVG
+            $el.empty().append($svg);
+
+            // Mark as processed with current icon type
+            $el.data("fa-current-icon", fooClass);
+        } catch (e) {
+            // eslint-disable-next-line no-console
+            console.error(`Failed to create FontAwesome icon for ${fooClass}:`, e);
+        }
+    }
+
+    /**
+     * Process all existing fooicon elements in the document
+     */
+    function processAllIcons() {
+        document.querySelectorAll(".fooicon[class*='fooicon-']").forEach(processIcon);
+    }
+
+    /**
+     * Initialize the MutationObserver to watch for icon changes
+     */
+    function initObserver() {
+        if (observer) return; // Already initialized
+
+        observer = new MutationObserver((mutations) => {
+            mutations.forEach((mutation) => {
+                // Handle added nodes
+                if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
+                    mutation.addedNodes.forEach((node) => {
+                        if (node.nodeType === Node.ELEMENT_NODE) {
+                            // Check if the node itself is a fooicon
+                            if (node.classList && node.classList.contains("fooicon")) {
+                                processIcon(node);
+                            }
+                            // Check for fooicon descendants
+                            if (node.querySelectorAll) {
+                                node.querySelectorAll(".fooicon[class*='fooicon-']").forEach(processIcon);
+                            }
+                        }
+                    });
+                }
+
+                // Handle class attribute changes (e.g., sort icon toggling)
+                if (mutation.type === "attributes" && mutation.attributeName === "class") {
+                    const {target} = mutation;
+                    if (target.classList && target.classList.contains("fooicon")) {
+                        processIcon(target);
+                    }
+                }
+            });
+        });
+
+        // Observe the entire document for maximum coverage
+        observer.observe(document.body, {
+            childList: true,
+            subtree: true,
+            attributes: true,
+            attributeFilter: ["class"]
+        });
+    }
+
+    /**
+     * Public API
+     */
+
+    return {
+
+        /**
+         * Initialize icon replacement globally
+         * Should be called once when the app starts
+         */
+        init: () => {
+            // Process any existing icons
+            processAllIcons();
+
+            // Start observing for future changes
+            initObserver();
+        },
+
+        /**
+         * Manually process icons in a specific container
+         * Useful for immediate processing without waiting for observer
+         */
+        process: (container) => {
+            const $container = (typeof container === "string") ? $(container) : container;
+            $container.find(".fooicon[class*='fooicon-']").each(function () {
+                processIcon(this);
+            });
+        },
+
+        /**
+         * Destroy the observer (cleanup)
+         */
+        destroy: () => {
+            if (observer) {
+                observer.disconnect();
+                observer = null;
+            }
+        }
+    };
+});
index 0434549ef1754ed710ec5e0c6aff0c929d2cde1e..ca3319f28515578f5abbb568f8bc708a54b140d5 100644 (file)
@@ -254,6 +254,11 @@ define(["jquery", "app/common", "stickytabs", "visibility",
                 $("#preloader").addClass("d-none");
                 $("#navBar, #mainUI").removeClass("d-none");
                 $(".nav-tabs-sticky").stickyTabs({initialTab: "#status_nav"});
+
+                // Initialize FontAwesome icon replacement for FooTable
+                require(["app/footable-fontawesome"], (FootableFA) => {
+                    FootableFA.init();
+                });
             },
             errorMessage: "Cannot get server status",
             server: "All SERVERS"
index 0e226a0b5e5ad508eee8926b89130e0a4c4699a7..4cf97ae4c7b35a505c4fb399204a22d8947e2547 100644 (file)
@@ -55,7 +55,7 @@ requirejs.onError = function (e) {
             "<button type=\"button\" class=\"btn btn-info btn-xs float-end\" " +
                 "onClick=\"window.location.reload(); this.parentNode.parentNode.removeChild(this.parentNode);\" " +
                 "title=\"Reload current page\">" +
-                "<i class=\"glyphicon glyphicon-repeat\"></i> Reload" +
+                "Reload" +
             "</button>" +
         "</div>";
     throw e;