From 8d8f21368d0366b26d18d8befe50bb6709a2350b Mon Sep 17 00:00:00 2001 From: Richard Wei <54336863+WaysonWei@users.noreply.github.com> Date: Wed, 8 Sep 2021 11:06:18 +1200 Subject: [PATCH] feat(frontend): dark and high contrast theme supported EE-909 (#5353) * feat dark theme & high contrast theme supported --- api/http/handler/users/user_update.go | 5 + api/portainer.go | 2 + app/assets/css/app.css | 134 ++-- app/assets/css/index.js | 2 + app/assets/css/rdash.css | 21 +- app/assets/css/theme.css | 576 ++++++++++++++++++ app/assets/css/vendor-override.css | 399 ++++++++++++ .../containerNetworksDatatable.html | 6 +- .../networks-datatable/networksDatatable.html | 2 +- .../serviceTasksDatatable.html | 6 +- .../applicationsPortsDatatable.html | 12 +- .../applicationsStacksDatatable.html | 10 +- .../placements-datatable/template.html | 28 +- .../ingresses-datatable/template.html | 10 +- app/kubernetes/views/summary/summary.html | 2 +- .../volumes-storages-datatable/template.html | 10 +- .../components/box-selector/box-selector.css | 13 +- .../customTemplatesList.html | 2 +- .../components/datatables/datatable.css | 19 +- app/portainer/components/sidebar/sidebar.css | 20 +- .../template-list/templateList.html | 2 +- .../theme/theme-settings.controller.js | 70 +++ .../components/theme/theme-settings.html | 19 + .../components/theme/theme-settings.js | 7 + app/portainer/models/user.js | 1 + app/portainer/rest/user.js | 1 + app/portainer/services/api/userService.js | 4 + app/portainer/services/authentication.js | 12 +- app/portainer/services/stateManager.js | 5 + app/portainer/services/themeManager.js | 23 + app/portainer/views/account/account.html | 1 + .../views/account/accountController.js | 29 +- .../views/logout/logoutController.js | 5 +- app/portainer/views/main/mainController.js | 1 + 34 files changed, 1352 insertions(+), 107 deletions(-) create mode 100644 app/assets/css/theme.css create mode 100644 app/assets/css/vendor-override.css create mode 100644 app/portainer/components/theme/theme-settings.controller.js create mode 100644 app/portainer/components/theme/theme-settings.html create mode 100644 app/portainer/components/theme/theme-settings.js create mode 100644 app/portainer/services/themeManager.js diff --git a/api/http/handler/users/user_update.go b/api/http/handler/users/user_update.go index fdd7e1a65..02e337f3b 100644 --- a/api/http/handler/users/user_update.go +++ b/api/http/handler/users/user_update.go @@ -17,6 +17,7 @@ import ( type userUpdatePayload struct { Username string `validate:"required" example:"bob"` Password string `validate:"required" example:"cg9Wgky3"` + UserTheme string `example:"dark"` // User role (1 for administrator account and 2 for regular account) Role int `validate:"required" enums:"1,2" example:"2"` } @@ -104,6 +105,10 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http user.Role = portainer.UserRole(payload.Role) } + if payload.UserTheme != "" { + user.UserTheme = payload.UserTheme + } + err = handler.DataStore.User().UpdateUser(user.ID, user) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist user changes inside the database", err} diff --git a/api/portainer.go b/api/portainer.go index 87903d4b9..96af4f17e 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1003,6 +1003,8 @@ type ( ID UserID `json:"Id" example:"1"` Username string `json:"Username" example:"bob"` Password string `json:"Password,omitempty" example:"passwd"` + // User Theme + UserTheme string `example:"dark"` // User role (1 for administrator account and 2 for regular account) Role UserRole `json:"Role" example:"1"` diff --git a/app/assets/css/app.css b/app/assets/css/app.css index d88bfc110..74b1788b3 100644 --- a/app/assets/css/app.css +++ b/app/assets/css/app.css @@ -59,10 +59,10 @@ body, } .form-section-title { - border-bottom: 1px solid #777; + border-bottom: 1px solid var(--border-form-section-title-color); margin-top: 5px; margin-bottom: 15px; - color: #777; + color: var(--text-form-section-title-color); padding-left: 0; } @@ -108,12 +108,33 @@ a[ng-click] { pointer-events: none; } +.datatable-highlighted { + background-color: var(--bg-item-highlighted-color); +} + +.datatable-unhighlighted { + background-color: var(--bg-item-highlighted-null-color); +} + +.service-datatable { + background-color: var(--bg-item-highlighted-color); + padding: 2px; +} + +.service-datatable thead { + background-color: var(--bg-service-datatable-thead) !important; +} + +.service-datatable tbody { + background-color: var(--bg-service-datatable-tbody); +} + .tooltip.portainer-tooltip .tooltip-inner { font-family: Montserrat; - background-color: #ffffff; + background-color: var(--bg-tooltip-color); padding: 0.833em 1em; - color: #333333; - border: 1px solid #d4d4d5; + color: var(--text-tooltip-color); + border: 1px solid var(--border-tooltip-color); border-radius: 0.14285714rem; box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15); } @@ -145,7 +166,7 @@ a[ng-click] { .fa.blue-icon, .fab.blue-icon { - color: #337ab7; + color: var(--blue-2); } .text-warning { @@ -207,25 +228,25 @@ a[ng-click] { padding: 0.7rem; margin-bottom: 0.7rem; cursor: pointer; - border: 1px solid #cccccc; + border: 1px solid var(--border-blocklist-color); border-radius: 2px; - box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5); + box-shadow: var(--shadow-box-color); } .blocklist-item--disabled { cursor: auto; - background-color: #ececec; + background-color: var(--grey-12); } .blocklist-item--selected { - border: 2px solid #bbbbbb; - background-color: #ececec; - color: #2d3e63; + background-color: var(--bg-blocklist-item-selected-color); + border: 2px solid var(--border-blocklist-item-selected-color); + color: var(--text-blocklist-item-selected-color); } .blocklist-item:hover { - background-color: #ececec; - color: #2d3e63; + background-color: var(--bg-blocklist-hover-color); + color: var(--text-blocklist-hover-color); } .blocklist-item-box { @@ -360,7 +381,7 @@ a[ng-click] { .panel-body { padding-top: 30px; - background-color: #ffffff; + background-color: var(--white-color) fff; } .pagination-controls { @@ -443,8 +464,8 @@ a[ng-click] { display: inline-block; padding: 0px 6px; margin-left: 10px; - color: #555555; - background-color: #fff; + color: var(--text-small-select-color); + background-color: var(--bg-small-select-color); background-image: none; border-radius: 4px; font-size: 14px; @@ -462,11 +483,11 @@ a[ng-click] { } .visualizer_container .node { - border: 1px dashed #337ab7; + border: 1px dashed var(--blue-2); background-color: rgb(51, 122, 183); background-color: rgba(51, 122, 183, 0.1); border-radius: 4px; - box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5); + box-shadow: 0 3px 10px -2px var(--grey-50); padding: 15px; margin: 5px; } @@ -476,7 +497,7 @@ a[ng-click] { flex-direction: column; justify-content: center; text-align: center; - border-bottom: 1px solid #777; + border-bottom: 1px solid var(--grey-26); padding-bottom: 10px; } @@ -486,7 +507,7 @@ a[ng-click] { } .visualizer_container .node .node_info .node_labels { - border-top: 1px solid #777; + border-top: 1px solid var(--grey-26); padding-top: 10px; margin-top: 10px; } @@ -503,9 +524,9 @@ a[ng-click] { } .visualizer_container .node .tasks .task { - border: 1px solid #333333; + border: 1px solid var(--grey-6); border-radius: 2px; - box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5); + box-shadow: 0 3px 10px -2px var(--grey-50); padding: 10px; margin: 5px; font-size: 10px; @@ -547,9 +568,9 @@ a[ng-click] { .log_viewer { height: 100%; overflow-y: scroll; - color: black; + color: var(--text-log-viewer-color); font-size: 0.85em; - background-color: white; + background-color: var(--bg-log-viewer-color); } .log_viewer.wrap_lines { @@ -568,7 +589,7 @@ a[ng-click] { } .log_viewer .line_selected { - background-color: #c5cae9; + background-color: var(--bg-log-line-selected-color); } .row.header .meta .page { @@ -578,7 +599,7 @@ a[ng-click] { .tag { padding: 2px 6px; color: white; - background-color: #337ab7; + background-color: var(--blue-2); border: 1px solid #2e6da4; border-radius: 4px; } @@ -588,7 +609,7 @@ a[ng-click] { } .line-separator { - border-bottom: 1px solid #777; + border-bottom: 1px solid var(--grey-26); width: 50%; margin: 20px auto 10px auto; } @@ -613,7 +634,7 @@ a[ng-click] { } .striked::after { - border-bottom: 0.2em solid #777777; + border-bottom: 0.2em solid var(--grey-26); content: ''; left: 0; margin-top: calc(0.2em / 2 * -1); @@ -625,7 +646,7 @@ a[ng-click] { .striketext:before, .striketext:after { - background-color: #777777; + background-color: var(--grey-26); content: ''; display: inline-block; height: 1px; @@ -657,20 +678,51 @@ a[ng-click] { /*angular-multi-select override*/ .multiSelect > button { min-height: 30px !important; - background-color: unset; - background-image: unset; + background-image: var(--bg-image-multiselect-button); + border-color: var(--border-multiselect); + color: var(--text-multiselect); + background-color: var(--bg-multiselect-color); +} + +.multiSelect > button:hover { + background-image: var(--bg-image-multiselect-hover); +} + +.multiSelect .checkboxLayer { + border-color: var(--border-multiselect-checkboxlayer); +} + +.multiSelect .checkBoxContainer { + background-color: var(--bg-multiselect-checkboxcontainer); +} + +.multiSelect .multiSelectItem { + color: var(--text-multiselect-item); +} + +.multiSelect .helperContainer { + background-color: var(--bg-multiselect-helpercontainer); +} + +.multiSelect .multiSelectFocus { + background-image: var(--bg-image-multiselect); } .multiSelect .multiSelectItem:not(.multiSelectGroup).selected { - background-image: linear-gradient(#337ab7, #337ab7); - color: #fff; + background-image: var(--bg-image-multiselect); + color: var(--white-color); border: none; } .multiSelect .multiSelectItem:hover, .multiSelect .multiSelectGroup:hover { - background-image: linear-gradient(#337ab7, #337ab7) !important; - color: #fff !important; + border-color: var(--grey-3); +} + +.multiSelect .multiSelectItem:hover, +.multiSelect .multiSelectGroup:hover { + background-image: var(--bg-image-multiselect) !important; + color: var(--white-color) !important; } .multiSelect .tickMark, @@ -696,7 +748,7 @@ a[ng-click] { #loading-bar .bar { position: relative; height: 3px; - background: #738bc0; + background: var(--blue-3); } /*!angular-loading-bar override*/ @@ -708,11 +760,11 @@ a[ng-click] { /* json-tree override */ json-tree { font-size: 13px; - color: #30426a; + color: var(--blue-5); } json-tree .key { - color: #738bc0; + color: var(--blue-3); padding-right: 5px; } @@ -725,7 +777,7 @@ json-tree .branch-preview { /* uib-progressbar override */ .progress-bar { - color: #4e4e4e; + color: var(--text-progress-bar-color); } /* !uib-progressbar override */ @@ -756,7 +808,7 @@ json-tree .branch-preview { } .sk-fold-cube:before { - background-color: #337ab7; + background-color: var(--blue-2); } /* !spinkit override */ diff --git a/app/assets/css/index.js b/app/assets/css/index.js index c035bf022..b812316ab 100644 --- a/app/assets/css/index.js +++ b/app/assets/css/index.js @@ -1,2 +1,4 @@ import './rdash.css'; import './app.css'; +import './theme.css'; +import './vendor-override.css'; diff --git a/app/assets/css/rdash.css b/app/assets/css/rdash.css index a9dc4ff48..e885c51f3 100644 --- a/app/assets/css/rdash.css +++ b/app/assets/css/rdash.css @@ -52,7 +52,8 @@ * Header */ .row.header { - background: #fff; + height: 60px; + background: var(--bg-row-header-color); margin-bottom: 15px; } .row.header > div:last-child { @@ -202,9 +203,9 @@ html { overflow-y: scroll; } body { - background: #f3f3f3; + background: var(--bg-body-color); font-family: 'Montserrat'; - color: #333333 !important; + color: var(--text-body-color) !important; } .row { margin-left: 0 !important; @@ -236,7 +237,7 @@ body { background: #23ae89 !important; } .blue { - background: #2361ae !important; + background: var(--blue-color) !important; } .orange { background: #d3a938 !important; @@ -258,20 +259,20 @@ div.input-mask { -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); - background: #ffffff; + background: var(--bg-widget-color); border: 1px solid transparent; border-radius: 2px; - border-color: #e9e9e9; + border-color: var(--border-widget-color); } .widget .widget-header .pagination, .widget .widget-footer .pagination { margin: 0; } .widget .widget-header { - color: #767676; - background-color: #f6f6f6; + color: var(--text-widget-header-color); + background-color: var(--bg-widget-header-color); padding: 10px 15px; - border-bottom: 1px solid #e9e9e9; + border-bottom: 1px solid var(--border-widget-color); line-height: 30px; } .widget .widget-header i { @@ -281,7 +282,7 @@ div.input-mask { padding: 20px; } .widget .widget-body table thead { - background: #fafafa; + background: var(--bg-widget-table-color); } .widget .widget-body table thead * { font-size: 14px !important; diff --git a/app/assets/css/theme.css b/app/assets/css/theme.css new file mode 100644 index 000000000..b186f81c6 --- /dev/null +++ b/app/assets/css/theme.css @@ -0,0 +1,576 @@ +/* Color Variable */ +html { + --black-color: #000; + --white-color: #fff; + + --grey-1: #212121; + --grey-2: #181818; + --grey-3: #383838; + --grey-4: #585858; + --grey-5: #323c48; + --grey-6: #333333; + --grey-7: #767676; + --grey-8: #aaa; + --grey-9: #f3f3f3; + --grey-10: #f6f6f6; + --grey-11: #eeeeee; + --grey-12: #ececec; + --grey-13: #fafafa; + --grey-14: #f5f5f5; + --grey-15: #f9f2f4; + --grey-16: #eee; + --grey-17: #f7f7f7; + --grey-18: #c5cae9; + --grey-19: #ddd; + --grey-20: #dae3f3; + --grey-21: #d5e8f3; + --grey-22: #c3c3e4; + --grey-23: #e7f6ff; + --grey-24: #f1f9fd; + --grey-25: #555555; + --grey-26: #777777; + --grey-27: #4e4e4e; + --grey-28: #262626; + --grey-29: #555; + --grey-30: #444; + --grey-31: #868686; + --grey-32: #65798e; + --grey-34: #314252; + --grey-35: #546477; + --grey-36: #55637d; + --grey-37: #2d3e63; + --grey-38: #434343; + --grey-39: #194973; + --grey-40: #cfddfc; + --grey-41: #b4b4b4; + --grey-42: #d2d1d1; + --grey-43: #e9e9e9; + --grey-44: #ccc; + --grey-45: #e5e5e5; + --grey-46: #bbbbbb; + --grey-47: #d4d4d5; + --grey-48: #c6c6c6; + --grey-49: rgba(0, 0, 0, 0.54); + --grey-50: rgba(161, 170, 166, 0.5); + --grey-51: rgba(0, 0, 0, 0.15); + --grey-52: rgba(255, 255, 255, 0.3); + --grey-53: rgba(255, 255, 255, 0.6); + --grey-54: rgb(54, 54, 54); + --grey-55: rgba(255, 255, 255, 0.8); + --grey-56: #b2bfdc; + --grey-57: #999; + --grey-58: #ebf4f8; + --grey-59: #e6e6e6; + --grey-60: #cacaca; + + --blue-1: #219; + --blue-2: #337ab7; + --blue-3: #738bc0; + --blue-4: #23527c; + --blue-5: #30426a; + --blue-6: #577bc9; + --blue-7: #6b9aff; + --blue-8: #90ccff; + --blue-9: #3ea6ff; + --blue-10: #61b6ff; + --blue-11: #3ea5ff; + --blue-12: #41a6ff; + --blue-13: #2361ae; + --blue-14: #357ebd; + + --red-1: #a94442; + --red-2: #c7254e; + --red-3: #a11; + --red-4: #d9534f; + --red-5: #ff2727; + --red-6: #ff00e0; + --red-7: #f00; + + --green-1: #164; + --green-2: #1ec863; +} + +:root { + --bg-card-color: var(--grey-10); + --bg-main-color: var(--white-color); + --bg-body-color: var(--grey-9); + --bg-checkbox-border-color: var(--grey-49); + --bg-sidebar-color: var(--grey-37); + --bg-sidebar-header-color: var(--grey-37); + --bg-widget-color: var(--white-color); + --bg-widget-header-color: var(--grey-10); + --bg-widget-table-color: var(--grey-13); + --bg-header-color: var(--white-color); + --bg-hover-table-color: var(--grey-14); + --bg-switch-box-color: var(--white-color); + --bg-input-group-addon-color: var(--grey-11); + --bg-btn-default-color: var(--white-color); + --bg-blocklist-hover-color: var(--grey-12); + --bg-boxselector-color: var(--white-color); + --bg-table-color: var(--white-color); + --bg-md-checkbox-color: var(--grey-12); + --bg-form-control-disabled-color: var(--grey-11); + --bg-modal-content-color: var(--white-color); + --bg-code-color: var(--grey-15); + --bg-navtabs-color: var(--white-color); + --bg-navtabs-hover-color: var(--grey-16); + --bg-table-selected-color: var(--grey-14); + --bg-codemirror-gutters-color: var(--grey-17); + --bg-dropdown-menu-color: var(--white-color); + --bg-log-viewer-color: var(--white-color); + --bg-log-line-selected-color: var(--grey-18); + --bg-pre-color: var(--grey-14); + --bg-blocklist-item-selected-color: var(--grey-12); + --bg-progress-color: var(--grey-14); + --bg-pagination-color: var(--white-color); + --bg-pagination-span-color: var(--white-color); + --bg-pagination-hover-color: var(--grey-11); + --bg-ui-select-hover-color: var(--grey-14); + --bg-motd-body-color: var(--grey-20); + --bg-item-highlighted-color: var(--grey-21); + --bg-item-highlighted-null-color: var(--grey-14); + --bg-row-header-color: var(--white-color); + --bg-image-multiselect-button: linear-gradient(var(--white-color), var(--grey-17)); + --bg-multiselect-checkbox-color: var(--white-color); + --bg-sidebar-wrapper-color: var(--blue-5); + --bg-panel-body-color: var(--white-color); + --bg-codemirror-color: var(--white-color); + --bg-codemirror-selected-color: var(--grey-22); + --bg-multiselect-color: var(--white-color); + --bg-daterangepicker-color: var(--white-color); + --bg-calendar-color: var(--white-color); + --bg-calendar-table-color: var(--white-color); + --bg-daterangepicker-end-date: var(--white-color); + --bg-daterangepicker-hover: var(--grey-16); + --bg-daterangepicker-in-range: var(--grey-58); + --bg-daterangepicker-active: var(--blue-14); + --bg-tooltip-color: var(--white-color); + --bg-input-autofill-color: var(--white-color); + --bg-btn-default-hover-color: var(--grey-59); + --bg-btn-focus: var(--grey-59); + --bg-boxselector-disabled-color: var(--white-color); + --bg-small-select-color: var(--white-color); + + --text-main-color: var(--grey-7); + --text-body-color: var(--grey-6); + --text-sidebar-title-color: var(--blue-3); + --text-widget-header-color: var(--grey-7); + --text-form-control-color: var(--grey-25); + --text-muted-color: var(--grey-26); + --text-link-color: var(--blue-2); + --text-link-hover-color: var(--blue-4); + --text-input-group-addon-color: var(--grey-25); + --text-btn-default-color: var(--grey-6); + --text-blocklist-hover-color: var(--grey-37); + --text-dashboard-item-color: var(--grey-32); + --text-danger-color: var(--red-1); + --text-code-color: var(--red-2); + --text-navtabs-color: var(--grey-25); + --text-form-section-title-color: var(--grey-26); + --text-cm-default-color: var(--blue-1); + --text-cm-meta-color: var(--black-color); + --text-cm-string-color: var(--red-3); + --text-cm-number-color: var(--green-1); + --text-codemirror-color: var(--black-color); + --text-dropdown-menu-color: var(--grey-6); + --text-log-viewer-color: var(--black-color); + --text-json-tree-color: var(--blue-3); + --text-json-tree-leaf-color: var(--blue-5); + --text-json-tree-branch-preview-color: var(--blue-5); + --text-pre-color: var(--grey-6); + --text-blocklist-item-selected-color: var(--grey-37); + --text-progress-bar-color: var(--grey-27); + --text-pagination-color: var(--grey-26); + --text-pagination-span-color: var(--blue-2); + --text-pagination-span-hover-color: var(--blue-4); + --text-ui-select-color: var(--grey-6); + --text-ui-select-hover-color: var(--grey-28); + --text-summary-color: var(--black-color); + --text-multiselect-button-color: var(--grey-29); + --text-multiselect-item-color: var(--grey-30); + --text-sidebar-list-color: var(--grey-56); + --text-rzslider-color: var(--grey-36); + --text-rzslider-limit-color: var(--grey-36); + --text-daterangepicker-end-date: var(--grey-57); + --text-daterangepicker-in-range: var(--black-color); + --text-daterangepicker-active: var(--white-color); + --text-tooltip-color: var(--grey-6); + --text-input-autofill-color: var(--black-color); + --text-button-hover-color: var(--grey-6); + --text-small-select-color: var(--grey-25); + + --border-color: var(--grey-42); + --border-widget-color: var(--grey-43); + --border-sidebar-color: var(--white-color); + --border-form-control-color: var(--grey-44); + --border-table-color: var(--grey-19); + --border-table-top-color: var(--grey-19); + --border-datatable-top-color: var(--grey-10); + --border-blocklist-color: var(--grey-44) ccc; + --border-input-group-addon-color: var(--grey-44); + --border-btn-default-color: var(--grey-44); + --border-boxselector-color: var(--grey-6); + --border-md-checkbox-color: var(--grey-19); + --border-modal-header-color: var(--grey-45); + --border-navtabs-color: var(--grey-19); + --border-form-section-title-color: var(--grey-26); + --border-codemirror-cursor-color: var(--black-color); + --border-codemirror-gutters-color: var(--grey-19); + --border-pre-color: var(--grey-43); + --border-blocklist-item-selected-color: var(--grey-46); + --border-pagination-color: var(--grey-19); + --border-pagination-span-color: var(--grey-19); + --border-pagination-hover-color: var(--grey-19); + --border-multiselect-button-color: var(--grey-48); + --border-searchbar-color: var(--grey-10); + --border-panel-color: var(--white-color); + --border-daterangepicker-color: var(--grey-19); + --border-calendar-table: var(--white-color); + --border-daterangepicker: var(--grey-19); + --border-pre-next-month: var(--black-color); + --border-daterangepicker-after: var(--white-color); + --border-tooltip-color: var(--grey-47); + --border-modal: 0px; + + --hover-sidebar-color: var(--grey-37); + --shadow-box-color: 0 3px 10px -2px var(--grey-50); + --shadow-boxselector-color: 0 3px 10px -2px var(--grey-50); + --blue-color: var(--blue-13); + --button-close-color: var(--black-color); + --button-opacity: 0.2; + --button-opacity-hover: 0.5; + --bg-boxselector-wrapper-color: var(--grey-6); + + --bg-image-multiselect: linear-gradient(var(--blue-2), var(--blue-2)); + --bg-image-multiselect-button: linear-gradient(var(--white-color), var(--grey-17)); + --bg-image-multiselect-hover: linear-gradient(var(--white-color), var(--grey-43)); + --border-multiselect: var(--grey-48); + --border-multiselect-checkboxlayer: var(--grey-51); + --text-multiselect: var(--grey-29); + --text-multiselect-selectitem: var(--white-color); + --bg-multiselect-checkboxcontainer: var(--white-color); + --text-multiselect-item: var(--grey-30); + --bg-multiselect-helpercontainer: var(--white-color); + --text-input-textarea: var(--white-color); + --bg-service-datatable-thead: var(--grey-23); + --bg-service-datatable-tbody: var(--grey-24); +} + +:root[theme='dark'] { + --bg-card-color: var(--grey-1); + --bg-main-color: var(--grey-2); + --bg-body-color: var(--grey-2); + --bg-checkbox-border-color: var(--grey-8); + --bg-sidebar-color: var(--grey-3); + --bg-widget-color: var(--grey-1); + --bg-widget-header-color: var(--grey-1); + --bg-widget-table-color: var(--grey-1); + --bg-header-color: var(--grey-2); + --bg-hover-table-color: var(--grey-3); + --bg-switch-box-color: var(--grey-53); + --bg-input-group-addon-color: var(--grey-3); + --bg-btn-default-color: var(--grey-3); + --bg-blocklist-hover-color: var(--grey-3); + --bg-boxselector-color: var(--grey-54); + --bg-table-color: var(--grey-1); + --bg-md-checkbox-color: var(--grey-31); + --bg-form-control-disabled-color: var(--grey-3); + --bg-modal-content-color: var(--grey-1); + --bg-code-color: var(--red-4); + --bg-navtabs-color: var(--grey-3); + --bg-navtabs-hover-color: var(--grey-3); + --bg-table-selected-color: var(--grey-3); + --bg-codemirror-color: var(--grey-2); + --bg-codemirror-gutters-color: var(--grey-2); + --bg-dropdown-menu-color: var(--grey-1); + --bg-log-viewer-color: var(--grey-2); + --bg-log-line-selected-color: var(--grey-3); + --bg-pre-color: var(--grey-2); + --bg-blocklist-item-selected-color: var(--grey-3); + --bg-progress-color: var(--grey-3); + --bg-pagination-color: var(--grey-3); + --bg-pagination-span-color: var(--grey-3); + --bg-pagination-hover-color: var(--grey-4); + --bg-ui-select-hover-color: var(--grey-3); + --bg-motd-body-color: var(--grey-1); + --bg-item-highlighted-color: var(--grey-2); + --bg-item-highlighted-null-color: var(--grey-2); + --bg-row-header-color: var(--grey-2); + --bg-multiselect-button-color: var(--grey-3); + --bg-image-multiselect-button: none !important; + --bg-multiselect-checkbox-color: var(--grey-3); + --bg-sidebar-wrapper-color: var(--grey-1); + --bg-panel-body-color: var(--grey-1); + --bg-boxselector-wrapper-disabled-color: var(--grey-39); + --bg-codemirror-selected-color: var(--grey-3); + --bg-sidebar-header-color: var(--grey-1); + --bg-multiselect-color: var(--grey-1); + --bg-daterangepicker-color: var(--grey-3); + --bg-calendar-color: var(--grey-3); + --bg-calendar-table-color: var(--grey-3); + --bg-daterangepicker-end-date: var(--grey-4); + --bg-daterangepicker-hover: var(--grey-4); + --bg-daterangepicker-in-range: var(--grey-2); + --bg-daterangepicker-active: var(--blue-14); + --bg-tooltip-color: var(--grey-3); + --bg-input-autofill-color: var(--grey-2); + --bg-btn-default-hover-color: var(--grey-3); + --bg-btn-focus: var(--grey-3); + --bg-boxselector-disabled-color: var(--grey-54); + --bg-small-select-color: var(--grey-2); + + --text-main-color: var(--white-color); + --text-body-color: var(--white-color); + --text-sidebar-title-color: var(--grey-8); + --text-widget-header-color: var(--white-color); + --text-form-control-color: var(--grey-8); + --text-muted-color: var(--grey-8); + --text-link-color: var(--blue-9); + --text-link-hover-color: var(--blue-2); + --text-input-group-addon-color: var(--grey-8); + --text-btn-default-color: var(--grey-8); + --text-blocklist-hover-color: var(--white-color); + --text-dashboard-item-color: var(--blue-2); + --text-danger-color: var(--red-4); + --text-code-color: var(--white-color); + --text-navtabs-color: var(--white-color); + --text-form-section-title-color: var(--grey-8); + --text-cm-default-color: var(--blue-10); + --text-cm-meta-color: var(--white-color); + --text-cm-string-color: var(--red-5); + --text-cm-number-color: var(--green-2); + --text-codemirror-color: var(--white-color); + --text-dropdown-menu-color: var(--white-color); + --text-log-viewer-color: var(--white-color); + --text-json-tree-color: var(--grey-40); + --text-json-tree-leaf-color: var(--blue-6); + --text-json-tree-branch-preview-color: var(--blue-7); + --text-pre-color: var(--white-color); + --text-blocklist-item-selected-color: var(--white-color); + --text-progress-bar-color: var(--white-color); + --text-pagination-color: var(--white-color); + --text-pagination-span-color: var(--white-color); + --text-pagination-span-hover-color: var(--white-color); + --text-ui-select-color: var(--white-color); + --text-ui-select-hover-color: var(--white-color); + --text-summary-color: var(--white-color); + --text-multiselect-button-color: var(--white-color); + --text-multiselect-item-color: var(--white-color); + --text-sidebar-list-color: var(--white-color); + --text-boxselector-wrapper-color: var(--white-color); + --text-daterangepicker-end-date: var(--grey-7); + --text-daterangepicker-in-range: var(--white-color); + --text-daterangepicker-active: var(--white-color); + --text-tooltip-color: var(--white-color); + --text-btn-default-color: var(--white-color); + --text-input-autofill-color: var(--grey-8); + --text-button-hover-color: var(--white-color); + --text-small-select-color: var(--grey-7); + + --border-color: var(--grey-3); + --border-widget-color: var(--grey-1); + --border-sidebar-color: var(--blue-9); + --border-form-control-color: var(--grey-54); + --border-table-color: var(--grey-3); + --border-table-top-color: var(--grey-3); + --border-datatable-top-color: var(--grey-3); + --border-blocklist-color: var(--grey-3); + --border-input-group-addon-color: var(--grey-38); + --border-btn-default-color: var(--grey-38); + --border-boxselector-color: var(--grey-1); + --border-md-checkbox-color: var(--grey-41); + --border-modal-header-color: var(--grey-1); + --border-navtabs-color: var(--grey-38); + --border-form-section-title-color: var(--grey-8); + --border-codemirror-cursor-color: var(--white-color); + --border-codemirror-gutters-color: var(--grey-26); + --border-pre-color: var(--grey-3); + --border-blocklist-item-selected-color: var(--grey-38); + --border-pagination-color: var(--grey-3); + --border-pagination-span-color: var(--grey-3); + --border-pagination-hover-color: var(--grey-3); + --border-pagination-hover-color: var(--grey-3); + --border-multiselect-button-color: var(--grey-3); + --border-searchbar-color: var(--grey-1); + --border-panel-color: var(--grey-2); + --border-daterangepicker-color: var(--grey-3); + --border-calendar-table: var(--grey-3); + --border-daterangepicker: var(--grey-4); + --border-pre-next-month: var(--white-color); + --border-daterangepicker-after: var(--grey-3); + --border-tooltip-color: var(--grey-3); + --border-modal: 0px; + + --hover-sidebar-color: var(--grey-3); + --blue-color: var(--blue-2); + --button-close-color: var(--white-color); + --button-opacity: 0.6; + --button-opacity-hover: 0.3; + --shadow-box-color: none; + --shadow-boxselector-color: none; + + --bg-image-multiselect: linear-gradient(var(--grey-38), var(--grey-38)); + --bg-image-multiselect-button: linear-gradient(var(--grey-1), var(--grey-1)); + --bg-image-multiselect-hover: linear-gradient(var(--grey-3), var(--grey-3)); + --border-multiselect: var(--grey-3); + --border-multiselect-checkboxlayer: var(--grey-3); + --text-multiselect: var(--white-color); + --bg-multiselect-checkboxcontainer: var(--grey-3); + --text-multiselect-item: var(--white-color); + --bg-multiselect-helpercontainer: var(--grey-1); + --text-input-textarea: var(--grey-1); + --bg-service-datatable-thead: var(--grey-1); + --bg-service-datatable-tbody: var(--grey-1); +} + +:root[theme='highcontrast'] { + --bg-card-color: var(--black-color); + --bg-main-color: var(--black-color); + --bg-body-color: var(--black-color); + --bg-checkbox-border-color: var(--grey-8); + --bg-sidebar-color: var(--black-color); + --bg-widget-color: var(--black-color); + --bg-widget-header-color: var(--black-color); + --bg-widget-table-color: var(--black-color); + --bg-header-color: var(--black-color); + --bg-hover-table-color: var(--grey-3); + --bg-switch-box-color: var(--grey-53); + --bg-panel-body-color: var(--black-color); + --bg-boxselector-wrapper-disabled-color: var(--grey-39); + --bg-dropdown-menu-color: var(--black-color); + --bg-codemirror-selected-color: var(--grey-3); + --bg-row-header-color: var(--black-color); + --bg-sidebar-wrapper-color: var(--black-color); + --bg-motd-body-color: var(--black-color); + --bg-blocklist-hover-color: var(--black-color); + --bg-blocklist-item-selected-color: var(--black-color); + --bg-input-group-addon-color: var(--grey-1); + --bg-table-color: var(--black-color); + --bg-codemirror-gutters-color: var(--black-color); + --bg-codemirror-color: var(--black-color); + --bg-codemirror-selected-color: var(--grey-3); + --bg-log-viewer-color: var(--black-color); + --bg-log-line-selected-color: var(--grey-3); + --bg-sidebar-header-color: var(--black-color); + --bg-modal-content-color: var(--black-color); + --bg-form-control-disabled-color: var(--grey-1); + --bg-input-sm-color: var(--black-color); + --bg-item-highlighted-color: var(--black-color); + --bg-service-datatable-thead: var(--black-color); + --bg-service-datatable-tbody: var(--black-color); + --bg-pagination-color: var(--grey-3); + --bg-pagination-span-color: var(--grey-3); + --bg-multiselect-color: var(--grey-1); + --bg-daterangepicker-color: var(--black-color); + --bg-calendar-color: var(--black-color); + --bg-calendar-table-color: var(--black-color); + --bg-daterangepicker-end-date: var(--grey-3); + --bg-daterangepicker-hover: var(--grey-3); + --bg-daterangepicker-in-range: var(--grey-2); + --bg-daterangepicker-active: var(--blue-14); + --bg-tooltip-color: var(--black-color); + --bg-table-selected-color: var(--grey-3); + --bg-pre-color: var(--grey-2); + --bg-navtabs-hover-color: var(--grey-3); + --bg-btn-default-color: var(--black-color); + --bg-code-color: var(--red-4); + --bg-navtabs-color: var(--black-color); + --bg-input-autofill-color: var(--black-color); + --bg-code-color: var(--grey-2); + --bg-navtabs-color: var(--grey-2); + --bg-navtabs-hover-color: var(--grey-3); + --bg-btn-default-hover-color: var(--grey-3); + --bg-btn-default-color: var(--black-color); + --bg-btn-focus: var(--black-color); + --bg-boxselector-color: var(--black-color); + --bg-boxselector-disabled-color: var(--black-color); + --bg-small-select-color: var(--black-color); + + --text-main-color: var(--white-color); + --text-body-color: var(--white-color); + --text-sidebar-title-color: var(--grey-8); + --text-widget-header-color: var(--white-color); + --text-link-color: var(--blue-9); + --text-link-hover-color: var(--blue-9); + --text-danger-color: var(--red-7); + --text-code-color: var(--red-7); + --text-form-control-color: var(--white-color); + --text-blocklist-hover-color: var(--blue-11); + --text-boxselector-wrapper-color: var(--white-color); + --text-dashboard-item-color: var(--blue-12); + --text-form-section-title-color: var(--white-color); + --text-muted-color: var(--white-color); + --text-tooltip-color: var(--white-color); + --text-blocklist-item-selected-color: var(--blue-9); + --text-input-group-addon-color: var(--white-color); + --text-codemirror-color: var(--white-color); + --text-log-viewer-color: var(--white-color); + --text-summary-color: var(--white-color); + --text-rzslider-color: var(--white-color); + --text-rzslider-limit-color: var(--white-color); + --text-pagination-color: var(--white-color); + --text-daterangepicker-end-date: var(--grey-7); + --text-daterangepicker-in-range: var(--white-color); + --text-daterangepicker-active: var(--white-color); + --text-sidebar-list-color: var(--white-color); + --text-ui-select-color: var(--white-color); + --text-btn-default-color: var(--white-color); + --text-json-tree-color: var(--white-color); + --text-json-tree-leaf-color: var(--white-color); + --text-json-tree-branch-preview-color: var(--white-color); + --text-pre-color: var(--white-color); + --text-navtabs-color: var(--white-color); + --text-input-autofill-color: var(--white-color); + --text-navtabs-color: var(--white-color); + --text-button-hover-color: var(--white-color); + --text-btn-default-color: var(--white-color); + --text-small-select-color: var(--white-color); + + --border-color: var(--grey-55); + --border-widget-color: var(--white-color); + --border-sidebar-color: var(--blue-9); + --border-form-control-color: var(--grey-54); + --border-table-color: var(--grey-55); + --border-table-top-color: var(--grey-55); + --border-datatable-top-color: var(--grey-55); + --border-sidebar-high-contrast: 1px solid var(--blue-9); + --border-code-high-contrast: 1px solid var(--white-color); + --border-boxselector-wrapper: 3px solid var(--blue-2); + --border-boxselector-wrapper-hover: 3px solid var(--blue-8); + --border-panel-color: var(--white-color); + --border-input-group-addon-color: var(--grey-54); + --border-modal-header-color: var(--grey-3); + --border-input-sm-color: var(--white-color); + --border-pagination-color: var(--grey-3); + --border-pagination-span-color: var(--grey-3); + --border-daterangepicker-color: var(--white-color); + --border-calendar-table: var(--black-color); + --border-daterangepicker: var(--black-color); + --border-pre-next-month: var(--white-color); + --border-daterangepicker-after: var(--black-color); + --border-tooltip-color: var(--white-color); + --border-pre-color: var(--grey-3); + --border-codemirror-cursor-color: var(--white-color); + --border-modal: 1px solid var(--white-color); + + --hover-sidebar-color: var(--blue-9); + --hover-sidebar-color: var(--black-color); + --shadow-box-color: none; + --shadow-boxselector-color: none; + + --bg-image-multiselect: linear-gradient(var(--black-color), var(--black-color)); + --bg-image-multiselect-button: linear-gradient(var(--grey-1), var(--grey-1)); + --bg-image-multiselect-hover: linear-gradient(var(--grey-3), var(--grey-3)); + --border-multiselect: var(--black-color); + --border-multiselect-checkboxlayer: var(--grey-3); + --text-multiselect: var(--white-color); + --bg-multiselect-checkboxcontainer: var(--grey-3); + --text-multiselect-item: var(--white-color); + --bg-multiselect-helpercontainer: var(--grey-1); + --text-input-textarea: var(--black-color); + --bg-item-highlighted-null-color: var(--grey-2); + --text-cm-default-color: var(--blue-9); + --text-cm-meta-color: var(--white-color); + --text-cm-string-color: var(--red-7); + --text-progress-bar-color: var(--black-color); +} diff --git a/app/assets/css/vendor-override.css b/app/assets/css/vendor-override.css new file mode 100644 index 000000000..21572a7dd --- /dev/null +++ b/app/assets/css/vendor-override.css @@ -0,0 +1,399 @@ +/* Overide Vendor CSS */ +.form-control { + background-color: var(--bg-main-color) !important; + border: 1px solid var(--border-form-control-color); + color: var(--text-form-control-color); +} + +.text-muted { + color: var(--text-muted-color); +} + +.table > thead > tr > th { + border-bottom: 2px solid var(--border-table-color); +} + +.table-hover > tbody > tr:hover { + background-color: var(--bg-hover-table-color); +} + +.switch i, +.bootbox-form .checkbox i { + background: var(--bg-switch-box-color); +} + +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + border-top: 1px solid var(--border-table-top-color); +} + +a { + color: var(--text-link-color); +} + +a:hover, +a:focus { + color: var(--text-link-hover-color); +} + +.input-group-addon { + color: var(--text-input-group-addon-color); + background-color: var(--bg-input-group-addon-color); + border: 1px solid var(--border-input-group-addon-color); +} + +.btn-default { + color: var(--text-btn-default-color); + background-color: var(--bg-btn-default-color); + border-color: var(--border-btn-default-color); +} + +.text-danger { + color: var(--text-danger-color); +} + +.table .table { + background-color: var(--bg-table-color); +} + +.table-bordered { + border-color: var(--border-table-top-color); +} + +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border-color: var(--border-table-top-color); +} + +.md-checkbox input[type='checkbox']:disabled + label:before { + background: var(--bg-md-checkbox-color) !important; + border-color: var(--border-md-checkbox-color) !important; +} + +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--bg-form-control-disabled-color) !important; +} + +.modal.in .modal-dialog { + border: var(--border-modal); +} + +.modal-content { + background-color: var(--bg-modal-content-color); +} + +.modal-header { + border-bottom: 1px solid var(--border-modal-header-color); +} + +.modal-footer { + border-top: 1px solid var(--border-modal-header-color); +} + +.close { + color: var(--button-close-color); + opacity: var(--button-opacity); +} + +.close:hover, +.close:focus { + color: var(--button-close-color); + opacity: var(--button-opacity-hover); +} + +code { + color: var(--text-code-color); + background-color: var(--bg-code-color); +} + +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: var(--text-navtabs-color); + background-color: var(--bg-navtabs-color); + border: 1px solid var(--border-navtabs-color); +} + +.nav-tabs { + border-bottom: 1px solid var(--border-navtabs-color); +} + +.nav-tabs > li > a:hover { + border-color: var(--border-navtabs-color); +} + +.nav > li > a:hover, +.nav > li > a:focus { + background-color: var(--bg-navtabs-hover-color); +} + +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: var(--bg-table-selected-color); +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: var(--bg-table-selected-color); +} + +.CodeMirror-gutters { + background: var(--bg-codemirror-gutters-color); + border-right: 1px solid var(--border-codemirror-gutters-color); +} + +.CodeMirror { + background: var(--bg-codemirror-color); + color: var(--text-codemirror-color); +} + +.CodeMirror-selected { + background: var(--bg-codemirror-selected-color) !important; +} + +.CodeMirror-cursor { + border-left: 1px solid var(--border-codemirror-cursor-color); +} + +.cm-s-default .cm-atom { + color: var(--text-cm-default-color); +} + +.cm-s-default .cm-meta { + color: var(--text-cm-meta-color); +} + +.cm-s-default .cm-string { + color: var(--text-cm-string-color); +} + +.cm-s-default .cm-number { + color: var(--text-cm-number-color); +} + +.dropdown-menu { + background: var(--bg-dropdown-menu-color); +} + +.dropdown-menu > li > a { + color: var(--text-dropdown-menu-color); +} + +pre { + border: 1px solid var(--border-pre-color); + background-color: var(--bg-pre-color); + color: var(--text-pre-color); +} +json-tree .key { + color: var(--text-json-tree-color); +} + +json-tree .leaf-value { + color: var(--text-json-tree-leaf-color); +} + +json-tree .branch-preview { + color: var(--text-json-tree-branch-preview-color); +} + +.progress { + background-color: var(--bg-progress-color); +} + +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: var(--text-pagination-color); + background-color: var(--bg-pagination-color); + border-color: var(--border-pagination-color); +} + +.pagination > li > a, +.pagination > li > span { + background-color: var(--bg-pagination-span-color); + border-color: var(--border-pagination-span-color); + color: var(--text-pagination-span-color); +} + +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + background-color: var(--bg-pagination-hover-color); + border-color: var(--border-pagination-hover-color); +} + +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + color: var(--text-pagination-span-hover-color); +} + +.ui-select-bootstrap .ui-select-choices-row > span { + color: var(--text-ui-select-color); +} + +.ui-select-bootstrap .ui-select-choices-row > span:hover, +.ui-select-bootstrap .ui-select-choices-row > span:focus { + background-color: var(--bg-ui-select-hover-color); + color: var(--text-ui-select-hover-color); +} + +.motd-body { + background-color: var(--bg-motd-body-color) !important; +} + +.panel-body { + background-color: var(--bg-panel-body-color) !important; +} + +.panel { + border: 1px solid var(--border-panel-color); +} + +.theme-information .col-sm-12 { + padding-left: 0px; + padding-right: 0px; + margin-top: 15px; +} + +.theme-panel { + margin-top: 15px; +} + +.summary { + color: var(--text-summary-color); + font-weight: 700; +} + +.input-sm { + background-color: var(--bg-input-sm-color); + border: 1px solid var(--border-input-sm-color); +} + +.rzslider .rz-bubble { + color: var(--text-rzslider-color); +} + +.rzslider .rz-bubble.rz-limit { + color: var(--text-rzslider-limit-color); +} +input, +button, +select, +textarea { + background: var(--text-input-textarea); +} + +.daterangepicker { + background-color: var(--bg-daterangepicker-color); + border: 1px solid var(--border-daterangepicker-color); +} + +.daterangepicker .drp-calendar.left { + background: var(--bg-calendar-color); +} + +.daterangepicker .drp-calendar.left .calendar-table { + background: var(--bg-calendar-table-color); +} + +.daterangepicker .drp-calendar.right { + background: var(--bg-calendar-color); +} + +.daterangepicker .drp-calendar.right .calendar-table { + background: var(--bg-calendar-table-color); +} + +.daterangepicker .calendar-table { + border: 1px solid var(--border-calendar-table); +} + +.daterangepicker td.off, +.daterangepicker td.off.in-range, +.daterangepicker td.off.start-date, +.daterangepicker td.off.end-date { + background-color: var(--bg-daterangepicker-end-date); + color: var(--text-daterangepicker-end-date); +} + +.daterangepicker td.available:hover, +.daterangepicker th.available:hover { + background-color: var(--bg-daterangepicker-hover); +} + +.daterangepicker td.in-range { + background-color: var(--bg-daterangepicker-in-range); + color: var(--text-daterangepicker-in-range); +} + +.daterangepicker td.active, +.daterangepicker td.active:hover { + background-color: var(--bg-daterangepicker-active); + color: var(--text-daterangepicker-active); +} + +.daterangepicker .drp-buttons { + border-top: 1px solid var(--border-daterangepicker); +} + +.daterangepicker .calendar-table .next span, +.daterangepicker .calendar-table .prev span { + border-color: var(--border-pre-next-month); +} + +.daterangepicker:after { + border-bottom: 6px solid var(--border-daterangepicker-after); +} + +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +input:-webkit-autofill:active { + -webkit-box-shadow: 0 0 0 30px var(--bg-input-autofill-color) inset !important; + box-shadow: 0 0 0 30px var(--bg-input-autofill-color) inset !important; +} + +input:-webkit-autofill { + -webkit-text-fill-color: var(--text-input-autofill-color) !important; +} + +.btn:hover { + color: var(--text-button-hover-color); +} + +.btn-default:hover { + background-color: var(--bg-btn-default-hover-color); +} + +.btn-primary:hover { + color: var(--white-color) !important; +} +/* Overide Vendor CSS */ diff --git a/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html b/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html index 77ce34153..f1a81acaf 100644 --- a/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html +++ b/app/docker/components/datatables/container-networks-datatable/containerNetworksDatatable.html @@ -73,7 +73,11 @@ </button> </td> </tr> - <tr dir-paginate-end ng-show="$ctrl.itemCanExpand(value) && value.Expanded" ng-style="{ background: value.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr + dir-paginate-end + ng-show="$ctrl.itemCanExpand(value) && value.Expanded" + ng-class="{ 'datatable-highlighted': value.Highlighted, 'datatable-unhighlighted': !value.Highlighted }" + > <td colspan="1"></td> <td colspan="1"> {{ value.GlobalIPv6Address }} diff --git a/app/docker/components/datatables/networks-datatable/networksDatatable.html b/app/docker/components/datatables/networks-datatable/networksDatatable.html index bf54ca837..22aeb605f 100644 --- a/app/docker/components/datatables/networks-datatable/networksDatatable.html +++ b/app/docker/components/datatables/networks-datatable/networksDatatable.html @@ -171,7 +171,7 @@ allow-checkbox="true" > </tr> - <tr dir-paginate-end ng-show="item.Expanded" ng-repeat="it in item.Subs" style="background: #d5e8f3;" network-row-content item="it" parent-ctrl="$ctrl"> </tr> + <tr dir-paginate-end ng-show="item.Expanded" ng-repeat="it in item.Subs" class="datatable-highlighted" network-row-content item="it" parent-ctrl="$ctrl"> </tr> <tr ng-if="!$ctrl.dataset"> <td colspan="9" class="text-center text-muted">Loading...</td> </tr> diff --git a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html index 285f2263e..521e324a9 100644 --- a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html +++ b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html @@ -1,6 +1,6 @@ -<div style="background-color: #d5e8f3; padding: 2px;"> +<div class="service-datatable"> <table class="table table-condensed table-hover nowrap-cells"> - <thead style="background-color: #e7f6ff;"> + <thead> <tr> <th uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.filters.state.open" style="width: 10%;"> <a ng-click="$ctrl.changeOrderBy('Status.State')"> @@ -54,7 +54,7 @@ </th> </tr> </thead> - <tbody style="background-color: #f1f9fd;"> + <tbody> <tr ng-repeat="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter: $ctrl.applyFilters | filter:$ctrl.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder))" > diff --git a/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatable.html b/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatable.html index 814ff9ebf..ec593a0a0 100644 --- a/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatable.html +++ b/app/kubernetes/components/datatables/applications-ports-datatable/applicationsPortsDatatable.html @@ -105,8 +105,7 @@ <!-- dir-paginate-start track by $index --> <tr dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | filter: $ctrl.isDisplayed | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))" - ng-class="{ active: item.Checked }" - ng-style="{ background: item.Highlighted ? '#d5e8f3' : '' }" + ng-class="{ active: item.Checked, 'datatable-highlighted': item.Highlighted }" ng-click="$ctrl.expandItem(item, !item.Expanded)" pagination-id="$ctrl.tableKey" > @@ -177,7 +176,7 @@ </td> </tr> <!-- sub rows --> - <tr ng-show="item.Expanded" ng-repeat-start="port in item.Ports" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr ng-show="item.Expanded" ng-repeat-start="port in item.Ports" ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }"> <td ng-if="!$ctrl.portHasIngressRules(port)"></td> <td ng-if="!$ctrl.portHasIngressRules(port)">-</td> <td ng-if="!$ctrl.portHasIngressRules(port)">-</td> @@ -190,7 +189,12 @@ <td ng-if="!$ctrl.portHasIngressRules(port)">{{ port.TargetPort }}/{{ port.Protocol }}</td> <td ng-if="!$ctrl.portHasIngressRules(port)">-</td> </tr> - <tr ng-show="item.Expanded" ng-repeat-end ng-repeat="rule in port.IngressRules" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr + ng-show="item.Expanded" + ng-repeat-end + ng-repeat="rule in port.IngressRules" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" + > <td></td> <td>-</td> <td>-</td> diff --git a/app/kubernetes/components/datatables/applications-stacks-datatable/applicationsStacksDatatable.html b/app/kubernetes/components/datatables/applications-stacks-datatable/applicationsStacksDatatable.html index d27aa586a..324a75644 100644 --- a/app/kubernetes/components/datatables/applications-stacks-datatable/applicationsStacksDatatable.html +++ b/app/kubernetes/components/datatables/applications-stacks-datatable/applicationsStacksDatatable.html @@ -109,8 +109,7 @@ <tbody> <tr dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | filter: $ctrl.isDisplayed | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))" - ng-class="{ active: item.Checked }" - ng-style="{ background: item.Highlighted ? '#d5e8f3' : '' }" + ng-class="{ active: item.Checked, 'datatable-highlighted': item.Highlighted }" ng-click="$ctrl.expandItem(item, !item.Expanded)" pagination-id="$ctrl.tableKey" > @@ -141,7 +140,12 @@ <a ui-sref="kubernetes.stacks.stack.logs({ namespace: item.ResourcePool, name: item.Name })"> <i class="fa fa-file-alt" aria-hidden="true"></i> Logs </a> </td> </tr> - <tr dir-paginate-end ng-show="item.Expanded" ng-repeat="app in item.Applications" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr + dir-paginate-end + ng-show="item.Expanded" + ng-repeat="app in item.Applications" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" + > <td></td> <td colspan="4"> <a ui-sref="kubernetes.applications.application({ name: app.Name, namespace: app.ResourcePool })">{{ app.Name }}</a> diff --git a/app/kubernetes/views/applications/edit/components/placements-datatable/template.html b/app/kubernetes/views/applications/edit/components/placements-datatable/template.html index 50bd58670..9622f2225 100644 --- a/app/kubernetes/views/applications/edit/components/placements-datatable/template.html +++ b/app/kubernetes/views/applications/edit/components/placements-datatable/template.html @@ -75,8 +75,7 @@ <tbody> <tr dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | filter: $ctrl.isDisplayed | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))" - ng-class="{ active: item.Checked }" - ng-style="{ background: item.Highlighted ? '#d5e8f3' : '' }" + ng-class="{ active: item.Checked, 'datatable-highlighted': item.Highlighted }" ng-click="$ctrl.expandItem(item, !item.Expanded)" pagination-id="$ctrl.tableKey" > @@ -92,14 +91,23 @@ </td> </tr> <!-- ADMIN + UNMET TAINTS --> - <tr ng-if="$ctrl.isAdmin" ng-show="item.Expanded" ng-repeat="taint in item.UnmetTaints" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr + ng-if="$ctrl.isAdmin" + ng-show="item.Expanded" + ng-repeat="taint in item.UnmetTaints" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" + > <td colspan="2"> This application is missing a toleration for the taint <code>{{ taint.Key }}{{ taint.Value ? '=' + taint.Value : '' }}:{{ taint.Effect }}</code> </td> </tr> <!-- !ADMIN + UNMET TAINTS --> <!-- USER + UNMET TAINTS --> - <tr ng-if="!$ctrl.isAdmin && item.UnmetTaints.length" ng-show="item.Expanded" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr + ng-if="!$ctrl.isAdmin && item.UnmetTaints.length" + ng-show="item.Expanded" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" + > <td colspan="2"> Placement constraint not respected for that node. </td> @@ -110,7 +118,7 @@ ng-if="$ctrl.isAdmin" ng-show="item.Expanded" ng-repeat="label in item.UnmatchedNodeSelectorLabels" - ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" > <td colspan="2"> This application can only be scheduled on a node where the label <code>{{ label.key }}</code> is set to <code>{{ label.value }}</code> @@ -121,7 +129,7 @@ <tr ng-if="!$ctrl.isAdmin && (item.UnmatchedNodeSelectorLabels.length || item.UnmatchedNodeAffinities.length)" ng-show="item.Expanded" - ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" > <td colspan="2"> Placement label not respected for that node. @@ -129,7 +137,11 @@ </tr> <!-- ! USER + UNMET NODE SELECTOR LABELS || UNMET NODE AFFINITIES --> <!-- ADMIN + UNMET NODE AFFINITIES --> - <tr ng-if="$ctrl.isAdmin" ng-show="item.Expanded && item.UnmatchedNodeAffinities.length" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr + ng-if="$ctrl.isAdmin" + ng-show="item.Expanded && item.UnmatchedNodeAffinities.length" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" + > <td colspan="2"> This application can only be scheduled on nodes respecting one of the following labels combination: </td> @@ -139,7 +151,7 @@ ng-if="$ctrl.isAdmin" ng-show="item.Expanded" ng-repeat="aff in item.UnmatchedNodeAffinities" - ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" > <td></td> <td> diff --git a/app/kubernetes/views/resource-pools/edit/components/ingresses-datatable/template.html b/app/kubernetes/views/resource-pools/edit/components/ingresses-datatable/template.html index ef78e3734..27a359130 100644 --- a/app/kubernetes/views/resource-pools/edit/components/ingresses-datatable/template.html +++ b/app/kubernetes/views/resource-pools/edit/components/ingresses-datatable/template.html @@ -73,8 +73,7 @@ <tbody> <tr dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))" - ng-class="{ active: item.Checked }" - ng-style="{ background: item.Highlighted ? '#d5e8f3' : '' }" + ng-class="{ active: item.Checked, 'datatable-highlighted': item.Highlighted }" ng-click="$ctrl.expandItem(item, !item.Expanded)" pagination-id="$ctrl.tableKey" > @@ -84,7 +83,12 @@ >{{ item.Name }} </td> </tr> - <tr dir-paginate-end ng-show="item.Expanded" ng-repeat="path in item.Paths" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr + dir-paginate-end + ng-show="item.Expanded" + ng-repeat="path in item.Paths" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" + > <td> <a style="margin-left: 15px;" ng-href="http://{{ path.Host ? path.Host : path.IP }}{{ path.Path }}" target="_blank"> {{ path.Host ? path.Host : path.IP }}{{ path.Path }} diff --git a/app/kubernetes/views/summary/summary.html b/app/kubernetes/views/summary/summary.html index 8ab3e4f16..cb62834f9 100644 --- a/app/kubernetes/views/summary/summary.html +++ b/app/kubernetes/views/summary/summary.html @@ -22,7 +22,7 @@ <li ng-repeat="summary in $ctrl.state.resources" ng-if="summary.action && summary.kind && summary.name"> {{ summary.action }} {{ $ctrl.getArticle(summary.kind, summary.action) }} - <span style="color: black; font-weight: 700;">{{ summary.kind }}</span> named <code>{{ summary.name }}</code> + <span class="summary">{{ summary.kind }}</span> named <code>{{ summary.name }}</code> <span ng-if="summary.type"> of type <code>{{ summary.type }}</code></span > diff --git a/app/kubernetes/views/volumes/components/volumes-storages-datatable/template.html b/app/kubernetes/views/volumes/components/volumes-storages-datatable/template.html index b0130ab5c..b912d3f41 100644 --- a/app/kubernetes/views/volumes/components/volumes-storages-datatable/template.html +++ b/app/kubernetes/views/volumes/components/volumes-storages-datatable/template.html @@ -82,8 +82,7 @@ <tbody> <tr dir-paginate-start="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit: $ctrl.tableKey))" - ng-class="{ active: item.Checked }" - ng-style="{ background: item.Highlighted ? '#d5e8f3' : '' }" + ng-class="{ active: item.Checked, 'datatable-highlighted': item.Highlighted }" ng-click="$ctrl.expandItem(item, !item.Expanded)" pagination-id="$ctrl.tableKey" > @@ -95,7 +94,12 @@ <td>{{ item.Name }}</td> <td>{{ item.Size }}</td> </tr> - <tr dir-paginate-end ng-show="item.Expanded" ng-repeat="vol in item.Volumes" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }"> + <tr + dir-paginate-end + ng-show="item.Expanded" + ng-repeat="vol in item.Volumes" + ng-class="{ 'datatable-highlighted': item.Highlighted, 'datatable-unhighlighted': !item.Highlighted }" + > <td></td> <td> <a ui-sref="kubernetes.volumes.volume({ name: vol.PersistentVolumeClaim.Name, namespace: vol.PersistentVolumeClaim.Namespace })"> diff --git a/app/portainer/components/box-selector/box-selector.css b/app/portainer/components/box-selector/box-selector.css index b359cb4df..97091a4fa 100644 --- a/app/portainer/components/box-selector/box-selector.css +++ b/app/portainer/components/box-selector/box-selector.css @@ -27,22 +27,27 @@ .boxselector_wrapper input[type='radio']:not(:disabled) ~ label { cursor: pointer; + background-color: var(--bg-boxselector-wrapper-disabled-color); +} + +.boxselector_wrapper input[type='radio']:not(:disabled):hover ~ label:hover { + cursor: pointer; } .boxselector_wrapper label { font-weight: normal; font-size: 12px; display: block; - background: white; - border: 1px solid #333333; + background: var(--bg-boxselector-color); + border: 1px solid var(--border-boxselector-color); border-radius: 2px; padding: 10px 10px 0 10px; text-align: center; - box-shadow: 0 3px 10px -2px rgba(161, 170, 166, 0.5); + box-shadow: var(--shadow-boxselector-color); position: relative; } .boxselector_wrapper label.boxselector_disabled { - background: #cacaca; + background: var(--bg-boxselector-disabled-color) !important; border-color: #787878; color: #787878; cursor: not-allowed; diff --git a/app/portainer/components/custom-templates-list/customTemplatesList.html b/app/portainer/components/custom-templates-list/customTemplatesList.html index 5d7c9228b..73b96911f 100644 --- a/app/portainer/components/custom-templates-list/customTemplatesList.html +++ b/app/portainer/components/custom-templates-list/customTemplatesList.html @@ -8,7 +8,7 @@ <button type="button" class="btn btn-sm btn-primary" ui-state="$ctrl.createPath"> <i class="fa fa-plus space-right" aria-hidden="true"></i>Add Custom Template </button> </div> - <div class="searchBar" style="border-top: 2px solid #f6f6f6;"> + <div class="searchBar"> <i class="fa fa-search searchIcon" aria-hidden="true"></i> <input type="text" diff --git a/app/portainer/components/datatables/datatable.css b/app/portainer/components/datatables/datatable.css index 4f7c1b9f1..c7f17b4c5 100644 --- a/app/portainer/components/datatables/datatable.css +++ b/app/portainer/components/datatables/datatable.css @@ -1,6 +1,6 @@ .datatable .toolBar { - background-color: #f6f6f6; - color: #767676; + background-color: var(--bg-card-color); + color: var(--text-main-color); overflow: auto; padding: 10px; } @@ -30,9 +30,10 @@ } .datatable .searchBar { - border-top: 1px solid #d2d1d1; - border-bottom: 1px solid #d2d1d1; + border-top: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); padding: 8px; + background: var(--bg-main-color); } .datatable .searchInput { @@ -60,9 +61,10 @@ } .datatable .footer { - background-color: #f6f6f6; - color: #767676; + background-color: var(--bg-card-color); + color: var(--text-main-color); overflow: auto; + border-top: 1px solid var(--border-datatable-top-color); } .datatable .footer .infoBar { @@ -152,9 +154,8 @@ } .md-checkbox label:before { - background: #fff; - border: 2px solid black; - border: 2px solid rgba(0, 0, 0, 0.54); + background: var(--bg-main-color); + border: 2px solid var(--bg-checkbox-border-color); border-radius: 2px; cursor: pointer; height: 16px; diff --git a/app/portainer/components/sidebar/sidebar.css b/app/portainer/components/sidebar/sidebar.css index e5d27df9b..ad6016ddb 100644 --- a/app/portainer/components/sidebar/sidebar.css +++ b/app/portainer/components/sidebar/sidebar.css @@ -13,7 +13,7 @@ * Sidebar */ #sidebar-wrapper { - background: #30426a; + background: var(--bg-sidebar-wrapper-color); } ul.sidebar .sidebar-main a, @@ -21,7 +21,7 @@ ul.sidebar .sidebar-main a, ul.sidebar .sidebar-list a:hover, #page-wrapper:not(.open) ul.sidebar .sidebar-title.separator { /* Sidebar header and footer color */ - background: #2d3e63; + background: var(--hover-sidebar-color); } ul.sidebar { @@ -63,7 +63,7 @@ ul.sidebar .sidebar-main .menu-icon { } ul.sidebar .sidebar-title { - color: #738bc0; + color: var(--text-sidebar-title-color); font-size: 12px; height: 35px; line-height: 40px; @@ -74,7 +74,7 @@ ul.sidebar .sidebar-title { ul.sidebar .sidebar-list a { text-indent: 25px; font-size: 15px; - color: #b2bfdc; + color: var(--text-sidebar-list-color); line-height: 40px; padding-left: 5px; border-left: 3px solid transparent; @@ -132,7 +132,7 @@ ul.sidebar .sidebar-list .menu-icon { } .sidebar-footer div a { - color: #b2bfdc; + color: var(--text-sidebar-list-color); font-size: 12px; line-height: 43px; } @@ -172,8 +172,8 @@ ul.sidebar .sidebar-list a { ul.sidebar .sidebar-list a.active { color: #fff; - border-left-color: #fff; - background: #2d3e63; + border-left-color: var(--border-sidebar-color); + background: var(--hover-sidebar-color); } .sidebar-header { @@ -181,7 +181,7 @@ ul.sidebar .sidebar-list a.active { list-style: none; text-indent: 20px; font-size: 18px; - background: #2d3e63; + background: var(--bg-sidebar-header-color); } .sidebar-header a { @@ -255,7 +255,7 @@ ul.sidebar .sidebar-list a.active .menu-icon { ul.sidebar .sidebar-list .sidebar-sublist a { text-indent: 35px; font-size: 12px; - color: #b2bfdc; + color: var(--text-sidebar-list-color); line-height: 36px; } @@ -280,7 +280,7 @@ ul.sidebar .sidebar-list .menu-icon { ul.sidebar .sidebar-list .sidebar-sublist a.active { color: #fff; border-left: 3px solid #fff; - background: #2d3e63; + background: var(--bg-sidebar-color); } @media (max-height: 785px) { diff --git a/app/portainer/components/template-list/templateList.html b/app/portainer/components/template-list/templateList.html index d323b2700..d516651d5 100644 --- a/app/portainer/components/template-list/templateList.html +++ b/app/portainer/components/template-list/templateList.html @@ -32,7 +32,7 @@ </div> </div> - <div class="searchBar" style="border-top: 2px solid #f6f6f6;"> + <div class="searchBar"> <i class="fa fa-search searchIcon" aria-hidden="true"></i> <input type="text" diff --git a/app/portainer/components/theme/theme-settings.controller.js b/app/portainer/components/theme/theme-settings.controller.js new file mode 100644 index 000000000..b65e4553a --- /dev/null +++ b/app/portainer/components/theme/theme-settings.controller.js @@ -0,0 +1,70 @@ +import { buildOption } from '@/portainer/components/box-selector'; + +export default class ThemeSettingsController { + /* @ngInject */ + constructor($async, Authentication, ThemeManager, StateManager, UserService, Notifications) { + this.$async = $async; + this.Authentication = Authentication; + this.ThemeManager = ThemeManager; + this.StateManager = StateManager; + this.UserService = UserService; + this.Notifications = Notifications; + + this.setTheme = this.setTheme.bind(this); + } + + /** Theme Settings Panel */ + async updateTheme() { + try { + await this.UserService.updateUserTheme(this.state.userId, this.state.userTheme); + this.state.themeInProgress = false; + this.Notifications.success('Success', 'User theme successfully updated'); + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to update user theme'); + } + } + + setTheme(theme) { + this.ThemeManager.setTheme(theme); + this.state.themeInProgress = true; + } + + $onInit() { + return this.$async(async () => { + this.state = { + userId: null, + userTheme: '', + initTheme: '', + defaultTheme: 'light', + themeInProgress: false, + }; + + this.state.availableThemes = [ + buildOption('light', 'fas fa-sun', 'Light Theme', 'Default color mode', 'light'), + buildOption('dark', 'fas fa-moon', 'Dark Theme', 'Dark color mode', 'dark'), + buildOption('highcontrast', 'fas fa-adjust', 'High Contrast', 'High contrast color mode', 'highcontrast'), + ]; + + this.state.availableTheme = { + light: 'light', + dark: 'dark', + highContrast: 'highcontrast', + }; + + try { + this.state.userId = await this.Authentication.getUserDetails().ID; + const data = await this.UserService.user(this.state.userId); + this.state.userTheme = data.UserTheme || this.state.defaultTheme; + this.state.initTheme = this.state.userTheme; + } catch (err) { + this.Notifications.error('Failure', err, 'Unable to get user details'); + } + }); + } + + $onDestroy() { + if (this.state.themeInProgress) { + this.ThemeManager.setTheme(this.state.initTheme); + } + } +} diff --git a/app/portainer/components/theme/theme-settings.html b/app/portainer/components/theme/theme-settings.html new file mode 100644 index 000000000..1d5e8b348 --- /dev/null +++ b/app/portainer/components/theme/theme-settings.html @@ -0,0 +1,19 @@ +<information-panel class="theme-information" title-text="Information"> + <span class="small text-muted"> + <p> + <i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> + Dark and High-contrast theme are experimental. Some UI components might not display properly. + </p> + </span> +</information-panel> +<rd-widget> + <rd-widget-header icon="fa-palette" title-text="Change user theme"></rd-widget-header> + <rd-widget-body> + <form class="theme-panel"> + <!-- Theme --> + <box-selector radio-name="theme" ng-model="$ctrl.state.userTheme" options="$ctrl.state.availableThemes" on-change="($ctrl.setTheme)"></box-selector> + <button ng-click="$ctrl.updateTheme()" class="btn btn-primary btn-sm">Update theme</button> + <!-- !Theme --> + </form> + </rd-widget-body> +</rd-widget> diff --git a/app/portainer/components/theme/theme-settings.js b/app/portainer/components/theme/theme-settings.js new file mode 100644 index 000000000..d8d57beb5 --- /dev/null +++ b/app/portainer/components/theme/theme-settings.js @@ -0,0 +1,7 @@ +import angular from 'angular'; +import controller from './theme-settings.controller'; + +angular.module('portainer.app').component('themeSettings', { + templateUrl: './theme-settings.html', + controller, +}); diff --git a/app/portainer/models/user.js b/app/portainer/models/user.js index e86be99f0..4090e9100 100644 --- a/app/portainer/models/user.js +++ b/app/portainer/models/user.js @@ -2,6 +2,7 @@ export function UserViewModel(data) { this.Id = data.Id; this.Username = data.Username; this.Role = data.Role; + this.UserTheme = data.UserTheme; if (data.Role === 1) { this.RoleName = 'administrator'; } else { diff --git a/app/portainer/rest/user.js b/app/portainer/rest/user.js index f87314103..2f2bb24c6 100644 --- a/app/portainer/rest/user.js +++ b/app/portainer/rest/user.js @@ -12,6 +12,7 @@ angular.module('portainer.app').factory('Users', [ get: { method: 'GET', params: { id: '@id' } }, update: { method: 'PUT', params: { id: '@id' }, ignoreLoadingBar: true }, updatePassword: { method: 'PUT', params: { id: '@id', entity: 'passwd' } }, + updateTheme: { method: 'PUT', params: { id: '@id' } }, remove: { method: 'DELETE', params: { id: '@id' } }, queryMemberships: { method: 'GET', isArray: true, params: { id: '@id', entity: 'memberships' } }, checkAdminUser: { method: 'GET', params: { id: 'admin', entity: 'check' }, isArray: true, ignoreLoadingBar: true }, diff --git a/app/portainer/services/api/userService.js b/app/portainer/services/api/userService.js index ef23fffc3..bb6e8622c 100644 --- a/app/portainer/services/api/userService.js +++ b/app/portainer/services/api/userService.js @@ -91,6 +91,10 @@ angular.module('portainer.app').factory('UserService', [ return Users.updatePassword({ id: id }, payload).$promise; }; + service.updateUserTheme = function (id, userTheme) { + return Users.updateTheme({ id }, { userTheme }).$promise; + }; + service.userMemberships = function (id) { var deferred = $q.defer(); diff --git a/app/portainer/services/authentication.js b/app/portainer/services/authentication.js index c4bdcca9d..3a5073301 100644 --- a/app/portainer/services/authentication.js +++ b/app/portainer/services/authentication.js @@ -9,7 +9,9 @@ angular.module('portainer.app').factory('Authentication', [ 'LocalStorage', 'StateManager', 'EndpointProvider', - function AuthenticationFactory($async, $state, Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider) { + 'UserService', + 'ThemeManager', + function AuthenticationFactory($async, $state, Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider, UserService, ThemeManager) { 'use strict'; var service = {}; @@ -82,12 +84,20 @@ angular.module('portainer.app').factory('Authentication', [ return user; } + async function setUserTheme() { + const data = await UserService.user(user.ID); + // Initialize user theme base on Usertheme from database + const userTheme = data.UserTheme; + ThemeManager.setTheme(userTheme); + } + async function setUser(jwt) { LocalStorage.storeJWT(jwt); var tokenPayload = jwtHelper.decodeToken(jwt); user.username = tokenPayload.username; user.ID = tokenPayload.id; user.role = tokenPayload.role; + await setUserTheme(); } function isAdmin() { diff --git a/app/portainer/services/stateManager.js b/app/portainer/services/stateManager.js index 9e9dbd383..047a29813 100644 --- a/app/portainer/services/stateManager.js +++ b/app/portainer/services/stateManager.js @@ -54,6 +54,11 @@ angular.module('portainer.app').factory('StateManager', [ LocalStorage.storeApplicationState(state.application); }; + manager.updateTheme = function (theme) { + state.application.theme = theme; + LocalStorage.storeApplicationState(state.application); + }; + manager.updateSnapshotInterval = function (interval) { state.application.snapshotInterval = interval; LocalStorage.storeApplicationState(state.application); diff --git a/app/portainer/services/themeManager.js b/app/portainer/services/themeManager.js new file mode 100644 index 000000000..9cf18137b --- /dev/null +++ b/app/portainer/services/themeManager.js @@ -0,0 +1,23 @@ +angular.module('portainer.app').service('ThemeManager', ThemeManager); + +/* @ngInject */ + +export function ThemeManager(StateManager) { + return { + setTheme, + defaultTheme, + }; + + function setTheme(theme) { + if (!theme) { + document.documentElement.removeAttribute('theme'); + } else { + document.documentElement.setAttribute('theme', theme); + } + StateManager.updateTheme(theme); + } + + function defaultTheme() { + document.documentElement.removeAttribute('theme'); + } +} diff --git a/app/portainer/views/account/account.html b/app/portainer/views/account/account.html index db2a4d078..f387f9f21 100644 --- a/app/portainer/views/account/account.html +++ b/app/portainer/views/account/account.html @@ -78,5 +78,6 @@ </form> </rd-widget-body> </rd-widget> + <theme-settings></theme-settings> </div> </div> diff --git a/app/portainer/views/account/accountController.js b/app/portainer/views/account/accountController.js index 7be63dc95..2bde28460 100644 --- a/app/portainer/views/account/accountController.js +++ b/app/portainer/views/account/accountController.js @@ -5,11 +5,14 @@ angular.module('portainer.app').controller('AccountController', [ 'UserService', 'Notifications', 'SettingsService', - function ($scope, $state, Authentication, UserService, Notifications, SettingsService) { + 'StateManager', + 'ThemeManager', + function ($scope, $state, Authentication, UserService, Notifications, SettingsService, StateManager, ThemeManager) { $scope.formValues = { currentPassword: '', newPassword: '', confirmPassword: '', + userTheme: '', }; $scope.updatePassword = function () { @@ -23,8 +26,30 @@ angular.module('portainer.app').controller('AccountController', [ }); }; - function initView() { + // Update DOM for theme attribute & LocalStorage + $scope.setTheme = function (theme) { + ThemeManager.setTheme(theme); + StateManager.updateTheme(theme); + }; + + // Rest API Call to update theme with userID in DB + $scope.updateTheme = function () { + UserService.updateUserTheme($scope.userID, $scope.formValues.userTheme) + .then(function success() { + Notifications.success('Success', 'User theme successfully updated'); + $state.reload(); + }) + .catch(function error(err) { + Notifications.error('Failure', err, err.msg); + }); + }; + + async function initView() { $scope.userID = Authentication.getUserDetails().ID; + + const data = await UserService.user($scope.userID); + + $scope.formValues.userTheme = data.Usertheme; SettingsService.publicSettings() .then(function success(data) { $scope.AuthenticationMethod = data.AuthenticationMethod; diff --git a/app/portainer/views/logout/logoutController.js b/app/portainer/views/logout/logoutController.js index 5db7c8ba5..43c9d8878 100644 --- a/app/portainer/views/logout/logoutController.js +++ b/app/portainer/views/logout/logoutController.js @@ -2,7 +2,7 @@ import angular from 'angular'; class LogoutController { /* @ngInject */ - constructor($async, $state, $transition$, $window, Authentication, StateManager, Notifications, LocalStorage, SettingsService) { + constructor($async, $state, $transition$, $window, Authentication, StateManager, Notifications, LocalStorage, SettingsService, ThemeManager) { this.$async = $async; this.$state = $state; this.$transition$ = $transition$; @@ -13,6 +13,7 @@ class LogoutController { this.Notifications = Notifications; this.LocalStorage = LocalStorage; this.SettingsService = SettingsService; + this.ThemeManager = ThemeManager; this.logo = this.StateManager.getState().application.logo; this.logoutAsync = this.logoutAsync.bind(this); @@ -28,6 +29,8 @@ class LogoutController { const performApiLogout = this.$transition$.params().performApiLogout; const settings = await this.SettingsService.publicSettings(); + this.ThemeManager.defaultTheme(); + try { await this.Authentication.logout(performApiLogout); } finally { diff --git a/app/portainer/views/main/mainController.js b/app/portainer/views/main/mainController.js index 00fb4436c..a942b62f3 100644 --- a/app/portainer/views/main/mainController.js +++ b/app/portainer/views/main/mainController.js @@ -3,6 +3,7 @@ angular.module('portainer.app').controller('MainController', [ 'LocalStorage', 'StateManager', 'EndpointProvider', + 'ThemeManager', function ($scope, LocalStorage, StateManager, EndpointProvider) { /** * Sidebar Toggle & Cookie Control