diff --git a/client/app/assets/less/ant.less b/client/app/assets/less/ant.less index 5eb913eee7..0b9020d0e8 100644 --- a/client/app/assets/less/ant.less +++ b/client/app/assets/less/ant.less @@ -47,6 +47,7 @@ @zindex-dropdown: 2050; @zindex-picker: 2050; @zindex-tooltip: 2060; +@item-hover-bg: #e5f8ff; .@{drawer-prefix-cls} { &.help-drawer { diff --git a/client/app/assets/less/inc/base.less b/client/app/assets/less/inc/base.less index 749d6ef4e0..1fb33c7f2f 100755 --- a/client/app/assets/less/inc/base.less +++ b/client/app/assets/less/inc/base.less @@ -29,7 +29,7 @@ body { padding-top: 10px; } - .nav.app-header, .navbar { + .app-header-wrapper { display: none; } } @@ -182,7 +182,7 @@ text.slicetext { color: #111; } -.profile__image--navbar { +.profile__image--sidebar { border-radius: 100%; margin-right: 3px; margin-top: -2px; diff --git a/client/app/assets/less/inc/navbar.less b/client/app/assets/less/inc/navbar.less deleted file mode 100755 index 3b912e8dea..0000000000 --- a/client/app/assets/less/inc/navbar.less +++ /dev/null @@ -1,295 +0,0 @@ -a.navbar-brand { - padding: 5px 5px 0px 0px; -} - -.navbar .fa { - font-size: 18px; -} - -.navbar .collapse.in { - background: #222; -} - -a.navbar-brand img { - height: 40px; -} - -.avatar { - margin-top: 5px; - margin-bottom: 5px; -} - -.avatar img { - width: 40px; - height: 40px; -} - -#logout { - color: white; - position: relative; - left: -9px; - bottom: -11px; -} - -.caret--nav { - border-top: none; -} - -.caret--nav:after { - content: ""; - position: absolute; - right: 5px; - top: 9px; - width: 13px; - height: 13px; - display: block; - background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='11px' height='6px' viewBox='0 0 11 6' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3C!-- Generator: Sketch 42 %2836781%29 - http://www.bohemiancoding.com/sketch --%3E%3Ctitle%3EShape%3C/title%3E%3Cdesc%3ECreated with Sketch.%3C/desc%3E%3Cdefs%3E%3C/defs%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M5.296,4.288 L9.382,0.2 C9.66086822,-0.0716916976 10.1065187,-0.068122925 10.381,0.208 C10.661,0.488 10.661,0.932 10.388,1.206 L5.792,5.803 C5.6602899,5.93388911 5.48167943,6.00662966 5.296,6.005 C5.10997499,6.00689786 4.93095449,5.93413702 4.799,5.803 L0.204,1.207 C0.072163111,1.07394937 -0.00121750401,0.893846387 9.62313189e-05,0.706545264 C0.00140996665,0.519244142 0.0773097323,0.340188219 0.211,0.209 C0.485365732,-0.0664648737 0.930253538,-0.0700311086 1.209,0.201 L5.296,4.288 L5.296,4.288 Z' id='Shape' fill='%23000000'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"); - background-size: 100% 100%; - transition: transform .2s cubic-bezier(.75,0,.25,1); -} - -.navbar .caret--nav:after { - top: 19px; -} - -.dropdown--profile .caret--nav:after { - right: 8px; -} - -.btn--create { - padding-right: 20px; - - .caret--nav:after { - top: 10px; - right: 10px; - background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='11px' height='6px' viewBox='0 0 11 6' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3C!-- Generator: Sketch 42 %2836781%29 - http://www.bohemiancoding.com/sketch --%3E%3Ctitle%3EShape%3C/title%3E%3Cdesc%3ECreated with Sketch.%3C/desc%3E%3Cdefs%3E%3C/defs%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M5.29592111,4.28945339 L9.38192111,0.201453387 C9.66078932,-0.0702383105 10.1064398,-0.0666695379 10.3809211,0.209453387 C10.6609211,0.489453387 10.6609211,0.933453387 10.3879211,1.20745339 L5.79192111,5.80445339 C5.66021101,5.9353425 5.48160054,6.00808305 5.29592111,6.00645339 C5.1098961,6.00835125 4.9308756,5.9355904 4.79892111,5.80445339 L0.203921109,1.20845339 C0.0720842204,1.07540275 -0.00129639464,0.895299774 1.73406884e-05,0.707998651 C0.00133107602,0.520697529 0.0772308417,0.341641606 0.210921109,0.210453387 C0.485286842,-0.0650114866 0.930174648,-0.0685777215 1.20892111,0.202453387 L5.29592111,4.28945339 L5.29592111,4.28945339 Z' id='Shape' fill='%23FCFCFC'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"); - } -} - -.dropdown.open .caret--nav:after { - transform: rotate(180deg); -} - -.navbar { - box-shadow: fade(@redash-gray, 15%) 0px 4px 9px -3px; - - .navbar-collapse { - padding-left: 0; - } - - a.dropdown--profile { - padding-top: 10px; - padding-bottom: 10px; - line-height: 2.35; - } - - .navbar-inverse { - background-color: @redash-gray; - border: none; - } -} - -.navbar-btn { - margin-top: 10px; - margin-bottom: 9px; -} - -.navbar-brand { - position: absolute; - left: 50%; - margin-left: -25px !important; // center - display: block; - zoom: 0.9; -} - -.menu-search { - margin-top: 2px; -} - -.dropdown-menu--profile { - li { - width: 200px; - } -} - -.navbar .collapse.in { - background: #fff; - position: relative; - z-index: 999; - padding: 0 10px 0 10px; -} -.navbar { - min-height: initial; - height: 50px; - border: 1px solid #fff; - border-top: none; - border-radius: 0; - background: #fff; - margin-bottom: 10px; - - .btn-group.open .dropdown-toggle { - -webkit-box-shadow: none; - box-shadow: none; - } - - .btn-group .btn:active { - box-shadow: none; - } -} - -.navbar-link-ANGULAR_REMOVE_ME { - line-height: 18px; - padding: 10px 15px; - display: block; - - @media (min-width: 768px) { - padding-top: 16px; - padding-bottom: 16px; - } -} - -.navbar-link-ANGULAR_REMOVE_ME, -.navbar-default .navbar-nav > li > a { - color: #000; - font-weight: 500; - - &:active, &:hover, &:focus { - color: #000; - } -} - -.navbar-default .btn__new button { - font-weight: 500; -} - -.btn__new { - margin-left: 15px; -} - -.navbar-default .navbar-nav > li > a:hover { - //background-color: fade(@redash-gray, 10%); - //text-decoration: underline; - //border-radius: 0; -} - -.navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { - background-color: fade(@redash-gray, 15%); - color: #111; -} - - -// Responsive fixes -@media (max-width: 767px) { - .navbar-brand { - left: 2%; - margin-left: 0 !important; - } - - //Fix navbar collapse - .navbar .collapse.in { - border: none; - - .dropdown-menu--profile { - li { - width: auto; - } - } - - .dropdown--profile { - .caret--nav:after { - right: initial !important; - } - } - - .dropdown--profile__username { - display: inline-block; - } - - .nav__main li a { - padding: 10px 15px; - display: block; - text-align: left; - float: none !important; - } - - .navbar-form { - margin-bottom: 0; - margin-top: 0; - } - - .navbar-right { - margin-bottom: 0; - } - } -} - -@media (min-width: 768px) { - @media (max-width: 880px) { - .navbar-link-ANGULAR_REMOVE_ME, - .navbar-default .navbar-nav > li > a, - .navbar-form { - padding-left: 10px !important; - padding-right: 10px !important; - } - - a.navbar-brand { - margin-left: -15px !important; - } - } - - @media (max-width: 810px) { - .menu-search { - width: 175px; - } - - a.navbar-brand { - margin-left: 13px !important; - } - } -} - -@media (max-width: 1084px) { - .dropdown--profile__username { - display: none; - } -} - - - - -// Cross-browser fixes - -// Firefox -@-moz-document url-prefix() { - .caret--nav::after { - height: 7px; - } - - .navbar .caret--nav::after { - top: 22px; - } - - .navbar .btn--create .caret--nav::after { - top: 12px; - } -} - -// IE10+ -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { - .caret--nav::after { - height: 7px; - } - - .navbar .caret--nav::after { - top: 22px; - } - - .navbar .btn--create .caret--nav::after { - top: 12px; - } -} - - -.navbar li a .btn-favourite .fa, .navbar li a .btn-archive .fa { - font-size: 100%; -} \ No newline at end of file diff --git a/client/app/assets/less/main.less b/client/app/assets/less/main.less index 0f764f8144..2e4cc000e9 100644 --- a/client/app/assets/less/main.less +++ b/client/app/assets/less/main.less @@ -44,7 +44,6 @@ @import 'inc/profile'; @import 'inc/404'; @import 'inc/ie-warning'; -@import 'inc/navbar'; @import 'inc/edit-in-place'; @import 'inc/growl'; @import 'inc/flex'; diff --git a/client/app/components/BeaconConsent.jsx b/client/app/components/BeaconConsent.jsx index e551eb6a0f..665ffe0c30 100644 --- a/client/app/components/BeaconConsent.jsx +++ b/client/app/components/BeaconConsent.jsx @@ -4,7 +4,7 @@ import Card from 'antd/lib/card'; import Button from 'antd/lib/button'; import Typography from 'antd/lib/typography'; import { clientConfig } from '@/services/auth'; -import { HelpTrigger } from '@/components/HelpTrigger'; +import HelpTrigger from '@/components/HelpTrigger'; import DynamicComponent from '@/components/DynamicComponent'; import OrgSettings from '@/services/organizationSettings'; diff --git a/client/app/components/CreateSourceDialog.jsx b/client/app/components/CreateSourceDialog.jsx index 3f7c2eda5e..cc5818601e 100644 --- a/client/app/components/CreateSourceDialog.jsx +++ b/client/app/components/CreateSourceDialog.jsx @@ -11,7 +11,7 @@ import { PreviewCard } from '@/components/PreviewCard'; import EmptyState from '@/components/items-list/components/EmptyState'; import DynamicForm from '@/components/dynamic-form/DynamicForm'; import helper from '@/components/dynamic-form/dynamicFormHelper'; -import { HelpTrigger, TYPES as HELP_TRIGGER_TYPES } from '@/components/HelpTrigger'; +import HelpTrigger, { TYPES as HELP_TRIGGER_TYPES } from '@/components/HelpTrigger'; const { Step } = Steps; const { Search } = Input; diff --git a/client/app/components/EmailSettingsWarning.jsx b/client/app/components/EmailSettingsWarning.jsx index 999052ee74..eabe0ee04d 100644 --- a/client/app/components/EmailSettingsWarning.jsx +++ b/client/app/components/EmailSettingsWarning.jsx @@ -4,7 +4,7 @@ import cx from 'classnames'; import { clientConfig, currentUser } from '@/services/auth'; import Tooltip from 'antd/lib/tooltip'; import Alert from 'antd/lib/alert'; -import { HelpTrigger } from '@/components/HelpTrigger'; +import HelpTrigger from '@/components/HelpTrigger'; export default function EmailSettingsWarning({ featureName, className, mode, adminOnly }) { if (!clientConfig.mailSettingsMissing) { diff --git a/client/app/components/HelpTrigger.jsx b/client/app/components/HelpTrigger.jsx index ae57b250da..75b2a6f8dd 100644 --- a/client/app/components/HelpTrigger.jsx +++ b/client/app/components/HelpTrigger.jsx @@ -1,4 +1,3 @@ -import { react2angular } from 'react2angular'; import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; @@ -80,9 +79,13 @@ export const TYPES = { '/user-guide/alerts/custom-alert-notifications', 'Guide: Custom Alerts Notifications', ], + FAVORITES: [ + '/user-guide/querying/favorites-tagging/#Favorites', + 'Guide: Favorites', + ], }; -export class HelpTrigger extends React.Component { +export default class HelpTrigger extends React.Component { static propTypes = { type: PropTypes.oneOf(Object.keys(TYPES)).isRequired, className: PropTypes.string, @@ -236,9 +239,3 @@ export class HelpTrigger extends React.Component { ); } } - -export default function init(ngModule) { - ngModule.component('helpTrigger', react2angular(HelpTrigger)); -} - -init.init = true; diff --git a/client/app/components/ParameterMappingInput.jsx b/client/app/components/ParameterMappingInput.jsx index bb138f2b8e..05c2f80925 100644 --- a/client/app/components/ParameterMappingInput.jsx +++ b/client/app/components/ParameterMappingInput.jsx @@ -18,7 +18,7 @@ import Tooltip from 'antd/lib/tooltip'; import ParameterValueInput from '@/components/ParameterValueInput'; import { ParameterMappingType } from '@/services/widget'; import { Parameter } from '@/services/query'; -import { HelpTrigger } from '@/components/HelpTrigger'; +import HelpTrigger from '@/components/HelpTrigger'; import './ParameterMappingInput.less'; diff --git a/client/app/components/app-header/AppHeader.jsx b/client/app/components/app-header/AppHeader.jsx new file mode 100644 index 0000000000..69a4f68045 --- /dev/null +++ b/client/app/components/app-header/AppHeader.jsx @@ -0,0 +1,257 @@ +/* eslint-disable no-template-curly-in-string */ + +import React, { useRef } from 'react'; +import { react2angular } from 'react2angular'; + +import Dropdown from 'antd/lib/dropdown'; +import Button from 'antd/lib/button'; +import Icon from 'antd/lib/icon'; +import Menu from 'antd/lib/menu'; +import Input from 'antd/lib/input'; +import Tooltip from 'antd/lib/tooltip'; + +import FavoritesDropdown from './components/FavoritesDropdown'; +import HelpTrigger from '@/components/HelpTrigger'; +import CreateDashboardDialog from '@/components/dashboards/CreateDashboardDialog'; + +import { currentUser, Auth, clientConfig } from '@/services/auth'; +import { $location, $route } from '@/services/ng'; +import { Dashboard } from '@/services/dashboard'; +import { Query } from '@/services/query'; +import frontendVersion from '@/version.json'; +import logoUrl from '@/assets/images/redash_icon_small.png'; + +import './AppHeader.less'; + +function onSearch(q) { + $location.path('/queries').search({ q }); + $route.reload(); +} + +function DesktopNavbar() { + return ( +
+
+ + {currentUser.hasPermission('list_dashboards') && ( + + + + + )} + {currentUser.hasPermission('view_query') && ( + + + + + )} + {currentUser.hasPermission('list_alerts') && ( + + + + )} + + + {currentUser.hasPermission('create_query') && ( + + New Query + + )} + {currentUser.hasPermission('create_dashboard') && ( + + New Dashboard + + )} + + New Alert + + + )} + > + + +
+
+ + Redash + +
+
+ + + + + + {currentUser.isAdmin && ( + + + + + + )} + + + + Edit Profile + + {currentUser.hasPermission('super_admin') && ( + + )} + {currentUser.isAdmin && ( + + Data Sources + + )} + {currentUser.hasPermission('list_users') && ( + + Groups + + )} + {currentUser.hasPermission('list_users') && ( + + Users + + )} + + Query Snippets + + {currentUser.hasPermission('list_users') && ( + + Alert Destinations + + )} + {currentUser.hasPermission('super_admin') && ( + + )} + {currentUser.hasPermission('super_admin') && ( + + System Status + + )} + + Auth.logout()}>Log out + + + Version: {clientConfig.version} + {frontendVersion !== clientConfig.version && ` (${frontendVersion.substring(0, 8)})`} + {clientConfig.newVersionAvailable && currentUser.hasPermission('super_admin') && ( + + {' '} + {/* eslint-disable-next-line react/jsx-no-target-blank */} + + + + + )} + + + )} + > + + + + +
+
+ ); +} + +function MobileNavbar() { + const ref = useRef(); + + return ( +
+
+ + Redash + +
+
+ ref.current} // so the overlay menu stays with the fixed header when page scrolls + overlay={( + + {currentUser.hasPermission('list_dashboards') && ( + + Dashboards + + )} + {currentUser.hasPermission('view_query') && ( + + Queries + + )} + {currentUser.hasPermission('list_alerts') && ( + + Alerts + + )} + + Edit Profile + + + {currentUser.isAdmin && ( + + Settings + + )} + {currentUser.hasPermission('super_admin') && ( + + System Status + + )} + {currentUser.hasPermission('super_admin') && ( + + )} + + {/* eslint-disable-next-line react/jsx-no-target-blank */} + Help + + Auth.logout()}>Log out + + )} + > + + +
+
+ ); +} + +export function AppHeader() { + return ( + + ); +} + +export default function init(ngModule) { + ngModule.component('appHeader', react2angular(AppHeader)); +} + +init.init = true; diff --git a/client/app/components/app-header/AppHeader.less b/client/app/components/app-header/AppHeader.less new file mode 100644 index 0000000000..9a0ec84762 --- /dev/null +++ b/client/app/components/app-header/AppHeader.less @@ -0,0 +1,207 @@ +@mobileBreakpoint: ~"(max-width: 767px)"; + +nav .app-header { + height: 49px; + padding-bottom: 1px; + box-sizing: content-box; + display: flex; + justify-content: space-between; + margin-bottom: 10px; + background: white; + box-shadow: 0 4px 9px -3px rgba(102, 136, 153, .15); + + .darker { + color: #333 !important; + + &:hover { + color: #2196F3 !important; + } + } + + & > * { + display: flex; + align-items: center; + } + + &[data-platform="mobile"] { + display: none; + } + + .menu-item-button { + padding: 0 15px; + font-size: 18px; + .darker(); + } + + .ant-menu-root { + margin: 0 10px; + line-height: 50px; + height: 50px; + border-bottom: 0; + } + + .ant-btn { + font-weight: 500; + + .anticon { + margin-right: 0; + } + } + + &[data-platform="desktop"] .ant-btn:not(.ant-btn-primary) { + border: 0; + box-shadow: none; + height: 40px; + line-height: 40px; + background-color: transparent; //so it doesn't interfere with click animation of adjacent buttons + .darker(); + } + + .ant-menu-item { + padding: 0; + height: 52px; + display: inline-flex; + align-items: center; + + .anticon-down { + font-size: 13px !important; + transform: none; + position: relative; + top: 2px; + + svg { + transition: transform .2s cubic-bezier(.75,0,.25,1); + } + } + + .ant-dropdown-open .anticon-down svg, + .anticon-down.ant-dropdown-open svg { + transform: rotate(180deg); + } + } + + .dropdown-menu-item { + .ant-btn { + padding-right: 5px; + padding-left: 5px; + margin-right: 30px; + margin-left: 10px; + position: relative; + z-index: 1; + } + + // this is a trick to get the dropdown menu to be placed at the bottom left + // of the menu item and not the dropdown trigger + .ant-dropdown-trigger { + position: absolute; + top: 5px; + right: 0; + left: 10px; + bottom: 5px; + text-align: right; + padding-top: 14px; + padding-right: 10px; + margin-right: 0; + user-select: none; // or else double clicking it causes the header logo to get selected + .darker(); + } + } + + .header-logo img { + height: 40px; + width: 40px; + } + + .searchbar { + width: 185px; + } + + .profile-dropdown { + display: flex; + align-items: center; + + span { + max-width: 130px; // arbitrary, prevents layout mess up if username long + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + img { + height: 20px; + width: 20px; + border-radius: 50%; + margin-right: 5px; + } + } + + @media (max-width: 960px) { + .ant-btn, + .menu-item-button { + padding: 0 10px; + } + + .ant-menu-root { + margin: 0 5px; + } + + .profile-dropdown { + span { + display: none; + } + + img { + margin-right: 0; + } + } + } + + @media (max-width: 800px) { + .searchbar { + width: 140px; + } + } + + @media @mobileBreakpoint { + &[data-platform="desktop"] { + display: none; + } + + &[data-platform="mobile"] { + display: flex; + padding: 0 15px; + position: fixed; + top: 0; + left: 0; + width: 100%; + box-sizing: border-box; + z-index: 1000; + } + } +} + +@media @mobileBreakpoint { + .app-header-wrapper { + margin-top: 59px !important; // compensate for app header fixed position + } +} + +.update-available { + display: inline !important; + + .fa { + color: #52c41a; + vertical-align: text-bottom; + font-size: 16px; + } +} + +.ant-dropdown-menu-item .help-trigger { + display: inline; + color: #2196F3; + vertical-align: bottom; +} + +.ant-dropdown-menu.favorites-dropdown { + margin-left: -10px; +} \ No newline at end of file diff --git a/client/app/components/app-header/app-header.css b/client/app/components/app-header/app-header.css deleted file mode 100644 index bf50033bcd..0000000000 --- a/client/app/components/app-header/app-header.css +++ /dev/null @@ -1,13 +0,0 @@ -.menu-search input[type="text"] { - height: 30px; -} - -.dropdown-menu__version { - padding: 5px 10px 8px 17px; -} - -.update-available .fa { - color: #52c41a; - vertical-align: bottom; - font-size: 16px !important; -} \ No newline at end of file diff --git a/client/app/components/app-header/app-header.html b/client/app/components/app-header/app-header.html deleted file mode 100644 index 64d442f369..0000000000 --- a/client/app/components/app-header/app-header.html +++ /dev/null @@ -1,234 +0,0 @@ - diff --git a/client/app/components/app-header/components/FavoritesDropdown.jsx b/client/app/components/app-header/components/FavoritesDropdown.jsx new file mode 100644 index 0000000000..5155decde7 --- /dev/null +++ b/client/app/components/app-header/components/FavoritesDropdown.jsx @@ -0,0 +1,69 @@ +import React, { useState, useMemo, useCallback, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { isEmpty, template } from 'lodash'; + +import Dropdown from 'antd/lib/dropdown'; +import Icon from 'antd/lib/icon'; +import Menu from 'antd/lib/menu'; + +import HelpTrigger from '@/components/HelpTrigger'; + +export default function FavoritesDropdown({ fetch, urlTemplate }) { + const [items, setItems] = useState(); + const [loading, setLoading] = useState(false); + + const noItems = isEmpty(items); + const urlCompiled = useMemo(() => template(urlTemplate), [urlTemplate]); + + const fetchItems = useCallback(() => { + setLoading(true); + fetch().$promise + .then(({ results }) => { + setItems(results); + }) + .finally(() => { + setLoading(false); + }); + }, [fetch]); + + // fetch items on init + useEffect(fetchItems, []); + + // fetch items on click + const onVisibleChange = visible => visible && fetchItems(); + + const menu = ( + + {noItems ? ( + + + + + No favorites selected yet + + ) : ( + items.map(item => ( + + + + + + {item.name} + + + )) + )} + + ); + + return ( + + {loading ? : } + + ); +} + +FavoritesDropdown.propTypes = { + fetch: PropTypes.func.isRequired, + urlTemplate: PropTypes.string.isRequired, +}; diff --git a/client/app/components/app-header/index.js b/client/app/components/app-header/index.js deleted file mode 100644 index d29fdca658..0000000000 --- a/client/app/components/app-header/index.js +++ /dev/null @@ -1,58 +0,0 @@ -import debug from 'debug'; -import CreateDashboardDialog from '@/components/dashboards/CreateDashboardDialog'; - -import logoUrl from '@/assets/images/redash_icon_small.png'; -import frontendVersion from '@/version.json'; -import template from './app-header.html'; -import './app-header.css'; - -const logger = debug('redash:appHeader'); - -function controller($rootScope, $location, $route, $uibModal, Auth, currentUser, clientConfig, Dashboard, Query) { - this.logoUrl = logoUrl; - this.basePath = clientConfig.basePath; - this.currentUser = currentUser; - this.showQueriesMenu = currentUser.hasPermission('view_query'); - this.showAlertsLink = currentUser.hasPermission('list_alerts'); - this.showNewQueryMenu = currentUser.hasPermission('create_query'); - this.showSettingsMenu = currentUser.hasPermission('list_users'); - this.showDashboardsMenu = currentUser.hasPermission('list_dashboards'); - - this.frontendVersion = frontendVersion; - this.backendVersion = clientConfig.version; - this.newVersionAvailable = clientConfig.newVersionAvailable && currentUser.isAdmin; - - this.reload = () => { - logger('Reloading dashboards and queries.'); - Dashboard.favorites().$promise.then((data) => { - this.dashboards = data.results; - }); - Query.favorites().$promise.then((data) => { - this.queries = data.results; - }); - }; - - this.reload(); - - $rootScope.$on('reloadFavorites', this.reload); - - this.newDashboard = () => CreateDashboardDialog.showModal(); - - this.searchQueries = () => { - $location.path('/queries').search({ q: this.searchTerm }); - $route.reload(); - }; - - this.logout = () => { - Auth.logout(); - }; -} - -export default function init(ngModule) { - ngModule.component('appHeader', { - template, - controller, - }); -} - -init.init = true; diff --git a/client/app/components/items-list/components/Sidebar.jsx b/client/app/components/items-list/components/Sidebar.jsx index 89684a2274..c850c28adb 100644 --- a/client/app/components/items-list/components/Sidebar.jsx +++ b/client/app/components/items-list/components/Sidebar.jsx @@ -105,7 +105,7 @@ export function ProfileImage({ user }) { if (!isString(user.profile_image_url) || (user.profile_image_url === '')) { return null; } - return {user.name}; + return {user.name}; } ProfileImage.propTypes = { diff --git a/client/app/pages/alert/AlertEdit.jsx b/client/app/pages/alert/AlertEdit.jsx index 0caa1cc0f9..b3a38c9947 100644 --- a/client/app/pages/alert/AlertEdit.jsx +++ b/client/app/pages/alert/AlertEdit.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { HelpTrigger } from '@/components/HelpTrigger'; +import HelpTrigger from '@/components/HelpTrigger'; import { Alert as AlertType } from '@/components/proptypes'; import Form from 'antd/lib/form'; diff --git a/client/app/pages/alert/AlertNew.jsx b/client/app/pages/alert/AlertNew.jsx index 26b18f9f66..f192e43821 100644 --- a/client/app/pages/alert/AlertNew.jsx +++ b/client/app/pages/alert/AlertNew.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { HelpTrigger } from '@/components/HelpTrigger'; +import HelpTrigger from '@/components/HelpTrigger'; import { Alert as AlertType } from '@/components/proptypes'; import Form from 'antd/lib/form'; diff --git a/client/app/pages/alert/components/NotificationTemplate.jsx b/client/app/pages/alert/components/NotificationTemplate.jsx index 31e10bdc12..4e500c0f4f 100644 --- a/client/app/pages/alert/components/NotificationTemplate.jsx +++ b/client/app/pages/alert/components/NotificationTemplate.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { head, isEmpty, isNull, isUndefined } from 'lodash'; import Mustache from 'mustache'; -import { HelpTrigger } from '@/components/HelpTrigger'; +import HelpTrigger from '@/components/HelpTrigger'; import { Alert as AlertType, Query as QueryType } from '@/components/proptypes'; import Input from 'antd/lib/input'; diff --git a/client/app/pages/dashboards/ShareDashboardDialog.jsx b/client/app/pages/dashboards/ShareDashboardDialog.jsx index be555af857..0b131c2667 100644 --- a/client/app/pages/dashboards/ShareDashboardDialog.jsx +++ b/client/app/pages/dashboards/ShareDashboardDialog.jsx @@ -9,7 +9,7 @@ import { $http } from '@/services/ng'; import notification from '@/services/notification'; import { wrap as wrapDialog, DialogPropType } from '@/components/DialogWrapper'; import InputWithCopy from '@/components/InputWithCopy'; -import { HelpTrigger } from '@/components/HelpTrigger'; +import HelpTrigger from '@/components/HelpTrigger'; const API_SHARE_URL = 'api/dashboards/{id}/share'; diff --git a/client/app/pages/data-sources/EditDataSource.jsx b/client/app/pages/data-sources/EditDataSource.jsx index 4db6045387..32910e110a 100644 --- a/client/app/pages/data-sources/EditDataSource.jsx +++ b/client/app/pages/data-sources/EditDataSource.jsx @@ -11,7 +11,7 @@ import PromiseRejectionError from '@/lib/promise-rejection-error'; import LoadingState from '@/components/items-list/components/LoadingState'; import DynamicForm from '@/components/dynamic-form/DynamicForm'; import helper from '@/components/dynamic-form/dynamicFormHelper'; -import { HelpTrigger, TYPES as HELP_TRIGGER_TYPES } from '@/components/HelpTrigger'; +import HelpTrigger, { TYPES as HELP_TRIGGER_TYPES } from '@/components/HelpTrigger'; class EditDataSource extends React.Component { static propTypes = { diff --git a/client/app/pages/settings/OrganizationSettings.jsx b/client/app/pages/settings/OrganizationSettings.jsx index 32e4d6c83b..cfb9700086 100644 --- a/client/app/pages/settings/OrganizationSettings.jsx +++ b/client/app/pages/settings/OrganizationSettings.jsx @@ -16,7 +16,7 @@ import { clientConfig } from '@/services/auth'; import settingsMenu from '@/services/settingsMenu'; import recordEvent from '@/services/recordEvent'; import OrgSettings from '@/services/organizationSettings'; -import { HelpTrigger } from '@/components/HelpTrigger'; +import HelpTrigger from '@/components/HelpTrigger'; import DynamicComponent from '@/components/DynamicComponent'; const Option = Select.Option; diff --git a/client/cypress/integration/dashboard/dashboard_spec.js b/client/cypress/integration/dashboard/dashboard_spec.js index 42876c1b13..d9f7f8d297 100644 --- a/client/cypress/integration/dashboard/dashboard_spec.js +++ b/client/cypress/integration/dashboard/dashboard_spec.js @@ -12,7 +12,7 @@ describe('Dashboard', () => { cy.visit('/dashboards'); cy.getByTestId('CreateButton').click(); cy.get('li[role="menuitem"]') - .contains('Dashboard') + .contains('New Dashboard') .click(); cy.server();