diff --git a/.gitattributes b/.gitattributes index 8ff0e2bc276a0abe8478e5f7da3bfa0ff9b65d7b..5716dd550ae9654193990b90520d1da208c124c6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1858,59 +1858,96 @@ LCU/Maintenance/DBInterface/test/t_rtsm_models.run -text LCU/Maintenance/DBInterface/test/t_rtsm_models.sh -text LCU/Maintenance/DBInterface/test/test_rtsm_models.py -text LCU/Maintenance/MDB_WebView/CMakeLists.txt -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/.env -text LCU/Maintenance/MDB_WebView/maintenancedb_view/CMakeLists.txt -text LCU/Maintenance/MDB_WebView/maintenancedb_view/package.json -text LCU/Maintenance/MDB_WebView/maintenancedb_view/public/favicon.ico -text svneol=unset#image/x-icon LCU/Maintenance/MDB_WebView/maintenancedb_view/public/index.html -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/public/lofar_logo.svg -text svneol=unset#image/svg%2Bxml +LCU/Maintenance/MDB_WebView/maintenancedb_view/public/logo.svg -text svneol=unset#image/svg LCU/Maintenance/MDB_WebView/maintenancedb_view/public/manifest.json -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.css -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.scss -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.test.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/api_configuration.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaErrorDetails.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaView.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.scss -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.scss -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationDetails.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationList.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationList.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.scss -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationStatistics.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.scss -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestDetails.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.scss -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.scss -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.scss -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.css -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.scss -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/lofar_logo.svg -text svneol=unset#image/svg%2Bxml -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AutoLoadWrapper/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AutoLoadWrapper/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/EnlargeableImage/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillViewportHeightDiv/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/NavigationBar/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/NavigationBar/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/PopoverWithTitle/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaIdSelector/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaIdSelector/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaTypeSelector/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/DateRangeSelector/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/DateRangeSelector/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/ErrorTypesSelector/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/ErrorsOnlySelector/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/PeriodSelector/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationAutoComplete/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationAutoComplete/styles.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationGroupSelector/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/TestTypeSelector/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/styles.module.scss -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/logo.svg -text svneol=unset#image/svg -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/DetailsPage.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/RTSMPage.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationTestPage.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/Badge/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/Badge/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/RTSMBadge/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/SORow/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/StationTestBadge/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/StationTestBadge/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/StationTestRow/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestChildView/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestChildView/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaErrorDetails/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaErrorDetails/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/styles.module.css -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/index.js -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/styles.module.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/index.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/redux/actions/antennaOverviewPageActions.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/redux/actions/appInitDataActions.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/redux/actions/landingPageActions.js -text @@ -1935,8 +1972,8 @@ LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.css -t LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.scss -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.scss -text +LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/rtsm_collapsable.module.scss -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/utils/LOFARDefinitions.js -text -LCU/Maintenance/MDB_WebView/maintenancedb_view/src/utils/autoLoader.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/utils/constants.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/utils/grid.js -text LCU/Maintenance/MDB_WebView/maintenancedb_view/src/utils/highlightClass.js -text diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/.env b/LCU/Maintenance/MDB_WebView/maintenancedb_view/.env new file mode 100644 index 0000000000000000000000000000000000000000..5bea5d27f20dc6a85cb4de1c6ba9cd16073a8010 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/.env @@ -0,0 +1 @@ +NODE_PATH='src/' diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/lofar_logo.svg b/LCU/Maintenance/MDB_WebView/maintenancedb_view/public/lofar_logo.svg similarity index 100% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/lofar_logo.svg rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/public/lofar_logo.svg diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/logo.svg b/LCU/Maintenance/MDB_WebView/maintenancedb_view/public/logo.svg similarity index 100% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/logo.svg rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/public/logo.svg diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.css index 7681cfa869e66af645fdcb89cfbfbed2a40c4cde..1a30c4ba31d0deeed2e46ca805a13da4cd9aa649 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.css @@ -1,4 +1,6468 @@ +/* COLORS */ +/* Color palette interface (created with https://material.io/tools/color/) */ +/* font color */ +/* font color */ +/* hover color, e.g. for background */ +/* Data colors */ +/*! + * Bootstrap v4.1.3 (https://getbootstrap.com/) + * Copyright 2011-2018 The Bootstrap Authors + * Copyright 2011-2018 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +:root { + --blue: #007bff; + --indigo: #6610f2; + --purple: #6f42c1; + --pink: #e83e8c; + --red: #dc3545; + --orange: #fd7e14; + --yellow: #ffc107; + --green: #28a745; + --teal: #20c997; + --cyan: #17a2b8; + --white: #fff; + --gray: #6c757d; + --gray-dark: #343a40; + --primary: #1c3e5c; + --secondary: #b6b6ba; + --success: #28a745; + --info: #17a2b8; + --warning: #fbfb83; + --danger: #dc3545; + --light: #f8f9fa; + --dark: #343a40; + --breakpoint-xs: 0; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 992px; + --breakpoint-xl: 1200px; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } +*, +*::before, +*::after { + box-sizing: border-box; } + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } + +@-ms-viewport { + width: device-width; } + +article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; } + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; } + +[tabindex="-1"]:focus { + outline: 0 !important; } + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; } + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; } + +p { + margin-top: 0; + margin-bottom: 1rem; } + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; } + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; } + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; } + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; } + +dt { + font-weight: 700; } + +dd { + margin-bottom: .5rem; + margin-left: 0; } + +blockquote { + margin: 0 0 1rem; } + +dfn { + font-style: italic; } + +b, +strong { + font-weight: bolder; } + +small { + font-size: 80%; } + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; } + +sub { + bottom: -.25em; } + +sup { + top: -.5em; } + +a { + color: #1c3e5c; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; } + a:hover { + color: #0a1621; + text-decoration: underline; } + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; } + a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; } + a:not([href]):not([tabindex]):focus { + outline: 0; } + +pre, +code, +kbd, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 1em; } + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; } + +figure { + margin: 0 0 1rem; } + +img { + vertical-align: middle; + border-style: none; } + +svg { + overflow: hidden; + vertical-align: middle; } + +table { + border-collapse: collapse; } + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #6c757d; + text-align: left; + caption-side: bottom; } + +th { + text-align: inherit; } + +label { + display: inline-block; + margin-bottom: 0.5rem; } + +button { + border-radius: 0; } + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; } + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; } + +button, +input { + overflow: visible; } + +button, +select { + text-transform: none; } + +button, +html [type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; } + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; } + +input[type="radio"], +input[type="checkbox"] { + box-sizing: border-box; + padding: 0; } + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; } + +textarea { + overflow: auto; + resize: vertical; } + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; } + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; } + +progress { + vertical-align: baseline; } + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; } + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; } + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; } + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; } + +output { + display: inline-block; } + +summary { + display: list-item; + cursor: pointer; } + +template { + display: none; } + +[hidden] { + display: none !important; } + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 500; + line-height: 1.2; + color: inherit; } + +h1, .h1 { + font-size: 2.5rem; } + +h2, .h2 { + font-size: 2rem; } + +h3, .h3 { + font-size: 1.75rem; } + +h4, .h4 { + font-size: 1.5rem; } + +h5, .h5 { + font-size: 1.25rem; } + +h6, .h6 { + font-size: 1rem; } + +.lead { + font-size: 1.25rem; + font-weight: 300; } + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.2; } + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.2; } + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.2; } + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.2; } + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); } + +small, +.small { + font-size: 80%; + font-weight: 400; } + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; } + +.list-unstyled { + padding-left: 0; + list-style: none; } + +.list-inline { + padding-left: 0; + list-style: none; } + +.list-inline-item { + display: inline-block; } + .list-inline-item:not(:last-child) { + margin-right: 0.5rem; } + +.initialism { + font-size: 90%; + text-transform: uppercase; } + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; } + +.blockquote-footer { + display: block; + font-size: 80%; + color: #6c757d; } + .blockquote-footer::before { + content: "\2014 \00A0"; } + +.img-fluid { + max-width: 100%; + height: auto; } + +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + max-width: 100%; + height: auto; } + +.figure { + display: inline-block; } + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; } + +.figure-caption { + font-size: 90%; + color: #6c757d; } + +code { + font-size: 87.5%; + color: #e83e8c; + word-break: break-word; } + a > code { + color: inherit; } + +kbd { + padding: 0.2rem 0.4rem; + font-size: 87.5%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; } + kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; } + +pre { + display: block; + font-size: 87.5%; + color: #212529; } + pre code { + font-size: inherit; + color: inherit; + word-break: normal; } + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; } + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; } + @media (min-width: 576px) { + .container { + max-width: 540px; } } + @media (min-width: 768px) { + .container { + max-width: 720px; } } + @media (min-width: 992px) { + .container { + max-width: 960px; } } + @media (min-width: 1200px) { + .container { + max-width: 1140px; } } + +.container-fluid { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; } + +.row { + display: flex; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; } + +.no-gutters { + margin-right: 0; + margin-left: 0; } + .no-gutters > .col, + .no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; } + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; } + +.col { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + +.col-auto { + flex: 0 0 auto; + width: auto; + max-width: none; } + +.col-1 { + flex: 0 0 8.33333%; + max-width: 8.33333%; } + +.col-2 { + flex: 0 0 16.66667%; + max-width: 16.66667%; } + +.col-3 { + flex: 0 0 25%; + max-width: 25%; } + +.col-4 { + flex: 0 0 33.33333%; + max-width: 33.33333%; } + +.col-5 { + flex: 0 0 41.66667%; + max-width: 41.66667%; } + +.col-6 { + flex: 0 0 50%; + max-width: 50%; } + +.col-7 { + flex: 0 0 58.33333%; + max-width: 58.33333%; } + +.col-8 { + flex: 0 0 66.66667%; + max-width: 66.66667%; } + +.col-9 { + flex: 0 0 75%; + max-width: 75%; } + +.col-10 { + flex: 0 0 83.33333%; + max-width: 83.33333%; } + +.col-11 { + flex: 0 0 91.66667%; + max-width: 91.66667%; } + +.col-12 { + flex: 0 0 100%; + max-width: 100%; } + +.order-first { + order: -1; } + +.order-last { + order: 13; } + +.order-0 { + order: 0; } + +.order-1 { + order: 1; } + +.order-2 { + order: 2; } + +.order-3 { + order: 3; } + +.order-4 { + order: 4; } + +.order-5 { + order: 5; } + +.order-6 { + order: 6; } + +.order-7 { + order: 7; } + +.order-8 { + order: 8; } + +.order-9 { + order: 9; } + +.order-10 { + order: 10; } + +.order-11 { + order: 11; } + +.order-12 { + order: 12; } + +.offset-1 { + margin-left: 8.33333%; } + +.offset-2 { + margin-left: 16.66667%; } + +.offset-3 { + margin-left: 25%; } + +.offset-4 { + margin-left: 33.33333%; } + +.offset-5 { + margin-left: 41.66667%; } + +.offset-6 { + margin-left: 50%; } + +.offset-7 { + margin-left: 58.33333%; } + +.offset-8 { + margin-left: 66.66667%; } + +.offset-9 { + margin-left: 75%; } + +.offset-10 { + margin-left: 83.33333%; } + +.offset-11 { + margin-left: 91.66667%; } + +@media (min-width: 576px) { + .col-sm { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + .col-sm-auto { + flex: 0 0 auto; + width: auto; + max-width: none; } + .col-sm-1 { + flex: 0 0 8.33333%; + max-width: 8.33333%; } + .col-sm-2 { + flex: 0 0 16.66667%; + max-width: 16.66667%; } + .col-sm-3 { + flex: 0 0 25%; + max-width: 25%; } + .col-sm-4 { + flex: 0 0 33.33333%; + max-width: 33.33333%; } + .col-sm-5 { + flex: 0 0 41.66667%; + max-width: 41.66667%; } + .col-sm-6 { + flex: 0 0 50%; + max-width: 50%; } + .col-sm-7 { + flex: 0 0 58.33333%; + max-width: 58.33333%; } + .col-sm-8 { + flex: 0 0 66.66667%; + max-width: 66.66667%; } + .col-sm-9 { + flex: 0 0 75%; + max-width: 75%; } + .col-sm-10 { + flex: 0 0 83.33333%; + max-width: 83.33333%; } + .col-sm-11 { + flex: 0 0 91.66667%; + max-width: 91.66667%; } + .col-sm-12 { + flex: 0 0 100%; + max-width: 100%; } + .order-sm-first { + order: -1; } + .order-sm-last { + order: 13; } + .order-sm-0 { + order: 0; } + .order-sm-1 { + order: 1; } + .order-sm-2 { + order: 2; } + .order-sm-3 { + order: 3; } + .order-sm-4 { + order: 4; } + .order-sm-5 { + order: 5; } + .order-sm-6 { + order: 6; } + .order-sm-7 { + order: 7; } + .order-sm-8 { + order: 8; } + .order-sm-9 { + order: 9; } + .order-sm-10 { + order: 10; } + .order-sm-11 { + order: 11; } + .order-sm-12 { + order: 12; } + .offset-sm-0 { + margin-left: 0; } + .offset-sm-1 { + margin-left: 8.33333%; } + .offset-sm-2 { + margin-left: 16.66667%; } + .offset-sm-3 { + margin-left: 25%; } + .offset-sm-4 { + margin-left: 33.33333%; } + .offset-sm-5 { + margin-left: 41.66667%; } + .offset-sm-6 { + margin-left: 50%; } + .offset-sm-7 { + margin-left: 58.33333%; } + .offset-sm-8 { + margin-left: 66.66667%; } + .offset-sm-9 { + margin-left: 75%; } + .offset-sm-10 { + margin-left: 83.33333%; } + .offset-sm-11 { + margin-left: 91.66667%; } } + +@media (min-width: 768px) { + .col-md { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + .col-md-auto { + flex: 0 0 auto; + width: auto; + max-width: none; } + .col-md-1 { + flex: 0 0 8.33333%; + max-width: 8.33333%; } + .col-md-2 { + flex: 0 0 16.66667%; + max-width: 16.66667%; } + .col-md-3 { + flex: 0 0 25%; + max-width: 25%; } + .col-md-4 { + flex: 0 0 33.33333%; + max-width: 33.33333%; } + .col-md-5 { + flex: 0 0 41.66667%; + max-width: 41.66667%; } + .col-md-6 { + flex: 0 0 50%; + max-width: 50%; } + .col-md-7 { + flex: 0 0 58.33333%; + max-width: 58.33333%; } + .col-md-8 { + flex: 0 0 66.66667%; + max-width: 66.66667%; } + .col-md-9 { + flex: 0 0 75%; + max-width: 75%; } + .col-md-10 { + flex: 0 0 83.33333%; + max-width: 83.33333%; } + .col-md-11 { + flex: 0 0 91.66667%; + max-width: 91.66667%; } + .col-md-12 { + flex: 0 0 100%; + max-width: 100%; } + .order-md-first { + order: -1; } + .order-md-last { + order: 13; } + .order-md-0 { + order: 0; } + .order-md-1 { + order: 1; } + .order-md-2 { + order: 2; } + .order-md-3 { + order: 3; } + .order-md-4 { + order: 4; } + .order-md-5 { + order: 5; } + .order-md-6 { + order: 6; } + .order-md-7 { + order: 7; } + .order-md-8 { + order: 8; } + .order-md-9 { + order: 9; } + .order-md-10 { + order: 10; } + .order-md-11 { + order: 11; } + .order-md-12 { + order: 12; } + .offset-md-0 { + margin-left: 0; } + .offset-md-1 { + margin-left: 8.33333%; } + .offset-md-2 { + margin-left: 16.66667%; } + .offset-md-3 { + margin-left: 25%; } + .offset-md-4 { + margin-left: 33.33333%; } + .offset-md-5 { + margin-left: 41.66667%; } + .offset-md-6 { + margin-left: 50%; } + .offset-md-7 { + margin-left: 58.33333%; } + .offset-md-8 { + margin-left: 66.66667%; } + .offset-md-9 { + margin-left: 75%; } + .offset-md-10 { + margin-left: 83.33333%; } + .offset-md-11 { + margin-left: 91.66667%; } } + +@media (min-width: 992px) { + .col-lg { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + .col-lg-auto { + flex: 0 0 auto; + width: auto; + max-width: none; } + .col-lg-1 { + flex: 0 0 8.33333%; + max-width: 8.33333%; } + .col-lg-2 { + flex: 0 0 16.66667%; + max-width: 16.66667%; } + .col-lg-3 { + flex: 0 0 25%; + max-width: 25%; } + .col-lg-4 { + flex: 0 0 33.33333%; + max-width: 33.33333%; } + .col-lg-5 { + flex: 0 0 41.66667%; + max-width: 41.66667%; } + .col-lg-6 { + flex: 0 0 50%; + max-width: 50%; } + .col-lg-7 { + flex: 0 0 58.33333%; + max-width: 58.33333%; } + .col-lg-8 { + flex: 0 0 66.66667%; + max-width: 66.66667%; } + .col-lg-9 { + flex: 0 0 75%; + max-width: 75%; } + .col-lg-10 { + flex: 0 0 83.33333%; + max-width: 83.33333%; } + .col-lg-11 { + flex: 0 0 91.66667%; + max-width: 91.66667%; } + .col-lg-12 { + flex: 0 0 100%; + max-width: 100%; } + .order-lg-first { + order: -1; } + .order-lg-last { + order: 13; } + .order-lg-0 { + order: 0; } + .order-lg-1 { + order: 1; } + .order-lg-2 { + order: 2; } + .order-lg-3 { + order: 3; } + .order-lg-4 { + order: 4; } + .order-lg-5 { + order: 5; } + .order-lg-6 { + order: 6; } + .order-lg-7 { + order: 7; } + .order-lg-8 { + order: 8; } + .order-lg-9 { + order: 9; } + .order-lg-10 { + order: 10; } + .order-lg-11 { + order: 11; } + .order-lg-12 { + order: 12; } + .offset-lg-0 { + margin-left: 0; } + .offset-lg-1 { + margin-left: 8.33333%; } + .offset-lg-2 { + margin-left: 16.66667%; } + .offset-lg-3 { + margin-left: 25%; } + .offset-lg-4 { + margin-left: 33.33333%; } + .offset-lg-5 { + margin-left: 41.66667%; } + .offset-lg-6 { + margin-left: 50%; } + .offset-lg-7 { + margin-left: 58.33333%; } + .offset-lg-8 { + margin-left: 66.66667%; } + .offset-lg-9 { + margin-left: 75%; } + .offset-lg-10 { + margin-left: 83.33333%; } + .offset-lg-11 { + margin-left: 91.66667%; } } + +@media (min-width: 1200px) { + .col-xl { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + .col-xl-auto { + flex: 0 0 auto; + width: auto; + max-width: none; } + .col-xl-1 { + flex: 0 0 8.33333%; + max-width: 8.33333%; } + .col-xl-2 { + flex: 0 0 16.66667%; + max-width: 16.66667%; } + .col-xl-3 { + flex: 0 0 25%; + max-width: 25%; } + .col-xl-4 { + flex: 0 0 33.33333%; + max-width: 33.33333%; } + .col-xl-5 { + flex: 0 0 41.66667%; + max-width: 41.66667%; } + .col-xl-6 { + flex: 0 0 50%; + max-width: 50%; } + .col-xl-7 { + flex: 0 0 58.33333%; + max-width: 58.33333%; } + .col-xl-8 { + flex: 0 0 66.66667%; + max-width: 66.66667%; } + .col-xl-9 { + flex: 0 0 75%; + max-width: 75%; } + .col-xl-10 { + flex: 0 0 83.33333%; + max-width: 83.33333%; } + .col-xl-11 { + flex: 0 0 91.66667%; + max-width: 91.66667%; } + .col-xl-12 { + flex: 0 0 100%; + max-width: 100%; } + .order-xl-first { + order: -1; } + .order-xl-last { + order: 13; } + .order-xl-0 { + order: 0; } + .order-xl-1 { + order: 1; } + .order-xl-2 { + order: 2; } + .order-xl-3 { + order: 3; } + .order-xl-4 { + order: 4; } + .order-xl-5 { + order: 5; } + .order-xl-6 { + order: 6; } + .order-xl-7 { + order: 7; } + .order-xl-8 { + order: 8; } + .order-xl-9 { + order: 9; } + .order-xl-10 { + order: 10; } + .order-xl-11 { + order: 11; } + .order-xl-12 { + order: 12; } + .offset-xl-0 { + margin-left: 0; } + .offset-xl-1 { + margin-left: 8.33333%; } + .offset-xl-2 { + margin-left: 16.66667%; } + .offset-xl-3 { + margin-left: 25%; } + .offset-xl-4 { + margin-left: 33.33333%; } + .offset-xl-5 { + margin-left: 41.66667%; } + .offset-xl-6 { + margin-left: 50%; } + .offset-xl-7 { + margin-left: 58.33333%; } + .offset-xl-8 { + margin-left: 66.66667%; } + .offset-xl-9 { + margin-left: 75%; } + .offset-xl-10 { + margin-left: 83.33333%; } + .offset-xl-11 { + margin-left: 91.66667%; } } + +.table { + width: 100%; + margin-bottom: 1rem; + background-color: transparent; } + .table th, + .table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #dee2e6; } + .table thead th { + vertical-align: bottom; + border-bottom: 2px solid #dee2e6; } + .table tbody + tbody { + border-top: 2px solid #dee2e6; } + .table .table { + background-color: #fff; } + +.table-sm th, +.table-sm td { + padding: 0.3rem; } + +.table-bordered { + border: 1px solid #dee2e6; } + .table-bordered th, + .table-bordered td { + border: 1px solid #dee2e6; } + .table-bordered thead th, + .table-bordered thead td { + border-bottom-width: 2px; } + +.table-borderless th, +.table-borderless td, +.table-borderless thead th, +.table-borderless tbody + tbody { + border: 0; } + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); } + +.table-hover tbody tr:hover { + background-color: rgba(0, 0, 0, 0.075); } + +.table-primary, +.table-primary > th, +.table-primary > td { + background-color: #bfc9d1; } + +.table-hover .table-primary:hover { + background-color: #b0bcc6; } + .table-hover .table-primary:hover > td, + .table-hover .table-primary:hover > th { + background-color: #b0bcc6; } + +.table-secondary, +.table-secondary > th, +.table-secondary > td { + background-color: #ebebec; } + +.table-hover .table-secondary:hover { + background-color: #dedee0; } + .table-hover .table-secondary:hover > td, + .table-hover .table-secondary:hover > th { + background-color: #dedee0; } + +.table-success, +.table-success > th, +.table-success > td { + background-color: #c3e6cb; } + +.table-hover .table-success:hover { + background-color: #b1dfbb; } + .table-hover .table-success:hover > td, + .table-hover .table-success:hover > th { + background-color: #b1dfbb; } + +.table-info, +.table-info > th, +.table-info > td { + background-color: #bee5eb; } + +.table-hover .table-info:hover { + background-color: #abdde5; } + .table-hover .table-info:hover > td, + .table-hover .table-info:hover > th { + background-color: #abdde5; } + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #fefedc; } + +.table-hover .table-warning:hover { + background-color: #fdfdc3; } + .table-hover .table-warning:hover > td, + .table-hover .table-warning:hover > th { + background-color: #fdfdc3; } + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #f5c6cb; } + +.table-hover .table-danger:hover { + background-color: #f1b0b7; } + .table-hover .table-danger:hover > td, + .table-hover .table-danger:hover > th { + background-color: #f1b0b7; } + +.table-light, +.table-light > th, +.table-light > td { + background-color: #fdfdfe; } + +.table-hover .table-light:hover { + background-color: #ececf6; } + .table-hover .table-light:hover > td, + .table-hover .table-light:hover > th { + background-color: #ececf6; } + +.table-dark, +.table-dark > th, +.table-dark > td { + background-color: #c6c8ca; } + +.table-hover .table-dark:hover { + background-color: #b9bbbe; } + .table-hover .table-dark:hover > td, + .table-hover .table-dark:hover > th { + background-color: #b9bbbe; } + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); } + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); } + .table-hover .table-active:hover > td, + .table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); } + +.table .thead-dark th { + color: #fff; + background-color: #212529; + border-color: #32383e; } + +.table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #dee2e6; } + +.table-dark { + color: #fff; + background-color: #212529; } + .table-dark th, + .table-dark td, + .table-dark thead th { + border-color: #32383e; } + .table-dark.table-bordered { + border: 0; } + .table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); } + .table-dark.table-hover tbody tr:hover { + background-color: rgba(255, 255, 255, 0.075); } + +@media (max-width: 575.98px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .table-responsive-sm > .table-bordered { + border: 0; } } + +@media (max-width: 767.98px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .table-responsive-md > .table-bordered { + border: 0; } } + +@media (max-width: 991.98px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .table-responsive-lg > .table-bordered { + border: 0; } } + +@media (max-width: 1199.98px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .table-responsive-xl > .table-bordered { + border: 0; } } + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .table-responsive > .table-bordered { + border: 0; } + +.form-control { + display: block; + width: 100%; + height: calc(2.25rem + 2px); + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media screen and (prefers-reduced-motion: reduce) { + .form-control { + transition: none; } } + .form-control::-ms-expand { + background-color: transparent; + border: 0; } + .form-control:focus { + color: #495057; + background-color: #fff; + border-color: #3a80be; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(28, 62, 92, 0.25); } + .form-control::placeholder { + color: #6c757d; + opacity: 1; } + .form-control:disabled, .form-control[readonly] { + background-color: #e9ecef; + opacity: 1; } + +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff; } + +.form-control-file, +.form-control-range { + display: block; + width: 100%; } + +.col-form-label { + padding-top: calc(0.375rem + 1px); + padding-bottom: calc(0.375rem + 1px); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5; } + +.col-form-label-lg { + padding-top: calc(0.5rem + 1px); + padding-bottom: calc(0.5rem + 1px); + font-size: 1.25rem; + line-height: 1.5; } + +.col-form-label-sm { + padding-top: calc(0.25rem + 1px); + padding-bottom: calc(0.25rem + 1px); + font-size: 0.875rem; + line-height: 1.5; } + +.form-control-plaintext { + display: block; + width: 100%; + padding-top: 0.375rem; + padding-bottom: 0.375rem; + margin-bottom: 0; + line-height: 1.5; + color: #212529; + background-color: transparent; + border: solid transparent; + border-width: 1px 0; } + .form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { + padding-right: 0; + padding-left: 0; } + +.form-control-sm { + height: calc(1.8125rem + 2px); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; } + +.form-control-lg { + height: calc(2.875rem + 2px); + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; } + +select.form-control[size], select.form-control[multiple] { + height: auto; } + +textarea.form-control { + height: auto; } + +.form-group { + margin-bottom: 1rem; } + +.form-text { + display: block; + margin-top: 0.25rem; } + +.form-row { + display: flex; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px; } + .form-row > .col, + .form-row > [class*="col-"] { + padding-right: 5px; + padding-left: 5px; } + +.form-check { + position: relative; + display: block; + padding-left: 1.25rem; } + +.form-check-input { + position: absolute; + margin-top: 0.3rem; + margin-left: -1.25rem; } + .form-check-input:disabled ~ .form-check-label { + color: #6c757d; } + +.form-check-label { + margin-bottom: 0; } + +.form-check-inline { + display: inline-flex; + align-items: center; + padding-left: 0; + margin-right: 0.75rem; } + .form-check-inline .form-check-input { + position: static; + margin-top: 0; + margin-right: 0.3125rem; + margin-left: 0; } + +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #28a745; } + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: .1rem; + font-size: 0.875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(40, 167, 69, 0.9); + border-radius: 0.25rem; } + +.was-validated .form-control:valid, .form-control.is-valid, .was-validated +.custom-select:valid, +.custom-select.is-valid { + border-color: #28a745; } + .was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated + .custom-select:valid:focus, + .custom-select.is-valid:focus { + border-color: #28a745; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } + .was-validated .form-control:valid ~ .valid-feedback, + .was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, + .form-control.is-valid ~ .valid-tooltip, .was-validated + .custom-select:valid ~ .valid-feedback, + .was-validated + .custom-select:valid ~ .valid-tooltip, + .custom-select.is-valid ~ .valid-feedback, + .custom-select.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .form-control-file:valid ~ .valid-feedback, +.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback, +.form-control-file.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { + color: #28a745; } + +.was-validated .form-check-input:valid ~ .valid-feedback, +.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, +.form-check-input.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { + color: #28a745; } + .was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { + background-color: #71dd8a; } + +.was-validated .custom-control-input:valid ~ .valid-feedback, +.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback, +.custom-control-input.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { + background-color: #34ce57; } + +.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } + +.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { + border-color: #28a745; } + .was-validated .custom-file-input:valid ~ .custom-file-label::after, .custom-file-input.is-valid ~ .custom-file-label::after { + border-color: inherit; } + +.was-validated .custom-file-input:valid ~ .valid-feedback, +.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback, +.custom-file-input.is-valid ~ .valid-tooltip { + display: block; } + +.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } + +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #dc3545; } + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: .1rem; + font-size: 0.875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(220, 53, 69, 0.9); + border-radius: 0.25rem; } + +.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated +.custom-select:invalid, +.custom-select.is-invalid { + border-color: #dc3545; } + .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated + .custom-select:invalid:focus, + .custom-select.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } + .was-validated .form-control:invalid ~ .invalid-feedback, + .was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, + .form-control.is-invalid ~ .invalid-tooltip, .was-validated + .custom-select:invalid ~ .invalid-feedback, + .was-validated + .custom-select:invalid ~ .invalid-tooltip, + .custom-select.is-invalid ~ .invalid-feedback, + .custom-select.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .form-control-file:invalid ~ .invalid-feedback, +.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback, +.form-control-file.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { + color: #dc3545; } + +.was-validated .form-check-input:invalid ~ .invalid-feedback, +.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, +.form-check-input.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { + color: #dc3545; } + .was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { + background-color: #efa2a9; } + +.was-validated .custom-control-input:invalid ~ .invalid-feedback, +.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback, +.custom-control-input.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { + background-color: #e4606d; } + +.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } + +.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { + border-color: #dc3545; } + .was-validated .custom-file-input:invalid ~ .custom-file-label::after, .custom-file-input.is-invalid ~ .custom-file-label::after { + border-color: inherit; } + +.was-validated .custom-file-input:invalid ~ .invalid-feedback, +.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback, +.custom-file-input.is-invalid ~ .invalid-tooltip { + display: block; } + +.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } + +.form-inline { + display: flex; + flex-flow: row wrap; + align-items: center; } + .form-inline .form-check { + width: 100%; } + @media (min-width: 576px) { + .form-inline label { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0; } + .form-inline .form-group { + display: flex; + flex: 0 0 auto; + flex-flow: row wrap; + align-items: center; + margin-bottom: 0; } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; } + .form-inline .form-control-plaintext { + display: inline-block; } + .form-inline .input-group, + .form-inline .custom-select { + width: auto; } + .form-inline .form-check { + display: flex; + align-items: center; + justify-content: center; + width: auto; + padding-left: 0; } + .form-inline .form-check-input { + position: relative; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; } + .form-inline .custom-control { + align-items: center; + justify-content: center; } + .form-inline .custom-control-label { + margin-bottom: 0; } } + +.btn { + display: inline-block; + font-weight: 400; + text-align: center; + white-space: nowrap; + vertical-align: middle; + user-select: none; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media screen and (prefers-reduced-motion: reduce) { + .btn { + transition: none; } } + .btn:hover, .btn:focus { + text-decoration: none; } + .btn:focus, .btn.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(28, 62, 92, 0.25); } + .btn.disabled, .btn:disabled { + opacity: 0.65; } + .btn:not(:disabled):not(.disabled) { + cursor: pointer; } + +a.btn.disabled, +fieldset:disabled a.btn { + pointer-events: none; } + +.btn-primary { + color: #fff; + background-color: #1c3e5c; + border-color: #1c3e5c; } + .btn-primary:hover { + color: #fff; + background-color: #132a3f; + border-color: #102435; } + .btn-primary:focus, .btn-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(28, 62, 92, 0.5); } + .btn-primary.disabled, .btn-primary:disabled { + color: #fff; + background-color: #1c3e5c; + border-color: #1c3e5c; } + .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, + .show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #102435; + border-color: #0d1d2b; } + .btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, + .show > .btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(28, 62, 92, 0.5); } + +.btn-secondary { + color: #212529; + background-color: #b6b6ba; + border-color: #b6b6ba; } + .btn-secondary:hover { + color: #212529; + background-color: #a2a2a7; + border-color: #9c9ca1; } + .btn-secondary:focus, .btn-secondary.focus { + box-shadow: 0 0 0 0.2rem rgba(182, 182, 186, 0.5); } + .btn-secondary.disabled, .btn-secondary:disabled { + color: #212529; + background-color: #b6b6ba; + border-color: #b6b6ba; } + .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, + .show > .btn-secondary.dropdown-toggle { + color: #212529; + background-color: #9c9ca1; + border-color: #95959b; } + .btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, + .show > .btn-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(182, 182, 186, 0.5); } + +.btn-success { + color: #fff; + background-color: #28a745; + border-color: #28a745; } + .btn-success:hover { + color: #fff; + background-color: #218838; + border-color: #1e7e34; } + .btn-success:focus, .btn-success.focus { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } + .btn-success.disabled, .btn-success:disabled { + color: #fff; + background-color: #28a745; + border-color: #28a745; } + .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, + .show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #1e7e34; + border-color: #1c7430; } + .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, + .show > .btn-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } + +.btn-info { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; } + .btn-info:hover { + color: #fff; + background-color: #138496; + border-color: #117a8b; } + .btn-info:focus, .btn-info.focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } + .btn-info.disabled, .btn-info:disabled { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; } + .btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, + .show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #117a8b; + border-color: #10707f; } + .btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, + .show > .btn-info.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } + +.btn-warning { + color: #212529; + background-color: #fbfb83; + border-color: #fbfb83; } + .btn-warning:hover { + color: #212529; + background-color: #fafa5e; + border-color: #f9f952; } + .btn-warning:focus, .btn-warning.focus { + box-shadow: 0 0 0 0.2rem rgba(251, 251, 131, 0.5); } + .btn-warning.disabled, .btn-warning:disabled { + color: #212529; + background-color: #fbfb83; + border-color: #fbfb83; } + .btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, + .show > .btn-warning.dropdown-toggle { + color: #212529; + background-color: #f9f952; + border-color: #f9f945; } + .btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, + .show > .btn-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(251, 251, 131, 0.5); } + +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; } + .btn-danger:hover { + color: #fff; + background-color: #c82333; + border-color: #bd2130; } + .btn-danger:focus, .btn-danger.focus { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } + .btn-danger.disabled, .btn-danger:disabled { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; } + .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, + .show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #bd2130; + border-color: #b21f2d; } + .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, + .show > .btn-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } + +.btn-light { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; } + .btn-light:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; } + .btn-light:focus, .btn-light.focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } + .btn-light.disabled, .btn-light:disabled { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; } + .btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, + .show > .btn-light.dropdown-toggle { + color: #212529; + background-color: #dae0e5; + border-color: #d3d9df; } + .btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, + .show > .btn-light.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; } + .btn-dark:focus, .btn-dark.focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } + .btn-dark.disabled, .btn-dark:disabled { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, + .show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1d2124; + border-color: #171a1d; } + .btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, + .show > .btn-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } + +.btn-outline-primary { + color: #1c3e5c; + background-color: transparent; + background-image: none; + border-color: #1c3e5c; } + .btn-outline-primary:hover { + color: #fff; + background-color: #1c3e5c; + border-color: #1c3e5c; } + .btn-outline-primary:focus, .btn-outline-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(28, 62, 92, 0.5); } + .btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #1c3e5c; + background-color: transparent; } + .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, + .show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #1c3e5c; + border-color: #1c3e5c; } + .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, + .show > .btn-outline-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(28, 62, 92, 0.5); } + +.btn-outline-secondary { + color: #b6b6ba; + background-color: transparent; + background-image: none; + border-color: #b6b6ba; } + .btn-outline-secondary:hover { + color: #212529; + background-color: #b6b6ba; + border-color: #b6b6ba; } + .btn-outline-secondary:focus, .btn-outline-secondary.focus { + box-shadow: 0 0 0 0.2rem rgba(182, 182, 186, 0.5); } + .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #b6b6ba; + background-color: transparent; } + .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, + .show > .btn-outline-secondary.dropdown-toggle { + color: #212529; + background-color: #b6b6ba; + border-color: #b6b6ba; } + .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, + .show > .btn-outline-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(182, 182, 186, 0.5); } + +.btn-outline-success { + color: #28a745; + background-color: transparent; + background-image: none; + border-color: #28a745; } + .btn-outline-success:hover { + color: #fff; + background-color: #28a745; + border-color: #28a745; } + .btn-outline-success:focus, .btn-outline-success.focus { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } + .btn-outline-success.disabled, .btn-outline-success:disabled { + color: #28a745; + background-color: transparent; } + .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, + .show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: #28a745; + border-color: #28a745; } + .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, + .show > .btn-outline-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } + +.btn-outline-info { + color: #17a2b8; + background-color: transparent; + background-image: none; + border-color: #17a2b8; } + .btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; } + .btn-outline-info:focus, .btn-outline-info.focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } + .btn-outline-info.disabled, .btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent; } + .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, + .show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; } + .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, + .show > .btn-outline-info.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } + +.btn-outline-warning { + color: #fbfb83; + background-color: transparent; + background-image: none; + border-color: #fbfb83; } + .btn-outline-warning:hover { + color: #212529; + background-color: #fbfb83; + border-color: #fbfb83; } + .btn-outline-warning:focus, .btn-outline-warning.focus { + box-shadow: 0 0 0 0.2rem rgba(251, 251, 131, 0.5); } + .btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #fbfb83; + background-color: transparent; } + .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, + .show > .btn-outline-warning.dropdown-toggle { + color: #212529; + background-color: #fbfb83; + border-color: #fbfb83; } + .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, + .show > .btn-outline-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(251, 251, 131, 0.5); } + +.btn-outline-danger { + color: #dc3545; + background-color: transparent; + background-image: none; + border-color: #dc3545; } + .btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; } + .btn-outline-danger:focus, .btn-outline-danger.focus { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } + .btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #dc3545; + background-color: transparent; } + .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, + .show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; } + .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, + .show > .btn-outline-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } + +.btn-outline-light { + color: #f8f9fa; + background-color: transparent; + background-image: none; + border-color: #f8f9fa; } + .btn-outline-light:hover { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; } + .btn-outline-light:focus, .btn-outline-light.focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } + .btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; } + .btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, + .show > .btn-outline-light.dropdown-toggle { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; } + .btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, + .show > .btn-outline-light.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } + +.btn-outline-dark { + color: #343a40; + background-color: transparent; + background-image: none; + border-color: #343a40; } + .btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-outline-dark:focus, .btn-outline-dark.focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } + .btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; } + .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, + .show > .btn-outline-dark.dropdown-toggle { + color: #fff; + background-color: #343a40; + border-color: #343a40; } + .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, + .show > .btn-outline-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } + +.btn-link { + font-weight: 400; + color: #1c3e5c; + background-color: transparent; } + .btn-link:hover { + color: #0a1621; + text-decoration: underline; + background-color: transparent; + border-color: transparent; } + .btn-link:focus, .btn-link.focus { + text-decoration: underline; + border-color: transparent; + box-shadow: none; } + .btn-link:disabled, .btn-link.disabled { + color: #6c757d; + pointer-events: none; } + +.btn-lg, .btn-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; } + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; } + +.btn-block { + display: block; + width: 100%; } + .btn-block + .btn-block { + margin-top: 0.5rem; } + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; } + +.fade { + transition: opacity 0.15s linear; } + @media screen and (prefers-reduced-motion: reduce) { + .fade { + transition: none; } } + .fade:not(.show) { + opacity: 0; } + +.collapse:not(.show) { + display: none; } + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + transition: height 0.35s ease; } + @media screen and (prefers-reduced-motion: reduce) { + .collapsing { + transition: none; } } + +.dropup, +.dropright, +.dropdown, +.dropleft { + position: relative; } + +.dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; } + +.dropdown-toggle:empty::after { + margin-left: 0; } + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; } + +.dropdown-menu-right { + right: 0; + left: auto; } + +.dropup .dropdown-menu { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: 0.125rem; } + +.dropup .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; } + +.dropup .dropdown-toggle:empty::after { + margin-left: 0; } + +.dropright .dropdown-menu { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: 0.125rem; } + +.dropright .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; } + +.dropright .dropdown-toggle:empty::after { + margin-left: 0; } + +.dropright .dropdown-toggle::after { + vertical-align: 0; } + +.dropleft .dropdown-menu { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: 0.125rem; } + +.dropleft .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; } + +.dropleft .dropdown-toggle::after { + display: none; } + +.dropleft .dropdown-toggle::before { + display: inline-block; + width: 0; + height: 0; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; } + +.dropleft .dropdown-toggle:empty::after { + margin-left: 0; } + +.dropleft .dropdown-toggle::before { + vertical-align: 0; } + +.dropdown-menu[x-placement^="top"], .dropdown-menu[x-placement^="right"], .dropdown-menu[x-placement^="bottom"], .dropdown-menu[x-placement^="left"] { + right: auto; + bottom: auto; } + +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef; } + +.dropdown-item { + display: block; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: 400; + color: #212529; + text-align: inherit; + white-space: nowrap; + background-color: transparent; + border: 0; } + .dropdown-item:hover, .dropdown-item:focus { + color: #16181b; + text-decoration: none; + background-color: #f8f9fa; } + .dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #1c3e5c; } + .dropdown-item.disabled, .dropdown-item:disabled { + color: #6c757d; + background-color: transparent; } + +.dropdown-menu.show { + display: block; } + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #6c757d; + white-space: nowrap; } + +.dropdown-item-text { + display: block; + padding: 0.25rem 1.5rem; + color: #212529; } + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; } + .btn-group > .btn, + .btn-group-vertical > .btn { + position: relative; + flex: 0 1 auto; } + .btn-group > .btn:hover, + .btn-group-vertical > .btn:hover { + z-index: 1; } + .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, + .btn-group-vertical > .btn:focus, + .btn-group-vertical > .btn:active, + .btn-group-vertical > .btn.active { + z-index: 1; } + .btn-group .btn + .btn, + .btn-group .btn + .btn-group, + .btn-group .btn-group + .btn, + .btn-group .btn-group + .btn-group, + .btn-group-vertical .btn + .btn, + .btn-group-vertical .btn + .btn-group, + .btn-group-vertical .btn-group + .btn, + .btn-group-vertical .btn-group + .btn-group { + margin-left: -1px; } + +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; } + .btn-toolbar .input-group { + width: auto; } + +.btn-group > .btn:first-child { + margin-left: 0; } + +.btn-group > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + +.btn-group > .btn:not(:first-child), +.btn-group > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; } + .dropdown-toggle-split::after, + .dropup .dropdown-toggle-split::after, + .dropright .dropdown-toggle-split::after { + margin-left: 0; } + .dropleft .dropdown-toggle-split::before { + margin-right: 0; } + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; } + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; } + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; } + .btn-group-vertical .btn, + .btn-group-vertical .btn-group { + width: 100%; } + .btn-group-vertical > .btn + .btn, + .btn-group-vertical > .btn + .btn-group, + .btn-group-vertical > .btn-group + .btn, + .btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; } + .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), + .btn-group-vertical > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } + .btn-group-vertical > .btn:not(:first-child), + .btn-group-vertical > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.btn-group-toggle > .btn, +.btn-group-toggle > .btn-group > .btn { + margin-bottom: 0; } + .btn-group-toggle > .btn input[type="radio"], + .btn-group-toggle > .btn input[type="checkbox"], + .btn-group-toggle > .btn-group > .btn input[type="radio"], + .btn-group-toggle > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; } + +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100%; } + .input-group > .form-control, + .input-group > .custom-select, + .input-group > .custom-file { + position: relative; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; } + .input-group > .form-control + .form-control, + .input-group > .form-control + .custom-select, + .input-group > .form-control + .custom-file, + .input-group > .custom-select + .form-control, + .input-group > .custom-select + .custom-select, + .input-group > .custom-select + .custom-file, + .input-group > .custom-file + .form-control, + .input-group > .custom-file + .custom-select, + .input-group > .custom-file + .custom-file { + margin-left: -1px; } + .input-group > .form-control:focus, + .input-group > .custom-select:focus, + .input-group > .custom-file .custom-file-input:focus ~ .custom-file-label { + z-index: 3; } + .input-group > .custom-file .custom-file-input:focus { + z-index: 4; } + .input-group > .form-control:not(:last-child), + .input-group > .custom-select:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .input-group > .form-control:not(:first-child), + .input-group > .custom-select:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + .input-group > .custom-file { + display: flex; + align-items: center; } + .input-group > .custom-file:not(:last-child) .custom-file-label, + .input-group > .custom-file:not(:last-child) .custom-file-label::after { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .input-group > .custom-file:not(:first-child) .custom-file-label { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.input-group-prepend, +.input-group-append { + display: flex; } + .input-group-prepend .btn, + .input-group-append .btn { + position: relative; + z-index: 2; } + .input-group-prepend .btn + .btn, + .input-group-prepend .btn + .input-group-text, + .input-group-prepend .input-group-text + .input-group-text, + .input-group-prepend .input-group-text + .btn, + .input-group-append .btn + .btn, + .input-group-append .btn + .input-group-text, + .input-group-append .input-group-text + .input-group-text, + .input-group-append .input-group-text + .btn { + margin-left: -1px; } + +.input-group-prepend { + margin-right: -1px; } + +.input-group-append { + margin-left: -1px; } + +.input-group-text { + display: flex; + align-items: center; + padding: 0.375rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + white-space: nowrap; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 0.25rem; } + .input-group-text input[type="radio"], + .input-group-text input[type="checkbox"] { + margin-top: 0; } + +.input-group-lg > .form-control, +.input-group-lg > .input-group-prepend > .input-group-text, +.input-group-lg > .input-group-append > .input-group-text, +.input-group-lg > .input-group-prepend > .btn, +.input-group-lg > .input-group-append > .btn { + height: calc(2.875rem + 2px); + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; } + +.input-group-sm > .form-control, +.input-group-sm > .input-group-prepend > .input-group-text, +.input-group-sm > .input-group-append > .input-group-text, +.input-group-sm > .input-group-prepend > .btn, +.input-group-sm > .input-group-append > .btn { + height: calc(1.8125rem + 2px); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; } + +.input-group > .input-group-prepend > .btn, +.input-group > .input-group-prepend > .input-group-text, +.input-group > .input-group-append:not(:last-child) > .btn, +.input-group > .input-group-append:not(:last-child) > .input-group-text, +.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + +.input-group > .input-group-append > .btn, +.input-group > .input-group-append > .input-group-text, +.input-group > .input-group-prepend:not(:first-child) > .btn, +.input-group > .input-group-prepend:not(:first-child) > .input-group-text, +.input-group > .input-group-prepend:first-child > .btn:not(:first-child), +.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + +.custom-control { + position: relative; + display: block; + min-height: 1.5rem; + padding-left: 1.5rem; } + +.custom-control-inline { + display: inline-flex; + margin-right: 1rem; } + +.custom-control-input { + position: absolute; + z-index: -1; + opacity: 0; } + .custom-control-input:checked ~ .custom-control-label::before { + color: #fff; + background-color: #1c3e5c; } + .custom-control-input:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(28, 62, 92, 0.25); } + .custom-control-input:active ~ .custom-control-label::before { + color: #fff; + background-color: #5d99ce; } + .custom-control-input:disabled ~ .custom-control-label { + color: #6c757d; } + .custom-control-input:disabled ~ .custom-control-label::before { + background-color: #e9ecef; } + +.custom-control-label { + position: relative; + margin-bottom: 0; } + .custom-control-label::before { + position: absolute; + top: 0.25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + content: ""; + user-select: none; + background-color: #dee2e6; } + .custom-control-label::after { + position: absolute; + top: 0.25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + content: ""; + background-repeat: no-repeat; + background-position: center center; + background-size: 50% 50%; } + +.custom-checkbox .custom-control-label::before { + border-radius: 0.25rem; } + +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::before { + background-color: #1c3e5c; } + +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); } + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { + background-color: #1c3e5c; } + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); } + +.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(28, 62, 92, 0.5); } + +.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { + background-color: rgba(28, 62, 92, 0.5); } + +.custom-radio .custom-control-label::before { + border-radius: 50%; } + +.custom-radio .custom-control-input:checked ~ .custom-control-label::before { + background-color: #1c3e5c; } + +.custom-radio .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); } + +.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(28, 62, 92, 0.5); } + +.custom-select { + display: inline-block; + width: 100%; + height: calc(2.25rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; + background-size: 8px 10px; + border: 1px solid #ced4da; + border-radius: 0.25rem; + appearance: none; } + .custom-select:focus { + border-color: #3a80be; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(58, 128, 190, 0.5); } + .custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; } + .custom-select[multiple], .custom-select[size]:not([size="1"]) { + height: auto; + padding-right: 0.75rem; + background-image: none; } + .custom-select:disabled { + color: #6c757d; + background-color: #e9ecef; } + .custom-select::-ms-expand { + opacity: 0; } + +.custom-select-sm { + height: calc(1.8125rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 75%; } + +.custom-select-lg { + height: calc(2.875rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 125%; } + +.custom-file { + position: relative; + display: inline-block; + width: 100%; + height: calc(2.25rem + 2px); + margin-bottom: 0; } + +.custom-file-input { + position: relative; + z-index: 2; + width: 100%; + height: calc(2.25rem + 2px); + margin: 0; + opacity: 0; } + .custom-file-input:focus ~ .custom-file-label { + border-color: #3a80be; + box-shadow: 0 0 0 0.2rem rgba(28, 62, 92, 0.25); } + .custom-file-input:focus ~ .custom-file-label::after { + border-color: #3a80be; } + .custom-file-input:disabled ~ .custom-file-label { + background-color: #e9ecef; } + .custom-file-input:lang(en) ~ .custom-file-label::after { + content: "Browse"; } + +.custom-file-label { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + height: calc(2.25rem + 2px); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.25rem; } + .custom-file-label::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 3; + display: block; + height: 2.25rem; + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + content: "Browse"; + background-color: #e9ecef; + border-left: 1px solid #ced4da; + border-radius: 0 0.25rem 0.25rem 0; } + +.custom-range { + width: 100%; + padding-left: 0; + background-color: transparent; + appearance: none; } + .custom-range:focus { + outline: none; } + .custom-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(28, 62, 92, 0.25); } + .custom-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(28, 62, 92, 0.25); } + .custom-range:focus::-ms-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(28, 62, 92, 0.25); } + .custom-range::-moz-focus-outer { + border: 0; } + .custom-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + background-color: #1c3e5c; + border: 0; + border-radius: 1rem; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + appearance: none; } + @media screen and (prefers-reduced-motion: reduce) { + .custom-range::-webkit-slider-thumb { + transition: none; } } + .custom-range::-webkit-slider-thumb:active { + background-color: #5d99ce; } + .custom-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; } + .custom-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + background-color: #1c3e5c; + border: 0; + border-radius: 1rem; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + appearance: none; } + @media screen and (prefers-reduced-motion: reduce) { + .custom-range::-moz-range-thumb { + transition: none; } } + .custom-range::-moz-range-thumb:active { + background-color: #5d99ce; } + .custom-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; } + .custom-range::-ms-thumb { + width: 1rem; + height: 1rem; + margin-top: 0; + margin-right: 0.2rem; + margin-left: 0.2rem; + background-color: #1c3e5c; + border: 0; + border-radius: 1rem; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + appearance: none; } + @media screen and (prefers-reduced-motion: reduce) { + .custom-range::-ms-thumb { + transition: none; } } + .custom-range::-ms-thumb:active { + background-color: #5d99ce; } + .custom-range::-ms-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: transparent; + border-color: transparent; + border-width: 0.5rem; } + .custom-range::-ms-fill-lower { + background-color: #dee2e6; + border-radius: 1rem; } + .custom-range::-ms-fill-upper { + margin-right: 15px; + background-color: #dee2e6; + border-radius: 1rem; } + +.custom-control-label::before, +.custom-file-label, +.custom-select { + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media screen and (prefers-reduced-motion: reduce) { + .custom-control-label::before, + .custom-file-label, + .custom-select { + transition: none; } } + +.nav { + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; } + +.nav-link { + display: block; + padding: 0.5rem 1rem; } + .nav-link:hover, .nav-link:focus { + text-decoration: none; } + .nav-link.disabled { + color: #6c757d; } + +.nav-tabs { + border-bottom: 1px solid #dee2e6; } + .nav-tabs .nav-item { + margin-bottom: -1px; } + .nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { + border-color: #e9ecef #e9ecef #dee2e6; } + .nav-tabs .nav-link.disabled { + color: #6c757d; + background-color: transparent; + border-color: transparent; } + .nav-tabs .nav-link.active, + .nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #dee2e6 #dee2e6 #fff; } + .nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.nav-pills .nav-link { + border-radius: 0.25rem; } + +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #1c3e5c; } + +.nav-fill .nav-item { + flex: 1 1 auto; + text-align: center; } + +.nav-justified .nav-item { + flex-basis: 0; + flex-grow: 1; + text-align: center; } + +.tab-content > .tab-pane { + display: none; } + +.tab-content > .active { + display: block; } + +.navbar { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: 0.5rem 1rem; } + .navbar > .container, + .navbar > .container-fluid { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; } + +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; } + .navbar-brand:hover, .navbar-brand:focus { + text-decoration: none; } + +.navbar-nav { + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; } + .navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; } + .navbar-nav .dropdown-menu { + position: static; + float: none; } + +.navbar-text { + display: inline-block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; } + +.navbar-collapse { + flex-basis: 100%; + flex-grow: 1; + align-items: center; } + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background-color: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; } + .navbar-toggler:hover, .navbar-toggler:focus { + text-decoration: none; } + .navbar-toggler:not(:disabled):not(.disabled) { + cursor: pointer; } + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; } + +@media (max-width: 575.98px) { + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + padding-right: 0; + padding-left: 0; } } + +@media (min-width: 576px) { + .navbar-expand-sm { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand-sm .navbar-nav { + flex-direction: row; } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand-sm .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand-sm .navbar-toggler { + display: none; } } + +@media (max-width: 767.98px) { + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + padding-right: 0; + padding-left: 0; } } + +@media (min-width: 768px) { + .navbar-expand-md { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand-md .navbar-nav { + flex-direction: row; } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand-md .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand-md .navbar-toggler { + display: none; } } + +@media (max-width: 991.98px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + padding-right: 0; + padding-left: 0; } } + +@media (min-width: 992px) { + .navbar-expand-lg { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand-lg .navbar-nav { + flex-direction: row; } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand-lg .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand-lg .navbar-toggler { + display: none; } } + +@media (max-width: 1199.98px) { + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + padding-right: 0; + padding-left: 0; } } + +@media (min-width: 1200px) { + .navbar-expand-xl { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand-xl .navbar-nav { + flex-direction: row; } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand-xl .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand-xl .navbar-toggler { + display: none; } } + +.navbar-expand { + flex-flow: row nowrap; + justify-content: flex-start; } + .navbar-expand > .container, + .navbar-expand > .container-fluid { + padding-right: 0; + padding-left: 0; } + .navbar-expand .navbar-nav { + flex-direction: row; } + .navbar-expand .navbar-nav .dropdown-menu { + position: absolute; } + .navbar-expand .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; } + .navbar-expand > .container, + .navbar-expand > .container-fluid { + flex-wrap: nowrap; } + .navbar-expand .navbar-collapse { + display: flex !important; + flex-basis: auto; } + .navbar-expand .navbar-toggler { + display: none; } + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); } + .navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { + color: rgba(0, 0, 0, 0.9); } + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); } + .navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { + color: rgba(0, 0, 0, 0.7); } + .navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); } + +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); } + +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); } + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); } + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); } + .navbar-light .navbar-text a { + color: rgba(0, 0, 0, 0.9); } + .navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { + color: rgba(0, 0, 0, 0.9); } + +.navbar-dark .navbar-brand { + color: #fff; } + .navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { + color: #fff; } + +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); } + .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { + color: rgba(255, 255, 255, 0.75); } + .navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); } + +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; } + +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.1); } + +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); } + +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.5); } + .navbar-dark .navbar-text a { + color: #fff; } + .navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { + color: #fff; } + +.card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; } + .card > hr { + margin-right: 0; + margin-left: 0; } + .card > .list-group:first-child .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + +.card-body { + flex: 1 1 auto; + padding: 1.25rem; } + +.card-title { + margin-bottom: 0.75rem; } + +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; } + +.card-text:last-child { + margin-bottom: 0; } + +.card-link:hover { + text-decoration: none; } + +.card-link + .card-link { + margin-left: 1.25rem; } + +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); } + .card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; } + .card-header + .list-group .list-group-item:first-child { + border-top: 0; } + +.card-footer { + padding: 0.75rem 1.25rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); } + .card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); } + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; } + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; } + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; } + +.card-img { + width: 100%; + border-radius: calc(0.25rem - 1px); } + +.card-img-top { + width: 100%; + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); } + +.card-img-bottom { + width: 100%; + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); } + +.card-deck { + display: flex; + flex-direction: column; } + .card-deck .card { + margin-bottom: 15px; } + @media (min-width: 576px) { + .card-deck { + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px; } + .card-deck .card { + display: flex; + flex: 1 0 0%; + flex-direction: column; + margin-right: 15px; + margin-bottom: 0; + margin-left: 15px; } } + +.card-group { + display: flex; + flex-direction: column; } + .card-group > .card { + margin-bottom: 15px; } + @media (min-width: 576px) { + .card-group { + flex-flow: row wrap; } + .card-group > .card { + flex: 1 0 0%; + margin-bottom: 0; } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; } + .card-group > .card:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .card-group > .card:first-child .card-img-top, + .card-group > .card:first-child .card-header { + border-top-right-radius: 0; } + .card-group > .card:first-child .card-img-bottom, + .card-group > .card:first-child .card-footer { + border-bottom-right-radius: 0; } + .card-group > .card:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + .card-group > .card:last-child .card-img-top, + .card-group > .card:last-child .card-header { + border-top-left-radius: 0; } + .card-group > .card:last-child .card-img-bottom, + .card-group > .card:last-child .card-footer { + border-bottom-left-radius: 0; } + .card-group > .card:only-child { + border-radius: 0.25rem; } + .card-group > .card:only-child .card-img-top, + .card-group > .card:only-child .card-header { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .card-group > .card:only-child .card-img-bottom, + .card-group > .card:only-child .card-footer { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) { + border-radius: 0; } + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer { + border-radius: 0; } } + +.card-columns .card { + margin-bottom: 0.75rem; } + +@media (min-width: 576px) { + .card-columns { + column-count: 3; + column-gap: 1.25rem; + orphans: 1; + widows: 1; } + .card-columns .card { + display: inline-block; + width: 100%; } } + +.accordion .card:not(:first-of-type):not(:last-of-type) { + border-bottom: 0; + border-radius: 0; } + +.accordion .card:not(:first-of-type) .card-header:first-child { + border-radius: 0; } + +.accordion .card:first-of-type { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } + +.accordion .card:last-of-type { + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.breadcrumb { + display: flex; + flex-wrap: wrap; + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: 0.25rem; } + +.breadcrumb-item + .breadcrumb-item { + padding-left: 0.5rem; } + .breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + color: #6c757d; + content: "/"; } + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; } + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; } + +.breadcrumb-item.active { + color: #6c757d; } + +.pagination { + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; } + +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #1c3e5c; + background-color: #fff; + border: 1px solid #dee2e6; } + .page-link:hover { + z-index: 2; + color: #0a1621; + text-decoration: none; + background-color: #e9ecef; + border-color: #dee2e6; } + .page-link:focus { + z-index: 2; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(28, 62, 92, 0.25); } + .page-link:not(:disabled):not(.disabled) { + cursor: pointer; } + +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; } + +.page-item.active .page-link { + z-index: 1; + color: #fff; + background-color: #1c3e5c; + border-color: #1c3e5c; } + +.page-item.disabled .page-link { + color: #6c757d; + pointer-events: none; + cursor: auto; + background-color: #fff; + border-color: #dee2e6; } + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; } + +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; } + +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; } + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; } + +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; } + +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; } + +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; } + .badge:empty { + display: none; } + +.btn .badge { + position: relative; + top: -1px; } + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; } + +.badge-primary { + color: #fff; + background-color: #1c3e5c; } + .badge-primary[href]:hover, .badge-primary[href]:focus { + color: #fff; + text-decoration: none; + background-color: #102435; } + +.badge-secondary { + color: #212529; + background-color: #b6b6ba; } + .badge-secondary[href]:hover, .badge-secondary[href]:focus { + color: #212529; + text-decoration: none; + background-color: #9c9ca1; } + +.badge-success { + color: #fff; + background-color: #28a745; } + .badge-success[href]:hover, .badge-success[href]:focus { + color: #fff; + text-decoration: none; + background-color: #1e7e34; } + +.badge-info { + color: #fff; + background-color: #17a2b8; } + .badge-info[href]:hover, .badge-info[href]:focus { + color: #fff; + text-decoration: none; + background-color: #117a8b; } + +.badge-warning { + color: #212529; + background-color: #fbfb83; } + .badge-warning[href]:hover, .badge-warning[href]:focus { + color: #212529; + text-decoration: none; + background-color: #f9f952; } + +.badge-danger { + color: #fff; + background-color: #dc3545; } + .badge-danger[href]:hover, .badge-danger[href]:focus { + color: #fff; + text-decoration: none; + background-color: #bd2130; } + +.badge-light { + color: #212529; + background-color: #f8f9fa; } + .badge-light[href]:hover, .badge-light[href]:focus { + color: #212529; + text-decoration: none; + background-color: #dae0e5; } + +.badge-dark { + color: #fff; + background-color: #343a40; } + .badge-dark[href]:hover, .badge-dark[href]:focus { + color: #fff; + text-decoration: none; + background-color: #1d2124; } + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: 0.3rem; } + @media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; } } + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; } + +.alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; } + +.alert-heading { + color: inherit; } + +.alert-link { + font-weight: 700; } + +.alert-dismissible { + padding-right: 4rem; } + .alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: 0.75rem 1.25rem; + color: inherit; } + +.alert-primary { + color: #0f2030; + background-color: #d2d8de; + border-color: #bfc9d1; } + .alert-primary hr { + border-top-color: #b0bcc6; } + .alert-primary .alert-link { + color: #030609; } + +.alert-secondary { + color: #5f5f61; + background-color: #f0f0f1; + border-color: #ebebec; } + .alert-secondary hr { + border-top-color: #dedee0; } + .alert-secondary .alert-link { + color: #464647; } + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; } + .alert-success hr { + border-top-color: #b1dfbb; } + .alert-success .alert-link { + color: #0b2e13; } + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; } + .alert-info hr { + border-top-color: #abdde5; } + .alert-info .alert-link { + color: #062c33; } + +.alert-warning { + color: #838344; + background-color: #fefee6; + border-color: #fefedc; } + .alert-warning hr { + border-top-color: #fdfdc3; } + .alert-warning .alert-link { + color: #616133; } + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; } + .alert-danger hr { + border-top-color: #f1b0b7; } + .alert-danger .alert-link { + color: #491217; } + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; } + .alert-light hr { + border-top-color: #ececf6; } + .alert-light .alert-link { + color: #686868; } + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; } + .alert-dark hr { + border-top-color: #b9bbbe; } + .alert-dark .alert-link { + color: #040505; } + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; } + to { + background-position: 0 0; } } + +.progress { + display: flex; + height: 1rem; + overflow: hidden; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; } + +.progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #1c3e5c; + transition: width 0.6s ease; } + @media screen and (prefers-reduced-motion: reduce) { + .progress-bar { + transition: none; } } + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 1rem 1rem; } + +.progress-bar-animated { + animation: progress-bar-stripes 1s linear infinite; } + +.media { + display: flex; + align-items: flex-start; } + +.media-body { + flex: 1; } + +.list-group { + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; } + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; } + .list-group-item-action:hover, .list-group-item-action:focus { + color: #495057; + text-decoration: none; + background-color: #f8f9fa; } + .list-group-item-action:active { + color: #212529; + background-color: #e9ecef; } + +.list-group-item { + position: relative; + display: block; + padding: 0.75rem 1.25rem; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); } + .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + .list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + .list-group-item:hover, .list-group-item:focus { + z-index: 1; + text-decoration: none; } + .list-group-item.disabled, .list-group-item:disabled { + color: #6c757d; + background-color: #fff; } + .list-group-item.active { + z-index: 2; + color: #fff; + background-color: #1c3e5c; + border-color: #1c3e5c; } + +.list-group-flush .list-group-item { + border-right: 0; + border-left: 0; + border-radius: 0; } + +.list-group-flush:first-child .list-group-item:first-child { + border-top: 0; } + +.list-group-flush:last-child .list-group-item:last-child { + border-bottom: 0; } + +.list-group-item-primary { + color: #0f2030; + background-color: #bfc9d1; } + .list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { + color: #0f2030; + background-color: #b0bcc6; } + .list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #0f2030; + border-color: #0f2030; } + +.list-group-item-secondary { + color: #5f5f61; + background-color: #ebebec; } + .list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { + color: #5f5f61; + background-color: #dedee0; } + .list-group-item-secondary.list-group-item-action.active { + color: #fff; + background-color: #5f5f61; + border-color: #5f5f61; } + +.list-group-item-success { + color: #155724; + background-color: #c3e6cb; } + .list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { + color: #155724; + background-color: #b1dfbb; } + .list-group-item-success.list-group-item-action.active { + color: #fff; + background-color: #155724; + border-color: #155724; } + +.list-group-item-info { + color: #0c5460; + background-color: #bee5eb; } + .list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { + color: #0c5460; + background-color: #abdde5; } + .list-group-item-info.list-group-item-action.active { + color: #fff; + background-color: #0c5460; + border-color: #0c5460; } + +.list-group-item-warning { + color: #838344; + background-color: #fefedc; } + .list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { + color: #838344; + background-color: #fdfdc3; } + .list-group-item-warning.list-group-item-action.active { + color: #fff; + background-color: #838344; + border-color: #838344; } + +.list-group-item-danger { + color: #721c24; + background-color: #f5c6cb; } + .list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { + color: #721c24; + background-color: #f1b0b7; } + .list-group-item-danger.list-group-item-action.active { + color: #fff; + background-color: #721c24; + border-color: #721c24; } + +.list-group-item-light { + color: #818182; + background-color: #fdfdfe; } + .list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { + color: #818182; + background-color: #ececf6; } + .list-group-item-light.list-group-item-action.active { + color: #fff; + background-color: #818182; + border-color: #818182; } + +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca; } + .list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { + color: #1b1e21; + background-color: #b9bbbe; } + .list-group-item-dark.list-group-item-action.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21; } + +.close { + float: right; + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5; } + .close:not(:disabled):not(.disabled) { + cursor: pointer; } + .close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus { + color: #000; + text-decoration: none; + opacity: .75; } + +button.close { + padding: 0; + background-color: transparent; + border: 0; + -webkit-appearance: none; } + +.modal-open { + overflow: hidden; } + .modal-open .modal { + overflow-x: hidden; + overflow-y: auto; } + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + outline: 0; } + +.modal-dialog { + position: relative; + width: auto; + margin: 0.5rem; + pointer-events: none; } + .modal.fade .modal-dialog { + transition: transform 0.3s ease-out; + transform: translate(0, -25%); } + @media screen and (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + transition: none; } } + .modal.show .modal-dialog { + transform: translate(0, 0); } + +.modal-dialog-centered { + display: flex; + align-items: center; + min-height: calc(100% - (0.5rem * 2)); } + .modal-dialog-centered::before { + display: block; + height: calc(100vh - (0.5rem * 2)); + content: ""; } + +.modal-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + outline: 0; } + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; } + .modal-backdrop.fade { + opacity: 0; } + .modal-backdrop.show { + opacity: 0.5; } + +.modal-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + padding: 1rem; + border-bottom: 1px solid #e9ecef; + border-top-left-radius: 0.3rem; + border-top-right-radius: 0.3rem; } + .modal-header .close { + padding: 1rem; + margin: -1rem -1rem -1rem auto; } + +.modal-title { + margin-bottom: 0; + line-height: 1.5; } + +.modal-body { + position: relative; + flex: 1 1 auto; + padding: 1rem; } + +.modal-footer { + display: flex; + align-items: center; + justify-content: flex-end; + padding: 1rem; + border-top: 1px solid #e9ecef; } + .modal-footer > :not(:first-child) { + margin-left: .25rem; } + .modal-footer > :not(:last-child) { + margin-right: .25rem; } + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; } + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 1.75rem auto; } + .modal-dialog-centered { + min-height: calc(100% - (1.75rem * 2)); } + .modal-dialog-centered::before { + height: calc(100vh - (1.75rem * 2)); } + .modal-sm { + max-width: 300px; } } + +@media (min-width: 992px) { + .modal-lg { + max-width: 800px; } } + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + opacity: 0; } + .tooltip.show { + opacity: 0.9; } + .tooltip .arrow { + position: absolute; + display: block; + width: 0.8rem; + height: 0.4rem; } + .tooltip .arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; } + +.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { + padding: 0.4rem 0; } + .bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { + bottom: 0; } + .bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { + top: 0; + border-width: 0.4rem 0.4rem 0; + border-top-color: #000; } + +.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { + padding: 0 0.4rem; } + .bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { + left: 0; + width: 0.4rem; + height: 0.8rem; } + .bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { + right: 0; + border-width: 0.4rem 0.4rem 0.4rem 0; + border-right-color: #000; } + +.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { + padding: 0.4rem 0; } + .bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { + top: 0; } + .bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { + bottom: 0; + border-width: 0 0.4rem 0.4rem; + border-bottom-color: #000; } + +.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { + padding: 0 0.4rem; } + .bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { + right: 0; + width: 0.4rem; + height: 0.8rem; } + .bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { + left: 0; + border-width: 0.4rem 0 0.4rem 0.4rem; + border-left-color: #000; } + +.tooltip-inner { + max-width: 200px; + padding: 0.25rem 0.5rem; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; } + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; } + .popover .arrow { + position: absolute; + display: block; + width: 1rem; + height: 0.5rem; + margin: 0 0.3rem; } + .popover .arrow::before, .popover .arrow::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; } + +.bs-popover-top, .bs-popover-auto[x-placement^="top"] { + margin-bottom: 0.5rem; } + .bs-popover-top .arrow, .bs-popover-auto[x-placement^="top"] .arrow { + bottom: calc((0.5rem + 1px) * -1); } + .bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before, + .bs-popover-top .arrow::after, + .bs-popover-auto[x-placement^="top"] .arrow::after { + border-width: 0.5rem 0.5rem 0; } + .bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before { + bottom: 0; + border-top-color: rgba(0, 0, 0, 0.25); } + + .bs-popover-top .arrow::after, + .bs-popover-auto[x-placement^="top"] .arrow::after { + bottom: 1px; + border-top-color: #fff; } + +.bs-popover-right, .bs-popover-auto[x-placement^="right"] { + margin-left: 0.5rem; } + .bs-popover-right .arrow, .bs-popover-auto[x-placement^="right"] .arrow { + left: calc((0.5rem + 1px) * -1); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; } + .bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before, + .bs-popover-right .arrow::after, + .bs-popover-auto[x-placement^="right"] .arrow::after { + border-width: 0.5rem 0.5rem 0.5rem 0; } + .bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before { + left: 0; + border-right-color: rgba(0, 0, 0, 0.25); } + + .bs-popover-right .arrow::after, + .bs-popover-auto[x-placement^="right"] .arrow::after { + left: 1px; + border-right-color: #fff; } + +.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { + margin-top: 0.5rem; } + .bs-popover-bottom .arrow, .bs-popover-auto[x-placement^="bottom"] .arrow { + top: calc((0.5rem + 1px) * -1); } + .bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before, + .bs-popover-bottom .arrow::after, + .bs-popover-auto[x-placement^="bottom"] .arrow::after { + border-width: 0 0.5rem 0.5rem 0.5rem; } + .bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before { + top: 0; + border-bottom-color: rgba(0, 0, 0, 0.25); } + + .bs-popover-bottom .arrow::after, + .bs-popover-auto[x-placement^="bottom"] .arrow::after { + top: 1px; + border-bottom-color: #fff; } + .bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 1rem; + margin-left: -0.5rem; + content: ""; + border-bottom: 1px solid #f7f7f7; } + +.bs-popover-left, .bs-popover-auto[x-placement^="left"] { + margin-right: 0.5rem; } + .bs-popover-left .arrow, .bs-popover-auto[x-placement^="left"] .arrow { + right: calc((0.5rem + 1px) * -1); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; } + .bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before, + .bs-popover-left .arrow::after, + .bs-popover-auto[x-placement^="left"] .arrow::after { + border-width: 0.5rem 0 0.5rem 0.5rem; } + .bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before { + right: 0; + border-left-color: rgba(0, 0, 0, 0.25); } + + .bs-popover-left .arrow::after, + .bs-popover-auto[x-placement^="left"] .arrow::after { + right: 1px; + border-left-color: #fff; } + +.popover-header { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + color: inherit; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); } + .popover-header:empty { + display: none; } + +.popover-body { + padding: 0.5rem 0.75rem; + color: #212529; } + +.carousel { + position: relative; } + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; } + +.carousel-item { + position: relative; + display: none; + align-items: center; + width: 100%; + backface-visibility: hidden; + perspective: 1000px; } + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; + transition: transform 0.6s ease; } + @media screen and (prefers-reduced-motion: reduce) { + .carousel-item.active, + .carousel-item-next, + .carousel-item-prev { + transition: none; } } + +.carousel-item-next, +.carousel-item-prev { + position: absolute; + top: 0; } + +.carousel-item-next.carousel-item-left, +.carousel-item-prev.carousel-item-right { + transform: translateX(0); } + @supports (transform-style: preserve-3d) { + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + transform: translate3d(0, 0, 0); } } + +.carousel-item-next, +.active.carousel-item-right { + transform: translateX(100%); } + @supports (transform-style: preserve-3d) { + .carousel-item-next, + .active.carousel-item-right { + transform: translate3d(100%, 0, 0); } } + +.carousel-item-prev, +.active.carousel-item-left { + transform: translateX(-100%); } + @supports (transform-style: preserve-3d) { + .carousel-item-prev, + .active.carousel-item-left { + transform: translate3d(-100%, 0, 0); } } + +.carousel-fade .carousel-item { + opacity: 0; + transition-duration: .6s; + transition-property: opacity; } + +.carousel-fade .carousel-item.active, +.carousel-fade .carousel-item-next.carousel-item-left, +.carousel-fade .carousel-item-prev.carousel-item-right { + opacity: 1; } + +.carousel-fade .active.carousel-item-left, +.carousel-fade .active.carousel-item-right { + opacity: 0; } + +.carousel-fade .carousel-item-next, +.carousel-fade .carousel-item-prev, +.carousel-fade .carousel-item.active, +.carousel-fade .active.carousel-item-left, +.carousel-fade .active.carousel-item-prev { + transform: translateX(0); } + @supports (transform-style: preserve-3d) { + .carousel-fade .carousel-item-next, + .carousel-fade .carousel-item-prev, + .carousel-fade .carousel-item.active, + .carousel-fade .active.carousel-item-left, + .carousel-fade .active.carousel-item-prev { + transform: translate3d(0, 0, 0); } } + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: 0.5; } + .carousel-control-prev:hover, .carousel-control-prev:focus, + .carousel-control-next:hover, + .carousel-control-next:focus { + color: #fff; + text-decoration: none; + outline: 0; + opacity: .9; } + +.carousel-control-prev { + left: 0; } + +.carousel-control-next { + right: 0; } + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 20px; + height: 20px; + background: transparent no-repeat center center; + background-size: 100% 100%; } + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); } + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); } + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 10px; + left: 0; + z-index: 15; + display: flex; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; } + .carousel-indicators li { + position: relative; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: rgba(255, 255, 255, 0.5); } + .carousel-indicators li::before { + position: absolute; + top: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; } + .carousel-indicators li::after { + position: absolute; + bottom: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; } + .carousel-indicators .active { + background-color: #fff; } + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; } + +.align-baseline { + vertical-align: baseline !important; } + +.align-top { + vertical-align: top !important; } + +.align-middle { + vertical-align: middle !important; } + +.align-bottom { + vertical-align: bottom !important; } + +.align-text-bottom { + vertical-align: text-bottom !important; } + +.align-text-top { + vertical-align: text-top !important; } + +.bg-primary { + background-color: #1c3e5c !important; } + +a.bg-primary:hover, a.bg-primary:focus, +button.bg-primary:hover, +button.bg-primary:focus { + background-color: #102435 !important; } + +.bg-secondary { + background-color: #b6b6ba !important; } + +a.bg-secondary:hover, a.bg-secondary:focus, +button.bg-secondary:hover, +button.bg-secondary:focus { + background-color: #9c9ca1 !important; } + +.bg-success { + background-color: #28a745 !important; } + +a.bg-success:hover, a.bg-success:focus, +button.bg-success:hover, +button.bg-success:focus { + background-color: #1e7e34 !important; } + +.bg-info { + background-color: #17a2b8 !important; } + +a.bg-info:hover, a.bg-info:focus, +button.bg-info:hover, +button.bg-info:focus { + background-color: #117a8b !important; } + +.bg-warning { + background-color: #fbfb83 !important; } + +a.bg-warning:hover, a.bg-warning:focus, +button.bg-warning:hover, +button.bg-warning:focus { + background-color: #f9f952 !important; } + +.bg-danger { + background-color: #dc3545 !important; } + +a.bg-danger:hover, a.bg-danger:focus, +button.bg-danger:hover, +button.bg-danger:focus { + background-color: #bd2130 !important; } + +.bg-light { + background-color: #f8f9fa !important; } + +a.bg-light:hover, a.bg-light:focus, +button.bg-light:hover, +button.bg-light:focus { + background-color: #dae0e5 !important; } + +.bg-dark { + background-color: #343a40 !important; } + +a.bg-dark:hover, a.bg-dark:focus, +button.bg-dark:hover, +button.bg-dark:focus { + background-color: #1d2124 !important; } + +.bg-white { + background-color: #fff !important; } + +.bg-transparent { + background-color: transparent !important; } + +.border { + border: 1px solid #dee2e6 !important; } + +.border-top { + border-top: 1px solid #dee2e6 !important; } + +.border-right { + border-right: 1px solid #dee2e6 !important; } + +.border-bottom { + border-bottom: 1px solid #dee2e6 !important; } + +.border-left { + border-left: 1px solid #dee2e6 !important; } + +.border-0 { + border: 0 !important; } + +.border-top-0 { + border-top: 0 !important; } + +.border-right-0 { + border-right: 0 !important; } + +.border-bottom-0 { + border-bottom: 0 !important; } + +.border-left-0 { + border-left: 0 !important; } + +.border-primary { + border-color: #1c3e5c !important; } + +.border-secondary { + border-color: #b6b6ba !important; } + +.border-success { + border-color: #28a745 !important; } + +.border-info { + border-color: #17a2b8 !important; } + +.border-warning { + border-color: #fbfb83 !important; } + +.border-danger { + border-color: #dc3545 !important; } + +.border-light { + border-color: #f8f9fa !important; } + +.border-dark { + border-color: #343a40 !important; } + +.border-white { + border-color: #fff !important; } + +.rounded { + border-radius: 0.25rem !important; } + +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; } + +.rounded-right { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; } + +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; } + +.rounded-left { + border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; } + +.rounded-circle { + border-radius: 50% !important; } + +.rounded-0 { + border-radius: 0 !important; } + +.clearfix::after { + display: block; + clear: both; + content: ""; } + +.d-none { + display: none !important; } + +.d-inline { + display: inline !important; } + +.d-inline-block { + display: inline-block !important; } + +.d-block { + display: block !important; } + +.d-table { + display: table !important; } + +.d-table-row { + display: table-row !important; } + +.d-table-cell { + display: table-cell !important; } + +.d-flex { + display: flex !important; } + +.d-inline-flex { + display: inline-flex !important; } + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; } + .d-sm-inline { + display: inline !important; } + .d-sm-inline-block { + display: inline-block !important; } + .d-sm-block { + display: block !important; } + .d-sm-table { + display: table !important; } + .d-sm-table-row { + display: table-row !important; } + .d-sm-table-cell { + display: table-cell !important; } + .d-sm-flex { + display: flex !important; } + .d-sm-inline-flex { + display: inline-flex !important; } } + +@media (min-width: 768px) { + .d-md-none { + display: none !important; } + .d-md-inline { + display: inline !important; } + .d-md-inline-block { + display: inline-block !important; } + .d-md-block { + display: block !important; } + .d-md-table { + display: table !important; } + .d-md-table-row { + display: table-row !important; } + .d-md-table-cell { + display: table-cell !important; } + .d-md-flex { + display: flex !important; } + .d-md-inline-flex { + display: inline-flex !important; } } + +@media (min-width: 992px) { + .d-lg-none { + display: none !important; } + .d-lg-inline { + display: inline !important; } + .d-lg-inline-block { + display: inline-block !important; } + .d-lg-block { + display: block !important; } + .d-lg-table { + display: table !important; } + .d-lg-table-row { + display: table-row !important; } + .d-lg-table-cell { + display: table-cell !important; } + .d-lg-flex { + display: flex !important; } + .d-lg-inline-flex { + display: inline-flex !important; } } + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; } + .d-xl-inline { + display: inline !important; } + .d-xl-inline-block { + display: inline-block !important; } + .d-xl-block { + display: block !important; } + .d-xl-table { + display: table !important; } + .d-xl-table-row { + display: table-row !important; } + .d-xl-table-cell { + display: table-cell !important; } + .d-xl-flex { + display: flex !important; } + .d-xl-inline-flex { + display: inline-flex !important; } } + +@media print { + .d-print-none { + display: none !important; } + .d-print-inline { + display: inline !important; } + .d-print-inline-block { + display: inline-block !important; } + .d-print-block { + display: block !important; } + .d-print-table { + display: table !important; } + .d-print-table-row { + display: table-row !important; } + .d-print-table-cell { + display: table-cell !important; } + .d-print-flex { + display: flex !important; } + .d-print-inline-flex { + display: inline-flex !important; } } + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; } + .embed-responsive::before { + display: block; + content: ""; } + .embed-responsive .embed-responsive-item, + .embed-responsive iframe, + .embed-responsive embed, + .embed-responsive object, + .embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; } + +.embed-responsive-21by9::before { + padding-top: 42.85714%; } + +.embed-responsive-16by9::before { + padding-top: 56.25%; } + +.embed-responsive-4by3::before { + padding-top: 75%; } + +.embed-responsive-1by1::before { + padding-top: 100%; } + +.flex-row { + flex-direction: row !important; } + +.flex-column { + flex-direction: column !important; } + +.flex-row-reverse { + flex-direction: row-reverse !important; } + +.flex-column-reverse { + flex-direction: column-reverse !important; } + +.flex-wrap { + flex-wrap: wrap !important; } + +.flex-nowrap { + flex-wrap: nowrap !important; } + +.flex-wrap-reverse { + flex-wrap: wrap-reverse !important; } + +.flex-fill { + flex: 1 1 auto !important; } + +.flex-grow-0 { + flex-grow: 0 !important; } + +.flex-grow-1 { + flex-grow: 1 !important; } + +.flex-shrink-0 { + flex-shrink: 0 !important; } + +.flex-shrink-1 { + flex-shrink: 1 !important; } + +.justify-content-start { + justify-content: flex-start !important; } + +.justify-content-end { + justify-content: flex-end !important; } + +.justify-content-center { + justify-content: center !important; } + +.justify-content-between { + justify-content: space-between !important; } + +.justify-content-around { + justify-content: space-around !important; } + +.align-items-start { + align-items: flex-start !important; } + +.align-items-end { + align-items: flex-end !important; } + +.align-items-center { + align-items: center !important; } + +.align-items-baseline { + align-items: baseline !important; } + +.align-items-stretch { + align-items: stretch !important; } + +.align-content-start { + align-content: flex-start !important; } + +.align-content-end { + align-content: flex-end !important; } + +.align-content-center { + align-content: center !important; } + +.align-content-between { + align-content: space-between !important; } + +.align-content-around { + align-content: space-around !important; } + +.align-content-stretch { + align-content: stretch !important; } + +.align-self-auto { + align-self: auto !important; } + +.align-self-start { + align-self: flex-start !important; } + +.align-self-end { + align-self: flex-end !important; } + +.align-self-center { + align-self: center !important; } + +.align-self-baseline { + align-self: baseline !important; } + +.align-self-stretch { + align-self: stretch !important; } + +@media (min-width: 576px) { + .flex-sm-row { + flex-direction: row !important; } + .flex-sm-column { + flex-direction: column !important; } + .flex-sm-row-reverse { + flex-direction: row-reverse !important; } + .flex-sm-column-reverse { + flex-direction: column-reverse !important; } + .flex-sm-wrap { + flex-wrap: wrap !important; } + .flex-sm-nowrap { + flex-wrap: nowrap !important; } + .flex-sm-wrap-reverse { + flex-wrap: wrap-reverse !important; } + .flex-sm-fill { + flex: 1 1 auto !important; } + .flex-sm-grow-0 { + flex-grow: 0 !important; } + .flex-sm-grow-1 { + flex-grow: 1 !important; } + .flex-sm-shrink-0 { + flex-shrink: 0 !important; } + .flex-sm-shrink-1 { + flex-shrink: 1 !important; } + .justify-content-sm-start { + justify-content: flex-start !important; } + .justify-content-sm-end { + justify-content: flex-end !important; } + .justify-content-sm-center { + justify-content: center !important; } + .justify-content-sm-between { + justify-content: space-between !important; } + .justify-content-sm-around { + justify-content: space-around !important; } + .align-items-sm-start { + align-items: flex-start !important; } + .align-items-sm-end { + align-items: flex-end !important; } + .align-items-sm-center { + align-items: center !important; } + .align-items-sm-baseline { + align-items: baseline !important; } + .align-items-sm-stretch { + align-items: stretch !important; } + .align-content-sm-start { + align-content: flex-start !important; } + .align-content-sm-end { + align-content: flex-end !important; } + .align-content-sm-center { + align-content: center !important; } + .align-content-sm-between { + align-content: space-between !important; } + .align-content-sm-around { + align-content: space-around !important; } + .align-content-sm-stretch { + align-content: stretch !important; } + .align-self-sm-auto { + align-self: auto !important; } + .align-self-sm-start { + align-self: flex-start !important; } + .align-self-sm-end { + align-self: flex-end !important; } + .align-self-sm-center { + align-self: center !important; } + .align-self-sm-baseline { + align-self: baseline !important; } + .align-self-sm-stretch { + align-self: stretch !important; } } + +@media (min-width: 768px) { + .flex-md-row { + flex-direction: row !important; } + .flex-md-column { + flex-direction: column !important; } + .flex-md-row-reverse { + flex-direction: row-reverse !important; } + .flex-md-column-reverse { + flex-direction: column-reverse !important; } + .flex-md-wrap { + flex-wrap: wrap !important; } + .flex-md-nowrap { + flex-wrap: nowrap !important; } + .flex-md-wrap-reverse { + flex-wrap: wrap-reverse !important; } + .flex-md-fill { + flex: 1 1 auto !important; } + .flex-md-grow-0 { + flex-grow: 0 !important; } + .flex-md-grow-1 { + flex-grow: 1 !important; } + .flex-md-shrink-0 { + flex-shrink: 0 !important; } + .flex-md-shrink-1 { + flex-shrink: 1 !important; } + .justify-content-md-start { + justify-content: flex-start !important; } + .justify-content-md-end { + justify-content: flex-end !important; } + .justify-content-md-center { + justify-content: center !important; } + .justify-content-md-between { + justify-content: space-between !important; } + .justify-content-md-around { + justify-content: space-around !important; } + .align-items-md-start { + align-items: flex-start !important; } + .align-items-md-end { + align-items: flex-end !important; } + .align-items-md-center { + align-items: center !important; } + .align-items-md-baseline { + align-items: baseline !important; } + .align-items-md-stretch { + align-items: stretch !important; } + .align-content-md-start { + align-content: flex-start !important; } + .align-content-md-end { + align-content: flex-end !important; } + .align-content-md-center { + align-content: center !important; } + .align-content-md-between { + align-content: space-between !important; } + .align-content-md-around { + align-content: space-around !important; } + .align-content-md-stretch { + align-content: stretch !important; } + .align-self-md-auto { + align-self: auto !important; } + .align-self-md-start { + align-self: flex-start !important; } + .align-self-md-end { + align-self: flex-end !important; } + .align-self-md-center { + align-self: center !important; } + .align-self-md-baseline { + align-self: baseline !important; } + .align-self-md-stretch { + align-self: stretch !important; } } + +@media (min-width: 992px) { + .flex-lg-row { + flex-direction: row !important; } + .flex-lg-column { + flex-direction: column !important; } + .flex-lg-row-reverse { + flex-direction: row-reverse !important; } + .flex-lg-column-reverse { + flex-direction: column-reverse !important; } + .flex-lg-wrap { + flex-wrap: wrap !important; } + .flex-lg-nowrap { + flex-wrap: nowrap !important; } + .flex-lg-wrap-reverse { + flex-wrap: wrap-reverse !important; } + .flex-lg-fill { + flex: 1 1 auto !important; } + .flex-lg-grow-0 { + flex-grow: 0 !important; } + .flex-lg-grow-1 { + flex-grow: 1 !important; } + .flex-lg-shrink-0 { + flex-shrink: 0 !important; } + .flex-lg-shrink-1 { + flex-shrink: 1 !important; } + .justify-content-lg-start { + justify-content: flex-start !important; } + .justify-content-lg-end { + justify-content: flex-end !important; } + .justify-content-lg-center { + justify-content: center !important; } + .justify-content-lg-between { + justify-content: space-between !important; } + .justify-content-lg-around { + justify-content: space-around !important; } + .align-items-lg-start { + align-items: flex-start !important; } + .align-items-lg-end { + align-items: flex-end !important; } + .align-items-lg-center { + align-items: center !important; } + .align-items-lg-baseline { + align-items: baseline !important; } + .align-items-lg-stretch { + align-items: stretch !important; } + .align-content-lg-start { + align-content: flex-start !important; } + .align-content-lg-end { + align-content: flex-end !important; } + .align-content-lg-center { + align-content: center !important; } + .align-content-lg-between { + align-content: space-between !important; } + .align-content-lg-around { + align-content: space-around !important; } + .align-content-lg-stretch { + align-content: stretch !important; } + .align-self-lg-auto { + align-self: auto !important; } + .align-self-lg-start { + align-self: flex-start !important; } + .align-self-lg-end { + align-self: flex-end !important; } + .align-self-lg-center { + align-self: center !important; } + .align-self-lg-baseline { + align-self: baseline !important; } + .align-self-lg-stretch { + align-self: stretch !important; } } + +@media (min-width: 1200px) { + .flex-xl-row { + flex-direction: row !important; } + .flex-xl-column { + flex-direction: column !important; } + .flex-xl-row-reverse { + flex-direction: row-reverse !important; } + .flex-xl-column-reverse { + flex-direction: column-reverse !important; } + .flex-xl-wrap { + flex-wrap: wrap !important; } + .flex-xl-nowrap { + flex-wrap: nowrap !important; } + .flex-xl-wrap-reverse { + flex-wrap: wrap-reverse !important; } + .flex-xl-fill { + flex: 1 1 auto !important; } + .flex-xl-grow-0 { + flex-grow: 0 !important; } + .flex-xl-grow-1 { + flex-grow: 1 !important; } + .flex-xl-shrink-0 { + flex-shrink: 0 !important; } + .flex-xl-shrink-1 { + flex-shrink: 1 !important; } + .justify-content-xl-start { + justify-content: flex-start !important; } + .justify-content-xl-end { + justify-content: flex-end !important; } + .justify-content-xl-center { + justify-content: center !important; } + .justify-content-xl-between { + justify-content: space-between !important; } + .justify-content-xl-around { + justify-content: space-around !important; } + .align-items-xl-start { + align-items: flex-start !important; } + .align-items-xl-end { + align-items: flex-end !important; } + .align-items-xl-center { + align-items: center !important; } + .align-items-xl-baseline { + align-items: baseline !important; } + .align-items-xl-stretch { + align-items: stretch !important; } + .align-content-xl-start { + align-content: flex-start !important; } + .align-content-xl-end { + align-content: flex-end !important; } + .align-content-xl-center { + align-content: center !important; } + .align-content-xl-between { + align-content: space-between !important; } + .align-content-xl-around { + align-content: space-around !important; } + .align-content-xl-stretch { + align-content: stretch !important; } + .align-self-xl-auto { + align-self: auto !important; } + .align-self-xl-start { + align-self: flex-start !important; } + .align-self-xl-end { + align-self: flex-end !important; } + .align-self-xl-center { + align-self: center !important; } + .align-self-xl-baseline { + align-self: baseline !important; } + .align-self-xl-stretch { + align-self: stretch !important; } } + +.float-left { + float: left !important; } + +.float-right { + float: right !important; } + +.float-none { + float: none !important; } + +@media (min-width: 576px) { + .float-sm-left { + float: left !important; } + .float-sm-right { + float: right !important; } + .float-sm-none { + float: none !important; } } + +@media (min-width: 768px) { + .float-md-left { + float: left !important; } + .float-md-right { + float: right !important; } + .float-md-none { + float: none !important; } } + +@media (min-width: 992px) { + .float-lg-left { + float: left !important; } + .float-lg-right { + float: right !important; } + .float-lg-none { + float: none !important; } } + +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; } + .float-xl-right { + float: right !important; } + .float-xl-none { + float: none !important; } } + +.position-static { + position: static !important; } + +.position-relative { + position: relative !important; } + +.position-absolute { + position: absolute !important; } + +.position-fixed { + position: fixed !important; } + +.position-sticky { + position: sticky !important; } + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; } + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; } + +@supports (position: sticky) { + .sticky-top { + position: sticky; + top: 0; + z-index: 1020; } } + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; } + +.shadow-sm { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; } + +.shadow { + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; } + +.shadow-lg { + box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; } + +.shadow-none { + box-shadow: none !important; } + +.w-25 { + width: 25% !important; } + +.w-50 { + width: 50% !important; } + +.w-75 { + width: 75% !important; } + +.w-100 { + width: 100% !important; } + +.w-auto { + width: auto !important; } + +.h-25 { + height: 25% !important; } + +.h-50 { + height: 50% !important; } + +.h-75 { + height: 75% !important; } + +.h-100 { + height: 100% !important; } + +.h-auto { + height: auto !important; } + +.mw-100 { + max-width: 100% !important; } + +.mh-100 { + max-height: 100% !important; } + +.m-0 { + margin: 0 !important; } + +.mt-0, +.my-0 { + margin-top: 0 !important; } + +.mr-0, +.mx-0 { + margin-right: 0 !important; } + +.mb-0, +.my-0 { + margin-bottom: 0 !important; } + +.ml-0, +.mx-0 { + margin-left: 0 !important; } + +.m-1 { + margin: 0.25rem !important; } + +.mt-1, +.my-1 { + margin-top: 0.25rem !important; } + +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; } + +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; } + +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; } + +.m-2 { + margin: 0.5rem !important; } + +.mt-2, +.my-2 { + margin-top: 0.5rem !important; } + +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; } + +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; } + +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; } + +.m-3 { + margin: 1rem !important; } + +.mt-3, +.my-3 { + margin-top: 1rem !important; } + +.mr-3, +.mx-3 { + margin-right: 1rem !important; } + +.mb-3, +.my-3 { + margin-bottom: 1rem !important; } + +.ml-3, +.mx-3 { + margin-left: 1rem !important; } + +.m-4 { + margin: 1.5rem !important; } + +.mt-4, +.my-4 { + margin-top: 1.5rem !important; } + +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; } + +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; } + +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; } + +.m-5 { + margin: 3rem !important; } + +.mt-5, +.my-5 { + margin-top: 3rem !important; } + +.mr-5, +.mx-5 { + margin-right: 3rem !important; } + +.mb-5, +.my-5 { + margin-bottom: 3rem !important; } + +.ml-5, +.mx-5 { + margin-left: 3rem !important; } + +.p-0 { + padding: 0 !important; } + +.pt-0, +.py-0 { + padding-top: 0 !important; } + +.pr-0, +.px-0 { + padding-right: 0 !important; } + +.pb-0, +.py-0 { + padding-bottom: 0 !important; } + +.pl-0, +.px-0 { + padding-left: 0 !important; } + +.p-1 { + padding: 0.25rem !important; } + +.pt-1, +.py-1 { + padding-top: 0.25rem !important; } + +.pr-1, +.px-1 { + padding-right: 0.25rem !important; } + +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; } + +.pl-1, +.px-1 { + padding-left: 0.25rem !important; } + +.p-2 { + padding: 0.5rem !important; } + +.pt-2, +.py-2 { + padding-top: 0.5rem !important; } + +.pr-2, +.px-2 { + padding-right: 0.5rem !important; } + +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; } + +.pl-2, +.px-2 { + padding-left: 0.5rem !important; } + +.p-3 { + padding: 1rem !important; } + +.pt-3, +.py-3 { + padding-top: 1rem !important; } + +.pr-3, +.px-3 { + padding-right: 1rem !important; } + +.pb-3, +.py-3 { + padding-bottom: 1rem !important; } + +.pl-3, +.px-3 { + padding-left: 1rem !important; } + +.p-4 { + padding: 1.5rem !important; } + +.pt-4, +.py-4 { + padding-top: 1.5rem !important; } + +.pr-4, +.px-4 { + padding-right: 1.5rem !important; } + +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; } + +.pl-4, +.px-4 { + padding-left: 1.5rem !important; } + +.p-5 { + padding: 3rem !important; } + +.pt-5, +.py-5 { + padding-top: 3rem !important; } + +.pr-5, +.px-5 { + padding-right: 3rem !important; } + +.pb-5, +.py-5 { + padding-bottom: 3rem !important; } + +.pl-5, +.px-5 { + padding-left: 3rem !important; } + +.m-auto { + margin: auto !important; } + +.mt-auto, +.my-auto { + margin-top: auto !important; } + +.mr-auto, +.mx-auto { + margin-right: auto !important; } + +.mb-auto, +.my-auto { + margin-bottom: auto !important; } + +.ml-auto, +.mx-auto { + margin-left: auto !important; } + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; } + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; } + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; } + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; } + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; } + .m-sm-1 { + margin: 0.25rem !important; } + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; } + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; } + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; } + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; } + .m-sm-2 { + margin: 0.5rem !important; } + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; } + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; } + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; } + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; } + .m-sm-3 { + margin: 1rem !important; } + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; } + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; } + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; } + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; } + .m-sm-4 { + margin: 1.5rem !important; } + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; } + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; } + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; } + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; } + .m-sm-5 { + margin: 3rem !important; } + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; } + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; } + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; } + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; } + .p-sm-0 { + padding: 0 !important; } + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; } + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; } + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; } + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; } + .p-sm-1 { + padding: 0.25rem !important; } + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; } + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; } + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; } + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; } + .p-sm-2 { + padding: 0.5rem !important; } + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; } + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; } + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; } + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; } + .p-sm-3 { + padding: 1rem !important; } + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; } + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; } + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; } + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; } + .p-sm-4 { + padding: 1.5rem !important; } + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; } + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; } + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; } + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; } + .p-sm-5 { + padding: 3rem !important; } + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; } + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; } + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; } + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; } + .m-sm-auto { + margin: auto !important; } + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; } + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; } + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; } + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; } } + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; } + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; } + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; } + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; } + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; } + .m-md-1 { + margin: 0.25rem !important; } + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; } + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; } + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; } + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; } + .m-md-2 { + margin: 0.5rem !important; } + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; } + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; } + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; } + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; } + .m-md-3 { + margin: 1rem !important; } + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; } + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; } + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; } + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; } + .m-md-4 { + margin: 1.5rem !important; } + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; } + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; } + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; } + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; } + .m-md-5 { + margin: 3rem !important; } + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; } + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; } + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; } + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; } + .p-md-0 { + padding: 0 !important; } + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; } + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; } + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; } + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; } + .p-md-1 { + padding: 0.25rem !important; } + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; } + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; } + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; } + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; } + .p-md-2 { + padding: 0.5rem !important; } + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; } + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; } + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; } + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; } + .p-md-3 { + padding: 1rem !important; } + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; } + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; } + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; } + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; } + .p-md-4 { + padding: 1.5rem !important; } + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; } + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; } + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; } + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; } + .p-md-5 { + padding: 3rem !important; } + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; } + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; } + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; } + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; } + .m-md-auto { + margin: auto !important; } + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; } + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; } + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; } + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; } } + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; } + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; } + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; } + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; } + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; } + .m-lg-1 { + margin: 0.25rem !important; } + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; } + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; } + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; } + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; } + .m-lg-2 { + margin: 0.5rem !important; } + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; } + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; } + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; } + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; } + .m-lg-3 { + margin: 1rem !important; } + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; } + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; } + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; } + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; } + .m-lg-4 { + margin: 1.5rem !important; } + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; } + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; } + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; } + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; } + .m-lg-5 { + margin: 3rem !important; } + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; } + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; } + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; } + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; } + .p-lg-0 { + padding: 0 !important; } + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; } + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; } + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; } + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; } + .p-lg-1 { + padding: 0.25rem !important; } + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; } + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; } + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; } + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; } + .p-lg-2 { + padding: 0.5rem !important; } + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; } + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; } + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; } + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; } + .p-lg-3 { + padding: 1rem !important; } + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; } + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; } + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; } + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; } + .p-lg-4 { + padding: 1.5rem !important; } + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; } + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; } + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; } + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; } + .p-lg-5 { + padding: 3rem !important; } + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; } + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; } + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; } + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; } + .m-lg-auto { + margin: auto !important; } + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; } + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; } + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; } + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; } } + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; } + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; } + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; } + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; } + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; } + .m-xl-1 { + margin: 0.25rem !important; } + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; } + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; } + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; } + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; } + .m-xl-2 { + margin: 0.5rem !important; } + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; } + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; } + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; } + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; } + .m-xl-3 { + margin: 1rem !important; } + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; } + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; } + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; } + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; } + .m-xl-4 { + margin: 1.5rem !important; } + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; } + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; } + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; } + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; } + .m-xl-5 { + margin: 3rem !important; } + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; } + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; } + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; } + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; } + .p-xl-0 { + padding: 0 !important; } + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; } + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; } + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; } + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; } + .p-xl-1 { + padding: 0.25rem !important; } + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; } + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; } + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; } + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; } + .p-xl-2 { + padding: 0.5rem !important; } + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; } + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; } + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; } + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; } + .p-xl-3 { + padding: 1rem !important; } + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; } + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; } + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; } + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; } + .p-xl-4 { + padding: 1.5rem !important; } + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; } + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; } + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; } + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; } + .p-xl-5 { + padding: 3rem !important; } + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; } + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; } + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; } + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; } + .m-xl-auto { + margin: auto !important; } + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; } + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; } + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; } + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; } } + +.text-monospace { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } + +.text-justify { + text-align: justify !important; } + +.text-nowrap { + white-space: nowrap !important; } + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + +.text-left { + text-align: left !important; } + +.text-right { + text-align: right !important; } + +.text-center { + text-align: center !important; } + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; } + .text-sm-right { + text-align: right !important; } + .text-sm-center { + text-align: center !important; } } + +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; } + .text-md-right { + text-align: right !important; } + .text-md-center { + text-align: center !important; } } + +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; } + .text-lg-right { + text-align: right !important; } + .text-lg-center { + text-align: center !important; } } + +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; } + .text-xl-right { + text-align: right !important; } + .text-xl-center { + text-align: center !important; } } + +.text-lowercase { + text-transform: lowercase !important; } + +.text-uppercase { + text-transform: uppercase !important; } + +.text-capitalize { + text-transform: capitalize !important; } + +.font-weight-light { + font-weight: 300 !important; } + +.font-weight-normal { + font-weight: 400 !important; } + +.font-weight-bold { + font-weight: 700 !important; } + +.font-italic { + font-style: italic !important; } + +.text-white { + color: #fff !important; } + +.text-primary { + color: #1c3e5c !important; } + +a.text-primary:hover, a.text-primary:focus { + color: #102435 !important; } + +.text-secondary { + color: #b6b6ba !important; } + +a.text-secondary:hover, a.text-secondary:focus { + color: #9c9ca1 !important; } + +.text-success { + color: #28a745 !important; } + +a.text-success:hover, a.text-success:focus { + color: #1e7e34 !important; } + +.text-info { + color: #17a2b8 !important; } + +a.text-info:hover, a.text-info:focus { + color: #117a8b !important; } + +.text-warning { + color: #fbfb83 !important; } + +a.text-warning:hover, a.text-warning:focus { + color: #f9f952 !important; } + +.text-danger { + color: #dc3545 !important; } + +a.text-danger:hover, a.text-danger:focus { + color: #bd2130 !important; } + +.text-light { + color: #f8f9fa !important; } + +a.text-light:hover, a.text-light:focus { + color: #dae0e5 !important; } + +.text-dark { + color: #343a40 !important; } + +a.text-dark:hover, a.text-dark:focus { + color: #1d2124 !important; } + +.text-body { + color: #212529 !important; } + +.text-muted { + color: #6c757d !important; } + +.text-black-50 { + color: rgba(0, 0, 0, 0.5) !important; } + +.text-white-50 { + color: rgba(255, 255, 255, 0.5) !important; } + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; } + +.visible { + visibility: visible !important; } + +.invisible { + visibility: hidden !important; } + +@media print { + *, + *::before, + *::after { + text-shadow: none !important; + box-shadow: none !important; } + a:not(.btn) { + text-decoration: underline; } + abbr[title]::after { + content: " (" attr(title) ")"; } + pre { + white-space: pre-wrap !important; } + pre, + blockquote { + border: 1px solid #adb5bd; + page-break-inside: avoid; } + thead { + display: table-header-group; } + tr, + img { + page-break-inside: avoid; } + p, + h2, + h3 { + orphans: 3; + widows: 3; } + h2, + h3 { + page-break-after: avoid; } + @page { + size: a3; } + body { + min-width: 992px !important; } + .container { + min-width: 992px !important; } + .navbar { + display: none; } + .badge { + border: 1px solid #000; } + .table { + border-collapse: collapse !important; } + .table td, + .table th { + background-color: #fff !important; } + .table-bordered th, + .table-bordered td { + border: 1px solid #dee2e6 !important; } + .table-dark { + color: inherit; } + .table-dark th, + .table-dark td, + .table-dark thead th, + .table-dark tbody + tbody { + border-color: #dee2e6; } + .table .thead-dark th { + color: inherit; + border-color: #dee2e6; } } + +/*.react-grid-item:not(.react-grid-placeholder) {*/ +/* COLORS */ +/* Color palette interface (created with https://material.io/tools/color/) */ +/* font color */ +/* font color */ +/* hover color, e.g. for background */ +/* Data colors */ +body { + margin: 0; + padding: 0; } + +/* For alt text */ +img { + font-weight: 400 !important; } + +.hilite-serious { + background-color: #f17171; + color: white; } + +.hilite-alarming { + background-color: #ffcd74; + color: black; } + +.hilite-warning { + background-color: #fbfb83; + color: black; } + +.hilite-good { + background-color: #6fbd6f; + color: white; } + +.nowrap { + white-space: nowrap; } + +.react-grid-item { + background-color: white; + border: 1px solid #e8e8ec; + border-radius: 0.25rem; } + +.react-grid-item-header { + color: #86868a; + background-color: #e8e8ec; + font-weight: bold; + padding: .3rem; + /* Note: must be same as the Bootstrap table for nice alignment */ + margin-bottom: 0; + height: 2rem; } + +.react-grid-item-body { + height: calc(100% - 2rem); + overflow: auto; + position: relative; } + +.react-grid-item > .react-resizable-handle::after { + border-color: #bce8f1; } + +/* Reacstrap only support sm, md and lg */ +.btn-group-xs > .btn, +.btn.btn-xs { + padding: .25rem .5rem; + font-size: .875rem; + line-height: 1.0; + border-radius: .2rem; } + +.popover { + max-width: 40em !important; + /* Max Width of the popover (depending on the container!) */ } + +.popover-header { + text-align: center; + font-weight: 700; } + +.tooltip > .tooltip-inner { + background-color: white !important; + color: black !important; + border: 1px solid #86868a; } + +.react-select-container { + min-width: 20rem; } + +.form-input { + display: inline-block; } + +.form-input button { + font-size: .8rem; + background-color: white !important; + color: black !important; + border: none; } + +.form-input button:hover { + background-color: #e8e8ec !important; } + +/* Padding on column in bootstrap grid */ +.row .col-padding { + padding-top: 15px; } + +/* COLORS */ +/* Color palette interface (created with https://material.io/tools/color/) */ +/* font color */ +/* font color */ +/* hover color, e.g. for background */ +/* Data colors */ +.rtsm_summary_row { + background-color: #eeeeee; } + +.rtsm_summary_row .row_header { + width: 12rem !important; + min-width: 12rem !important; + cursor: pointer; } + +@keyframes animation-open { + from { + transform: rotate(0deg); } + to { + transform: rotate(180deg); } } + +@keyframes animation-close { + from { + transform: rotate(180deg); } + to { + transform: rotate(0deg); } } + +.rtsm_summary_row .dropdownbutton { + display: inline; + float: right; + animation: animation-close; + animation-duration: 100ms; + animation-iteration-count: 1; + animation-timing-function: linear; } + +.rtsm_summary_row .dropdownbutton_up { + transform: rotate(180deg); + animation: animation-open; + animation-duration: 100ms; + animation-iteration-count: 1; + animation-timing-function: linear; } + +.rtsm_summary_badge { + background-color: #fbfb83; + color: black; + text-shadow: 1px 2px white; + font-size: 80%; + text-align: center; } + +/* .App { text-align: center; } @@ -26,3 +6490,4 @@ from { transform: rotate(0deg); } to { transform: rotate(360deg); } } +*/ diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.js index 5c4c238b5b2a2972d80a129fb1112b7519637c69..84327cf3fd50230064b8e4c327bef66ac48d3ef5 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.js @@ -1,11 +1,14 @@ import React, { Component } from 'react'; -import LandingPage from './pages/LandingPage.js' -import StationOverviewPage from './pages/StationOverviewPage.js' -import TilesPage from './pages/TilesPage.js' -import DetailsPage from './pages/DetailsPage.js' + +//Place on top to get the correct order of loading +import './App.css'; + +import LandingPage from 'pages/LandingPage' +import StationOverviewPage from 'pages/StationOverviewPage' +import TilesPage from 'pages/TilesPage' import { connect } from "react-redux"; -import { fetchErrorTypes, fetchStations } from "./redux/actions/appInitDataActions.js"; +import { fetchErrorTypes, fetchStations } from "redux/actions/appInitDataActions.js"; import { //BrowserRouter as Router, @@ -13,9 +16,8 @@ import { Switch } from 'react-router-dom'; import { ConnectedRouter as Router } from 'connected-react-router'; -import { history } from "./redux/store.js"; +import { history } from "redux/store.js"; -import './App.css'; class AppC extends Component { @@ -34,7 +36,6 @@ class AppC extends Component { <Route exact path="/" component={LandingPage}/> <Route path="/station_overview/:name?" component={StationOverviewPage}/> <Route exact path="/tiles/:antenna?" component={TilesPage}/> - <Route exact path="/details" component={DetailsPage}/> </Switch> </div> </Router> diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.scss new file mode 100644 index 0000000000000000000000000000000000000000..5b06a7036ec18ed85312c0050c4bf199d257703f --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/App.scss @@ -0,0 +1,33 @@ + +@import "themes/lofar.scss"; + + +/* +.App { + text-align: center; +} + +.App-logo { + animation: App-logo-spin infinite 20s linear; + height: 80px; +} + +.App-header { + background-color: #0c89b8; + padding: 20px; + color: white; +} + +.App-title { + font-size: 1.5em; +} + +.App-intro { + font-size: large; +} + +@keyframes App-logo-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} +*/ diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaView.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaView.js deleted file mode 100644 index 9fe8768a0c929773dda40cf8ad24fb8f23a9e4ef..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaView.js +++ /dev/null @@ -1,352 +0,0 @@ -import React, { - Component -} from 'react'; - -import ReactTableContainer from 'react-table-container'; -import AutoLoadWrapper from '../utils/autoLoader.js'; -import {connect} from 'react-redux'; -import {componentErrorTypes, datetime_format} from "../utils/constants.js"; -import moment from 'moment'; -import { IoMdArrowDropdown as DropDownIcon} from 'react-icons/io'; - -import {selectAntennaError, pinAntennaError} from '../redux/actions/antennaOverviewPageActions'; -import {renderDateRange} from '../utils/utils'; - -import classnames from 'classnames'; - -// CSS -import './StationOverview.scss'; -import './StationTestView.scss'; -import FillHeight from './FillHeight.js'; - - -class GenericStatusC extends Component { - - mouseOver = () => { - if (this.props.data !== undefined && this.props.isPinned === false) { - this.props.selectAntennaError(this.props.data); - } - } - - mouseOut = () => { - if (this.props.data !== undefined && this.props.isPinned === false) { - this.props.selectAntennaError({}); - } - } - - renderStationTest() { - const label = this.props.isGood ? '' : 'X'; - return (<React.Fragment>{label}</React.Fragment>); - } - - renderRTSM() { - const xPolStyle = { - float: 'left', - position: 'relative', - top: '-0.3rem' - }; - const yPolStyle = { - float: 'right', - position: 'relative', - right: '0.rem', - bottom: '-.2rem' - }; - - - const xPol = ( - <div style={xPolStyle} key={'X'}>X</div> - ); - const yPol = ( - <div style={yPolStyle} key={'Y'}>Y</div> - ); - let presentErrors = []; - const errorsPerPolarization = this.props.data.content; - if (errorsPerPolarization.hasOwnProperty('X')) presentErrors.push(xPol); - if (errorsPerPolarization.hasOwnProperty('Y')) presentErrors.push(yPol); - - return <div>{presentErrors}</div>; - } - - renderError() { - switch (this.props.data.test_type) { - case 'R': - return this.renderRTSM(); - case 'S': - return this.renderStationTest(); - default: - return - } - } - - onContextMenu = (e) => { - e.preventDefault(); - if (this.props.data !== undefined) { - this.props.selectAntennaError(this.props.data); - this.props.pinAntennaError(true); - } - } - - render() { - - const color = this.props.isGood ? "so-good" : "so-serious"; - const label = this.props.isGood ? "" : this.renderError(); - return ( - <td className={"stv-component-status " + color} - onContextMenu={this.onContextMenu} - onMouseOver={this.mouseOver} - onMouseOut={this.mouseOut}> - {label} - </td>); - } -} - -// TestLine is connected to Redux store -const GenericStatus = connect(state => { - return { - ...state.antenna_page.main_panel, - ...state.antenna_page.child_panel - }; -}, { - selectAntennaError, - pinAntennaError -})(GenericStatusC); - - -class AntennaErrorLineC extends Component { - - state = { - isCollapsed: true - } - - static composeHeaderLine(data) { - return (<React.Fragment> - <th>{data.test_type}</th> - <th>{moment(data.start_date).format(datetime_format)}</th> - </React.Fragment>); - } - - - - composeTestLine(data) { - let line = []; - let element = {}; - let component_errors; - component_errors = data.component_errors; - - for (let i = 0; i < this.props.errorTypes.length; i++) { - const error_type = this.props.errorTypes[i]; - if (component_errors && component_errors.hasOwnProperty(error_type)) { - const component_errors_per_type = component_errors[error_type]; - - if (component_errors_per_type.hasOwnProperty('element_errors')) { - for (let element_id of Object.keys(component_errors_per_type.element_errors)) { - if (!element.hasOwnProperty(element_id)) element[element_id] = []; - element[element_id].push({ - 'error type': error_type, - 'content': component_errors_per_type.element_errors[element_id] - }) - } - } - line.push(<GenericStatus isGood={false} - key={i} data={{ - test_type: data.test_type, - start_date: data.start_date, - end_date: data.end_date, - - error_type: error_type, - content: component_errors_per_type - }}/>) - } else { - line.push(<GenericStatus key={i} isGood={true}/>) - } - - - } - line.push(<td key="space"></td>); - for (let i = 1; i <= 16; i++) { - if (element.hasOwnProperty(i)) { - const errorType = element[i].length > 1 ? 'Multiple': element[i][0]['error type']; - const elementErrors = {}; - elementErrors['details'] = {}; - if(element[i].length > 1){ - for(const key in element[i]){ - elementErrors.details[`Error #${key}`] = element[i][key] - } - }else{ - elementErrors.details = {...element[i][0]} - } - - line.push(<GenericStatus key={`element${i}`} isGood={false} - data={ - { - test_type: data.test_type, - start_date: data.start_date, - end_date: data.end_date, - error_type: errorType, - element_id: i, - content: elementErrors - } - }/>); - - } else if (this.props.data.test_type === 'S') { - line.push(<GenericStatus key={`element${i}`} isGood={true}/>); - } - } - - return line - } - - dropdownClick = () => { - this.setState({isCollapsed: !this.state.isCollapsed}) - this.props.update() - } - - renderCollapsedLine() { - const errorTypes = this.props.errorTypes; - const errors = this.props.data; - const totalNumberOfErrors = errors.length; - const timeSpan = renderDateRange(errors); - const summary = {}; - for (const type of errorTypes) { - for (const error of errors) { - if (error.component_errors.hasOwnProperty(type)) { - if (!summary.hasOwnProperty(type)) summary[type] = 0.; - summary[type] += 1.; - } - } - } - const summaryItems = errorTypes.map(item => { - if (summary.hasOwnProperty(item)) { - return <td key={`element-${item}`} - className="stv-rtsm-summary-badge">{(summary[item] * 100 / totalNumberOfErrors).toFixed(0)}%</td> - } else { - return <td key={`element-${item}`}></td> - } - }); - const dropdownClass = classnames('dropdownbutton', {'dropdownbutton-up': !this.state.isCollapsed}); - return <tr className={'stv-rtsm-summary-row'}> - <td >R</td> - <td onClick={this.dropdownClick}>{timeSpan} - <DropDownIcon className={dropdownClass}/> - </td> - {summaryItems}</tr> - } - - renderAllLines() { - return this.props.data.map((test, id) => - <tr key={id}>{AntennaErrorLineC.composeHeaderLine(test)}{this.composeTestLine(test)}</tr>); - - } - - renderMultilineSummary() { - - const summaryLine = this.renderCollapsedLine(); - let additionalLines; - if (!this.state.isCollapsed) { - additionalLines = this.renderAllLines(); - } - return <React.Fragment>{summaryLine}{additionalLines}</React.Fragment> - } - - render() { - const data = this.props.data; - if (data.hasOwnProperty('test_type')) { - return <React.Fragment> - <tr>{AntennaErrorLineC.composeHeaderLine(data)}{this.composeTestLine(data)}</tr> - </React.Fragment>; - } else { - return <React.Fragment>{this.renderMultilineSummary()}</React.Fragment>; - } - } -} - -const AntennaErrorLine = connect(state => { - return { - ...state.mainFilters, - ...state.appInitData - }; -})(AntennaErrorLineC); - - -class AntennaViewC extends Component { - - renderHeader() { - const errors_columns = this.props.errorTypes.map((error_type, key) => { - return (<td key={key} title={error_type}>{componentErrorTypes[error_type]}</td>) - }); - - const element_ids = []; - for (let i = 1; i <= 16; i++) element_ids.push(<td key={i}>{i}</td>); - return <tr> - <th title="Test type">T</th> - <th style={{width: "10em"}}>Date</th> - {errors_columns} - <th></th> - {element_ids} - </tr>; - }; - - static groupData(data) { - let grouped = []; - let group = []; - for (let test of data) { - - if (test.test_type !== 'R') { - if (group.length > 0) { - grouped.push(group); - group = []; - } - grouped.push(test) - } else { - group.push(test) - } - } - if (group.length > 0) grouped.push(group); - - return grouped - } - - updateIfContentChanges = () => { - this.setState({state:this.state}) - } - - renderBody() { - if (this.props.data.hasOwnProperty('errors')) { - let groupedData = AntennaViewC.groupData(this.props.data.errors); - return groupedData.map((test, id) => <AntennaErrorLine key={id} data={test} update={this.updateIfContentChanges}/>); - } - - - } - - render() { - - const jsx = ( - <ReactTableContainer width="100%" height={this.props.height+'px'}> - <table className="stv-table table-sm table-hover table-bordered"> - <thead className={"stv-tableheader"} >{this.renderHeader()}</thead> - <tbody>{this.renderBody()}</tbody> - </table> - </ReactTableContainer> - ); - return jsx; - } -} - -const AntennaViewContent = function(props){ - return ( - <FillHeight className="border-right"> - { ({height}) => <AntennaViewC {...props} height={height} />} - </FillHeight> - ) -} - -const AntennaViewController = connect(state => { - return { - ...state.mainFilters, - ...state.appInitData - }; -})(AntennaViewContent); -const AntennaView = AutoLoadWrapper(AntennaViewController); - -export default AntennaView; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/utils/autoLoader.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AutoLoadWrapper/index.js similarity index 66% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/utils/autoLoader.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AutoLoadWrapper/index.js index 29dfba285ad2998660421ddeb35bac548a4fe20c..aad8f7beb5efbcd0206f1c54dbb92602f945915e 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/utils/autoLoader.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AutoLoadWrapper/index.js @@ -3,12 +3,36 @@ import axios from 'axios'; import {Alert} from 'reactstrap'; +// CSS +import styles from './styles.module.scss' + +/** + * Spinner: simple component to show a CSS Spinner + */ function Spinner() { - return <div className="autoloader-loading"></div>; + return <div className={styles.autoloader_loading}></div>; } +/** + * ErrorAlert: simple component to show a error message + * @arg {string} message - The message text to display + */ +function ErrorAlert({message}) { + return <Alert className="py-1" color="danger"> + <strong>Error:</strong> {message} + </Alert> +} +/** + * AutoLoadWrapper: HOC for automatic reloading of data at a specified interval. + * See defaultProps fro accepted props, other props are passed through to then + * WrappedComponent. + * @param {class} WrappedComponent - The component to wrap. + * @returns {class} The wrapped component. + * @example + * const MyComponentWithLoader = AutoLoadWrapper(MyComponent); + */ function AutoLoadWrapper(WrappedComponent) { // Note: returns another component @@ -26,8 +50,9 @@ function AutoLoadWrapper(WrappedComponent) { } static defaultProps = { + addLoaderDiv: false, // wrap component in another <div> reloadInterval: 60000, - url: "" // When url = "" no request will be issued and data will be set to [] + url: "" // When url = "" no request will be issued and data will be set to [] } /* Called when props changed, before the render phase */ @@ -120,22 +145,24 @@ function AutoLoadWrapper(WrappedComponent) { } render() { - let loadingHtml = ""; - let errorHtml = ""; - if (this.state.isLoading) { - loadingHtml = <Spinner />; - } - if (this.state.hasError) { - errorHtml = <Alert className="py-1" color="danger"><strong>Error: </strong>{this.state.strError}</Alert>; + const { addLoaderDiv, ...otherProps } = this.props; + + let body = <React.Fragment> + {this.state.isLoading && <Spinner />} + {this.state.hasError && <ErrorAlert message={this.state.strError} />} + <WrappedComponent data={this.state.data} isLoading={this.state.isLoading} {...otherProps} /> + </React.Fragment> + + // Wrap in <div> when requested + if (addLoaderDiv) { + return( + <div className={styles.autoloader_container}> + {body} + </div> + ); } - return( - <div className="autoloader-container"> - {loadingHtml} - {errorHtml} - <WrappedComponent data={this.state.data} isLoading={this.state.isLoading} {...this.props} /> - </div> - ); + return body; } }; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AutoLoadWrapper/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AutoLoadWrapper/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..135cb1fdad406a817199257d3aadc178dc4f67aa --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AutoLoadWrapper/styles.module.scss @@ -0,0 +1,26 @@ +@import '../../themes/lofar-variables.scss'; + + +:local .autoloader_container { + position: relative; + /*height: 100%;*/ +} + +/* autoLoader; CSS spinner*/ +:local .autoloader_loading { + position: absolute; + top: 2.25em; /* Check with height of .react-grid-item-header */ + left: 0.25em; + border: .55rem solid $secondary-light; + border-top: .55rem solid $primary-light; + border-radius: 50%; + width: 3rem; + height: 3rem; + animation: spin 2s linear infinite; + z-index: 1000; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/EnlargeableImage/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/EnlargeableImage/index.js new file mode 100644 index 0000000000000000000000000000000000000000..54fe7310a3e865bc0a6ed1b51555ddd8da69c538 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/EnlargeableImage/index.js @@ -0,0 +1,74 @@ +import React, { + Component +} from 'react'; +import 'react-treeview/react-treeview.css'; +import { + Modal, + ModalHeader, + ModalBody +} from 'reactstrap'; + + +/** + * EnlargeableImage: + * Creates a clickable <img> tag. When the image is clicked, it is opened in a modal + * and typically enlarged. + * + * @prop {string} url - the URL of the image + * @prop {string} className - classname applied to the Modal + */ +class EnlargeableImage extends Component { + + state = { + modal: false, + modalUrl: '' + }; + + timeout = null + + toggleModal = (e) => { + this.setState({ + modal: !this.state.modal, + modalUrl: e.currentTarget.src + }); + }; + + onImgError = (e) => { + const img = e.currentTarget; + img.alt = 'Reloading in 2 sec..'; + // let the window figure out if the timeout id is still valid + if(this.timeout){ + clearTimeout(this.timeout); + } + // eslint-disable-next-line no-self-assign + this.timeout = setTimeout(() => { img.src = img.src; }, 2000 ); + }; + + clearTimeout() { + clearTimeout(this.timeout); + this.timeout = null; + } + + render(){ + this.clearTimeout(); + + return ( + <React.Fragment> + <img src={this.props.url} + onClick={this.toggleModal} + onError={this.onImgError} + title="Click to enlarge" + alt="Not present"/> + <Modal isOpen={this.state.modal} fade={false} size="lg" toggle={this.toggle} className={this.props.className}> + <ModalHeader toggle={this.toggleModal}></ModalHeader> + <ModalBody> + <img width="100%" src={this.state.modalUrl} alt="Not present" /> + </ModalBody> + </Modal> + </React.Fragment> + ); + } +} + + +export default EnlargeableImage; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillViewportHeightDiv/index.js similarity index 80% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillViewportHeightDiv/index.js index f36a94934af6d9cc28eaf3c7986d37549a94afbb..b177de6ff20e97684e6c0252ae644d579b33bb96 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillHeight.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/FillViewportHeightDiv/index.js @@ -1,18 +1,24 @@ import React from "react"; -/* - * FillHeight: Creates a div that fills the remaining height of the viewport. +/** + * FillViewportHeightDiv: Creates a div that fills the remaining height of the viewport. * Window resize is monitored to scale the div accordingly. * * This component uses the 'render prop' pattern where the 'children' prop is used * as the render function. The height of the div (in an object) is passed to * the function for use by subcomponents. E.g.: * - * <FillHeight> - * { (props) => <MyComponent hgt={props.height} /> } - * </FillHeight> + * @prop {string} className - Class applied to the container div + * @prop {integer} gutterBottom - Bottom margin for the container div + * @prop {integer} minHeight - Mininum height of container div + * @prop {jsx} children - Content of container div + * + * @example + * <FillViewportHeightDiv> + * { (props) => <MyComponent hgt={props.height} /> } + * </FillViewportHeightDiv> */ -class FillHeight extends React.Component { +class FillViewportHeightDiv extends React.Component { static defaultProps = { className: 'fill-height-container', // classname for the container div @@ -85,4 +91,4 @@ class FillHeight extends React.Component { } }; -export default FillHeight; +export default FillViewportHeightDiv; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.scss deleted file mode 100644 index d1199fbe15d96ead401f3bc8d3678f7ac038afde..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.scss +++ /dev/null @@ -1,16 +0,0 @@ -@import '../themes/lofar-variables.scss'; - -.hoverable { - -} - -.hoverable:hover { - background-color: $secondary; -} - -.table-wrapper { - width: 10em; - max-height: 10em; - overflow: auto; - display: block; -} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown.js deleted file mode 100644 index 75e0d715aadf3c486551d3a3f7065aa0842c246d..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown.js +++ /dev/null @@ -1,124 +0,0 @@ -import React, {Component} from 'react'; -import { IoMdCheckmark as IsSelectIcon } from 'react-icons/io'; -import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap' - -class SelectableOption extends Component { - - selectedItem = () => {this.props.onSelectedItem(this.props.value)} - - render(){ - const selectMark = this.props.isSelected ? <IsSelectIcon style={{width:'1rem'}} /> : <div style={{paddingRight:'1rem'}}/>; - const jsx = ( - <DropdownItem onClick={this.selectedItem} > - <table><tbody> - <tr style={{position:'relative', left: '-1rem'}}> - <td>{selectMark}</td> - <td>{this.props.children}</td></tr></tbody></table> - </DropdownItem> - ); - return jsx - } -} - -export class MultiSelectDropdown extends Component{ - constructor(props){ - super(props); - - this.state = { - isOpen: false, - selectedItems: {} - } - } - // Toggle the dropdown state - toggle = () => { - if(!this.state.isOpen) - { - this.setState({isOpen:true}); - }else if(!this.state.mouseOverMenu){ - this.closeMenu() - } - - } - - closeMenu(){ - this.setState({isOpen:false}) - this.props.onSelectionChange(this.getSelectedItemsList()) - } - - itemSelected = (e) => { - if (e === 'all'){ - this.setState({selectedItems: {}}) - this.props.onSelectionChange([]) - this.setState({isOpen:false}) - }else{ - const newSelectedItems = this.state.selectedItems - newSelectedItems[e] = !newSelectedItems[e] - this.setState({selectedItems: newSelectedItems}) - } - } - - getSelectedItemsList (selectedItems) { - if (selectedItems === undefined){ - selectedItems = this.state.selectedItems; - } - return Object.keys(selectedItems).filter(item => this.state.selectedItems[item]); - - - } - - renderLabel(){ - const selectedItemsList = this.getSelectedItemsList() - if(selectedItemsList.length === 0 ){ - return this.props.placeHolder; - }else if(selectedItemsList.length <= 4){ - return selectedItemsList.join(', ') - }else { - const firstFour = selectedItemsList.slice(0, 4) - return firstFour.join(', ') + ', ...' - } - } - - isItemSelected = (e) => { - if(this.state.selectedItems.hasOwnProperty(e)) - return this.state.selectedItems[e]; - return false - } - - componentDidMount() { - const selectedItems = this.state.selectedItems - let update = false - if(this.props.selectedItems === undefined) return - for(let item of this.props.selectedItems){ - if(!selectedItems.hasOwnProperty(item) || !selectedItems[item] ){ - selectedItems[item] = true; - update = true - } - } - - if(update) this.setState({selectedItems: selectedItems}) - } - - mouseOverMenu = () => {this.setState({mouseOverMenu:true})} - mouseExitsMenu = () => {this.setState({mouseOverMenu:false})} - - render(){ - let allOptions = [{value:'all', label:'<ALL>'}].concat(this.props.options) - let options = allOptions.map((item, key) => <SelectableOption key={key} value={item.value} isSelected={this.isItemSelected(item.value)} onSelectedItem={this.itemSelected}>{item.label}</SelectableOption>) - - const jsx = ( - <Dropdown isOpen={this.state.isOpen} toggle={this.toggle} className={this.props.className}> - <DropdownToggle caret> - {this.renderLabel()} - </DropdownToggle> - <DropdownMenu style={{width:'max-content'}} - onMouseOver={this.mouseOverMenu} - onMouseOut={this.mouseExitsMenu}> - {options} - </DropdownMenu> - </Dropdown> - ) - return jsx; - } -} - -export default MultiSelectDropdown diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d9387d1597bdc47d34aad0f4b9778c8aa61ecf5e --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown/index.js @@ -0,0 +1,170 @@ +import React, {Component} from 'react'; +import { IoMdCheckmark as IsSelectIcon } from 'react-icons/io'; +import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap' + +// CSS +import styles from './styles.module.scss' + + +/** + * SelectableOption: renders the boilerplate JSX for selectable items in the DropdownMenu. + * + * @prop {bool} isSelected - if the item is selected + * @prop {function} onSelectedItem - callback for click event. Gets the option value as argument. + * @prop {jsx} children - label content + */ +function SelectableOption(props) { + const selectMark = props.isSelected ? <IsSelectIcon /> : null; + + return ( + <DropdownItem onClick={() => {props.onSelectedItem(props.value)}} > + <table> + <tbody> + <tr className={styles.selectable_option}> + <td className={styles.marker}> + {selectMark} + </td> + <td> + {props.children} + </td> + </tr> + </tbody> + </table> + </DropdownItem> + ); +} + +/** + * MultiSelectDropdown: + * Creates a multi-select box. The selectable items are presented in a dropdown. + * + * @props: + * options: list of objects {value, label} + */ +export class MultiSelectDropdown extends Component{ + + state = { + isOpen: false, + selectedItems: {} + } + + // Toggle the dropdown state + toggle = () => { + if (!this.state.isOpen) { + this.setState({ + isOpen: true + }); + } + else if (!this.state.mouseOverMenu) { + this.closeMenu() + } + + } + + closeMenu(){ + this.setState({ + isOpen:false + }) + this.props.onSelectionChange(this.getSelectedItemsList()) + } + + itemSelected = (e) => { + if (e === 'all'){ + this.setState({ + selectedItems: {} + }) + this.props.onSelectionChange([]) + this.setState({ + isOpen:false + }) + }else{ + const newSelectedItems = this.state.selectedItems + newSelectedItems[e] = !newSelectedItems[e] + this.setState({ + selectedItems: newSelectedItems + }) + } + } + + getSelectedItemsList (selectedItems) { + if (selectedItems === undefined) { + selectedItems = this.state.selectedItems; + } + return Object.keys(selectedItems).filter(item => this.state.selectedItems[item]); + + + } + + renderLabel(){ + const selectedItemsList = this.getSelectedItemsList() + if (selectedItemsList.length === 0 ) { + return this.props.placeHolder; + } + else if (selectedItemsList.length <= 4) { + return selectedItemsList.join(', ') + } else { + const firstFour = selectedItemsList.slice(0, 4) + return firstFour.join(', ') + ', ...' + } + } + + isItemSelected = (e) => { + if (this.state.selectedItems.hasOwnProperty(e)) { + return this.state.selectedItems[e]; + } + return false + } + + componentDidMount() { + const selectedItems = this.state.selectedItems + let update = false + if (this.props.selectedItems === undefined) { + return + } + for (let item of this.props.selectedItems) { + if (!selectedItems.hasOwnProperty(item) || !selectedItems[item]) { + selectedItems[item] = true; + update = true + } + } + + if (update) { + this.setState({ + selectedItems: selectedItems + }) + } + } + + mouseOverMenu = () => { + this.setState({mouseOverMenu:true}) + } + + mouseExitsMenu = () => { + this.setState({mouseOverMenu:false}) + } + + render() { + let allOptions = [{value:'all', label:'<ALL>'}].concat(this.props.options) + let options = allOptions.map((item, key) => + <SelectableOption key={key} value={item.value} isSelected={this.isItemSelected(item.value)} onSelectedItem={this.itemSelected}> + {item.label} + </SelectableOption> + ) + + const jsx = ( + <Dropdown isOpen={this.state.isOpen} toggle={this.toggle} className={this.props.className}> + <DropdownToggle caret> + {this.renderLabel()} + </DropdownToggle> + <DropdownMenu className={styles.dropdown} + onMouseOver={this.mouseOverMenu} + onMouseOut={this.mouseExitsMenu}> + {options} + </DropdownMenu> + </Dropdown> + ) + return jsx; + } +} + +export default MultiSelectDropdown diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..d7dcb5f323d08a69f735f40b5b04fef685685ee1 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/MultiSelectDropdown/styles.module.scss @@ -0,0 +1,13 @@ + +:local .selectable_option { + position: relative; + left: -1rem; +} + +:local .marker { + width: 1rem; +} + +:local .dropdown { + width: max-content; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/NavigationBar/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/NavigationBar/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6815e6dc046049db988b01a8e1d73fa8c8efda75 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/NavigationBar/index.js @@ -0,0 +1,68 @@ +import React, { PureComponent } from 'react'; +import { Link } from 'react-router-dom'; +import { Collapse, + Nav, + NavbarToggler, + Navbar, + NavbarBrand, + NavItem, + NavLink } from 'reactstrap'; + +// CSS +import styles from './styles.module.scss' + +/** + * NavigationBar + * Creates the top navigation bar with app name and collapsable main menu. + */ +class NavigationBar extends PureComponent { + + state = { + isOpen: false + } + + toggle = () => { + this.setState({ + isOpen:!this.state.isOpen + }); + } + + check_status(item){ + if (this.props.active_page.pathname === item){ + return "active" + } + } + + render() { + return ( + <Navbar className={styles.header_navbar} dark expand="lg"> + <NavbarBrand className={styles.header_brand}> + <h1> + <em>LOFAR</em> Station monitor <span>v0.1</span> + </h1> + </NavbarBrand> + <NavbarToggler onClick={this.toggle} /> + <Collapse isOpen={this.state.isOpen} navbar> + <Nav className="ml-auto" navbar> + <NavItem className={this.check_status("/")}> + <NavLink tag={Link} to="/"> + <h3>Dashboard</h3> + </NavLink> + </NavItem> + <NavItem className={this.check_status("/station_overview")}> + <NavLink tag={Link} to="/station_overview"> + <h3>Station Overview</h3> + </NavLink> + </NavItem> + <NavItem className={this.check_status("/tiles")}> + <NavLink tag={Link} to="/tiles"> + <h3>Tiles</h3> + </NavLink> + </NavItem> + </Nav> + </Collapse> + </Navbar>); + } +} + +export default NavigationBar; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/NavigationBar/styles.module.scss similarity index 59% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.scss rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/NavigationBar/styles.module.scss index f0e366076c6e65a817143c04335f09578bab6f08..bd89058b7d2f1f4605a3bc42a914ea3894752229 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.scss +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/NavigationBar/styles.module.scss @@ -1,17 +1,17 @@ -@import '../themes/lofar-variables.scss'; +@import '../../themes/lofar-variables.scss'; -.header-navbar.navbar { +:local(.header_navbar):global(.navbar) { background-color: $primary; padding: .3rem 1rem; color: white; } -.header-brand em { +:local .header_brand em { font-weight: bold; font-style: normal; } -.header-brand span { +:local .header_brand span { color: $secondary-dark; font-size: 1.5rem; font-style: italic; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag.js deleted file mode 100644 index e01ea17b8e62b64dec86ed4a02e8f1307dc0042a..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag.js +++ /dev/null @@ -1,18 +0,0 @@ -import React, { Component } from 'react'; -import { getInspectPageURLFromSASid } from '../utils/LOFARDefinitions.js' - -class ObservationInspectTag extends Component { - - clicked = () => { - const url = getInspectPageURLFromSASid(this.props.observationId) - window.open(url) - } - - render () { - const observationId = this.props.observationId; - return ( - <div onClick={this.clicked} style={{fontWeight:'600'}}>{observationId}</div> - ) - } -} -export default ObservationInspectTag; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag/index.js new file mode 100644 index 0000000000000000000000000000000000000000..b006e678228948e771188c4811de3b181120e340 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag/index.js @@ -0,0 +1,28 @@ +import React, { PureComponent } from 'react'; +import { getInspectPageURLFromSASid } from 'utils/LOFARDefinitions.js' + +import styles from './styles.module.scss' + +/** + * ObservationInspectTag: create a clickable observation ID element. + * On click, open 'inspect' page (external web app) in new window. + */ +class ObservationInspectTag extends PureComponent { + + tooltip = "Click to open inspection page in new window"; + + clicked = () => { + const url = getInspectPageURLFromSASid(this.props.observationId) + window.open(url) + } + + render () { + const observationId = this.props.observationId; + return ( + <span onClick={this.clicked} className={styles.link} title={this.tooltip}> + {observationId} + </span> + ) + } +} +export default ObservationInspectTag; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..d037125772be17d6bc215ed5c59fd0217efcc297 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/ObservationInspectTag/styles.module.scss @@ -0,0 +1,5 @@ + +:local .link { + font-weight: 600; + cursor: pointer; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/PopoverWithTitle/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/PopoverWithTitle/index.js new file mode 100644 index 0000000000000000000000000000000000000000..b8525a661e7e7e592de1cf30a165fb3aecd7abe1 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/PopoverWithTitle/index.js @@ -0,0 +1,34 @@ +import React from 'react'; +import {Popover, PopoverHeader, PopoverBody} from 'reactstrap'; + + +/** + * PopoverWithTitle: + * Function component that renders a Popover with a title + * + * @props: + * target: [string] id of the HTML element that triggers the popover + * isOpen: [bool] should the Popover be displayed (true) or hidden (false) + * togglePopover: [function] function that is called to toggle the popover + * title: [string] title of the Popover + * children: [JSX fragment] the body of the Popover + */ +const PopoverWithTitle = ({target, isOpen, togglePopover, title, children}) => { + + if (!isOpen) { + return null; + } + + return ( + <Popover placement="auto" isOpen={isOpen} target={target} toggle={togglePopover} key={title}> + <PopoverHeader> + {title} + </PopoverHeader> + <PopoverBody> + { children() } + </PopoverBody> + </Popover> + ); +} + +export default PopoverWithTitle; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.css deleted file mode 100644 index b7172e7d24fd1e8a071985ffb4592b35bc7f0e36..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.css +++ /dev/null @@ -1,77 +0,0 @@ -/* COLORS */ -/* Color palette interface (created with https://material.io/tools/color/) */ -/* font color */ -/* font color */ -/* Data colors */ -.react-autosuggest__container { - position: relative; - display: inline-block; } - -.react-autosuggest__container svg { - font-size: 1.2rem; } - -.react-autosuggest__container button.btn.btn-info { - padding: .25rem .3rem; } - -/* -.react-autosuggest__input { -} -*/ -.react-autosuggest__container .react-autosuggest__input { - border-radius: 0; } - -.react-autosuggest__suggestions-container { - display: none; } - -.react-autosuggest__container--open .react-autosuggest__suggestions-container { - display: block; - position: absolute; - width: 100%; - border: 1px solid #aaa; - background-color: #fff; - color: black; - font-family: Helvetica, sans-serif; - font-weight: 300; - font-size: 16px; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - z-index: 2000; - overflow-y: auto; - max-height: 30em; } - -.react-autosuggest__suggestions-list { - margin: 0; - padding: 0; - list-style-type: none; } - -.react-autosuggest__suggestion { - cursor: pointer; - padding: 10px 20px; } - -.react-autosuggest__suggestion:not(:first-child) { - border-top: 1px solid #ddd; } - -.react-autosuggest__suggestion--focused { - background-color: #0C7EAF; - color: #fff; } - -.suggestion-content { - display: flex; - align-items: center; - background-repeat: no-repeat; } - -.react-autosuggest__suggestion--highlighted { - background-color: #86868a; } - -.react-autosuggest__container .btn-clear { - position: absolute; - right: 4px; - top: 0; - bottom: 0; - height: 100%; - margin: auto; - cursor: pointer; - color: #b6b6ba; } - -.react-autosuggest__container .btn-clear:hover { - color: #86868a; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationDetails.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationDetails.js deleted file mode 100644 index 94be8877bb218d3ee2435cfa3b0a984e35495993..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationDetails.js +++ /dev/null @@ -1,132 +0,0 @@ -import React, { Component } from 'react'; -import { Table, Badge, Popover, PopoverHeader, PopoverBody } from 'reactstrap'; -import { unique_id } from '../utils/utils.js' -import './StationOverview.css' - - -/** - * StationTestBadge; class to render one stationtest badge in the SORow. - */ -class StationTestBadge extends Component { - - constructor(props) { - super(props); - - this.id = unique_id(); - this.togglePopover = this.togglePopover.bind(this); - this.state = { - popoverOpen: false - }; - } - - getClass() { - let color = this.props.data.total_component_errors>0 ? "so-red" : "so-green"; - return "so-stationtestbadge " + color; - } - - togglePopover() { - this.setState({ - popoverOpen: !this.state.popoverOpen - }); - } - - render () { - let data = this.props.data; - return ( - <div> - <div id={ this.id } onMouseOver={this.togglePopover} onMouseOut={this.togglePopover} className={ this.getClass() }>{ this.props.data.total_component_errors }</div> - <Popover placement="auto" isOpen={this.state.popoverOpen} target={ this.id } toggle={this.togglePopover}> - <PopoverHeader>{this.props.station}</PopoverHeader> - <PopoverBody> - <strong>Start:</strong> { data.start_datetime}<br/> - <strong>End:</strong> { data.end_datetime }<br/> - <strong>Checks:</strong> { data.checks } - </PopoverBody> - </Popover> - </div> - ); - } -} - - -/** - * RTSMBadge; class to render one RTSM badge in the SORow. - */ -class RTSMBadge extends Component { - - getClass() { - let color = this.props.data.total_component_errors>0 ? "so-red" : "so-green"; - return "so-pill " + color; - } - - render () { - let data = this.props.data; - return <div className="so-rtsmbadge">{ data.observation_id }<span className={ this.getClass() }>{ data.total_component_errors }</span></div>; - } -} - - -/** - * SORow; Class to render the row for a station in the StationOverview. - */ -class SORow extends Component { - - renderStationName() { - return this.props.data.station_name; - } - - renderStationTests() { - let data = this.props.data; - return data.station_tests.map( (testData) => <StationTestBadge station={data.station_name} data={ testData } /> ); - } - - renderRTSM() { - if (! this.props.data.rtsm || this.props.data.rtsm.length == 0) { - return "No RTSM data found" - } - return this.props.data.rtsm.map( (testData) => <RTSMBadge data={ testData } /> ); - } - - render() { - return ( - <tr> - <th scope="row">{ this.renderStationName() }</th> - <td>{ this.renderStationTests() }</td> - <td>{ this.renderRTSM() }</td> - </tr> - ); - } -} - - -/** - * StationDetails class. - */ -class StationDetails extends Component { - - getTableRows() { - return this.props.data.map( (stationData) => <SORow data={ stationData } /> ); - } - - render() { - return ( - <div className="station-overview-ctrl"> - <Table size="sm" className="so-table"> - <thead> - <tr> - <th>Name</th> - <th>Station tests</th> - <th>Latest observations</th> - </tr> - </thead> - <tbody> - { this.getTableRows() } - </tbody> - </Table> - </div> - ); - } - -} - -export default StationOverview; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationList.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationList.css deleted file mode 100644 index 8e89d5347497e8c4f18b7f66f9081d16b8e8762a..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationList.css +++ /dev/null @@ -1,4 +0,0 @@ -.pagenumber { - position: relative; - top: 30%; -} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationList.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationList.js deleted file mode 100644 index 605c1fd2503508e6ce468ee7b92f36bcea12e9f5..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationList.js +++ /dev/null @@ -1,142 +0,0 @@ -import React, { Component } from 'react'; -import { Nav, NavItem, NavLink, InputGroup, Input, InputGroupAddon, - Container, Row, Table, Pagination, PaginationItem, - PaginationLink, Badge, Button } from 'reactstrap'; -import StationTestSummary from './StationTestSummary.js' -import RESTAPI from '../api_configuration.js' -import { Link } from 'react-router-dom'; -import './StationList.css' - -class StationList extends Component { - - constructor(props){ - super(props); - this.state = { - stations: [], - isLoading: null, - query: { - station_name:'', - station_type: '', - from_date:'', - to_date:''} - }; - } - - fillResultUrl(query){ - let parameters = query; - let url = RESTAPI.get_url('station_test', this.props.current_page, parameters); - return url; - } - - fetchData(query){ - this.setState({isLoading:true}); - let current_request = this.fillResultUrl(query) - console.log(current_request) - fetch(current_request).then( - (response) => response.json()).then( - (content) => { - this.setState({total_pages:content.total_pages, - isLoading: false}); - this.setState({stations: content.results}); - } - ); - } - - componentDidMount(){ - this.fetchData(this.state.query); - } - - getNextPageNumber(){ - const current_page = this.props.current_page - return current_page < this.state.total_pages ? - current_page + 1 : - current_page - - } - - getNextPage(){ - return '/stationtest/' + this.getNextPageNumber(); - } - - getPrevPageNumber(){ - const current_page = this.props.current_page - return current_page > 1 ? - current_page - 1 : - current_page - - } - - getPrevPage(){ - return '/stationtest/' + this.getPrevPageNumber(); - } - - stationSelectedEvent(evt){ - let query = this.state.query - query.station_name = evt.target.value - - this.setState({'query': query}); - - this.fetchData(query); - } - - stationTypeSelectedEvent(evt){ - let query = this.state.query - query.station_type = evt.target.value - - this.setState({'query': query}); - this.fetchData(query); - } - - fromDateSelectedEvent(evt){ - let query = this.state.query - query.from_date = evt.target.value - - this.setState({'query': query}); - this.fetchData(query); - } - - toDateSelectedEvent(evt){ - let query = this.state.query - query.to_date = evt.target.value - - this.setState({'query': query}); - this.fetchData(query); - } - - render() { - var station_names = this.state.stations.map( (item)=> <StationTestSummary data={item} key={item.id} />); - const prev_page = this.getPrevPage(); - const next_page = this.getNextPage(); - return ( - <div> - <InputGroup> - <Input type='search' placeholder='Station' - onInput={(evt)=>this.stationSelectedEvent(evt)}></Input> - <Input placeholder='Station type' - onChange={(evt)=>this.stationTypeSelectedEvent(evt)}></Input> - <InputGroupAddon addonType="prepend">From</InputGroupAddon> - <Input type='date' - onChange={(evt)=>this.fromDateSelectedEvent(evt)}></Input> - <InputGroupAddon addonType="prepend">To</InputGroupAddon> - <Input type='date' - onChange={(evt)=>this.toDateSelectedEvent(evt)}></Input> - <Badge> - <div className='pagenumber'> - Page {this.props.current_page} / {this.state.total_pages} - </div> - </Badge> - </InputGroup> - {station_names} - <Nav className="ml-auto float-right"> - <NavItem> - <NavLink href={prev_page}><Button>previous</Button></NavLink> - </NavItem> - <NavItem> - <NavLink href={next_page}><Button>next</Button></NavLink></NavItem> - </Nav> - </div> - ); - } -} - -export default StationList; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.css deleted file mode 100644 index 11a0240c2c57a99f62ab7ef9cb93dbed336b962b..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.css +++ /dev/null @@ -1,81 +0,0 @@ -/* COLORS */ -/* Color palette interface (created with https://material.io/tools/color/) */ -/* font color */ -/* font color */ -/* Data colors */ -.so, .so-serious, .so-alarming, .so-warning, .so-good { - font-weight: 600 !important; } - -.so-serious { - background-color: #f17171; - color: white; } - -.so-alarming { - background-color: #ffcd74; - color: black; } - -.so-warning { - background-color: #fbfb83; - color: black; } - -.so-good { - background-color: #6fbd6f; - color: white; } - -.station-overview-ctrl { - font-size: .9rem; } - -.so-table { - width: auto !important; } - -.so-table th { - padding-right: 1.4em !important; } - -.so-stationtestbadge { - float: left; - width: 1.4rem; - height: 1.4rem; - line-height: 1.4rem; - border: 1px solid #999; - border-radius: .2rem; - padding: 0; - text-align: center; - margin-left: 1px; - font-size: 90%; } - -/* General badge styling */ -.so-badge { - float: left; - height: 1.4rem; - line-height: 1.4rem; - background: #e8e8ec; - border: 1px solid #86868a; - border-radius: .2rem; - padding: 0 0.5em; - text-align: left; - margin-left: 2px; - font-size: 90%; } - -/* RTSM Badge styling */ -.so-rtsmbadge { - width: 7em; } - -.so-stationtestbadge:hover, -.so-badge:hover { - color: #fff; - background-color: #86868a; - border-color: #86868a; } - -.so-pill { - display: block; - float: right; - padding: .25em .45em; - font-size: 90%; - font-weight: 500; - line-height: 1; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: 1em; - margin-top: 0.1rem; - margin-left: 0.5em; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.js deleted file mode 100644 index d61e96a4f824764cc59971de04c8cf6e6699f172..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.js +++ /dev/null @@ -1,263 +0,0 @@ -import React, {Component, PureComponent} from 'react'; -import {withRouter} from "react-router"; -import {Table, Popover, PopoverHeader, PopoverBody} from 'reactstrap'; -import {unique_id} from '../utils/utils.js' -import AutoLoadWrapper from '../utils/autoLoader.js' -import * as moment from 'moment'; -import { datetime_format } from '../utils/constants' - -// CSS -import './StationOverview.css' - -/** - * Badge; class to render a badge with label and pill. - */ -class Badge extends PureComponent { - - getClass() { - let cnt = this.props.count; - let color = cnt > 10 - ? "so-serious" - : cnt > 5 - ? "so-alarming" - : cnt > 0 - ? "so-warning" - : "so-good"; - return "so-pill " + color; - } - - render() { - let props = this.props; - return (<div id={props.myid} className={"so-badge "+this.props.className} onMouseOver={props.togglePopOver} onMouseOut={props.togglePopOver}> - {props.label} - <span className={this.getClass()}>{props.count}</span> - </div>); - } -} - - -const SOPopover = ({target, isOpen, togglePopover, title, lines}) => { - - if (!isOpen) { - return null; - } - - return (<Popover placement="auto" isOpen={isOpen} target={target} toggle={togglePopover} key={title}> - <PopoverHeader> - {title} - </PopoverHeader> - <PopoverBody> - <Table borderless size="sm"> - <tbody> - {lines.map( (obj, id) => <tr key={id}> - <th>{obj.th}</th> - <td>{obj.td}</td> - </tr>) - } - </tbody> - </Table> - </PopoverBody> - </Popover>); -} - - -/** - * StationTestBadge; class to render one stationtest badge in the SORow. - */ -class StationTestBadgeC extends Component { - - id = unique_id(); - - state = { - popoverOpen: false - }; - - getClass() { - let total = this.props.data.total_component_errors; - let color = total > 10 - ? "so-serious" - : total > 5 - ? "so-alarming" - : total > 0 - ? "so-warning" - : "so-good"; - return `so-stationtestbadge ${color}`; - } - - onClick = () => { - let station = this.props.station; - this.props.history.push(`/station_overview?station=${station}`); - } - - togglePopover = () => { - this.setState({ - popoverOpen: !this.state.popoverOpen - }); - } - - popoverLines() { - let data = this.props.data; - let summary = data.component_error_summary; - - let lines = [ - { th: 'Start:' , td: moment.utc(data.start_datetime).format(datetime_format)}, - { th: 'End:', td: moment.utc(data.end_datetime).format(datetime_format)}, - { th: 'Checks:', td: data.checks} - ]; - - let components = Object.keys(summary).sort(); - components.forEach((component) => { - let comp_sum = summary[component]; - let errors = Object.keys(comp_sum).sort(); - lines.push({ - th: component, td: errors.map((e, id) => <Badge key={id} count={comp_sum[e]} label={e}/>) - }); - }); - - return lines; - } - - render() { - let lines = this.state.popoverOpen ? this.popoverLines() : []; - - return (<div> - <div id={this.id} onClick={this.onClick} onMouseOver={this.togglePopover} onMouseOut={this.togglePopover} className={this.getClass()}> - {this.props.data.total_component_errors} - </div> - <SOPopover target={this.id} isOpen={this.state.popoverOpen} togglePopover={this.togglePopover} title={this.props.data.observation_id} lines={lines} /> - </div>); - } -} - -const StationTestBadge = withRouter(StationTestBadgeC); - -/** - * RTSMBadge; class to render one RTSM badge in the SORow. - */ -class RTSMBadge extends Component { - - id = unique_id(); - - state = { - popoverOpen: false - }; - - getClass() { - let total = this.props.data.total_component_errors; - let color = total > 10 - ? "so-serious" - : total > 5 - ? "so-alarming" - : total > 0 - ? "so-warning" - : "so-good"; - return "so-pill " + color; - } - - togglePopover = () => { - this.setState({ - popoverOpen: !this.state.popoverOpen - }); - } - - popoverLines() { - let data = this.props.data; - - let errors = Object.keys(data.error_summary).sort(); - let badges = errors.map((e, i) => <Badge key={i} count={data.error_summary[e]} label={e}/>); - - let lines = [ - { th: 'Start:' , td: moment.utc(data.start_datetime).format(datetime_format)}, - { th: 'End:', td: moment.utc(data.end_datetime).format(datetime_format)}, - { th: 'Mode:', td: data.mode }, - { th: 'Errors:', td: badges} - ]; - - return lines; - } - - render() { - let data = this.props.data; - - let lines = []; - if (this.state.popoverOpen) { - lines = this.popoverLines(); - } - - return (<React.Fragment> - <Badge myid={this.id} className={'obs-'+data.observation_id} togglePopOver={this.togglePopover} count={data.total_component_errors} label={data.observation_id}/> - <SOPopover target={this.id} isOpen={this.state.popoverOpen} togglePopover={this.togglePopover} title={data.observation_id} lines={lines} /> - </React.Fragment>); - } -} - -/** - * SORow; Class to render the row for a station in the StationOverview. - */ -class SORowC extends Component { - - renderStationName() { - return this.props.data.station_name; - } - - renderStationTests() { - let data = this.props.data; - return data.station_tests.map((testData) => <StationTestBadge key={testData.start_datetime} station={data.station_name} data={testData}/>); - } - - renderRTSM() { - if (!this.props.data.rtsm || this.props.data.rtsm.length === 0) { - return "No RTSM data found" - } - - return this.props.data.rtsm.map((testData) => <RTSMBadge key={testData.observation_id} data={testData}/>); - } - onClick() { - let station = this.props.data.station_name; - this.props.history.push(`/station_overview?station=${station}`); - } - - render() { - return (<tr> - <th scope="row" onClick={()=>this.onClick()}>{this.renderStationName()}</th> - <td>{this.renderStationTests()}</td> - <td>{this.renderRTSM()}</td> - </tr>); - } -} -const SORow = withRouter(SORowC); -/** - * StationOverview class. - */ -class StationOverviewC extends Component { - - getTableRows() { - let d = this.props.data; - return d.map((stationData) => <SORow key={stationData.station_name} data={stationData}/>); - } - - render() { - return (<div className="station-overview-ctrl"> - <Table size="sm" className="so-table"> - <thead> - <tr> - <th>Name</th> - <th>Station tests</th> - <th>Latest observations</th> - </tr> - </thead> - <tbody> - {this.getTableRows()} - </tbody> - </Table> - </div>); - } - -} - -/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the - auto-loading of the data for StationOverviewC. - */ -const StationOverview = AutoLoadWrapper(StationOverviewC); - -export default StationOverview; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.scss deleted file mode 100644 index 3997fddaefd41b8483d319f88e83315327157938..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationOverview.scss +++ /dev/null @@ -1,94 +0,0 @@ -@import '../themes/lofar-variables.scss'; - -.so { - font-weight: 600 !important; -} - -.so-serious { - @extend .so; - background-color: $serious; - color: $serious_fontcolor; -} -.so-alarming { - @extend .so; - background-color: $alarming; - color: $alarming_fontcolor; -} -.so-warning { - @extend .so; - background-color: $warning; - color: $warning_fontcolor; -} -.so-good { - @extend .so; - background-color: $good; - color: $good_fontcolor; -} - - -.station-overview-ctrl { - font-size: .9rem; -} - -.so-table { - width: auto!important; -} - -.so-table th { - padding-right: 1.4em!important; -} - -.so-stationtestbadge { - float: left; - width: 1.4rem; - height: 1.4rem; - line-height: 1.4rem; - border: 1px solid #999; - border-radius: .2rem; - padding: 0; - text-align: center; - margin-left: 1px; - font-size: 90%; -} - -/* General badge styling */ -.so-badge { - float: left; - height: 1.4rem; - line-height: 1.4rem; - background: $secondary-light; - border: 1px solid $secondary-dark; - border-radius: .2rem; - padding: 0 0.5em; - text-align: left; - margin-left: 2px; - font-size: 90%; -} - -/* RTSM Badge styling */ -.so-rtsmbadge { - width: 7em; -} - -.so-stationtestbadge:hover, -.so-badge:hover { - color: #fff; - background-color: $secondary-dark; - border-color: $secondary-dark; -} - - -.so-pill { - display: block; - float: right; - padding: .25em .45em; - font-size: 90%; - font-weight: 500; - line-height: 1; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: 1em; - margin-top: 0.1rem; - margin-left: 0.5em; -} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.js deleted file mode 100644 index 679263de7c633c9e8028d472726985f9ae18794c..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.js +++ /dev/null @@ -1,165 +0,0 @@ -import React, {Component} from 'react'; -import {connect} from "react-redux"; -import {unpinChildPanelData} from '../redux/actions/stationOverviewPageActions' -import {Badge, Button, Modal, ModalBody, ModalHeader, Table} from 'reactstrap'; -import {IoMdCloseCircleOutline as CloseIcon} from 'react-icons/io'; -import './StationOverview.css' -import './StationTestView.css' -import '../themes/lofar-styles.css' -// CSS -import './StationTestChildView.css'; - - -class StationTestChildViewC extends Component { - - state = { - modal: false, - modalUrl: "" - }; - - imgLoadTimeout = {}; - - toggleModal = (e) => { - this.setState({ - modal: !this.state.modal, - modalUrl: e.currentTarget.src - }); - }; - - unpinPanel = () => { - this.props.unpinChildPanelData(); - }; - - onImgError = (e) => { - let img = e.currentTarget; - img.alt = "Reloading in 2 sec.."; - // let the window figure out if the timeout id is still valid - if (this.imgLoadTimeout[img.id]) { - clearTimeout(this.imgLoadTimeout[img.id]); - } - this.imgLoadTimeout[img.id] = setTimeout(() => { img.src = img.src; }, 2000 ); - }; - - clearTimeouts() { - let key; - for (key in this.imgLoadTimeout) { - clearTimeout(this.imgLoadTimeout[key]); - } - this.imgLoadTimeout = {}; - }; - - renderErrorDetails(pdata, key){ - - let pol = pdata['polarization']; - let rcuId = pdata['rcu_id']; - let err_items = [], - ignore = { - start_frequency: 1, - stop_frequency: 1, - polarization: 1, - rcu_id: 1, - antenna_id: 1, - url: 1 - }, - img = ""; - - // first process frequency range and url - if (pdata.details.hasOwnProperty("start_frequency")) { - err_items.push(<li key="freq">frequency-range: {pdata.details.start_frequency}-{pdata.details.stop_frequency} MHz</li>); - } - if (pdata.details.url) { - img = <img id={'img-'+pol+'-'+key} src={pdata.details.url} onClick={this.toggleModal} onError={this.onImgError} title="Click to enlarge" alt="Not present"/>; - } - - // process remaining items - Object.keys(pdata.details).forEach((parameter, key) =>{ - if (! ignore.hasOwnProperty(parameter)) { - const parameter_value = pdata.details[parameter]; - const rendered_parameter = parameter === 'percentage'? parameter_value.toFixed(2) + '%' : parameter_value; - err_items.push(<li key={parameter}>{parameter}: {rendered_parameter}</li>); - }}); - - if (err_items.length === 0) { - err_items = <li><em>See element error.</em></li>; - } - const imgHeader = (<th scope="row">RCU {rcuId} ({pol}){img}</th>); - return ( - <tr key={key}> - {rcuId ? imgHeader : undefined} - <td> - <ul style={{listStyleType: 'none', paddingLeft: '0.5em'}}> - <li><Badge className='error-type-badge' color="danger">{pdata.error_type}</Badge></li> - {err_items} - </ul> - </td> - </tr> - ); - }; - - render() { - let rows, - title; - - this.clearTimeouts(); - - // defaults when data is null - rows = <tr><td><i>Hover the mouse over an error to view the details. Right-click on the error to pin it on this panel.</i></td></tr>; - title = "Error details"; - - if (this.props.data !== null) { - rows = []; - title = `${this.props.data.component_type}, Antenna ${this.props.data.component_id}, Test ${this.props.data.datetime}`; - - const data = this.props.data.errors; - for (const error_type of Object.keys(data)){ - let pdata = data[error_type]; - const key = error_type; - if(pdata.hasOwnProperty('X') || pdata.hasOwnProperty('Y')){ - for(const pol of ['X', 'Y']){ - if(!pdata.hasOwnProperty(pol)){ - continue; - } - rows.push(this.renderErrorDetails(pdata[pol], key + pol)) - } - }else{ - rows.push(this.renderErrorDetails(pdata, key)) - } - } - } - - let unpinButton = ""; - if (this.props.isPinned) { - unpinButton = <Button title="Click to unpin the error details" color="info" size="xs" style={{float:'right'}} onClick={this.unpinPanel}> - <CloseIcon/> unpin - </Button> - } - - return <div className="stcv"> - <div className="stcv-header">{title} {unpinButton}</div> - <Table size='sm'> - <tbody> - {rows} - </tbody> - </Table> - <Modal isOpen={this.state.modal} fade={false} size="lg" toggle={this.toggle} className={this.props.className}> - <ModalHeader toggle={this.toggleModal}></ModalHeader> - <ModalBody> - <img style={{width: '100%'}} src={this.state.modalUrl} alt="Not present" /> - </ModalBody> - </Modal> - </div>; - } -} -/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the - auto-loading of the data for StationOverviewC. - */ -const StationTestChildView = connect(state => { - return { - ...state.station_page.child_panel, - }; -}, { - unpinChildPanelData -})(StationTestChildViewC); - - -export default StationTestChildView; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.scss deleted file mode 100644 index 72e949dee621482cf79152d61fa331d3a0be851b..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.scss +++ /dev/null @@ -1,12 +0,0 @@ -@import '../themes/lofar-variables.scss'; - -.stcv-header { - padding: .5rem 0; - color: $secondary-dark; -} - -.stcv img { - cursor: pointer; - display: block; - width: 90%; -} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestDetails.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestDetails.js deleted file mode 100644 index 788d33be71fca89f31b1de028cf6fa0830d35273..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestDetails.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, {Component} from 'react'; -import {withRouter} from "react-router"; -import {Table, Popover, PopoverHeader, PopoverBody} from 'reactstrap'; -import {unique_id} from '../utils/utils.js' -import AutoLoadWrapper from '../utils/autoLoader.js' -import './StationOverview.css' - -/** - * StationTestDetails class. - */ -class StationTestDetailsC extends Component { - - render() { - return (<div > - funny - </div>); - } - -} - -/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the - auto-loading of the data for StationOverviewC. - */ -const StationTestDetails = AutoLoadWrapper(StationTestDetailsC); - -export default StationTestDetails; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.css deleted file mode 100644 index 89cff5e0be1d41048634d5039ce833dec87d90cf..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.css +++ /dev/null @@ -1,20 +0,0 @@ -.sts-toolbar { - position: absolute; - right: 0.5rem; - top: 0.1rem; - display: inline-block; } - -.sts-table { - width: auto !important; - text-align: center; - font-size: .9rem; } - -.sts-table th { - word-break: break-all; - min-width: 3em; } - -.sts-table td { - text-align: center; } - -.sts-table.table-sm td, .sts-table.table-sm th { - padding: .2rem; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.js deleted file mode 100644 index ccd0605aca97e0709674aa70b6df9b1599341dd4..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.js +++ /dev/null @@ -1,206 +0,0 @@ -import React, { Component } from 'react'; -import { connect } from "react-redux"; -import { Table, Button } from 'reactstrap'; -import { unique_id } from '../utils/utils.js' -import { componentErrorTypes } from '../utils/constants.js' -import AutoLoadWrapper from '../utils/autoLoader.js' -import * as moment from 'moment'; -import { date_format, time_format } from '../utils/constants' - -//import stdata from '../testdata/station_test_summary.js' - -// CSS -import './StationTestSummary.css' - - -/** - * SORow; Class to render the row for a station in the StationOverview. - */ -class STSRow extends Component { - - renderStartDate() { - return this.props.date ? moment.utc(this.props.date).format(date_format) : ""; - } - - renderStartTime() { - return this.props.data.start_datetime ? moment.utc(this.props.data.start_datetime).format(time_format) : ""; - } - - render() { - let props = this.props, - component = props.component, - errors = props.data.component_error_summary[component] || {}, - cols = []; - - props.errorTypes.forEach((type) => { - cols.push(<td key={type}>{ errors[type] }</td>); - }); - - return ( - <tr> - <th scope="row" style={{whiteSpace: "nowrap"}}>{ this.renderStartDate() }</th> - <th>{ props.time }</th> - <th>{ props.station }</th> - <td>{ component }</td> - { cols } - </tr> - ); - } -} - - -class ToolBar extends Component { - - onErrorsOnlyClick = () => { - this.props.onChange('allErrorTypes', !this.props.allErrorTypes ); - } - - render() { - return ( - <div className="sts-toolbar"> - { this.props.allErrorTypes - ? <Button color="info" size="xs" onClick={this.onErrorsOnlyClick} active>All types</Button> - : <Button color="info" size="xs" onClick={this.onErrorsOnlyClick}>All types</Button> - } - </div> - ); - } -} - - -/** - * StationOverview class. - */ -class StationTestSummaryC extends Component { - - activeErrorTypes = []; // Result of filtering state.errorTypes - - state = { - allErrorTypes: false // true: error types shown, even when there are no errors for that type - }; - - /* Handle changes of selected filters in the ToolBar */ - onToolbarChange = (key, value) => { - let obj = {}; - obj[key] = value; - this.setState(obj); - } - - - filterErrorTypes() { - let typesFound = {}, - retTypes = []; - - if (this.props.errorTypes.length === 0 || this.props.data.length === 0){ - return []; - } - - // Create index object for all error types in the data - this.props.data.forEach( (stationData) => { - let esummary = stationData.component_error_summary; - let key; - for(key in esummary){ - let errors = Object.keys(esummary[key]); - errors.forEach((e) => typesFound[e] = 1); - } - }); - - // Loop over all error types and check which ones are present in the data - this.props.errorTypes.forEach((t) => { - if (typesFound[t]) { - retTypes.push(t); - } - }) - - return retTypes; - } - - setActiveErrorTypes() { - if (this.state.allErrorTypes) { - this.activeErrorTypes = this.props.errorTypes; - } else { - this.activeErrorTypes = this.filterErrorTypes(); - } - } - - getTableRows() { - let rows = [], - prevDate = null; - - this.props.data.forEach( (stationData) => { - let date = (stationData.date !== prevDate ? stationData.date : ""); - let components = Object.keys(stationData.component_error_summary).sort(); - let station = stationData.station_name; - let time = (stationData.start_datetime.match(/T(.*):..Z/))[1]; - - components.forEach( (component) => { - rows.push(<STSRow key={ unique_id() } date={date} time={time} component={component} station={station} data={stationData} errorTypes={this.activeErrorTypes}/>) - date = station = time = ""; - }); - if (components.length === 0) { - rows.push(<STSRow key={ unique_id() } date={date} time={time} component={"-"} station={station} data={stationData} errorTypes={this.activeErrorTypes}/>) - } - prevDate = stationData.date; - }); - - return rows; - } - - renderTableHeaders() { - let th = [] - this.activeErrorTypes.forEach((err) => { - th.push( <th key={err} title={err}>{ componentErrorTypes[err] ? componentErrorTypes[err] : err }</th> ); - }); - return th; - } - - // Do not (re)render when data is loading (performance improvement) - shouldComponentUpdate(nextProps, nextState) { - if (nextProps.isLoading) { - return false; - } - return true; - } - - render() { - this.setActiveErrorTypes(); - - return ( - <React.Fragment> - <h5 className="react-grid-item-header"> - Station test summary - <ToolBar onChange={this.onToolbarChange} allErrorTypes={this.state.allErrorTypes} /> - </h5> - <div className="react-grid-item-body"> - <Table bordered hover size="sm" className="sts-table"> - <thead> - <tr> - <th>Date</th> - <th>Time</th> - <th>Station</th> - <th>Comp.</th> - {this.renderTableHeaders()} - </tr> - </thead> - <tbody> - { this.getTableRows() } - </tbody> - </Table> - </div> - </React.Fragment> - ); - } - -} - -// Map the appInitData from store for the error types -const mapStateToProps = state => { - return { ...state.appInitData }; -}; - -/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the - auto-loading of the data for StationOverviewC. - */ -const StationTestSummary = AutoLoadWrapper( connect(mapStateToProps)(StationTestSummaryC) ); - -export default StationTestSummary; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.scss deleted file mode 100644 index e96c2c8a3acc41507b5c065720f74a9857699fd8..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestSummary.scss +++ /dev/null @@ -1,25 +0,0 @@ -.sts-toolbar { - position: absolute; - right: 0.5rem; - top: 0.1rem; - display: inline-block; -} - -.sts-table { - width: auto!important; - text-align: center; - font-size: .9rem; -} - -.sts-table th { - word-break: break-all; - min-width: 3em; -} - -.sts-table td { - text-align: center; -} - -.sts-table.table-sm td, .sts-table.table-sm th { - padding: .2rem; -} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.css deleted file mode 100644 index aefc047c97d4cbd290e08d3f3cbf7705b5d5904f..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.css +++ /dev/null @@ -1,96 +0,0 @@ -/* COLORS */ -/* Color palette interface (created with https://material.io/tools/color/) */ -/* font color */ -/* font color */ -/* Data colors */ -.stv-table { - font-size: .9rem; } - -.stv-table.table-sm td, .stv-table.table-sm th { - padding: .1rem; - min-width: 1.8em; } - -.stv-tableheader { - position: relative; - background-color: white; - text-align: center; } - -.stv-rtsm-summary-row { - background-color: #eeeeee; } - -.stv-component-status { - text-align: center; - font-size: 90%; } - -.stv-component-status.highlight, -.stv-testline.highlight { - background-color: #86868a !important; - color: white; } - -.stv-testline.highlight td:first-child::before { - content: "> "; } - -.stv-testline-header { - width: 12rem !important; - min-width: 12rem !important; } - -.stv-component-status:hover { - color: #fff; - background-color: #86868a; - border-color: #86868a; } - -.stv-rtsm-summary-badge { - background-color: #fbfb83; - color: black; - text-shadow: 1px 2px white; - font-size: 80%; - text-align: center; } - -.stv-rtsm-summary-row .row-header { - width: 12rem !important; - min-width: 12rem !important; - cursor: pointer; } - -.tab-navbar { - min-height: 2em !important; } - -.clickable-nav-link, .clickable-tab-active, .clickable-tab-unactive { - border-style: none; } - -.clickable-tab-active { - color: white !important; - background-color: #4a6889; } - -.clickable-tab { - cursor: pointer; - color: #001832; } - -.clickable-tab:hover { - color: #4a6889; } - -@keyframes animation-open { - from { - transform: rotate(0deg); } - to { - transform: rotate(180deg); } } - -@keyframes animation-close { - from { - transform: rotate(180deg); } - to { - transform: rotate(0deg); } } - -.stv-rtsm-summary-row .dropdownbutton { - display: inline; - float: right; - animation: animation-close; - animation-duration: 100ms; - animation-iteration-count: 1; - animation-timing-function: linear; } - -.stv-rtsm-summary-row .dropdownbutton-up { - transform: rotate(180deg); - animation: animation-open; - animation-duration: 100ms; - animation-iteration-count: 1; - animation-timing-function: linear; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js deleted file mode 100644 index 60824482bf264510ed02871cc4be2b72fc9c4068..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.js +++ /dev/null @@ -1,482 +0,0 @@ -import React, { - Component -} from 'react'; -import {connect} from "react-redux"; -import {setChildPanelData} from '../redux/actions/stationOverviewPageActions' -import { - Badge, - NavItem, - NavLink, - TabPane, - TabContent, - Nav -} from 'reactstrap'; -import {IoMdArrowDropdown as DropDownIcon} from 'react-icons/io'; -import AutoLoadWrapper from '../utils/autoLoader.js' -import * as LOFARDefinitions from '../utils/LOFARDefinitions' -import {datetime_format} from '../utils/constants' -import {renderDateRange} from '../utils/utils' -import ReactTableContainer from "react-table-container"; -import moment from 'moment'; -import {withRouter} from "react-router"; -import classNames from "classnames"; -import FillHeight from '../components/FillHeight.js'; - - -// CSS -import '../themes/lofar-styles.css'; -import './StationOverview.css'; -import './StationTestView.css'; - - -class GenericStatusTdC extends Component { - - getClass() { - let cls = 'stv-component-status'; - if (this.props.doHighlight) { - cls += " highlight"; - } - if (this.props.n_errors > 0) { - cls += ' so-serious' - } else { - cls += ' so-good' - } - return cls; - } - - onMouseOver = (e) => { - e.stopPropagation(); - if (this.props.n_errors > 0) { - this.props.onSelect(this.props.data); - } - }; - - onMouseOut = (e) => { - e.stopPropagation(); - if (this.props.n_errors > 0) { - this.props.onSelect(null); - } - - }; - - // left-click with mouse - onClick = () => { - this.props.history.push(`/tiles?antenna_id=${this.props.antenna_id}&antenna_type=${this.props.antenna_type}&station=${this.props.station_name}`); - }; - - // right-click with mouse - onContextMenu = (e) => { - e.preventDefault(); - if (this.props.n_errors > 0) { - this.props.onSelect(this.props.data, true); - } - }; - - render() { - const content = (this.props.n_errors===0 ? ' ' : this.props.n_errors); - - return ( - <td className={this.getClass()} - onContextMenu={this.onContextMenu} - onClick={this.onClick} - onMouseOver={this.onMouseOver} - onMouseOut={this.onMouseOut}> - {content} - </td> - ); - } -} - -const GenericStatusTd = withRouter(GenericStatusTdC); - -/** - * TestLine: renders a table row with the results of one station test or RTSM run. - */ -class TestLineC extends Component { - - doHighlight = false; - - static formatDate(date) { - return moment(date).format(datetime_format); - } - - shouldHighlight() { - const props = this.props, - errorData = props.highlightData; - - return errorData !== null && - errorData.component_type === props.component_type && - errorData.test_type === props.test_type && - errorData.datetime === TestLineC.formatDate(props.data.start_date); - } - - shouldComponentUpdate(nextProps, nextState, nextContext) { - // this.doHighlight will only be true for the previously and currently selected row - if (nextProps.highlightData !== this.props.highlightData && !this.doHighlight) { - return false; - } - return true; - } - - onSelect = (data, doPin) => { - if (doPin) { - this.doHighlight = true; - } - this.props.setChildPanelData(data, doPin); - }; - - renderComponentErrors() { - const componentErrors = this.props.data.component_errors; - - const renderedComponentErrors = this.props.ordered_component_ids.map((component_id, key) => { - let nErrors = 0, - errors = [], - errorData = {}; - - if (componentErrors.hasOwnProperty(component_id)) { - errors = componentErrors[component_id]; - nErrors = Object.keys(errors).length; - - // Data for child panel and checking if an antenna item must be highlighted permanently (see shouldHighlight) - errorData.errors = errors; - errorData.datetime= TestLineC.formatDate(this.props.data.start_date); - errorData.test_type = this.props.test_type; - errorData.component_id = component_id; - errorData.component_type = this.props.component_type; - } - - return <GenericStatusTd - doHighlight={this.doHighlight && component_id === this.props.highlightData.component_id} - key={key} - data={errorData} - antenna_id={component_id} - antenna_type={this.props.component_type} - station_name={this.props.station_name} - n_errors={nErrors} - onSelect={this.onSelect} /> - }); - - return renderedComponentErrors; - } - - render() { - const date = TestLineC.formatDate(this.props.data.start_date); - - // Determine if this row needs to be highlighted - this.doHighlight = this.shouldHighlight(); - - const cls = this.doHighlight ? "stv-testline highlight" : "stv-testline"; - - return ( - <tr className={cls}> - <td className="stv-testline-header">{this.props.test_type} {date}</td> - {this.renderComponentErrors()} - </tr> - ); - } -} - -// TestLine is connected to Redux store -const TestLine = connect(state => { - return { - ...state.station_page.main_panel - }; -}, { - setChildPanelData -})(TestLineC); - - -/* - * RTSMSummaryLine: create one table row with percentages of errors per antenna. - */ -function RTSMSummaryLine(props) { - const data = props.data; - - const cols = props.ordered_component_ids.map((item, key) => { - if (data[item] > 0) { - let perc = Math.ceil(data[item]); - return (<td key={key} className={'stv-rtsm-summary-badge'}>{perc + '%'} </td>); - } else { - return <td key={key}></td>; - } - }); - - const dropdownAdditionStyles = classNames( - 'dropdownbutton', {'dropdownbutton-up': props.isExpanded}); - - return ( - <tr className='stv-rtsm-summary-row'> - <td className="row-header" onClick={props.onClick}> - RT {props.dateRange} - <DropDownIcon className={"row-header-dropdownbutton " + dropdownAdditionStyles} color="black"/> - </td> - {cols} - </tr> - ); -} - -/* - * RTSMLines: create summary line + expandable data rows - */ -class RTSMLines extends Component { - - state = { - displaySingleTests: false - } - - computeSummary() { - let summary = {}; - let n_tests = this.props.data.length; - const component_id_list = this.props.ordered_component_ids; - component_id_list.forEach(component_id => summary[component_id] = 0); - - this.props.data.forEach((item, key) => { - Object.keys(item.component_errors).forEach((component_id) => { - summary[component_id] += 1 - }) - }); - - Object.keys(summary).forEach(item => summary[item] /= n_tests / 100.); - return summary - } - - toggleDisplaySingleTests = (e) => { - this.setState({ - displaySingleTests: !this.state.displaySingleTests - }); - this.props.update(); - }; - - render() { - let summaryData = this.computeSummary(); - - // RTSM data rows, only shown when expanded - let all_rtsm = this.state.displaySingleTests ? this.props.data : []; - - return ( - <React.Fragment> - <RTSMSummaryLine onClick={this.toggleDisplaySingleTests} - isExpanded={this.state.displaySingleTests} - data={summaryData} - ordered_component_ids={this.props.ordered_component_ids} - dateRange={renderDateRange(this.props.data)} /> - { // All RTSM lines in this block (expanded or folded) - all_rtsm.map((item, key) => - <TestLine className="collapse open" - key={key} - test_type="RT" - ordered_component_ids={this.props.ordered_component_ids} - component_type={this.props.component_type} - station_name={this.props.station_name} - station_type={this.props.station_type} - data={item}/> - ) - } - </React.Fragment> - ); - } -} - -/** - * ComponentClass; renders a table of station tests and rtsm data for one component (HBA, RSP, LBH, etc.) - * - * Props: - * station_type: C, R or I - * type: component type - * data: Data for this component - */ -class ComponentClass extends Component { - - computeComponentIDList(componentType) { - let componentIDSet = new Set(); - this.props.data.forEach(test => { - Object.keys(test.component_errors).forEach(item => componentIDSet.add(item)) - }); - // Numerical sort - return Array.from(componentIDSet).sort((a, b) => a - b); - } - - renderStationTestLine(key, data, component_ids) { - return (<TestLine key={key} - test_type="ST" - ordered_component_ids={component_ids} - component_type={this.props.type} - station_name={this.props.station_name} - station_type={this.props.station_type} - data={data}/>) - } - - renderRTSMLines(key, data, component_ids) { - return (<RTSMLines key={key} - ordered_component_ids={component_ids} - component_type={this.props.type} - station_name={this.props.station_name} - station_type={this.props.station_type} - data={data} - update={this.updateIfComponentChanges} />) - } - - renderTestLines(data, component_ids) { - const rows = []; - let tmp_rtsm_set = [], - num_items = data.length; - - for (let i = 0; i < num_items; i++) { - const current_item = data[i], - next_item = (i===num_items-1 ? null : data[i + 1]); - - // Temporarily store RTSM lines - if (current_item.test_type === 'R') { - tmp_rtsm_set.push(current_item) - - // Push lines when next item is a station test or when we are at the last item - if (next_item === null || next_item.test_type === 'S') { - rows.push(this.renderRTSMLines(rows.length, tmp_rtsm_set, component_ids)) - tmp_rtsm_set = [] - } - } - else if (current_item.test_type === 'S') { - rows.push(this.renderStationTestLine(rows.length, current_item, component_ids)); - } - } - - return rows - } - - updateIfComponentChanges = () => { - this.setState({ state: this.state }); - } - - render() { - const comp_ids = this.computeComponentIDList(this.props.type); - - return ( - <ReactTableContainer width="100%" height={this.props.height+'px'}> - <table className="stv-table table-sm table-hover table-bordered"> - <thead className="stv-tableheader"> - <tr> - <th style={{textAlign: "left"}} scope="col"> - <Badge color="info">{this.props.type}</Badge> - </th> - {comp_ids.map((item, key) => - <th scope="col" key={key}>{item}</th>) - } - </tr> - </thead> - <tbody> - {this.renderTestLines(this.props.data, comp_ids)} - </tbody> - </table> - </ReactTableContainer> - ); - } -} - -/* - * Render a Tab item - */ -function Tab({label, onClick, isActive}) { - const cls = isActive ? 'clickable-tab-active' : 'clickable-tab-unactive'; - - return ( - <NavItem className="clickable-tab"> - <NavLink className={cls} onClick={onClick}> - {label} - </NavLink> - </NavItem> - ); -} - -/** - * StationTestView class. - */ -class StationTestViewC extends Component { - - state = { - activeTab: undefined - } - - // Set the activeTab to the first component if it wasn't set yet or when - // the station changed and doesn't have the active component - static getDerivedStateFromProps(props, state) { - let componentTypes = Object.keys(props.data).sort(), - currentComponent = state.activeTab; - - if (componentTypes.length === 0) { - return null; - } - - if (! currentComponent || componentTypes.findIndex((c) => currentComponent === c) === -1) { - return { - activeTab: componentTypes[0] - }; - } - return null; - } - - // Do not (re)render when data is loading (performance improvement) - shouldComponentUpdate(nextProps, nextState, nextContext) { - return nextProps.isLoading ? false : true; - } - - toggleTab = (e) => { - let tab = e.currentTarget.innerHTML; - if (this.state.activeTab !== tab) { - this.setState({ - activeTab: tab - }); - } - } - - render() { - const stationType = LOFARDefinitions.stationTypeFromName(this.props.selectedStation); - const componentTypes = Object.keys(this.props.data).sort(); - - if (this.props.isLoading) { - return null; - } - - return ( - <div> - <Nav tabs className="component-type-selector"> - { - componentTypes.map((componentType, key) => - <Tab key={key} onClick={this.toggleTab} isActive={this.state.activeTab===componentType} label={componentType} /> - ) - } - </Nav> - <FillHeight className="border-right"> - { ({height}) => ( - <TabContent activeTab={this.state.activeTab}> - {componentTypes.map((componentType, key) => - <TabPane key={key} tabId={componentType}> - <ComponentClass key={componentType} - station_type={stationType} - type={componentType} - station_name={this.props.selectedStation} - data={this.props.data[componentType]} - height={height} - /> - </TabPane> - )} - </TabContent> - )} - </FillHeight> - </div>); - } - - -} - -/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the - auto-loading of the data for StationOverviewC. - */ -const StationTestViewController = connect(state => { - return { - selectedStation: state.mainFilters.selectedStation - }; -})(StationTestViewC); - -const StationTestView = AutoLoadWrapper(StationTestViewController); - -export default StationTestView; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.scss deleted file mode 100644 index 31c17f5985150fdcb3c2d6d3642e3db82565db34..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestView.scss +++ /dev/null @@ -1,120 +0,0 @@ -@import '../themes/lofar-variables.scss'; - - - -.stv-table { - font-size: .9rem; -} - -.stv-table.table-sm td, .stv-table.table-sm th { - padding: .1rem; - min-width: 1.8em; -} - -.stv-tableheader { - position: relative; - background-color: white; - text-align: center; -} - -.stv-rtsm-summary-row { - background-color: #eeeeee; -} - -.stv-component-status{ - text-align: center; - font-size: 90%; -} -.stv-component-status.highlight, -.stv-testline.highlight { - background-color: $secondary-dark!important; - color: $secondary-color; -} -.stv-testline.highlight td:first-child::before { - content: "> " -} -.stv-testline-header { - width: 12rem !important; - min-width: 12rem !important; -} - -.stv-component-status:hover { - color: #fff; - background-color: $secondary-dark; - border-color: $secondary-dark; -} - -.stv-rtsm-summary-badge{ - background-color: $warning; - color: $warning-fontcolor; - text-shadow: 1px 2px white; - font-size: 80%; - text-align: center; -} - -.stv-rtsm-summary-row .row-header { - width: 12rem !important; - min-width: 12rem !important; - cursor: pointer; -} - -.tab-navbar { - min-height: 2em !important; -} - -.clickable-nav-link{ - border-style: none; -} - -.clickable-tab-active{ - @extend .clickable-nav-link; - color: white !important; - background-color: $primary-light; -} - -.clickable-tab-unactive{ - @extend .clickable-nav-link; - -} - -.clickable-tab { - cursor: pointer; - color: $primary-dark; -} - -.clickable-tab:hover { - color: $primary-light; -} - -@keyframes animation-open { - from { - transform: rotate(0deg); - } to { - transform: rotate(180deg); - } -} - -@keyframes animation-close { - from { - transform: rotate(180deg); - } to { - transform: rotate(0deg); - } -} - -.stv-rtsm-summary-row .dropdownbutton { - display: inline; - float: right; - animation: animation-close; - animation-duration: 100ms; - animation-iteration-count: 1; - animation-timing-function: linear; -} - -.stv-rtsm-summary-row .dropdownbutton-up { - transform: rotate(180deg); - animation: animation-open; - animation-duration: 100ms; - animation-iteration-count: 1; - animation-timing-function: linear; -} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js deleted file mode 100644 index 0ffb52a22ab4ae8ffd5ebed86e0b3cdf08d0d938..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.js +++ /dev/null @@ -1,417 +0,0 @@ -import React, {Component} from 'react'; -import {connect} from "react-redux"; -import {Button, ButtonGroup, Input} from 'reactstrap'; -import { - setDateRange, - setTestType, - setErrorTypes, - setStationGroup, - setErrorsOnly, - setPeriod, setAntennaID, - setAntennaType -} from "../redux/actions/mainFiltersActions"; -import {stationTypeFromName} from "../utils/LOFARDefinitions"; -// History handling -import { push } from 'connected-react-router'; -import { store } from "../redux/store.js"; - -import DatePicker from 'react-datepicker'; -import moment from 'moment'; -import MultiSelectDropdown from '../components/MultiSelectDropdown.js' -import {AntennaIdsPerTypeStationType} from '../utils/LOFARDefinitions.js' -// CSS -import './Toolbar.css' -import 'react-datepicker/dist/react-datepicker.css'; - - -/** - * Class to display a secondary header for selecting data filters. - * The state is managed by the LandingPage class. - */ -class DateRangeSelectorC extends Component { - - handleChange(obj) { - var startDate = obj.startDate, - endDate = obj.endDate; - - startDate = startDate || this.props.startDate; - endDate = endDate || this.props.endDate; - - if (startDate.isAfter(endDate)) { - endDate = startDate; - } - - this.props.setDateRange({ - startDate: startDate, - endDate: endDate - }); - }; - - handleChangeStart = (startDate) => { - return this.handleChange({ - startDate: startDate - }); - }; - - handleChangeEnd = (endDate) => { - return this.handleChange({ - endDate: endDate - }); - }; - - onPeriodClick = (i) => { - this.handleChange({ - startDate: moment().subtract(i, 'days'), - endDate: moment() - }); - - } - - periodIsActive(ndays) { - let now = moment().format("YYYY-MMM-DD"), - past = moment().subtract(ndays, 'days').format("YYYY-MMM-DD"); - if (this.props.endDate.format("YYYY-MMM-DD") === now && this.props.startDate.format("YYYY-MMM-DD") === past) { - return true; - } - return false; - } - - render() { - return (<div className="toolbar-ctrl"> - <label>Period </label> - <ButtonGroup size="sm"> - <Button color="info" onClick={() => this.onPeriodClick(7)} active={this.periodIsActive(7)}>1 wk</Button> - <Button color="info" onClick={() => this.onPeriodClick(14)} active={this.periodIsActive(14)}>2 wk</Button> - <Button color="info" onClick={() => this.onPeriodClick(28)} active={this.periodIsActive(28)}>4 wk</Button> - </ButtonGroup> - - <div style={{display: 'inline-block', width: '7em'}}> - <DatePicker - selected={this.props.startDate} - selectsStart - dateFormat="YYYY-MMM-DD" - className='form-control form-control-sm' - startDate={this.props.startDate} - endDate={this.props.endDate} - onChange={this.handleChangeStart} - /> - </div> - - <div style={{display: 'inline-block', width: '7em'}}> - <DatePicker - selected={this.props.endDate} - selectsEnd - dateFormat="YYYY-MMM-DD" - className='form-control form-control-sm' - startDate={this.props.startDate} - endDate={this.props.endDate} - onChange={this.handleChangeEnd} - /> - </div> - </div>); - } -} - -const mapStateDateRangeSelector = state => { - return { - startDate: state.mainFilters.startDate, - endDate: state.mainFilters.endDate - }; -}; - -const mapDispatchDateRangeSelector = { - setDateRange -}; - -export const DateRangeSelector = connect(mapStateDateRangeSelector, mapDispatchDateRangeSelector)(DateRangeSelectorC); - - - -/** - * Radio buttons for Test Type - */ -class TestTypeSelectorC extends Component { - - onTestTypeClick(type) { - this.props.setTestType(type); - } - - render() { - return (<div className="toolbar-ctrl"> - <label>Type </label> - <ButtonGroup size="sm"> - <Button color="info" onClick={() => this.onTestTypeClick('B')} active={this.props.testType === 'B'}>BOTH</Button> - <Button color="info" onClick={() => this.onTestTypeClick('S')} active={this.props.testType === 'S'}>ST-TEST</Button> - <Button color="info" onClick={() => this.onTestTypeClick('R')} active={this.props.testType === 'R'}>RTSM</Button> - </ButtonGroup> - </div>); - } -} - -const mapStateTestTypeSelector = state => { - return { - testType: state.mainFilters.testType - }; -}; - -const mapDispatchTestTypeSelector = { - setTestType -}; - -export const TestTypeSelector = connect(mapStateTestTypeSelector, mapDispatchTestTypeSelector)(TestTypeSelectorC); - - -/** - * Radio buttons for Antenna Type - */ -class AntennaTypeSelectorC extends Component { - - onAntennaTypeClick = (e) => { - let type = e.currentTarget.innerHTML; - //this.props.setAntennaType(type); - - store.dispatch(push(`?antenna_type=${type}`)) - } - - render() { - return (<div className="toolbar-ctrl"> - <label>Type </label> - <ButtonGroup size="sm"> - <Button color="info" onClick={this.onAntennaTypeClick} active={this.props.antennaType === 'HBA'}>HBA</Button> - <Button color="info" onClick={this.onAntennaTypeClick} active={this.props.antennaType === 'LBH'}>LBH</Button> - <Button color="info" onClick={this.onAntennaTypeClick} active={this.props.antennaType === 'LBL'}>LBL</Button> - </ButtonGroup> - </div>); - } -} - -const mapStateAntennaTypeSelector = state => { - return { - antennaType: state.mainFilters.antennaType - }; -}; - -const mapDispatchAntennaTypeSelector = { - setAntennaType -}; - -export const AntennaTypeSelector = connect(mapStateAntennaTypeSelector, mapDispatchAntennaTypeSelector)(AntennaTypeSelectorC); - - -/** - * Radio buttons for Antenna Id - */ -class AntennaIdSelectorC extends Component { - - antennaIdList(type, stationType){ - const antennaIdRange = AntennaIdsPerTypeStationType[stationType][type]; - let options = []; - for(let i=antennaIdRange.start; i<antennaIdRange.end; i++){ - options.push(<option key={i} value={i}>{i}</option>); - } - return options - } - onSelectedAntenna = (e) => { - //this.props.setAntennaID(Number(e.target.value)); - store.dispatch(push(`?antenna_id=${e.target.value}`)) - }; - - render() { - const stationType = stationTypeFromName(this.props.selectedStation); - - const options = this.antennaIdList(this.props.antennaType, stationType); - - return ( - <div className="toolbar-ctrl"> - <label>Antenna id </label> - <Input type="select" - value={this.props.antennaId} - onChange={this.onSelectedAntenna} - bsSize="sm" - style={{display:"inline-block", width:"auto"}}> - {options} - </Input> - </div>); - } - -} - -const mapStateAntennaIdSelector = state => { - return { - selectedStation: state.mainFilters.selectedStation, - antennaId: state.mainFilters.antennaId, - antennaType: state.mainFilters.antennaType - }; -}; - -const mapDispatchAntennaIdSelector = { - setAntennaID -}; - -export const AntennaIdSelector = connect(mapStateAntennaIdSelector, mapDispatchAntennaIdSelector)(AntennaIdSelectorC); - - -/** - * Class to display a secondary header for selecting data filters. - * The state is managed by the LandingPage class. - */ -class ErrorTypesSelectorC extends Component { - - onSelectionErrorTypes = (errorTypes) => { - this.props.setErrorTypes(errorTypes) - } - - render() { - const errorTypes = this.props.errorTypes.map(item => ({value:item, label:item})) - - return (<div className="toolbar-ctrl"> - <label>Error type </label> - <MultiSelectDropdown - className="form-input" - placeHolder="All" - options={errorTypes} - selectedItems={this.props.selectedErrorTypes} - onSelectionChange={this.onSelectionErrorTypes} - /> - </div>); - } -} - -const mapStateErrorTypesSelector = state => { - return { - selectedErrorTypes: state.mainFilters.selectedErrorTypes, - errorTypes: state.appInitData.errorTypes - }; -}; - -const mapDispatchErrorTypesSelector = { - setErrorTypes -}; - -export const ErrorTypesSelector = connect(mapStateErrorTypesSelector, mapDispatchErrorTypesSelector)(ErrorTypesSelectorC); - - - -/* - * - */ -class StationGroupSelectorC extends Component { - - onStationGroupChange = (e) => { - this.props.setStationGroup(e.target.value); - } - - render() { - return (<div className="toolbar-ctrl"> - <label htmlFor="selected-group">Station group </label> - <select className="form-control custom-select custom-select-sm" id="selected-group" - value={this.props.selectedStationGroup} - onChange={this.onStationGroupChange} - style={{ width: 'auto' }}> - <option value="A">All stations</option> - <option value="C">Core stations</option> - <option value="R">Remote stations</option> - <option value="I">ILT stations</option> - </select> - </div>); - } -} - -const mapStateStationGroupSelector = state => { - return { - selectedStationGroup: state.mainFilters.selectedStationGroup - }; -}; - -const mapDispatchStationGroupSelector = { - setStationGroup -}; - -export const StationGroupSelector = connect(mapStateStationGroupSelector, mapDispatchStationGroupSelector)(StationGroupSelectorC); - - -/* - * Toggle button for "errorsOnly" - */ -class ErrorsOnlySelectorC extends Component { - - onErrorsOnlyClick = () => { - this.props.setErrorsOnly(!this.props.errorsOnly); - } - - render() { - return ( - <div className="toolbar-ctrl"> - <Button color="info" size="sm" onClick={this.onErrorsOnlyClick} active={this.props.errorsOnly}> - Errors only - </Button> - </div>); - } -} - -const mapStateErrorsOnlySelector = state => { - return { - errorsOnly: state.mainFilters.errorsOnly - }; -}; - -const mapDispatchErrorsOnlySelector = { - setErrorsOnly -}; - -export const ErrorsOnlySelector = connect(mapStateErrorsOnlySelector, mapDispatchErrorsOnlySelector)(ErrorsOnlySelectorC); - - - -/** - * Class to display a secondary header for selecting data filters. - * The state is managed by the LandingPage class. - */ -class PeriodSelectorC extends Component { - - onPeriodClick(i) { - this.props.setPeriod(i); - } - - render() { - - return (<div className="toolbar-ctrl"> - <label>Period </label> - <ButtonGroup size="sm"> - <Button color="info" onClick={() => this.onPeriodClick(7)} active={this.props.period === 7}>1 wk</Button> - <Button color="info" onClick={() => this.onPeriodClick(14)} active={this.props.period === 14}>2 wk</Button> - <Button color="info" onClick={() => this.onPeriodClick(21)} active={this.props.period === 21}>3 wk</Button> - <Button color="info" onClick={() => this.onPeriodClick(28)} active={this.props.period === 28}>4 wk</Button> - </ButtonGroup> - </div>); - } -} - -const mapStatePeriodSelector = state => { - return { - period: state.mainFilters.period - }; -}; - -const mapDispatchPeriodSelector = { - setPeriod -}; - -export const PeriodSelector = connect(mapStatePeriodSelector, mapDispatchPeriodSelector)(PeriodSelectorC); - - - -/* - * Toolbar: the wrapper for all toolbar components. Use as: - * <Toolbar> - * <StationAutoComplete /> - * <TestTypeSelector /> - * <..> - * </Toolbar> - */ -export function Toolbar(props) { - - return (<div className="toolbar-top"> - {props.children} - </div>); -} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaIdSelector/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaIdSelector/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c036c55d4635457a10164758a571435289f63afc --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaIdSelector/index.js @@ -0,0 +1,69 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {Input} from 'reactstrap'; + +// History handling +import { push } from 'connected-react-router'; +import { store } from "redux/store.js"; + +import {setAntennaID} from "redux/actions/mainFiltersActions"; +import {stationTypeFromName, AntennaIdsPerTypeStationType} from "utils/LOFARDefinitions"; + +// CSS +import toolbarStyles from '../styles.module.scss' +import styles from './styles.module.scss' + + + +/** + * AntennaIdSelector: + * Select box for selecting an antenna Id. This component is part of {@link module:Toolbar}. + */ +class AntennaIdSelectorC extends Component { + + antennaIdList(type, stationType){ + const antennaIdRange = AntennaIdsPerTypeStationType[stationType][type]; + let options = []; + for(let i=antennaIdRange.start; i<antennaIdRange.end; i++){ + options.push(<option key={i} value={i}>{i}</option>); + } + return options + } + onSelectedAntenna = (e) => { + //this.props.setAntennaID(Number(e.target.value)); + store.dispatch(push(`?antenna_id=${e.target.value}`)) + }; + + render() { + const stationType = stationTypeFromName(this.props.selectedStation); + + const options = this.antennaIdList(this.props.antennaType, stationType); + + return ( + <div className={toolbarStyles.toolbar_ctrl}> + <label>Antenna id</label> + <Input type="select" + value={this.props.antennaId} + onChange={this.onSelectedAntenna} + bsSize="sm" + className={styles.antenna_id_select}> + {options} + </Input> + </div>); + } + +} + +const mapStateAntennaIdSelector = state => { + return { + selectedStation: state.mainFilters.selectedStation, + antennaId: state.mainFilters.antennaId, + antennaType: state.mainFilters.antennaType + }; +}; + +const mapDispatchAntennaIdSelector = { + setAntennaID +}; + +export const AntennaIdSelector = connect(mapStateAntennaIdSelector, mapDispatchAntennaIdSelector)(AntennaIdSelectorC); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaIdSelector/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaIdSelector/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..59eddfaa1b9fc0c9445b72f50776baef38646205 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaIdSelector/styles.module.scss @@ -0,0 +1,5 @@ + +:local .antenna_id_select { + display: inline-block; + width: auto; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaTypeSelector/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaTypeSelector/index.js new file mode 100644 index 0000000000000000000000000000000000000000..586e0d42dfe8328e4011ba6e6f58ee33b75cfd07 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/AntennaTypeSelector/index.js @@ -0,0 +1,48 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {Button, ButtonGroup} from 'reactstrap'; +import {setAntennaType} from "redux/actions/mainFiltersActions"; + +// History handling +import { push } from 'connected-react-router'; +import { store } from "redux/store.js"; + +// CSS +import toolbarStyles from '../styles.module.scss' + + +/** + * Radio buttons for Antenna Type + */ +class AntennaTypeSelectorC extends Component { + + onAntennaTypeClick = (e) => { + let type = e.currentTarget.innerHTML; + //this.props.setAntennaType(type); + + store.dispatch(push(`?antenna_type=${type}`)) + } + + render() { + return (<div className={toolbarStyles.toolbar_ctrl}> + <label>Type</label> + <ButtonGroup size="sm"> + <Button color="info" onClick={this.onAntennaTypeClick} active={this.props.antennaType === 'HBA'}>HBA</Button> + <Button color="info" onClick={this.onAntennaTypeClick} active={this.props.antennaType === 'LBH'}>LBH</Button> + <Button color="info" onClick={this.onAntennaTypeClick} active={this.props.antennaType === 'LBL'}>LBL</Button> + </ButtonGroup> + </div>); + } +} + +const mapStateAntennaTypeSelector = state => { + return { + antennaType: state.mainFilters.antennaType + }; +}; + +const mapDispatchAntennaTypeSelector = { + setAntennaType +}; + +export const AntennaTypeSelector = connect(mapStateAntennaTypeSelector, mapDispatchAntennaTypeSelector)(AntennaTypeSelectorC); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/DateRangeSelector/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/DateRangeSelector/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6c652eaa29056be93d856317759da544bac2af7e --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/DateRangeSelector/index.js @@ -0,0 +1,123 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {Button, ButtonGroup} from 'reactstrap'; +import {setDateRange} from "redux/actions/mainFiltersActions"; + +import DatePicker from 'react-datepicker'; +import moment from 'moment'; + +// CSS +import 'react-datepicker/dist/react-datepicker.css'; +import toolbarStyles from '../styles.module.scss' +import './styles.module.scss' + + +/** + * Class to display a secondary header for selecting data filters. + * The state is managed by the LandingPage class. + */ +class DateRangeSelectorC extends Component { + + handleChange(obj) { + var startDate = obj.startDate, + endDate = obj.endDate; + + startDate = startDate || this.props.startDate; + endDate = endDate || this.props.endDate; + + if (startDate.isAfter(endDate)) { + endDate = startDate; + } + + this.props.setDateRange({ + startDate: startDate, + endDate: endDate + }); + }; + + handleChangeStart = (startDate) => { + return this.handleChange({ + startDate: startDate + }); + }; + + handleChangeEnd = (endDate) => { + return this.handleChange({ + endDate: endDate + }); + }; + + onPeriodClick = (i) => { + this.handleChange({ + startDate: moment().subtract(i, 'days'), + endDate: moment() + }); + + } + + periodIsActive(ndays) { + let now = moment().format("YYYY-MMM-DD"), + past = moment().subtract(ndays, 'days').format("YYYY-MMM-DD"); + if (this.props.endDate.format("YYYY-MMM-DD") === now && this.props.startDate.format("YYYY-MMM-DD") === past) { + return true; + } + return false; + } + + render() { + return (<div className={toolbarStyles.toolbar_ctrl}> + <label>Period</label> + <ButtonGroup size="sm"> + <Button color="info" + onClick={() => this.onPeriodClick(7)} + active={this.periodIsActive(7)}> + 1 wk + </Button> + <Button color="info" + onClick={() => this.onPeriodClick(14)} + active={this.periodIsActive(14)}> + 2 wk + </Button> + <Button color="info" + onClick={() => this.onPeriodClick(28)} + active={this.periodIsActive(28)}> + 4 wk + </Button> + </ButtonGroup> + <DatePicker + selected={this.props.startDate} + selectsStart + dateFormat="YYYY-MMM-DD" + className='form-control form-control-sm' + startDate={this.props.startDate} + endDate={this.props.endDate} + onChange={this.handleChangeStart} + /> + <DatePicker + selected={this.props.endDate} + selectsEnd + dateFormat="YYYY-MMM-DD" + className='form-control form-control-sm' + startDate={this.props.startDate} + endDate={this.props.endDate} + onChange={this.handleChangeEnd} + /> + </div>); + } +} + +const mapStateDateRangeSelector = state => { + return { + startDate: state.mainFilters.startDate, + endDate: state.mainFilters.endDate + }; +}; + +const mapDispatchDateRangeSelector = { + setDateRange +}; + +export const DateRangeSelector = connect( + mapStateDateRangeSelector, + mapDispatchDateRangeSelector +)(DateRangeSelectorC); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/DateRangeSelector/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/DateRangeSelector/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..a7a417add5ae8d172e1c671eded27c603176d088 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/DateRangeSelector/styles.module.scss @@ -0,0 +1,13 @@ +/* + * Global overrides + */ + +:global .react-datepicker-wrapper { + width: 7em; + margin-left: 0.25em; +} + +/* +:global .react-datepicker-popper { + z-index: 2 !important; +} */ diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/ErrorTypesSelector/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/ErrorTypesSelector/index.js new file mode 100644 index 0000000000000000000000000000000000000000..0dd827f386bb7561bca453c60a5c3291eed59b54 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/ErrorTypesSelector/index.js @@ -0,0 +1,47 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {setErrorTypes} from "redux/actions/mainFiltersActions"; + +import MultiSelectDropdown from 'components/MultiSelectDropdown' + +// CSS +import toolbarStyles from '../styles.module.scss' + +/** + * ErrorTypesSelector: + * Renders a multi-select dropdown for selecting error types (HIGH_NOISE, LOW_NOISE, ..) + */ +class ErrorTypesSelectorC extends Component { + + onSelectionErrorTypes = (errorTypes) => { + this.props.setErrorTypes(errorTypes) + } + + render() { + const errorTypes = this.props.errorTypes.map(item => ({value:item, label:item})) + + return (<div className={toolbarStyles.toolbar_ctrl}> + <label>Error type</label> + <MultiSelectDropdown + className="form-input" + placeHolder="All" + options={errorTypes} + selectedItems={this.props.selectedErrorTypes} + onSelectionChange={this.onSelectionErrorTypes} + /> + </div>); + } +} + +const mapStateErrorTypesSelector = state => { + return { + selectedErrorTypes: state.mainFilters.selectedErrorTypes, + errorTypes: state.appInitData.errorTypes + }; +}; + +const mapDispatchErrorTypesSelector = { + setErrorTypes +}; + +export const ErrorTypesSelector = connect(mapStateErrorTypesSelector, mapDispatchErrorTypesSelector)(ErrorTypesSelectorC); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/ErrorsOnlySelector/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/ErrorsOnlySelector/index.js new file mode 100644 index 0000000000000000000000000000000000000000..66e84a77e79082a524b8dddcbd7c9ff35da56a5c --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/ErrorsOnlySelector/index.js @@ -0,0 +1,39 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {Button} from 'reactstrap'; +import {setErrorsOnly} from "redux/actions/mainFiltersActions"; + +// CSS +import toolbarStyles from '../styles.module.scss' + + +/* + * Toggle button for "errorsOnly" + */ +class ErrorsOnlySelectorC extends Component { + + onErrorsOnlyClick = () => { + this.props.setErrorsOnly(!this.props.errorsOnly); + } + + render() { + return ( + <div className={toolbarStyles.toolbar_ctrl}> + <Button color="info" size="sm" onClick={this.onErrorsOnlyClick} active={this.props.errorsOnly}> + Errors only + </Button> + </div>); + } +} + +const mapStateErrorsOnlySelector = state => { + return { + errorsOnly: state.mainFilters.errorsOnly + }; +}; + +const mapDispatchErrorsOnlySelector = { + setErrorsOnly +}; + +export const ErrorsOnlySelector = connect(mapStateErrorsOnlySelector, mapDispatchErrorsOnlySelector)(ErrorsOnlySelectorC); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/PeriodSelector/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/PeriodSelector/index.js new file mode 100644 index 0000000000000000000000000000000000000000..a234887b9cc335ef0a4f6add3d5b682b5721d82f --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/PeriodSelector/index.js @@ -0,0 +1,44 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {Button, ButtonGroup} from 'reactstrap'; +import {setPeriod} from "redux/actions/mainFiltersActions"; + +// CSS +import toolbarStyles from '../styles.module.scss' + + +/** + * Class to display a secondary header for selecting data filters. + * The state is managed by the LandingPage class. + */ +class PeriodSelectorC extends Component { + + onPeriodClick(i) { + this.props.setPeriod(i); + } + + render() { + + return (<div className={toolbarStyles.toolbar_ctrl}> + <label>Period</label> + <ButtonGroup size="sm"> + <Button color="info" onClick={() => this.onPeriodClick(7)} active={this.props.period === 7}>1 wk</Button> + <Button color="info" onClick={() => this.onPeriodClick(14)} active={this.props.period === 14}>2 wk</Button> + <Button color="info" onClick={() => this.onPeriodClick(21)} active={this.props.period === 21}>3 wk</Button> + <Button color="info" onClick={() => this.onPeriodClick(28)} active={this.props.period === 28}>4 wk</Button> + </ButtonGroup> + </div>); + } +} + +const mapStatePeriodSelector = state => { + return { + period: state.mainFilters.period + }; +}; + +const mapDispatchPeriodSelector = { + setPeriod +}; + +export const PeriodSelector = connect(mapStatePeriodSelector, mapDispatchPeriodSelector)(PeriodSelectorC); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationAutoComplete/index.js similarity index 96% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationAutoComplete/index.js index b8def694ce7dd4d59935ce50205f8c53ffc93d28..4cb6288c55f396387b71b2c0f0be33ae946dce97 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationAutoComplete/index.js @@ -10,10 +10,11 @@ import { // History handling import { push } from 'connected-react-router'; -import { store } from "../redux/store.js"; +import { store } from "redux/store.js"; // CSS -import './StationAutoComplete.scss' +import toolbarStyles from '../styles.module.scss' +import './styles.scss' /** @@ -210,11 +211,12 @@ class StationAutoCompleteC extends Component { // The key on AutoComplete is important, see the desc of AutoComplete // That's the main reason why this wrapper exists. return( - <div className="toolbar-ctrl"> + <div className={toolbarStyles.toolbar_ctrl}> <AutoComplete key={this.props.selectedStation} stations={this.props.stations} selectedStation={this.props.selectedStation} - onChange={this.onStationChange}/> + onChange={this.onStationChange} + /> </div> ); } @@ -231,4 +233,4 @@ const mapStateToPropsToolBar = state => { const StationAutoComplete = connect(mapStateToPropsToolBar)(StationAutoCompleteC); -export default StationAutoComplete; +export { StationAutoComplete }; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationAutoComplete/styles.scss similarity index 97% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.scss rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationAutoComplete/styles.scss index 4b7f44a72a7e1696fe817bb016af9a41a53b7e6b..160c494558ff70b94aeab51ef2bc1582f884661d 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationAutoComplete.scss +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationAutoComplete/styles.scss @@ -1,5 +1,5 @@ -@import '../themes/lofar-variables.scss'; +@import '../../../themes/lofar-variables.scss'; .react-autosuggest__container { position: relative; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationGroupSelector/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationGroupSelector/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c973010ab252b8d159dc93e5ef5cd7b03bd8af4c --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/StationGroupSelector/index.js @@ -0,0 +1,44 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {setStationGroup} from "redux/actions/mainFiltersActions"; + +// CSS +import toolbarStyles from '../styles.module.scss' + +/** + * StationGroupSelector: + * Select box for selecting a station group (all, core, remote, ilt) + */ +class StationGroupSelectorC extends Component { + + onStationGroupChange = (e) => { + this.props.setStationGroup(e.target.value); + } + + render() { + return (<div className={toolbarStyles.toolbar_ctrl}> + <label htmlFor="selected-group">Station group</label> + <select className="form-control custom-select custom-select-sm" id="selected-group" + value={this.props.selectedStationGroup} + onChange={this.onStationGroupChange} + style={{ width: 'auto' }}> + <option value="A">All stations</option> + <option value="C">Core stations</option> + <option value="R">Remote stations</option> + <option value="I">ILT stations</option> + </select> + </div>); + } +} + +const mapStateStationGroupSelector = state => { + return { + selectedStationGroup: state.mainFilters.selectedStationGroup + }; +}; + +const mapDispatchStationGroupSelector = { + setStationGroup +}; + +export const StationGroupSelector = connect(mapStateStationGroupSelector, mapDispatchStationGroupSelector)(StationGroupSelectorC); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/TestTypeSelector/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/TestTypeSelector/index.js new file mode 100644 index 0000000000000000000000000000000000000000..47ea511c6e096b0ce0c462f6d7392bd234b181f9 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/TestTypeSelector/index.js @@ -0,0 +1,41 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {Button, ButtonGroup} from 'reactstrap'; +import {setTestType} from "redux/actions/mainFiltersActions"; + +// CSS +import toolbarStyles from '../styles.module.scss' + + +/** + * Radio buttons for Test Type + */ +class TestTypeSelectorC extends Component { + + onTestTypeClick(type) { + this.props.setTestType(type); + } + + render() { + return (<div className={toolbarStyles.toolbar_ctrl}> + <label>Type</label> + <ButtonGroup size="sm"> + <Button color="info" onClick={() => this.onTestTypeClick('B')} active={this.props.testType === 'B'}>BOTH</Button> + <Button color="info" onClick={() => this.onTestTypeClick('S')} active={this.props.testType === 'S'}>ST-TEST</Button> + <Button color="info" onClick={() => this.onTestTypeClick('R')} active={this.props.testType === 'R'}>RTSM</Button> + </ButtonGroup> + </div>); + } +} + +const mapStateTestTypeSelector = state => { + return { + testType: state.mainFilters.testType + }; +}; + +const mapDispatchTestTypeSelector = { + setTestType +}; + +export const TestTypeSelector = connect(mapStateTestTypeSelector, mapDispatchTestTypeSelector)(TestTypeSelectorC); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/index.js new file mode 100644 index 0000000000000000000000000000000000000000..9e3f193bbfbcb1bdd986efa58fbc4b890da17334 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/index.js @@ -0,0 +1,33 @@ +/** @module Toolbar */ + +import React from 'react'; + +// CSS +import styles from './styles.module.scss' + + +/** + * Toolbar: the wrapper for all toolbar components. Use as: + * <Toolbar> + * <StationAutoComplete /> + * <TestTypeSelector /> + * <..> + * </Toolbar> + */ +export function Toolbar(props) { + + return (<div className={styles.toolbar_top}> + {props.children} + </div>); +} + + +export { AntennaIdSelector } from './AntennaIdSelector'; +export { AntennaTypeSelector } from './AntennaTypeSelector'; +export { DateRangeSelector } from './DateRangeSelector'; +export { ErrorTypesSelector } from './ErrorTypesSelector'; +export { ErrorsOnlySelector } from './ErrorsOnlySelector'; +export { PeriodSelector } from './PeriodSelector'; +export { StationAutoComplete } from './StationAutoComplete'; +export { StationGroupSelector } from './StationGroupSelector'; +export { TestTypeSelector } from './TestTypeSelector'; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/styles.module.css similarity index 54% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.css rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/styles.module.css index ad6dc8f2444266c9f8b7fd79ad3da630df61288d..b6fd2e79934e6678a1b0472f306a57812486b473 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/styles.module.css @@ -2,41 +2,37 @@ /* Color palette interface (created with https://material.io/tools/color/) */ /* font color */ /* font color */ +/* hover color, e.g. for background */ /* Data colors */ -.toolbar-top { +:local .toolbar_top { background-color: #4a6889; color: #ffffff; padding: 5px 10px; /* Note: same padding as ResponsiveGridLayout */ width: 100%; - overflow: visible; } - -.toolbar-top { + overflow: visible; font-weight: 500; } -.toolbar-top .btn-info:not(:disabled):not(.disabled).active, -.sts-toolbar .btn-info:not(:disabled):not(.disabled).active, -.toolbar-top .btn-info:not(:disabled):not(.disabled):active, -.toolbar-top .show > .btn-info.dropdown-toggle { +:local .toolbar_top :global .btn-info:not(:disabled):not(.disabled).active, +:local .toolbar_top :global .btn-info:not(:disabled):not(.disabled):active, +:local .toolbar_top :global .show > .btn-info.dropdown-toggle { color: white; background-color: #86868a; border-color: #86868a; } -.toolbar-top .btn-info, -.sts-toolbar .btn-info { +:local .toolbar_top :global .btn-info { color: white; background-color: #b6b6ba; border-color: #b6b6ba; } -.toolbar-top .btn-info:hover, -.sts-toolbar .btn-info:hover { +:local .toolbar_top :global .btn-info:hover { color: white; background-color: #86868a; border-color: #86868a; } -.react-datepicker-popper { - z-index: 2 !important; } - -.toolbar-ctrl { +:local .toolbar_ctrl { display: inline-block; margin-right: 1em; } + +:local .toolbar_top label { + margin-right: 0.5em; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/styles.module.scss similarity index 52% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.scss rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/styles.module.scss index 050a6d5954be21965c598b77519a4a12954eda6a..b39695beb43df02eef072e2419d6e884c6737d48 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar.scss +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/Toolbar/styles.module.scss @@ -1,44 +1,40 @@ -@import '../themes/lofar-variables.scss'; +@import '../../themes/lofar-variables.scss'; +// TODO check if we want to override bootstrap classes globally -.toolbar-top { +:local .toolbar_top { background-color: $primary-light; color: $primary-color; padding: 5px 10px; /* Note: same padding as ResponsiveGridLayout */ width: 100%; overflow: visible; -} - -.toolbar-top { font-weight: 500; } -.toolbar-top .btn-info:not(:disabled):not(.disabled).active, -.sts-toolbar .btn-info:not(:disabled):not(.disabled).active, -.toolbar-top .btn-info:not(:disabled):not(.disabled):active, -.toolbar-top .show>.btn-info.dropdown-toggle { +:local .toolbar_top :global .btn-info:not(:disabled):not(.disabled).active, +:local .toolbar_top :global .btn-info:not(:disabled):not(.disabled):active, +:local .toolbar_top :global .show>.btn-info.dropdown-toggle { color: $secondary-color; background-color: $secondary-dark; border-color: $secondary-dark; } -.toolbar-top .btn-info, -.sts-toolbar .btn-info { +:local .toolbar_top :global .btn-info { color: $secondary-color; background-color: $secondary; border-color: $secondary; } -.toolbar-top .btn-info:hover, -.sts-toolbar .btn-info:hover { +:local .toolbar_top :global .btn-info:hover { color: $secondary-color; background-color: $secondary-dark; border-color: $secondary-dark; } -.react-datepicker-popper { - z-index: 2 !important; -} -.toolbar-ctrl { +:local .toolbar_ctrl { display: inline-block; margin-right: 1em; } + +:local .toolbar_top label { + margin-right: 0.5em; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.css deleted file mode 100644 index 504f1c3657d8fc1676916b406bac7dd4679edd07..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.css +++ /dev/null @@ -1,18 +0,0 @@ -/* COLORS */ -/* Color palette interface (created with https://material.io/tools/color/) */ -/* font color */ -/* font color */ -/* Data colors */ -.header-navbar.navbar { - background-color: #1c3e5c; - padding: .3rem 1rem; - color: white; } - -.header-brand em { - font-weight: bold; - font-style: normal; } - -.header-brand span { - color: #86868a; - font-size: 1.5rem; - font-style: italic; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.js deleted file mode 100644 index bc1daa61bf8fb50dd1da8380f4ee3d4d84b773b5..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/header.js +++ /dev/null @@ -1,61 +0,0 @@ -import React, { Component } from 'react'; -import { Link } from 'react-router-dom'; -import { Collapse, - Nav, - NavbarToggler, - Navbar, - NavbarBrand, - NavItem, - NavLink } from 'reactstrap'; - -// CSS -import './header.scss' - - -class Header extends Component { - - state = { - isOpen: false - } - - toggle = () => { - this.setState({ - isOpen:!this.state.isOpen - }); - } - - check_status(item){ - //console.log(this.props.active_page.pathname) - if (this.props.active_page.pathname === item){ - return "active" - } - } - - render() { - return ( - <Navbar className="header-navbar" dark expand="lg"> - <NavbarBrand className='header-brand'><h1><em>LOFAR</em> Station monitor <span>v0.1</span></h1></NavbarBrand> - <NavbarToggler onClick={this.toggle} /> - <Collapse isOpen={this.state.isOpen} navbar> - <Nav className="ml-auto" navbar> - <NavItem className={this.check_status("/")}> - <NavLink tag={Link} to="/"><h3>Dashboard</h3></NavLink> - </NavItem> - <NavItem className={this.check_status("/station_overview")}> - <NavLink tag={Link} to="/station_overview"><h3>Station Overview</h3></NavLink> - </NavItem> - <NavItem className={this.check_status("/tiles")}> - <NavLink tag={Link} to="/tiles"><h3>Tiles</h3></NavLink> - </NavItem> - { /*} - <NavItem className={this.check_status("/details")}> - <NavLink tag={Link} to="/details"><h3>Details</h3></NavLink> - </NavItem> - */ } - </Nav> - </Collapse> - </Navbar>); - } -} - -export default Header; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.css deleted file mode 100644 index b4cc7250b98cb3f1a2dd5bec134296c6942344d9..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.css +++ /dev/null @@ -1,5 +0,0 @@ -body { - margin: 0; - padding: 0; - font-family: sans-serif; -} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.js index b7894b80b432462e5704fd024939371de54af307..da8d8c299c458e404cb14a001d8d34d38f89f2ad 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/index.js @@ -1,8 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; + import App from './App'; -import 'bootstrap/dist/css/bootstrap.min.css'; import registerServiceWorker from './registerServiceWorker'; // Redux diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/DetailsPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/DetailsPage.js deleted file mode 100644 index 047689ab7e1dba2e6b74bdd050c6a8b14bb242f1..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/DetailsPage.js +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Component } from 'react'; -import Header from '../components/header.js' - -class DetailsPage extends Component { - - render() { - - return ( - <div> - <Header active_page={this.props.location} /> - <div>Details Overview!</div> - </div> - ); - } -} - -export default DetailsPage; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/index.js similarity index 55% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/index.js index 8bd24331a9b1fed54c786e8e68bbc650cc50ae14..99228bc8529ff92dbb656e989de114f09da52d67 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/index.js @@ -1,19 +1,20 @@ import React, { Component } from 'react'; import { Table, Tooltip } from 'reactstrap'; -import { unique_id } from '../utils/utils.js' -import AutoLoadWrapper from '../utils/autoLoader.js' +import { unique_id } from 'utils/utils.js' import * as moment from 'moment'; -import { datetime_format } from '../utils/constants' -import { highlightClass, unHighlightClass } from '../utils/highlightClass' -import ObservationInspectTag from './ObservationInspectTag.js' +import { datetime_format } from 'utils/constants' +import { highlightClass, unHighlightClass } from 'utils/highlightClass' +import ObservationInspectTag from 'components/ObservationInspectTag' + // CSS -import './LatestObservations.css' +import styles from './styles.module.scss' /** - * SORow; Class to render the row for a station in the StationOverview. + * ObsRow: + * Class to render the (RTSM) row for an observation. */ -class SORow extends Component { +class ObsRow extends Component { constructor(props){ super(props); @@ -68,8 +69,9 @@ class SORow extends Component { <th scope="row">{station.station_name}</th> <td>{station.n_errors}</td> </tr>); + return ( - <tr id={this.id} className="hoverable"> + <tr id={this.id} className={styles.hoverable}> <td><ObservationInspectTag observationId={this.props.data.observation_id} /></td> <td>{ moment.utc(start_datetime).format(datetime_format) }</td> @@ -77,18 +79,20 @@ class SORow extends Component { <td>{ this.renderStationsWithProblems(station_involved_list) }</td> <td>{ total_component_errors }</td> - <Tooltip placement="auto" isOpen={this.state.popoverOpen} - target={this.id } - toggle={this.togglePopover} - style={{backgroundColor: "white", color:"black", opacity: "1"}} - autohide={false}> - <div className='popover-header'>{data.observation_id}</div> + <Tooltip placement="auto" + isOpen={this.state.popoverOpen} + target={this.id } + toggle={this.togglePopover} + style={{backgroundColor: "white", color:"black", opacity: "1"}} + autohide={false}> + <div className='popover-header'> + {data.observation_id} + </div> <div> - <strong>Start:</strong> { moment.utc(start_datetime).format(datetime_format) }<br/> <strong>End:</strong> { moment.utc(end_datetime).format(datetime_format) }<br/> <strong>Mode:</strong> { mode.join(',') }<br/> - <Table size="sm" className="so-table table-wrapper"> + <Table size="sm" className={styles.table_wrapper}> <thead><tr><th>Station name</th><th>errors</th></tr></thead> <tbody>{stations_and_errors}</tbody> </Table> @@ -100,49 +104,4 @@ class SORow extends Component { } } - -/** - * StationOverview class. - */ -class LatestObservationsC extends Component { - - getTableRows() { - return this.props.data.map( (stationData) => <SORow key={stationData.observation_id} data={ stationData } /> ); - } - - // Do not (re)render when data is loading (performance improvement) - shouldComponentUpdate(nextProps, nextState) { - if (nextProps.isLoading) { - return false; - } - return true; - } - - render() { - return ( - <div className="station-overview-ctrl"> - <Table size="sm" className="so-table"> - <thead> - <tr> - <th>Observation</th> - <th>Start date</th> - <th>Stations</th> - <th>Stations with errors</th> - <th>Total errors</th> - </tr> - </thead> - <tbody>{ this.getTableRows() } - </tbody> - </Table> - </div> - ); - } - -} - -/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the - auto-loading of the data for StationOverviewC. - */ -const LatestObservations = AutoLoadWrapper(LatestObservationsC); - -export default LatestObservations; +export default ObsRow; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/styles.module.css similarity index 64% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.css rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/styles.module.css index 5621f63474414e48e745aa3863172fe5abd38c11..1731173967aec4e28ac3d03bcc713b3ba1ccb107 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/LatestObservations.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/styles.module.css @@ -2,11 +2,12 @@ /* Color palette interface (created with https://material.io/tools/color/) */ /* font color */ /* font color */ +/* hover color, e.g. for background */ /* Data colors */ -.hoverable:hover { - background-color: #b6b6ba; } +:local .hoverable:hover { + background-color: #86868a; } -.table-wrapper { +:local .table_wrapper { width: 10em; max-height: 10em; overflow: auto; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..8c43ffc1ee4f8847f8eea93c02d417a53a85342b --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/ObsRow/styles.module.scss @@ -0,0 +1,14 @@ +@import '../../../../../themes/lofar-variables.scss'; + + +:local .hoverable:hover { + background-color: $hover-color; +} + + +:local .table_wrapper { + width: 10em; + max-height: 10em; + overflow: auto; + display: block; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/index.js new file mode 100644 index 0000000000000000000000000000000000000000..18a91713fc521b9acb8dd27fa4db3b54b076b7f4 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/index.js @@ -0,0 +1,65 @@ +import React, { Component } from 'react'; +import { Table } from 'reactstrap'; +import AutoLoadWrapper from 'components/AutoLoadWrapper' + +import ObsRow from './ObsRow' + +// CSS +import styles from './styles.module.scss' + + +/** + * TableRows: function component returning the table rows. + * LatestObservations class. + * Component showing a table with the latest observations and number of errors. + */ +function TableRows({data}) { + return data.map( (stationData) => + <ObsRow key={stationData.observation_id} data={ stationData } /> + ); +} + + +/** + * LatestObservations class. + * Component showing a table with the latest observations and number of errors. + */ +class LatestObservationsC extends Component { + + // Do not (re)render when data is loading (performance improvement) + shouldComponentUpdate(nextProps, nextState) { + if (nextProps.isLoading) { + return false; + } + return true; + } + + render() { + return ( + <div className={styles.latest_obs_ctrl}> + <Table size="sm" className="so-table"> + <thead> + <tr> + <th>Observation</th> + <th>Start date</th> + <th>Stations</th> + <th>Stations with errors</th> + <th>Total errors</th> + </tr> + </thead> + <tbody> + <TableRows data={this.props.data} /> + </tbody> + </Table> + </div> + ); + } + +} + +/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the + auto-loading of the data for StationOverviewC. + */ +const LatestObservations = AutoLoadWrapper(LatestObservationsC); + +export default LatestObservations; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..86e30dc47c38aad00c7b30478e8935bacafa3749 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/LatestObservations/styles.module.scss @@ -0,0 +1,6 @@ +@import '../../../../themes/lofar-variables.scss'; + + +:local .latest_obs_ctrl { + font-size: .9rem; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/Badge/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/Badge/index.js new file mode 100644 index 0000000000000000000000000000000000000000..bc43ec0367209d762812467ca2fe75b62dc3cc85 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/Badge/index.js @@ -0,0 +1,35 @@ +import React, {PureComponent} from 'react'; +import classNames from "classnames"; + +// CSS +import styles from './styles.module.scss' + +/** + * Badge; class to render a badge with label and pill. + */ +class Badge extends PureComponent { + + getClass() { + let cnt = this.props.count; + + let cls = classNames({ + [styles.so_pill]: true, + 'hilite-serious': cnt > 10, + 'hilite-alarming': cnt > 5 && cnt <=10, + 'hilite-warning': cnt > 0 && cnt <= 5, + 'hilite-good': cnt === 0 + }); + + return cls; + } + + render() { + let props = this.props; + return (<div id={props.myid} className={styles.so_badge+" "+this.props.className} onMouseOver={props.togglePopOver} onMouseOut={props.togglePopOver}> + {props.label} + <span className={this.getClass()}>{props.count}</span> + </div>); + } +} + +export default Badge; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/Badge/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/Badge/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..38415c67e6d58f5ed9ed7af5942dbec369c3a586 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/Badge/styles.module.scss @@ -0,0 +1,38 @@ +@import '../../../../../themes/lofar-variables.scss'; + + +/* General badge styling */ +:local .so_badge { + float: left; + height: 1.4rem; + line-height: 1.4rem; + background: $secondary-light; + border: 1px solid $secondary-dark; + border-radius: .2rem; + padding: 0 0.5em; + text-align: left; + margin-left: 2px; + font-size: 90%; +} + +:local .so_badge:hover { + color: #fff; + background-color: $secondary-dark; + border-color: $secondary-dark; +} + + +:local .so_pill { + display: block; + float: right; + padding: .25em .45em; + font-size: 90%; + font-weight: 600; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 1em; + margin-top: 0.1rem; + margin-left: 0.5em; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/RTSMBadge/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/RTSMBadge/index.js new file mode 100644 index 0000000000000000000000000000000000000000..2a2cc8e8cd70a1bcf41f5d1ac613e1ad48c7c31d --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/RTSMBadge/index.js @@ -0,0 +1,65 @@ +import React, {Component} from 'react'; +import {unique_id} from 'utils/utils.js' +import * as moment from 'moment'; +import { datetime_format } from 'utils/constants' +import {Table} from 'reactstrap'; + +import PopoverWithTitle from 'components/PopoverWithTitle' +import Badge from '../Badge' + + +/** + * RTSMBadge; class to render one RTSM badge in the SORow. + */ +class RTSMBadge extends Component { + + id = unique_id(); + + state = { + popoverOpen: false + }; + + togglePopover = () => { + this.setState({ + popoverOpen: !this.state.popoverOpen + }); + } + + popoverBody = () => { + let data = this.props.data; + let errors = Object.keys(data.error_summary).sort(); + let badges = errors.map((e, i) => <Badge key={i} count={data.error_summary[e]} label={e}/>); + + return ( + <Table borderless size="sm"> + <tbody> + <tr> + <th>Start:</th><td>{moment.utc(data.start_datetime).format(datetime_format)}</td> + </tr> + <tr> + <th>End:</th><td>{moment.utc(data.end_datetime).format(datetime_format)}</td> + </tr> + <tr> + <th>Mode:</th><td>{data.mode}</td> + </tr> + <tr> + <th>Errors:</th><td>{badges}</td> + </tr> + </tbody> + </Table> + ) + } + + render() { + let data = this.props.data; + + return (<React.Fragment> + <Badge myid={this.id} className={'obs-'+data.observation_id} togglePopOver={this.togglePopover} count={data.total_component_errors} label={data.observation_id}/> + <PopoverWithTitle target={this.id} isOpen={this.state.popoverOpen} togglePopover={this.togglePopover} title={data.observation_id}> + {this.popoverBody} + </PopoverWithTitle> + </React.Fragment>); + } +} + +export default RTSMBadge; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/SORow/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/SORow/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3e857a0406be5d283bf0cf7d522232a35a85e225 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/SORow/index.js @@ -0,0 +1,49 @@ +import React, {Component} from 'react'; +import {withRouter} from "react-router"; + +import StationTestBadge from '../StationTestBadge' +import RTSMBadge from '../RTSMBadge' + +// CSS +//import {styles} from './styles.module.scss' + + +/** + * SORow; Class to render the row for a station in the StationOverview. + */ +class SORowC extends Component { + + renderStationName() { + return this.props.data.station_name; + } + + renderStationTests() { + let data = this.props.data; + return data.station_tests.map((testData) => <StationTestBadge key={testData.start_datetime} station={data.station_name} data={testData}/>); + } + + renderRTSM() { + if (!this.props.data.rtsm || this.props.data.rtsm.length === 0) { + return "No RTSM data found" + } + + return this.props.data.rtsm.map((testData) => <RTSMBadge key={testData.observation_id} data={testData}/>); + } + onClick() { + let station = this.props.data.station_name; + this.props.history.push(`/station_overview?station=${station}`); + } + + render() { + return (<tr> + <th scope="row" onClick={()=>this.onClick()}>{this.renderStationName()}</th> + <td>{this.renderStationTests()}</td> + <td>{this.renderRTSM()}</td> + </tr>); + } +} + + +const SORow = withRouter(SORowC); + +export default SORow; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/StationTestBadge/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/StationTestBadge/index.js new file mode 100644 index 0000000000000000000000000000000000000000..855312d9ad16dd99b295b93de73fe4df4daecd37 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/StationTestBadge/index.js @@ -0,0 +1,104 @@ +import React, {Component} from 'react'; +import {withRouter} from "react-router"; +import {unique_id} from 'utils/utils.js' +import * as moment from 'moment'; +import {Table} from 'reactstrap'; +import { datetime_format } from 'utils/constants' +import classNames from "classnames"; + +import PopoverWithTitle from 'components/PopoverWithTitle' +import Badge from '../Badge' + +// CSS +import styles from './styles.module.scss' + +/** + * StationTestBadge; class to render one stationtest badge in the SORow. + */ +class StationTestBadgeC extends Component { + + id = unique_id(); + + state = { + popoverOpen: false + }; + + getClass() { + let cnt = this.props.data.total_component_errors; + + let cls = classNames({ + [styles.stationtestbadge]: true, + 'hilite-serious': cnt > 10, + 'hilite-alarming': cnt > 5 && cnt <=10, + 'hilite-warning': cnt > 0 && cnt <= 5, + 'hilite-good': cnt === 0 + }); + + return cls; + } + + onClick = () => { + let station = this.props.station; + this.props.history.push(`/station_overview?station=${station}`); + } + + togglePopover = () => { + this.setState({ + popoverOpen: !this.state.popoverOpen + }); + } + + popoverBody = () => { + let data = this.props.data; + let summary = data.component_error_summary; + let components = Object.keys(summary).sort(); + + return ( + <Table borderless size="sm"> + <tbody> + <tr> + <th>Start:</th><td>{moment.utc(data.start_datetime).format(datetime_format)}</td> + </tr> + <tr> + <th>End:</th><td>{moment.utc(data.end_datetime).format(datetime_format)}</td> + </tr> + <tr> + <th>Checks:</th><td>{data.checks}</td> + </tr> + { components.map((comp) => { + let comp_sum = summary[comp]; + let errors = Object.keys(comp_sum).sort(); + return ( + <tr> + <th>{comp}</th> + <td> + {errors.map((e, id) => + <Badge key={id} count={comp_sum[e]} label={e}/> + )} + </td> + </tr> + ); + }) + } + </tbody> + </Table> + ) + } + + render() { + return ( + <div> + <div id={this.id} onClick={this.onClick} onMouseOver={this.togglePopover} onMouseOut={this.togglePopover} className={this.getClass()}> + {this.props.data.total_component_errors} + </div> + <PopoverWithTitle target={this.id} isOpen={this.state.popoverOpen} togglePopover={this.togglePopover} title={this.props.data.observation_id}> + {this.popoverBody} + </PopoverWithTitle> + </div> + ); + } +} + +const StationTestBadge = withRouter(StationTestBadgeC); + +export default StationTestBadge; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/StationTestBadge/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/StationTestBadge/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..bee748b14536f2452398ffa87eaf66fb24ba6323 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/StationTestBadge/styles.module.scss @@ -0,0 +1,22 @@ +@import '../../../../../themes/lofar-variables.scss'; + + +:local .stationtestbadge { + float: left; + width: 1.4rem; + height: 1.4rem; + line-height: 1.4rem; + border: 1px solid #999; + border-radius: .2rem; + padding: 0; + text-align: center; + margin-left: 1px; + font-size: 90%; + font-weight: 600; +} + +:local .stationtestbadge:hover { + color: #fff; + background-color: $hover-color; + border-color: $hover-color; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3e9c478a37457204892097e393efb7fdc0711d1b --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/index.js @@ -0,0 +1,48 @@ +import React from 'react'; +import {Table} from 'reactstrap'; +import AutoLoadWrapper from 'components/AutoLoadWrapper' + +import SORow from './SORow' + +// CSS +import styles from './styles.module.scss' + + +/** + * TableRows: function component returning the table rows. + */ +function TableRows({data}) { + return data.map((stationData) => + <SORow key={stationData.station_name} data={stationData} /> + ); +} + +/** + * StationOverview class. + */ +function StationOverviewC(props) { + + return ( + <div className={styles.station_overview_ctrl}> + <Table size="sm" className={styles.so_table}> + <thead> + <tr> + <th>Name</th> + <th>Station tests</th> + <th>Latest observations</th> + </tr> + </thead> + <tbody> + <TableRows data={props.data} /> + </tbody> + </Table> + </div> + ); +} + +/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the + auto-loading of the data for StationOverviewC. + */ +const StationOverview = AutoLoadWrapper(StationOverviewC); + +export default StationOverview; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..97b9f2b03b63d75144735fb8660e80e49328a690 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationOverview/styles.module.scss @@ -0,0 +1,14 @@ +@import '../../../../themes/lofar-variables.scss'; + + +:local .station_overview_ctrl { + font-size: .9rem; +} + +:local .so_table { + width: auto!important; +} + +:local .so_table th { + padding-right: 1.4em!important; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/index.js new file mode 100644 index 0000000000000000000000000000000000000000..79d2210cec9752c5122a3be6f94c804d812b1bc1 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/index.js @@ -0,0 +1,87 @@ +import React, {Component} from 'react'; +import { + Nav, + NavItem, + Input +} from 'reactstrap'; +import {connect} from 'react-redux'; +import {setStationStatisticsTestType, setStationStatisticsAveragingWindow} from 'redux/actions/landingPageActions' + +import styles from './styles.module.scss' + + +/** + * Toolbar + * Toolbar for the station statistics component providing graphing options. + */ +class ToolbarC extends Component { + + state = { + isNavbarCollapsed: true + } + + setAveragingWindow = (e) => { + this.props.setStationStatisticsAveragingWindow(e.target.value); + } + + setTestType = (e) => { + this.props.setStationStatisticsTestType(e.target.value); + } + + toggle = () => { + this.setState({ + isNavbarCollapsed: !this.state.isNavbarCollapsed + }); + } + + render() { + return ( + <Nav className={styles.toolbar + " ml-auto"}> + <NavItem> + <Input type="select" + className="form-control custom-select custom-select-sm" + id="selected-group" + value={this.props.test_type} + onChange={this.setTestType}> + <option value="B">Both test types</option> + <option value="R">RTSM only</option> + <option value="S">StationTest only</option> + </Input> + </NavItem> + <NavItem> + <Input type="select" + className="form-control custom-select custom-select-sm" + onChange={this.setAveragingWindow} + value={this.props.averaging_window}> + <option value={1}>day</option> + <option value={7}>week</option> + <option value={30}>month</option> + </Input> + </NavItem> + <NavItem> + <Input type="select" + className="form-control custom-select custom-select-sm" + onChange={this.props.switchHistogramEvent} + value={this.props.histogramType}> + <option value="per_error_type">per error type</option> + <option value="per_station">per station</option> + </Input> + </NavItem> + </Nav>); + } +} + +const mapStateToPropsToolBar = state => { + return { + ...state.landing_page.station_statistics + }; +}; + +const mapDispatchToPropsToolBar = { + setStationStatisticsAveragingWindow, + setStationStatisticsTestType +}; + +const Toolbar = connect(mapStateToPropsToolBar, mapDispatchToPropsToolBar)(ToolbarC); + +export default Toolbar; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/styles.module.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/styles.module.css new file mode 100644 index 0000000000000000000000000000000000000000..9c3d8443716e691606c143653a81ddf854fb150c --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/styles.module.css @@ -0,0 +1,2 @@ +:local .toolbar { + padding: 0; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..56d5fef3d060a76b42343f3b9908d2bd7c7b08cd --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/Toolbar/styles.module.scss @@ -0,0 +1,3 @@ +:local .toolbar { + padding: 0; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationStatistics.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/index.js similarity index 55% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationStatistics.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/index.js index 9fd2a6e3d046d0f0f83c7cf4f3b27f657d57458d..90adb0314dc1c5b11d303dd9e545d4bfe8c5ca6e 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationStatistics.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/index.js @@ -1,104 +1,24 @@ import React, {Component} from 'react'; -import AutoLoadWrapper from '../utils/autoLoader.js' +import AutoLoadWrapper from 'components/AutoLoadWrapper' import ReactVegaLite from 'react-vega-lite' import { Navbar, - Nav, - NavItem, - Input, NavbarBrand } from 'reactstrap'; -import {connect} from 'react-redux'; -import {setStationStatisticsTestType, setStationStatisticsAveragingWindow} from '../redux/actions/landingPageActions' +import Toolbar from './Toolbar' -class ToolBarC extends Component { - constructor(props){ - super(props); +// CSS +import styles from './styles.module.scss' - this.state = {isNavbarCollapsed: true}; - } - setAveragingWindow = (e) => { - this.props.setStationStatisticsAveragingWindow(e.target.value); - } - setTestType = (e) => { - this.props.setStationStatisticsTestType(e.target.value); - } - - toggle = () => { - this.setState({ - isNavbarCollapsed: !this.state.isNavbarCollapsed - }); - } +class StationStatisticsC extends Component { - render() { - return ( - <Nav className="ml-auto"> - <NavItem> - <select className="form-control custom-select custom-select-sm" - id="selected-group" - value={this.props.test_type} onChange={this.setTestType} style={{ - width: 'auto'}}> - <option value="B">Both test types</option> - <option value="R">RTSM only</option> - <option value="S">StationTest only</option> - </select> - </NavItem> - <NavItem> - <Input type="select" className="form-control custom-select custom-select-sm" style={{ - top: "0rem !important" - }} onChange={this.setAveragingWindow} value={this.props.averaging_window}> - <option value={1}>day</option> - <option value={7}>week</option> - <option value={30}>month</option> - </Input> - </NavItem> - <NavItem> - <Input type="select" - className="form-control custom-select custom-select-sm" - onChange={this.props.switchHistogramEvent} - value={this.props.histogramType}> - <option value="per_error_type">per error type</option> - <option value="per_station">per station</option> - </Input> - </NavItem> - </Nav>); + state = { + histogramType: 'per_error_type' } -} - -const mapStateToPropsToolBar = state => { - return { - ...state.landing_page.station_statistics - }; -}; - -const mapDispatchToPropsToolBar = { - setStationStatisticsAveragingWindow, - setStationStatisticsTestType -}; -const ToolBar = connect(mapStateToPropsToolBar, mapDispatchToPropsToolBar)(ToolBarC); - -class StationStatisticsC extends Component { - constructor(props) { - super(props); - this.state = { - histogramType: 'per_error_type' - }; - this.ref = React.createRef(); - StationStatisticsC.getErrorsPerTypeSpec.bind(this); - } - getErrorsPerStation() { - if (this.props.data.errors_per_station !== undefined) { - return {values: this.props.data.errors_per_station}; - } - } + ref = React.createRef() - getErrorsPerType() { - if (this.props.data.errors_per_type !== undefined) { - return {values: this.props.data.errors_per_type}; - } - } static getBaseSpec() { return { "$schema": "https://vega.github.io/schema/vega-lite/v2.json", @@ -178,6 +98,18 @@ class StationStatisticsC extends Component { return schema; } + getErrorsPerStation() { + if (this.props.data.errors_per_station !== undefined) { + return {values: this.props.data.errors_per_station}; + } + } + + getErrorsPerType() { + if (this.props.data.errors_per_type !== undefined) { + return {values: this.props.data.errors_per_type}; + } + } + getSpecData(histogram_type) { switch (histogram_type) { case "per_error_type": @@ -189,7 +121,9 @@ class StationStatisticsC extends Component { } } - onSwitchHistogramType = e => this.setState({histogramType: e.target.value}) + onSwitchHistogramType = (e) => { + this.setState({histogramType: e.target.value}) + } // Do not (re)render when data is loading (performance improvement) shouldComponentUpdate(nextProps, nextState) { @@ -209,22 +143,13 @@ class StationStatisticsC extends Component { } return (<React.Fragment> - <Navbar - className="react-grid-item-header justify-content-between" - style={{ - padding: "0", - zIndex: 1000 - }}> - <NavbarBrand style={{ - padding: "0" - }}> + <Navbar className={styles.navbar+" react-grid-item-header justify-content-between"} > + <NavbarBrand className={styles.brand}> <h5 className="react-grid-item-header"> - Station statistics</h5> + Station statistics + </h5> </NavbarBrand> - <ToolBar style={{ - padding: "0" - }} - histogramType={this.state.histogramType} + <Toolbar histogramType={this.state.histogramType} switchHistogramEvent={this.onSwitchHistogramType}/> </Navbar> <div className="react-grid-item-body" id="plot" ref={this.ref}> diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..791f2b4913c80b385fee3ef92b3f9169c7a55558 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationStatistics/styles.module.scss @@ -0,0 +1,9 @@ + +:local .navbar { + padding: 0; + z-index: 1000; +} + +:local .brand { + padding: 0; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/StationTestRow/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/StationTestRow/index.js new file mode 100644 index 0000000000000000000000000000000000000000..878373795f2074031f5792e859023ba086c552e9 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/StationTestRow/index.js @@ -0,0 +1,43 @@ +import React, { Component } from 'react'; +import * as moment from 'moment'; +import { date_format, time_format } from 'utils/constants' + + +/** + * StationTestRow + * Class to render the row for a station test. + */ +class StationTestRow extends Component { + + renderStartDate() { + return this.props.date ? moment.utc(this.props.date).format(date_format) : ""; + } + + renderStartTime() { + return this.props.data.start_datetime ? moment.utc(this.props.data.start_datetime).format(time_format) : ""; + } + + render() { + let props = this.props, + component = props.component, + errors = props.data.component_error_summary[component] || {}, + cols = []; + + props.errorTypes.forEach((type) => { + cols.push(<td key={type}>{ errors[type] }</td>); + }); + + return ( + <tr> + <th scope="row" className="nowrap">{ this.renderStartDate() }</th> + <th>{ props.time }</th> + <th>{ props.station }</th> + <td>{ component }</td> + { cols } + </tr> + ); + } +} + + +export default StationTestRow; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/index.js new file mode 100644 index 0000000000000000000000000000000000000000..8d323b65e726feffa82c99b6b660848ccc27c3fa --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/index.js @@ -0,0 +1,139 @@ +import React, { Component } from 'react'; +import { connect } from "react-redux"; +import { Table } from 'reactstrap'; +import { unique_id } from 'utils/utils.js' +import { componentErrorTypes } from 'utils/constants.js' +import AutoLoadWrapper from 'components/AutoLoadWrapper' + +import StationTestRow from './StationTestRow' + +// CSS +import styles from './styles.module.scss' + + +/** + * ColHeaders: function component returning the column headers. + */ +function ColHeaders({errorTypes}) { + return ( + <tr> + <th>Date</th> + <th>Time</th> + <th>Station</th> + <th>Comp.</th> + { errorTypes.map((err) => + <th key={err} title={err}> + { componentErrorTypes[err] ? componentErrorTypes[err] : err } + </th> + )} + </tr> + ); +} + + +/** + * TableRows: function component returning the table rows. + */ +function TableRows({data, errorTypes}) { + let prevDate = null; + let rows; + + rows = data.map( (stationData) => { + let date = (stationData.date !== prevDate ? stationData.date : ""); + let components = Object.keys(stationData.component_error_summary).sort(); + let station = stationData.station_name; + let time = (stationData.start_datetime.match(/T(.*):..Z/))[1]; + + prevDate = stationData.date; + + if (components.length === 0) { + return <StationTestRow key={ unique_id() } date={date} time={time} component={"-"} station={station} data={stationData} errorTypes={errorTypes}/> + } + else { + return components.map( (component) => { + let row = <StationTestRow key={ unique_id() } date={date} time={time} component={component} station={station} data={stationData} errorTypes={errorTypes}/> + date = station = time = ""; + return row; + }); + } + }); + + return rows; +} + + +/** + * StationOverview class. + */ +class StationTestSummaryC extends Component { + + activeErrorTypes = []; // Result of filtering state.errorTypes + + filterErrorTypes() { + let typesFound = {}, + retTypes = []; + + if (this.props.errorTypes.length === 0 || this.props.data.length === 0){ + return []; + } + + // Create index object for all error types in the data + this.props.data.forEach( (stationData) => { + let esummary = stationData.component_error_summary; + let key; + for(key in esummary){ + let errors = Object.keys(esummary[key]); + errors.forEach((e) => typesFound[e] = 1); + } + }); + + // Loop over all error types and check which ones are present in the data + this.props.errorTypes.forEach((t) => { + if (typesFound[t]) { + retTypes.push(t); + } + }) + + return retTypes; + } + + setActiveErrorTypes() { + this.activeErrorTypes = this.filterErrorTypes(); + } + + // Do not (re)render when data is loading (performance improvement) + shouldComponentUpdate(nextProps, nextState) { + if (nextProps.isLoading) { + return false; + } + return true; + } + + render() { + this.setActiveErrorTypes(); + + return ( + <Table bordered hover size="sm" className={styles.sts_table}> + <thead> + <ColHeaders errorTypes={this.activeErrorTypes} /> + </thead> + <tbody> + <TableRows errorTypes={this.activeErrorTypes} data={this.props.data} /> + </tbody> + </Table> + ); + } + +} + +// Map the appInitData from store for the error types +const mapStateToProps = state => { + return { ...state.appInitData }; +}; + +/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the + auto-loading of the data for StationOverviewC. + */ +const StationTestSummary = AutoLoadWrapper( connect(mapStateToProps)(StationTestSummaryC) ); + +export default StationTestSummary; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..cd1ddccc3241b1d454fc8232337e11e66f1a4ee8 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/components/StationTestSummary/styles.module.scss @@ -0,0 +1,22 @@ +@import '../../../../themes/lofar-variables.scss'; + + +:local .sts_table { + width: auto!important; + text-align: center; + font-size: .9rem; +} + +:local .sts_table th { + word-break: break-all; + min-width: 3em; +} + +:local .sts_table td { + text-align: center; +} + +:local(.sts_table):global(.table-sm) td, +:local(.sts_table):global(.table-sm) th { + padding: .2rem; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/index.js similarity index 81% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/index.js index fab99a8ad74b55a98f47719114ace6126ae3ef7f..f1749c3daab64702d056e30d3394e97d2c9d8d46 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/LandingPage/index.js @@ -1,27 +1,30 @@ import React, {Component} from 'react'; -import Header from '../components/header.js' +import NavigationBar from 'components/NavigationBar' -import StationOverview from '../components/StationOverview.js'; -import StationTestSummary from '../components/StationTestSummary.js'; -import LatestObservations from '../components/LatestObservations.js'; -import StationStatistics from '../components/StationStatistics.js'; +import StationOverview from './components/StationOverview'; +import StationTestSummary from './components/StationTestSummary'; +import LatestObservations from './components/LatestObservations'; +import StationStatistics from './components/StationStatistics'; import {Responsive, WidthProvider} from 'react-grid-layout'; import * as moment from 'moment'; import { connect } from "react-redux"; -import { setNewLayout } from "../redux/actions/landingPageActions"; -import { composeQueryString } from '../utils/utils.js'; -import { createGridPanel } from '../utils/grid.js'; -import { Toolbar, StationGroupSelector, ErrorTypesSelector, ErrorsOnlySelector, PeriodSelector } from '../components/Toolbar' +import { setNewLayout } from "redux/actions/landingPageActions"; +import { composeQueryString } from 'utils/utils.js'; +import { createGridPanel } from 'utils/grid.js'; +import { Toolbar, StationGroupSelector, ErrorTypesSelector, ErrorsOnlySelector, PeriodSelector } from 'components/Toolbar' import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; -import '../themes/lofar-styles.css'; const ResponsiveGridLayout = WidthProvider(Responsive); +/** + * LandingPage + * The 'dashboard' or home page with responsive grid and panels. + */ class LandingPageC extends Component { getStationOverviewURL() { @@ -98,7 +101,7 @@ class LandingPageC extends Component { render() { return (<div> - <Header active_page={this.props.location}/> + <NavigationBar active_page={this.props.location}/> <Toolbar> <StationGroupSelector /> <ErrorsOnlySelector /> @@ -108,9 +111,9 @@ class LandingPageC extends Component { <ResponsiveGridLayout className="layout" layouts={this.props.layout.panels} measureBeforeMount={true} breakpoints={this.props.layout.breakpoints} cols={this.props.layout.cols} onResizeStop={this.props.setNewLayout}> - {createGridPanel({key: "ul", renderHeader: true, title: "Station overview", body: <StationOverview url={this.getStationOverviewURL()}/>})} + {createGridPanel({key: "ul", renderHeader: true, title: "Station overview", body: <StationOverview addLoaderDiv={true} url={this.getStationOverviewURL()}/>})} {createGridPanel({key: "ur", renderHeader: true, title: "Latest observations", body: <LatestObservations url={this.getLatestObservationURL()}/>})} - {createGridPanel({key: "bl", renderHeader: false, body: <StationTestSummary url={this.getStationTestSummaryURL()}/>})} + {createGridPanel({key: "bl", renderHeader: true, title: "Station test summary", body: <StationTestSummary url={this.getStationTestSummaryURL()}/>})} {createGridPanel({key: "br", renderHeader: false, body: <StationStatistics url={this.getStationStatisticsURL()}/>})} </ResponsiveGridLayout> </div>); diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/RTSMPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/RTSMPage.js deleted file mode 100644 index 44f33298742bcfb1b1e903e2332410edbd1113c0..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/RTSMPage.js +++ /dev/null @@ -1,11 +0,0 @@ -import React, { Component } from 'react'; - -class RTSMPage extends Component { - render() { - return ( - <div>RTSM</div> - ); - } -} - -export default RTSMPage; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestChildView/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestChildView/index.js new file mode 100644 index 0000000000000000000000000000000000000000..9739d9a353ea69a59841812cc2128b5c874f7840 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestChildView/index.js @@ -0,0 +1,164 @@ +import React, {Component} from 'react'; +import {connect} from "react-redux"; +import {unpinChildPanelData} from 'redux/actions/stationOverviewPageActions' +import {Badge, Button, Table} from 'reactstrap'; +import {IoMdCloseCircleOutline as CloseIcon} from 'react-icons/io'; +import EnlargeableImage from 'components/EnlargeableImage' + + +// CSS +import styles from './styles.module.scss'; + + +/** + * UnpinButton: simple component to optionally display an unpin button + */ +function UnpinButton({doDisplay, onClick}) { + return ( !doDisplay ? null : + <Button title="Click to unpin the error details" + color="info" + size="xs" + className={styles.unpin_button} + onClick={onClick}> + <CloseIcon/> + unpin + </Button> + ); +} + + +/** + * ChildViewContainer: main structure for child view panel + */ +function ChildViewContainer({title, isPinned, onUnpin, children}) { + return <div className={styles.child_view_container}> + <div className={styles.header}> + {title} + <UnpinButton doDisplay={isPinned} onClick={onUnpin}/> + </div> + {children} + </div>; +} + + +/** + * ErrorDetailRow: render a row with the eror details and optional image. + * TODO: document the data structure, rendering depends on it.. + */ +function ErrorDetailRow({data, rowkey}) { + let pol = data['polarization']; + let rcuId = data['rcu_id']; + let err_items = [], + ignore = { + start_frequency: 1, + stop_frequency: 1, + polarization: 1, + rcu_id: 1, + antenna_id: 1, + url: 1 + }, + img = null; + + // first process frequency range + if (data.details.hasOwnProperty("start_frequency")) { + err_items.push(<li key="freq">frequency-range: {data.details.start_frequency}-{data.details.stop_frequency} MHz</li>); + } + + // ...then process remaining items that are not in 'ignore' + Object.keys(data.details).forEach((parameter, key) =>{ + if (! ignore.hasOwnProperty(parameter)) { + const parameter_value = data.details[parameter]; + const rendered_parameter = parameter === 'percentage'? parameter_value.toFixed(2) + '%' : parameter_value; + err_items.push(<li key={parameter}>{parameter}: {rendered_parameter}</li>); + }}); + + // No error details? Must be element error see tiles page. + if (err_items.length === 0) { + err_items.push(<li key="default"><em>See element error.</em></li>); + } + + if (data.details.url) { + img = <EnlargeableImage url={data.details.url} /> + } + + return ( + <tr> + {rcuId !== undefined && <th scope="row">RCU {rcuId} ({pol}){img}</th>} + <td> + <ul className={styles.details_list}> + <li><Badge className='error-type-badge' color="danger">{data.error_type}</Badge></li> + {err_items} + </ul> + </td> + </tr> + ); +} + +/** + * StationTestChildView: controller for child panel + */ +class StationTestChildViewC extends Component { + + unpinPanel = () => { + this.props.unpinChildPanelData(); + }; + + composeTitle(data) { + return `${data.component_type}, Antenna ${data.component_id}, Test ${data.datetime}` + } + + + render() { + let rows = [], + data = this.props.data; + + // Return default message when no data + if (data === null) { + return ( + <ChildViewContainer title="Error details" isPinned={this.props.isPinned} onUnpin={this.unpinPanel}> + <i>Hover the mouse over an error to view the details. Right-click on the error to pin it on this panel.</i> + </ChildViewContainer> + ) + } + + // data.errors is either array or object for error data (HIGH_NOISE, MODEM, ...) + for (const idx of Object.keys(data.errors)){ + let edata = data.errors[idx]; + const hasX = edata.hasOwnProperty('X'); + const hasY = edata.hasOwnProperty('Y'); + if (hasX) { + rows.push(<ErrorDetailRow data={edata['X']} key={edata.error_type+'X'} />) + } + if (hasY) { + rows.push(<ErrorDetailRow data={edata['Y']} key={edata.error_type+'Y'} />) + } + if (! hasX && ! hasY) { + rows.push(<ErrorDetailRow data={edata} key={edata.error_type} />) + } + } + + return ( + <ChildViewContainer title={this.composeTitle(data)} + isPinned={this.props.isPinned} + onUnpin={this.unpinPanel}> + <Table size='sm'> + <tbody>{rows}</tbody> + </Table> + </ChildViewContainer> + ); + } +} + +/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the + auto-loading of the data for StationOverviewC. + */ +const StationTestChildView = connect(state => { + return { + ...state.station_page.child_panel, + }; +}, { + unpinChildPanelData +})(StationTestChildViewC); + + +export default StationTestChildView; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestChildView/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestChildView/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..ef8884b4887370dbef554acdd2d2cee49587e26a --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestChildView/styles.module.scss @@ -0,0 +1,21 @@ +@import '../../../../themes/lofar-variables.scss'; + +:local .child_view_container .header { + padding: .5rem 0; + color: $secondary-dark; +} + +:local .child_view_container img { + cursor: pointer; + display: block; + width: 90%; +} + +:local ul.details_list { + list-style-type: none; + padding-left: 0.5em; +} + +:local .unpin_button { + float: right; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fe74f561f32816c9e8e259f5e96129348dd589eb --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/index.js @@ -0,0 +1,110 @@ +import React, { + Component +} from 'react'; +import {Badge} from 'reactstrap'; +import ReactTableContainer from "react-table-container"; + +import GenericTestRow from '../GenericTestRow'; +import RTSMRows from '../RTSMRows'; + +// CSS +import styles from './styles.module.scss'; + + +/** + * ComponentType; renders a table of station tests and rtsm data for one component (HBA, RSP, LBH, etc.) + * + * Props: + * station_type: C, R or I + * type: component type + * data: Data for this component + */ +class ComponentType extends Component { + + computeComponentIDList(componentType) { + let componentIDSet = new Set(); + this.props.data.forEach(test => { + Object.keys(test.component_errors).forEach(item => componentIDSet.add(item)) + }); + // Numerical sort + return Array.from(componentIDSet).sort((a, b) => a - b); + } + + renderGenericTestRow(key, data, component_ids) { + return (<GenericTestRow key={key} + test_type="ST" + ordered_component_ids={component_ids} + component_type={this.props.type} + station_name={this.props.station_name} + station_type={this.props.station_type} + data={data}/>) + } + + renderRTSMRows(key, data, component_ids) { + return (<RTSMRows key={key} + ordered_component_ids={component_ids} + component_type={this.props.type} + station_name={this.props.station_name} + station_type={this.props.station_type} + data={data} + update={this.updateIfComponentChanges} />) + } + + renderGenericTestRows(data, component_ids) { + const rows = []; + let tmp_rtsm_set = [], + num_items = data.length; + + for (let i = 0; i < num_items; i++) { + const current_item = data[i], + next_item = (i===num_items-1 ? null : data[i + 1]); + + // Temporarily store RTSM lines + if (current_item.test_type === 'R') { + tmp_rtsm_set.push(current_item) + + // Push lines when next item is a station test or when we are at the last item + if (next_item === null || next_item.test_type === 'S') { + rows.push(this.renderRTSMRows(rows.length, tmp_rtsm_set, component_ids)) + tmp_rtsm_set = [] + } + } + else if (current_item.test_type === 'S') { + rows.push(this.renderGenericTestRow(rows.length, current_item, component_ids)); + } + } + + return rows + } + + updateIfComponentChanges = () => { + this.setState({ state: this.state }); + } + + render() { + const comp_ids = this.computeComponentIDList(this.props.type); + + return ( + <ReactTableContainer width="100%" height={this.props.height+'px'}> + <table className={styles.comp_type_table+" table-sm table-hover table-bordered"}> + <thead className={styles.comp_type_header}> + <tr> + <th style={{textAlign: "left"}} scope="col"> + <Badge color="info">{this.props.type}</Badge> + </th> + {comp_ids.map((item, key) => + <th scope="col" key={key}>{item}</th>) + } + </tr> + </thead> + <tbody> + {this.renderGenericTestRows(this.props.data, comp_ids)} + </tbody> + </table> + </ReactTableContainer> + ); + } +} + + +export default ComponentType; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/styles.module.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/styles.module.css new file mode 100644 index 0000000000000000000000000000000000000000..a34d2fa7a01011f038601250e4fd5972d8774993 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/styles.module.css @@ -0,0 +1,12 @@ +:local .comp_type_table { + font-size: .9rem; } + +:local(.comp_type_table):global(.table-sm) td, +:local(.comp_type_table):global(.table-sm) th { + padding: .1rem; + min-width: 1.8em; } + +:local .comp_type_header { + position: relative; + background-color: white; + text-align: center; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..1a2cb599bc04694e9019e4be259456cce6d8f009 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/ComponentType/styles.module.scss @@ -0,0 +1,17 @@ + + +:local .comp_type_table { + font-size: .9rem; +} + +:local(.comp_type_table):global(.table-sm) td, +:local(.comp_type_table):global(.table-sm) th { + padding: .1rem; + min-width: 1.8em; +} + +:local .comp_type_header { + position: relative; + background-color: white; + text-align: center; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/index.js new file mode 100644 index 0000000000000000000000000000000000000000..474d16ef026e171e64d07bea105f44d22621a62c --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/index.js @@ -0,0 +1,73 @@ +import React, { + Component +} from 'react'; +import {withRouter} from "react-router"; +import classNames from "classnames"; + +// CSS +import styles from './styles.module.scss'; + + +/** + * GenericStatusTd; render a <td> showing the number of errors for an antenna. + * Show the error details in the detail panel on the right on mouse hover. + */ +class GenericStatusTdC extends Component { + + getClass() { + let cls = classNames({ + [styles.status]: true, + [styles.highlight]: this.props.doHighlight, + 'hilite-serious': this.props.n_errors > 0, + 'hilite-good': this.props.n_errors == 0 + }); + + return cls; + } + + onMouseOver = (e) => { + e.stopPropagation(); + if (this.props.n_errors > 0) { + this.props.onSelect(this.props.data); + } + }; + + onMouseOut = (e) => { + e.stopPropagation(); + if (this.props.n_errors > 0) { + this.props.onSelect(null); + } + + }; + + // left-click with mouse + onClick = () => { + this.props.history.push(`/tiles?antenna_id=${this.props.antenna_id}&antenna_type=${this.props.antenna_type}&station=${this.props.station_name}`); + }; + + // right-click with mouse + onContextMenu = (e) => { + e.preventDefault(); + if (this.props.n_errors > 0) { + this.props.onSelect(this.props.data, true); + } + }; + + render() { + const content = (this.props.n_errors===0 ? ' ' : this.props.n_errors); + + return ( + <td className={this.getClass()} + onContextMenu={this.onContextMenu} + onClick={this.onClick} + onMouseOver={this.onMouseOver} + onMouseOut={this.onMouseOut}> + {content} + </td> + ); + } +} + +const GenericStatusTd = withRouter(GenericStatusTdC); + +export default GenericStatusTd; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/styles.module.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/styles.module.css new file mode 100644 index 0000000000000000000000000000000000000000..268db1b71d5a450bb21c865644e92bb492e4744c --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/styles.module.css @@ -0,0 +1,19 @@ +/* COLORS */ +/* Color palette interface (created with https://material.io/tools/color/) */ +/* font color */ +/* font color */ +/* hover color, e.g. for background */ +/* Data colors */ +:local .status { + text-align: center; + font-size: 90%; + font-weight: 600; } + +:local .status.highlight { + background-color: #86868a !important; + color: white; } + +:local .status:hover { + color: #fff; + background-color: #86868a; + border-color: #86868a; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..aa9f18de183f7a215994e66c652b233394bcede3 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericStatusTd/styles.module.scss @@ -0,0 +1,19 @@ +@import '../../../../../themes/lofar-variables.scss'; + + +:local .status { + text-align: center; + font-size: 90%; + font-weight: 600; +} + +:local .status.highlight { + background-color: $secondary-dark!important; + color: $secondary-color; +} + +:local .status:hover { + color: #fff; + background-color: $hover-color; + border-color: $hover-color; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/index.js new file mode 100644 index 0000000000000000000000000000000000000000..b7ac9e7bd4c16a5ca8d48c55eabfb5944c258c18 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/index.js @@ -0,0 +1,115 @@ +import React, { + Component +} from 'react'; +import {connect} from "react-redux"; +import {setChildPanelData} from 'redux/actions/stationOverviewPageActions' +import {datetime_format} from 'utils/constants' +import moment from 'moment'; + +import GenericStatusTd from '../GenericStatusTd' + +// CSS +import styles from './styles.module.scss' + + +/** + * GenericTestRow: renders a table row with the results of one station test or RTSM run. + */ +class GenericTestRowC extends Component { + + doHighlight = false; + + static formatDate(date) { + return moment(date).format(datetime_format); + } + + shouldHighlight() { + const props = this.props, + errorData = props.highlightData; + + return errorData !== null && + errorData.component_type === props.component_type && + errorData.test_type === props.test_type && + errorData.datetime === GenericTestRowC.formatDate(props.data.start_date); + } + + shouldComponentUpdate(nextProps, nextState, nextContext) { + // this.doHighlight will only be true for the previously and currently selected row + if (nextProps.highlightData !== this.props.highlightData && !this.doHighlight) { + return false; + } + return true; + } + + onSelect = (data, doPin) => { + if (doPin) { + this.doHighlight = true; + } + this.props.setChildPanelData(data, doPin); + }; + + renderComponentErrors() { + const componentErrors = this.props.data.component_errors; + + const renderedComponentErrors = this.props.ordered_component_ids.map((component_id, key) => { + let nErrors = 0, + errors = [], + errorData = {}; + + if (componentErrors.hasOwnProperty(component_id)) { + errors = componentErrors[component_id]; + nErrors = Object.keys(errors).length; + + // Data for child panel and checking if an antenna item must be highlighted permanently (see shouldHighlight) + errorData.errors = errors; + errorData.datetime= GenericTestRowC.formatDate(this.props.data.start_date); + errorData.test_type = this.props.test_type; + errorData.component_id = component_id; + errorData.component_type = this.props.component_type; + } + + return <GenericStatusTd + doHighlight={this.doHighlight && component_id === this.props.highlightData.component_id} + key={key} + data={errorData} + antenna_id={component_id} + antenna_type={this.props.component_type} + station_name={this.props.station_name} + n_errors={nErrors} + onSelect={this.onSelect} /> + }); + + return renderedComponentErrors; + } + + render() { + const date = GenericTestRowC.formatDate(this.props.data.start_date); + + // Determine if this row needs to be highlighted + this.doHighlight = this.shouldHighlight(); + + let cls = styles.testrow; + if (this.doHighlight) { + cls += " "+styles.highlight; + } + + return ( + <tr className={cls}> + <td className={styles.testrow_header}>{this.props.test_type} {date}</td> + {this.renderComponentErrors()} + </tr> + ); + } +} + +// TestLine is connected to Redux store +const GenericTestRow = connect(state => { + return { + ...state.station_page.main_panel + }; +}, { + setChildPanelData +})(GenericTestRowC); + + +export default GenericTestRow; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/styles.module.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/styles.module.css new file mode 100644 index 0000000000000000000000000000000000000000..07a217bba1a6b2b433ec74944d67eeb941bb2dd0 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/styles.module.css @@ -0,0 +1,16 @@ +/* COLORS */ +/* Color palette interface (created with https://material.io/tools/color/) */ +/* font color */ +/* font color */ +/* hover color, e.g. for background */ +/* Data colors */ +:local .testrow.highlight { + background-color: #86868a !important; + color: white; } + +:local .testrow.highlight td:first-child::before { + content: "> "; } + +:local .testrow_header { + width: 12rem !important; + min-width: 12rem !important; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..2400c854ff42d8d69661ad0a547808c4166d40d2 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/GenericTestRow/styles.module.scss @@ -0,0 +1,16 @@ +@import '../../../../../themes/lofar-variables.scss'; + + +:local .testrow.highlight { + background-color: $secondary-dark!important; + color: $secondary-color; +} + +:local .testrow.highlight td:first-child::before { + content: "> " +} + +:local .testrow_header { + width: 12rem !important; + min-width: 12rem !important; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/index.js new file mode 100644 index 0000000000000000000000000000000000000000..55f806c81304b00689560cfec34efff97f96218d --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/index.js @@ -0,0 +1,104 @@ +import React, { + Component +} from 'react'; +import {IoMdArrowDropdown as DropDownIcon} from 'react-icons/io'; +import {renderDateRange} from 'utils/utils' + +import GenericTestRow from '../GenericTestRow' + +// CSS +import rtsmStyles from 'themes/rtsm_collapsable.module.scss'; + + +/** + * RTSMSummaryLine: create one table row with percentages of errors per antenna. + */ +function RTSMSummaryLine(props) { + const data = props.data; + + const cols = props.ordered_component_ids.map((item, key) => { + if (data[item] > 0) { + let perc = Math.ceil(data[item]); + return (<td key={key} className={rtsmStyles.rtsm_summary_badge}>{perc + '%'} </td>); + } else { + return <td key={key}></td>; + } + }); + + const dropdownAdditionStyles = props.isExpanded ? " "+rtsmStyles.dropdownbutton_up : ""; + + return ( + <tr className={rtsmStyles.rtsm_summary_row}> + <td className={rtsmStyles.row_header} onClick={props.onClick}> + RT {props.dateRange} + <DropDownIcon className={rtsmStyles.dropdownbutton + dropdownAdditionStyles} color="black"/> + </td> + {cols} + </tr> + ); +} + +/** + * RTSMRows: create summary line + expandable data rows + */ +class RTSMRows extends Component { + + state = { + displaySingleTests: false + } + + computeSummary() { + let summary = {}; + let n_tests = this.props.data.length; + const component_id_list = this.props.ordered_component_ids; + component_id_list.forEach(component_id => summary[component_id] = 0); + + this.props.data.forEach((item, key) => { + Object.keys(item.component_errors).forEach((component_id) => { + summary[component_id] += 1 + }) + }); + + Object.keys(summary).forEach(item => summary[item] /= n_tests / 100.); + return summary + } + + toggleDisplaySingleTests = (e) => { + this.setState({ + displaySingleTests: !this.state.displaySingleTests + }); + this.props.update(); + }; + + render() { + let summaryData = this.computeSummary(); + + // RTSM data rows, only shown when expanded + let all_rtsm = this.state.displaySingleTests ? this.props.data : []; + + return ( + <React.Fragment> + <RTSMSummaryLine onClick={this.toggleDisplaySingleTests} + isExpanded={this.state.displaySingleTests} + data={summaryData} + ordered_component_ids={this.props.ordered_component_ids} + dateRange={renderDateRange(this.props.data)} /> + { // All RTSM lines in this block (expanded or folded) + all_rtsm.map((item, key) => + <GenericTestRow className="collapse open" + key={key} + test_type="RT" + ordered_component_ids={this.props.ordered_component_ids} + component_type={this.props.component_type} + station_name={this.props.station_name} + station_type={this.props.station_type} + data={item}/> + ) + } + </React.Fragment> + ); + } +} + + +export default RTSMRows; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/styles.module.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/styles.module.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/RTSMRows/styles.module.scss @@ -0,0 +1 @@ + diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/index.js new file mode 100644 index 0000000000000000000000000000000000000000..b9b2272f037b300a06a0af042537a1926a728c37 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/index.js @@ -0,0 +1,127 @@ +import React, { + Component +} from 'react'; +import {connect} from "react-redux"; +import { + NavItem, + NavLink, + TabPane, + TabContent, + Nav +} from 'reactstrap'; +import AutoLoadWrapper from 'components/AutoLoadWrapper' +import * as LOFARDefinitions from 'utils/LOFARDefinitions' +import FillViewportHeightDiv from 'components/FillViewportHeightDiv'; +import ComponentType from './ComponentType'; + +// CSS +import styles from './styles.module.scss'; + + +/* + * Render a Tab item + */ +function Tab({label, onClick, isActive}) { + const cls = isActive ? styles.clickable_tab_active : styles.clickable_tab_inactive; + + return ( + <NavItem className={styles.clickable_tab}> + <NavLink className={cls} onClick={onClick}> + {label} + </NavLink> + </NavItem> + ); +} + +/** + * StationTestView class. + */ +class StationTestViewC extends Component { + + state = { + activeTab: undefined + } + + // Set the activeTab to the first component if it wasn't set yet or when + // the station changed and doesn't have the active component + static getDerivedStateFromProps(props, state) { + let componentTypes = Object.keys(props.data).sort(), + currentComponent = state.activeTab; + + if (componentTypes.length === 0) { + return null; + } + + if (! currentComponent || componentTypes.findIndex((c) => currentComponent === c) === -1) { + return { + activeTab: componentTypes[0] + }; + } + return null; + } + + // Do not (re)render when data is loading (performance improvement) + shouldComponentUpdate(nextProps, nextState, nextContext) { + return nextProps.isLoading ? false : true; + } + + toggleTab = (e) => { + let tab = e.currentTarget.innerHTML; + if (this.state.activeTab !== tab) { + this.setState({ + activeTab: tab + }); + } + } + + render() { + const stationType = LOFARDefinitions.stationTypeFromName(this.props.selectedStation); + const componentTypes = Object.keys(this.props.data).sort(); + + if (this.props.isLoading) { + return null; + } + + return ( + <div> + <Nav tabs className="component-type-selector"> + { + componentTypes.map((componentType, key) => + <Tab key={key} onClick={this.toggleTab} isActive={this.state.activeTab===componentType} label={componentType} /> + ) + } + </Nav> + <FillViewportHeightDiv className="border-right"> + { ({height}) => ( + <TabContent activeTab={this.state.activeTab}> + { + componentTypes.map((componentType, key) => + <TabPane key={key} tabId={componentType}> + <ComponentType key={componentType} + station_type={stationType} + type={componentType} + station_name={this.props.selectedStation} + data={this.props.data[componentType]} + height={height} /> + </TabPane> + )} + </TabContent> + )} + </FillViewportHeightDiv> + </div>); + } + +} + +/* Add some magic; use the AutoLoadWrapper to create a HOC that handles the + auto-loading of the data for StationOverviewC. + */ +const StationTestViewController = connect(state => { + return { + selectedStation: state.mainFilters.selectedStation + }; +})(StationTestViewC); + +const StationTestView = AutoLoadWrapper(StationTestViewController); + +export default StationTestView; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..52125333fcad9536d61af29f62d32623f81b132b --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/components/StationTestView/styles.module.scss @@ -0,0 +1,30 @@ +@import '../../../../themes/lofar-variables.scss'; + + +.tab-navbar { + min-height: 2em !important; +} + +:local .clickable_nav_link{ + border-style: none; +} + +:local .clickable_tab_active { + @extend .clickable_nav_link; + color: white !important; + background-color: $primary-light; +} + +:local .clickable_tab_inactive { + @extend .clickable_nav_link; + +} + +:local .clickable_tab { + cursor: pointer; + color: $primary-dark; +} + +:local .clickable_tab:hover { + color: $primary-light; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/index.js similarity index 84% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/index.js index 36cdae18940b23258fb11bd201dd12cd685ed968..0fac2c49260220682a927bb6dd7123732fce67b6 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationOverviewPage/index.js @@ -3,12 +3,11 @@ import {connect} from "react-redux"; import {Alert, Container, Row, Col} from 'reactstrap'; import moment from 'moment'; -import Header from '../components/header.js' -import StationAutoComplete from '../components/StationAutoComplete'; -import StationTestView from '../components/StationTestView'; -import StationTestChildView from '../components/StationTestChildView'; -import { composeQueryString } from '../utils/utils.js'; -import { Toolbar, DateRangeSelector, TestTypeSelector, ErrorTypesSelector } from '../components/Toolbar' +import NavigationBar from 'components/NavigationBar' +import StationTestView from './components/StationTestView'; +import StationTestChildView from './components/StationTestChildView'; +import { composeQueryString } from 'utils/utils.js'; +import { Toolbar, StationAutoComplete, DateRangeSelector, TestTypeSelector, ErrorTypesSelector } from 'components/Toolbar' /* * Display an Alert @@ -84,7 +83,7 @@ class StationOverviewPageC extends Component { return ( <React.Fragment> - <Header active_page={this.props.location} /> + <NavigationBar active_page={this.props.location} /> <Toolbar> <StationAutoComplete /> <TestTypeSelector /> diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationTestPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationTestPage.js deleted file mode 100644 index 622a0f3bbb124f7ce56aac54cd165468cb141af7..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/StationTestPage.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, { Component } from 'react'; -import {Container, - Row, - Col, - Input, - InputGroup, - InputGroupText, - InputGroupAddon} from 'reactstrap'; - -import StationList from '../components/StationList'; - -class StationTestPage extends Component { - constructor(props) { - super(props); - this.state = {current_page : 1} - let current_page = props.match.params.page - if (current_page !== undefined) { - this.state.current_page = Number(current_page); - } - } - - render() { - return ( - <StationList current_page={this.state.current_page}/> - ); - } -} - -export default StationTestPage; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaErrorDetails.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaErrorDetails/index.js similarity index 65% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaErrorDetails.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaErrorDetails/index.js index c0766214a0e77550525a9f0c3d37b1c9cde89ca5..427817033c6e7d056a32746d8eadc01ae90b26d3 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/AntennaErrorDetails.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaErrorDetails/index.js @@ -6,9 +6,6 @@ import 'react-treeview/react-treeview.css'; import moment from 'moment'; import { Button, - Modal, - ModalHeader, - ModalBody, Card, CardSubtitle, CardBody, @@ -17,56 +14,32 @@ import { Col } from 'reactstrap'; import { IoMdCloseCircleOutline as CloseIcon} from 'react-icons/io'; -import {pinAntennaError, selectAntennaError} from '../redux/actions/antennaOverviewPageActions'; -import {LOFARTESTS} from '../utils/LOFARDefinitions'; -import {datetime_format} from '../utils/constants'; +import {pinAntennaError, selectAntennaError} from 'redux/actions/antennaOverviewPageActions'; +import {LOFARTESTS} from 'utils/LOFARDefinitions'; +import {datetime_format} from 'utils/constants'; import {connect} from "react-redux"; +import EnlargeableImage from 'components/EnlargeableImage' + +// CSS +import styles from './styles.module.scss' + + +/** + * UnpinButton: simple component to optionally display an unpin button + */ +function UnpinButton({doDisplay, onClick}) { + return ( !doDisplay ? null : + <Button title="Click to unpin the error details" + color="info" + size="xs" + className={styles.unpin_button} + onClick={onClick}> + <CloseIcon/> unpin + </Button> + ); +} -class ModalPicture extends Component { - - state = { - modal: false, - modalUrl: '' - }; - - toggleModal = (e) => { - this.setState({ - modal: !this.state.modal, - modalUrl: e.currentTarget.src - }); - }; - - onImgError = (e) => { - const img = e.currentTarget; - img.alt = 'Reloading in 2 sec..'; - // let the window figure out if the timeout id is still valid - if(this.timeout){ - clearTimeout(this.timeout); - } - this.timeout = setTimeout(() => { img.src = img.src; }, 2000 ); - }; - - clearTimeouts() { - clearTimeout(this.timeout); - this.timeout = null; - } - - render(){ - this.clearTimeouts(); - return ( - <React.Fragment> - <img src={this.props.url} onClick={this.toggleModal} onError={this.onImgError} title="Click to enlarge" alt="Not present"/> - <Modal isOpen={this.state.modal} fade={false} size="lg" toggle={this.toggle} className={this.props.className}> - <ModalHeader toggle={this.toggleModal}></ModalHeader> - <ModalBody> - <img style={{width: '100%'}} src={this.state.modalUrl} alt="Not present" /> - </ModalBody> - </Modal> - </React.Fragment> - ); - } -} class AntennaErrorDetailsC extends Component { @@ -140,25 +113,24 @@ class AntennaErrorDetailsC extends Component { return ( <Card> <CardBody> - <CardSubtitle className="stcv-header"> - RCU {RTSMPerPolarization.rcu} - {polarization} - </CardSubtitle> - - <Container> - <Row> - <Col> - <Row> - <div>percentage: {RTSMPerPolarization.percentage.toFixed(2)} %</div> - </Row> - <Row> - <div>mode: {RTSMPerPolarization.mode}</div> - </Row> - </Col> - <Col> - <div style={{width: "20em"}}><ModalPicture url={RTSMPerPolarization.url} /></div> - </Col> - </Row> - </Container> + <CardSubtitle className={styles.header}> + RCU {RTSMPerPolarization.rcu} - {polarization} + </CardSubtitle> + <Container> + <Row noGutters={true}> + <Col md="4"> + <Row> + <div>percentage: {RTSMPerPolarization.percentage.toFixed(2)} %</div> + </Row> + <Row> + <div>mode: {RTSMPerPolarization.mode}</div> + </Row> + </Col> + <Col md="8"> + <EnlargeableImage url={RTSMPerPolarization.url} /> + </Col> + </Row> + </Container> </CardBody> </Card> ); @@ -168,8 +140,8 @@ class AntennaErrorDetailsC extends Component { if (this.props.hasOwnProperty('test_type') && this.props.test_type === 'S') return; return ( <React.Fragment> - {this.renderRTSMPolarizationDetails('X')} - {this.renderRTSMPolarizationDetails('Y')} + {this.renderRTSMPolarizationDetails('X')} + {this.renderRTSMPolarizationDetails('Y')} </React.Fragment> ) @@ -213,6 +185,8 @@ class AntennaErrorDetailsC extends Component { case 'R': jsx = this.renderRTSMDetails(); break; + default: + jsx = null; } return ( <React.Fragment> @@ -220,17 +194,16 @@ class AntennaErrorDetailsC extends Component { </React.Fragment> ); } + render(){ const title = this.composeTitle(); - let unpinButton = ""; - if (this.props.isPinned) { - unpinButton = <Button title="Click to unpin the error details" color="info" size="xs" style={{float:'right'}} onClick={this.unpinPanel}> - <CloseIcon/> unpin - </Button> - } + return ( - <div className="stcv"> - <div className="stcv-header">{title} {unpinButton}</div> + <div className={styles.child_view_container}> + <div className={styles.header}> + {title} + <UnpinButton doDisplay={this.props.isPinned} onClick={this.unpinPanel}/> + </div> {this.renderContent()} </div>) } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaErrorDetails/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaErrorDetails/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..02644754f2ed3b37ce73bc2390e7e4a83e4db565 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaErrorDetails/styles.module.scss @@ -0,0 +1,16 @@ +@import '../../../../themes/lofar-variables.scss'; + +:local .child_view_container .header { + padding-bottom: .5rem; + color: $secondary-dark; +} + +:local .child_view_container img { + cursor: pointer; + display: block; + width: 100%; +} + +:local .unpin_button { + float: right; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/index.js new file mode 100644 index 0000000000000000000000000000000000000000..96da2be1fbd55e48d1a11ca126678bda00c2339d --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/index.js @@ -0,0 +1,192 @@ +import React, { + Component +} from 'react'; + +import {connect} from 'react-redux'; +import {datetime_format} from "utils/constants.js"; +import moment from 'moment'; +import { IoMdArrowDropdown as DropDownIcon} from 'react-icons/io'; +import {renderDateRange} from 'utils/utils'; +import GenericStatusTd from '../GenericStatusTd' + +// CSS +import rtsmStyles from 'themes/rtsm_collapsable.module.scss'; + + +function RowHeader({test_type, start_date}) { + return ( + <React.Fragment> + <th>{test_type}</th> + <th>{moment(start_date).format(datetime_format)}</th> + </React.Fragment> + ); +} + + +class AntennaErrorRowC extends Component { + + state = { + isCollapsed: true + } + + renderGenericTestRow(data, key) { + let line = []; + let element = {}; + let component_errors; + component_errors = data.component_errors; + + for (let i = 0; i < this.props.errorTypes.length; i++) { + const error_type = this.props.errorTypes[i]; + if (component_errors && component_errors.hasOwnProperty(error_type)) { + const component_errors_per_type = component_errors[error_type]; + + if (component_errors_per_type.hasOwnProperty('element_errors')) { + for (let element_id of Object.keys(component_errors_per_type.element_errors)) { + if (!element.hasOwnProperty(element_id)) element[element_id] = []; + element[element_id].push({ + 'error type': error_type, + 'content': component_errors_per_type.element_errors[element_id] + }) + } + } + line.push(<GenericStatusTd isGood={false} + key={i} data={{ + test_type: data.test_type, + start_date: data.start_date, + end_date: data.end_date, + + error_type: error_type, + content: component_errors_per_type + }}/>) + } else { + line.push(<GenericStatusTd key={i} isGood={true}/>) + } + + + } + line.push(<td key="space"></td>); + for (let i = 1; i <= 16; i++) { + if (element.hasOwnProperty(i)) { + const errorType = element[i].length > 1 ? 'Multiple': element[i][0]['error type']; + const elementErrors = {}; + elementErrors['details'] = {}; + if(element[i].length > 1){ + for(const key in element[i]){ + elementErrors.details[`Error #${key}`] = element[i][key] + } + }else{ + elementErrors.details = {...element[i][0]} + } + + line.push(<GenericStatusTd key={`element${i}`} isGood={false} + data={ + { + test_type: data.test_type, + start_date: data.start_date, + end_date: data.end_date, + error_type: errorType, + element_id: i, + content: elementErrors + } + }/>); + + } else if (this.props.data.test_type === 'S') { + line.push(<GenericStatusTd key={`element${i}`} isGood={true}/>); + } + } + + return ( + <tr key={key}> + <RowHeader test_type={data.test_type} start_date={data.start_date} /> + {line} + </tr> + ); + } + + dropdownClick = () => { + this.setState({isCollapsed: !this.state.isCollapsed}) + this.props.update() + } + + + computeRTSMSummary(errors, errorTypes) { + const totalNumberOfErrors = errors.length; + const summary = {}; + + for (const type of errorTypes) { + for (const error of errors) { + if (error.component_errors.hasOwnProperty(type)) { + if (!summary.hasOwnProperty(type)) { + summary[type] = 0.; + } + summary[type] += 1.; + } + } + } + + const summaryItems = errorTypes.map(item => { + let value = ""; + if (summary.hasOwnProperty(item)) { + value = (summary[item] * 100 / totalNumberOfErrors).toFixed(0)+"%" + } + return {item, value} + }); + + return summaryItems; + } + + renderRTSMSummaryRow() { + const timeSpan = renderDateRange(this.props.data); + const summaryItems = this.computeRTSMSummary(this.props.data, this.props.errorTypes); + const dropdownAdditionStyles = !this.state.isCollapsed ? " "+rtsmStyles.dropdownbutton_up : ""; + + return ( + <tr className={rtsmStyles.rtsm_summary_row}> + <td> + R + </td> + <td onClick={this.dropdownClick}> + {timeSpan} + <DropDownIcon className={rtsmStyles.dropdownbutton + dropdownAdditionStyles}/> + </td> + { + summaryItems.map( obj => { + let c = obj.value !== "" ? rtsmStyles.rtsm_summary_badge : "" + return <td key={obj.item} className={c}> + {obj.value} + </td> + }) + } + </tr>); + } + + render() { + const data = this.props.data; + + // StationTest; return one data row + if (data.hasOwnProperty('test_type')) { + return this.renderGenericTestRow(data, ""); + } + + // RTSM; return summary row and data rows if expanded + return ( + <React.Fragment> + {this.renderRTSMSummaryRow()} + {!this.state.isCollapsed && this.props.data.map((test, id) => + this.renderGenericTestRow(test, id) + )} + </React.Fragment> + ); + + } +} + +const AntennaErrorRow = connect(state => { + return { + ...state.mainFilters, + ...state.appInitData + }; +})(AntennaErrorRowC); + + +export default AntennaErrorRow; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/styles.module.css similarity index 54% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.css rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/styles.module.css index 5dc5cc855b3ac5b93a00c83e21d2f5335ee67476..80a25ffaafe9fbc0706a2abd70c7625235c98daf 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/components/StationTestChildView.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/styles.module.css @@ -2,12 +2,5 @@ /* Color palette interface (created with https://material.io/tools/color/) */ /* font color */ /* font color */ +/* hover color, e.g. for background */ /* Data colors */ -.stcv-header { - padding: .5rem 0; - color: #86868a; } - -.stcv img { - cursor: pointer; - display: block; - width: 90%; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..f0674c4aa2a0443b5ec6fe813956725b2f3afbb4 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/AntennaErrorRow/styles.module.scss @@ -0,0 +1,2 @@ + +@import '../../../../../themes/lofar-variables.scss'; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f26b47acb9a96c978ba5cf83329b00a5b310ac66 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/index.js @@ -0,0 +1,114 @@ +import React, { + Component +} from 'react'; + +import {connect} from 'react-redux'; +import {selectAntennaError, pinAntennaError} from 'redux/actions/antennaOverviewPageActions'; + +// CSS +import styles from './styles.module.scss'; + + +/** + * GenericStatusTd; render a <td> showing the status for an error. Show the error + * details in the detail panel on the right on mouse hover. + * + * @prop {bool} isGood - indicates if this item has errors + * @prop {bool} isPinned - indicates if this item is pinned in the child panel, if so highlight it + * @prop {object} data - the data object + * @prop {function} pinAntennaError - handler to pin this item + * @prop {function} selectAntennaError - mouseOver handler + */ +class GenericStatusTdC extends Component { + + mouseOver = () => { + if (this.props.data !== undefined && this.props.isPinned === false) { + this.props.selectAntennaError(this.props.data); + } + } + + mouseOut = () => { + if (this.props.data !== undefined && this.props.isPinned === false) { + this.props.selectAntennaError({}); + } + } + + renderStationTest() { + const label = this.props.isGood ? '' : 'X'; + return (<React.Fragment>{label}</React.Fragment>); + } + + renderRTSM() { + const xPolStyle = { + float: 'left', + position: 'relative', + top: '-0.3rem' + }; + const yPolStyle = { + float: 'right', + position: 'relative', + right: '0.rem', + bottom: '-.2rem' + }; + + + const xPol = ( + <div style={xPolStyle} key={'X'}>X</div> + ); + const yPol = ( + <div style={yPolStyle} key={'Y'}>Y</div> + ); + let presentErrors = []; + const errorsPerPolarization = this.props.data.content; + if (errorsPerPolarization.hasOwnProperty('X')) presentErrors.push(xPol); + if (errorsPerPolarization.hasOwnProperty('Y')) presentErrors.push(yPol); + + return <div>{presentErrors}</div>; + } + + renderError() { + switch (this.props.data.test_type) { + case 'R': + return this.renderRTSM(); + case 'S': + return this.renderStationTest(); + default: + return + } + } + + onContextMenu = (e) => { + e.preventDefault(); + if (this.props.data !== undefined) { + this.props.selectAntennaError(this.props.data); + this.props.pinAntennaError(true); + } + } + + render() { + + const color = this.props.isGood ? " hilite-good" : " hilite-serious"; + const label = this.props.isGood ? "" : this.renderError(); + return ( + <td className={styles.status+color} + onContextMenu={this.onContextMenu} + onMouseOver={this.mouseOver} + onMouseOut={this.mouseOut}> + {label} + </td>); + } +} + +// TestLine is connected to Redux store +const GenericStatusTd = connect(state => { + return { + ...state.antenna_page.main_panel, + ...state.antenna_page.child_panel + }; +}, { + selectAntennaError, + pinAntennaError +})(GenericStatusTdC); + + +export default GenericStatusTd; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/styles.module.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/styles.module.css new file mode 100644 index 0000000000000000000000000000000000000000..268db1b71d5a450bb21c865644e92bb492e4744c --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/styles.module.css @@ -0,0 +1,19 @@ +/* COLORS */ +/* Color palette interface (created with https://material.io/tools/color/) */ +/* font color */ +/* font color */ +/* hover color, e.g. for background */ +/* Data colors */ +:local .status { + text-align: center; + font-size: 90%; + font-weight: 600; } + +:local .status.highlight { + background-color: #86868a !important; + color: white; } + +:local .status:hover { + color: #fff; + background-color: #86868a; + border-color: #86868a; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..64dec0b517f887c5c72b877c19855d710ab98cd4 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/GenericStatusTd/styles.module.scss @@ -0,0 +1,19 @@ +@import '../../../../../themes/lofar-variables.scss'; + + +:local .status { + text-align: center; + font-size: 90%; + font-weight: 600; +} + +:local .status.highlight { + background-color: $secondary-dark!important; + color: $secondary-color; +} + +:local .status:hover { + color: #fff; + background-color: $hover-color; + border-color: $hover-color; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/index.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d829d76e5135dc39eba3eb61b823b3666a119238 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/index.js @@ -0,0 +1,110 @@ +import React, { + Component +} from 'react'; + +import ReactTableContainer from 'react-table-container'; +import AutoLoadWrapper from 'components/AutoLoadWrapper'; +import {connect} from 'react-redux'; +import {componentErrorTypes} from "utils/constants.js"; +import FillViewportHeightDiv from 'components/FillViewportHeightDiv'; +import AntennaErrorRow from './AntennaErrorRow' + + +// CSS +import styles from './styles.module.scss'; + +/** + * AntennaTable: component with a chronological table of station test and RTSM + * results for a selected antenna. + * + * @prop {object} data - the data object + * @prop {array} errorTypes - array of all error types (from appInitData reducer) + * @prop {integer} height - height in pixels of the containing <div> + */ +class AntennaTable extends Component { + + renderHeader() { + const errors_columns = this.props.errorTypes.map((error_type, key) => { + return (<td key={key} title={error_type}>{componentErrorTypes[error_type]}</td>) + }); + + const element_ids = []; + for (let i = 1; i <= 16; i++) element_ids.push(<td key={i}>{i}</td>); + return <tr> + <th title="Test type">T</th> + <th style={{width: "10em"}}>Date</th> + {errors_columns} + <th></th> + {element_ids} + </tr>; + }; + + static groupData(data) { + let grouped = []; + let group = []; + for (let test of data) { + + if (test.test_type !== 'R') { + if (group.length > 0) { + grouped.push(group); + group = []; + } + grouped.push(test) + } else { + group.push(test) + } + } + if (group.length > 0) grouped.push(group); + + return grouped + } + + updateIfContentChanges = () => { + this.setState({state:this.state}) + } + + renderBody() { + if (this.props.data.hasOwnProperty('errors')) { + let groupedData = AntennaTable.groupData(this.props.data.errors); + return groupedData.map((test, id) => <AntennaErrorRow key={id} data={test} update={this.updateIfContentChanges}/>); + } + + + } + + render() { + + const jsx = ( + <ReactTableContainer width="100%" height={this.props.height+'px'}> + <table className={styles.antenna_table+" table-sm table-hover table-bordered"}> + <thead className={"stv-tableheader"} >{this.renderHeader()}</thead> + <tbody>{this.renderBody()}</tbody> + </table> + </ReactTableContainer> + ); + return jsx; + } +} + +/** + * AntennaViewC; antenna view component to display the test results for a selected antenna. + * + * It gets the props from the mainFilters and appInitData reducers. + */ +const AntennaViewC = function(props){ + return ( + <FillViewportHeightDiv className="border-right"> + { ({height}) => <AntennaTable {...props} height={height} />} + </FillViewportHeightDiv> + ) +} + +const AntennaView = AutoLoadWrapper( connect(state => { + return { + ...state.mainFilters, + ...state.appInitData + }; +})(AntennaViewC) ); + + +export default AntennaView; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/styles.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..f00653007c8074ef2e9e1e19ce22e227923aa64f --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/components/AntennaView/styles.module.scss @@ -0,0 +1,10 @@ + +:local .antenna_table { + font-size: .9rem; +} + +:local(.antenna_table):global(.table-sm) td, +:local(.antenna_table):global(.table-sm) th { + padding: .1rem; + min-width: 1.8em; +} diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/index.js similarity index 82% rename from LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js rename to LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/index.js index 43eabbd06801579f4a781667db46c262d5461728..3e9b64346f75211ee8a1baea254089ec114dd5cb 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/pages/TilesPage/index.js @@ -1,13 +1,12 @@ import React, { Component } from 'react'; -import Header from '../components/header.js' +import NavigationBar from 'components/NavigationBar' import moment from 'moment' import {connect} from "react-redux"; -import StationAutoComplete from '../components/StationAutoComplete'; -import { Toolbar, DateRangeSelector, TestTypeSelector, AntennaTypeSelector, AntennaIdSelector } from '../components/Toolbar' +import { Toolbar, StationAutoComplete, DateRangeSelector, TestTypeSelector, AntennaTypeSelector, AntennaIdSelector } from 'components/Toolbar' import {Alert, Container, Col, Row} from "reactstrap"; -import { composeQueryString } from '../utils/utils.js'; -import AntennaErrorDetails from '../components/AntennaErrorDetails' -import AntennaView from '../components/AntennaView'; +import { composeQueryString } from 'utils/utils.js'; +import AntennaErrorDetails from './components/AntennaErrorDetails' +import AntennaView from './components/AntennaView'; // Component to display an alert @@ -49,7 +48,7 @@ class TilesPageC extends Component { return ( <div> - <Header active_page={this.props.location} /> + <NavigationBar active_page={this.props.location} /> <Toolbar> <StationAutoComplete /> <AntennaTypeSelector /> diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/redux/reducers/stationOverviewPageReducers.js b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/redux/reducers/stationOverviewPageReducers.js index 75d9e46d994055aabac050d4740a5a8948667cea..f2994bcac1c46b27395bb1e07fc276536274053d 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/redux/reducers/stationOverviewPageReducers.js +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/redux/reducers/stationOverviewPageReducers.js @@ -3,7 +3,6 @@ import { UNPIN_CHILD_PANEL_DATA } from '../actions/stationOverviewPageActions.js' -//import { SET_STATION } from "../actions/mainFiltersActions"; import { LOCATION_CHANGE } from "connected-react-router"; const initialState = { diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css index 3a0c2bded0cb8179f7727639a6d37e2cfcd3716c..420a5c01a45dd1d9a3fac204b36fea19d2c0ad53 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.css @@ -3,11 +3,35 @@ /* Color palette interface (created with https://material.io/tools/color/) */ /* font color */ /* font color */ +/* hover color, e.g. for background */ /* Data colors */ +body { + margin: 0; + padding: 0; } + /* For alt text */ img { font-weight: 400 !important; } +.hilite-serious { + background-color: #f17171; + color: white; } + +.hilite-alarming { + background-color: #ffcd74; + color: black; } + +.hilite-warning { + background-color: #fbfb83; + color: black; } + +.hilite-good { + background-color: #6fbd6f; + color: white; } + +.nowrap { + white-space: nowrap; } + .react-grid-item { background-color: white; border: 1px solid #e8e8ec; @@ -30,30 +54,6 @@ img { .react-grid-item > .react-resizable-handle::after { border-color: #bce8f1; } -.autoloader-container { - position: relative; - height: 100%; } - -/* autoLoader; CSS spinner*/ -.autoloader-loading { - position: absolute; - top: 2.25em; - /* Check with height of .react-grid-item-header */ - left: 0.25em; - border: 0.55rem solid #e8e8ec; - border-top: 0.55rem solid #4a6889; - border-radius: 50%; - width: 3rem; - height: 3rem; - animation: spin 2s linear infinite; - z-index: 1000; } - -@keyframes spin { - 0% { - transform: rotate(0deg); } - 100% { - transform: rotate(360deg); } } - /* Reacstrap only support sm, md and lg */ .btn-group-xs > .btn, .btn.btn-xs { diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss index a06cfc16b5fc08b0b54b81b4d4e37317f9cd0a84..eb8eda6706354b382d98e1ab55458e7069487885 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-styles.scss @@ -3,7 +3,8 @@ @import '../themes/lofar-variables.scss'; body { - //background-color: #E1E2E1!important; + margin: 0; + padding: 0; } /* For alt text */ @@ -11,6 +12,28 @@ img { font-weight: 400!important; } + +.hilite-serious { + background-color: $serious; + color: $serious_fontcolor; +} +.hilite-alarming { + background-color: $alarming; + color: $alarming_fontcolor; +} +.hilite-warning { + background-color: $warning; + color: $warning_fontcolor; +} +.hilite-good { + background-color: $good; + color: $good_fontcolor; +} + +.nowrap { + white-space: nowrap; +} + $griditem: $secondary-light; $griditem-color: $secondary-dark; @@ -39,29 +62,6 @@ $griditem-color: $secondary-dark; } -.autoloader-container { - position: relative; - height: 100%; -} -/* autoLoader; CSS spinner*/ -.autoloader-loading { - position: absolute; - top: 2.25em; /* Check with height of .react-grid-item-header */ - left: 0.25em; - border: .55rem solid $secondary-light; - border-top: .55rem solid $primary-light; - border-radius: 50%; - width: 3rem; - height: 3rem; - animation: spin 2s linear infinite; - z-index: 1000; -} -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - - /* Reacstrap only support sm, md and lg */ .btn-group-xs>.btn, .btn.btn-xs { diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.css index 6a86564eb690cdb7b435a0b3a6f0d187ef46cdaa..80a25ffaafe9fbc0706a2abd70c7625235c98daf 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.css @@ -2,4 +2,5 @@ /* Color palette interface (created with https://material.io/tools/color/) */ /* font color */ /* font color */ +/* hover color, e.g. for background */ /* Data colors */ diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.scss index ce151724f088f43ac79938ed956baf1c4dd58cbb..3f75d98dfa44e6161d602bd5c83886999858ebdf 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.scss +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar-variables.scss @@ -13,6 +13,9 @@ $secondary-light: #e8e8ec; //#e1e1e1; $secondary-dark: #86868a; //#8d8d8d; $secondary-color: white; /* font color */ +/* hover color, e.g. for background */ +$hover-color: $secondary-dark; + /* Data colors */ $serious: #f17171; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css index aab891291b8281dacb619f0553f7465bc3fde7de..363a0162a66c27bc64b7ef832b9fdfc3647dec72 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.css @@ -2,6 +2,7 @@ /* Color palette interface (created with https://material.io/tools/color/) */ /* font color */ /* font color */ +/* hover color, e.g. for background */ /* Data colors */ /*! * Bootstrap v4.1.3 (https://getbootstrap.com/) @@ -6322,11 +6323,35 @@ a.text-dark:hover, a.text-dark:focus { /* Color palette interface (created with https://material.io/tools/color/) */ /* font color */ /* font color */ +/* hover color, e.g. for background */ /* Data colors */ +body { + margin: 0; + padding: 0; } + /* For alt text */ img { font-weight: 400 !important; } +.hilite-serious { + background-color: #f17171; + color: white; } + +.hilite-alarming { + background-color: #ffcd74; + color: black; } + +.hilite-warning { + background-color: #fbfb83; + color: black; } + +.hilite-good { + background-color: #6fbd6f; + color: white; } + +.nowrap { + white-space: nowrap; } + .react-grid-item { background-color: white; border: 1px solid #e8e8ec; @@ -6349,30 +6374,6 @@ img { .react-grid-item > .react-resizable-handle::after { border-color: #bce8f1; } -.autoloader-container { - position: relative; - height: 100%; } - -/* autoLoader; CSS spinner*/ -.autoloader-loading { - position: absolute; - top: 2.25em; - /* Check with height of .react-grid-item-header */ - left: 0.25em; - border: 0.55rem solid #e8e8ec; - border-top: 0.55rem solid #4a6889; - border-radius: 50%; - width: 3rem; - height: 3rem; - animation: spin 2s linear infinite; - z-index: 1000; } - -@keyframes spin { - 0% { - transform: rotate(0deg); } - 100% { - transform: rotate(360deg); } } - /* Reacstrap only support sm, md and lg */ .btn-group-xs > .btn, .btn.btn-xs { @@ -6412,3 +6413,51 @@ img { /* Padding on column in bootstrap grid */ .row .col-padding { padding-top: 15px; } + +/* COLORS */ +/* Color palette interface (created with https://material.io/tools/color/) */ +/* font color */ +/* font color */ +/* hover color, e.g. for background */ +/* Data colors */ +.rtsm_summary_row { + background-color: #eeeeee; } + +.rtsm_summary_row .row_header { + width: 12rem !important; + min-width: 12rem !important; + cursor: pointer; } + +@keyframes animation-open { + from { + transform: rotate(0deg); } + to { + transform: rotate(180deg); } } + +@keyframes animation-close { + from { + transform: rotate(180deg); } + to { + transform: rotate(0deg); } } + +.rtsm_summary_row .dropdownbutton { + display: inline; + float: right; + animation: animation-close; + animation-duration: 100ms; + animation-iteration-count: 1; + animation-timing-function: linear; } + +.rtsm_summary_row .dropdownbutton_up { + transform: rotate(180deg); + animation: animation-open; + animation-duration: 100ms; + animation-iteration-count: 1; + animation-timing-function: linear; } + +.rtsm_summary_badge { + background-color: #fbfb83; + color: black; + text-shadow: 1px 2px white; + font-size: 80%; + text-align: center; } diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.scss index 54ec705fa67a4ffc374863c9a3fa1ccf21b2bd7f..9d371dd282239b7b8f5a80052150d044f26cb817 100644 --- a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.scss +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/lofar.scss @@ -1,3 +1,4 @@ @import "lofar-variables.scss"; @import "node_modules/bootstrap/scss/bootstrap.scss"; @import "lofar-styles.scss"; +@import "rtsm_collapsable.module.scss"; diff --git a/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/rtsm_collapsable.module.scss b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/rtsm_collapsable.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..cd58e9417745fcb9ccf403e0a28d78aa98390f77 --- /dev/null +++ b/LCU/Maintenance/MDB_WebView/maintenancedb_view/src/themes/rtsm_collapsable.module.scss @@ -0,0 +1,53 @@ +@import './lofar-variables.scss'; + +.rtsm_summary_row { + background-color: #eeeeee; +} + +.rtsm_summary_row .row_header { + width: 12rem !important; + min-width: 12rem !important; + cursor: pointer; +} + +@keyframes animation-open { + from { + transform: rotate(0deg); + } to { + transform: rotate(180deg); + } +} + +@keyframes animation-close { + from { + transform: rotate(180deg); + } to { + transform: rotate(0deg); + } +} + +.rtsm_summary_row .dropdownbutton { + display: inline; + float: right; + animation: animation-close; + animation-duration: 100ms; + animation-iteration-count: 1; + animation-timing-function: linear; +} + +.rtsm_summary_row .dropdownbutton_up { + transform: rotate(180deg); + animation: animation-open; + animation-duration: 100ms; + animation-iteration-count: 1; + animation-timing-function: linear; +} + + +.rtsm_summary_badge { + background-color: $warning; + color: $warning-fontcolor; + text-shadow: 1px 2px white; + font-size: 80%; + text-align: center; +}