Переглянути джерело

Merge remote-tracking branch 'grafana/master' into all-data-as-table

* grafana/master:
  fix(explore/logs) not collapsing whitespace (#15737)
  Refactoring / fixing password hint PR #15868
  chore: Move sidemenu out of context service and use the logic we have in the router already for hiding the sidemenu
  Fix deduplication results displaying wrong data (#15755)
  Revert "Fix Datasource Update to no User/Password"
  fix imageurl in notification test
  changed all rems to pixels in defaults and template, changed back root font size
  Make password hint configurable from settings/defaults.ini
  changed root font to 100%(default 16px), changed font-size from px to rem, updated rem sizes in template and default.ts files, removed display classes and variables since not used, removed lead class and variables since not usedremoved serif font since not used and probably never should be used
  fix: Update test snapshot
  fix: Logo goes Home instead of toggling side menu #15482
  Fix: #14706 Incorrect index pattern padding in alerting queries
  Removed commented code
  Fixed alias in Cloudwatch Expressions
ryan 6 роки тому
батько
коміт
f4ae0cfaa8
35 змінених файлів з 332 додано та 265 видалено
  1. 1 0
      conf/defaults.ini
  2. 1 0
      conf/sample.ini
  3. 11 3
      docs/sources/installation/configuration.md
  4. 1 1
      packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss
  5. 33 47
      packages/grafana-ui/src/themes/_variables.scss.tmpl.ts
  6. 14 11
      packages/grafana-ui/src/themes/default.ts
  7. 5 2
      packages/grafana-ui/src/types/theme.ts
  8. 1 0
      pkg/api/login.go
  9. 1 1
      pkg/services/alerting/test_notification.go
  10. 6 1
      pkg/services/sqlstore/datasource.go
  11. 2 0
      pkg/setting/setting.go
  12. 5 4
      pkg/tsdb/cloudwatch/cloudwatch.go
  13. 1 1
      pkg/tsdb/elasticsearch/client/index_pattern.go
  14. 9 0
      pkg/tsdb/elasticsearch/client/index_pattern_test.go
  15. 0 16
      public/app/core/components/sidemenu/SideMenu.test.tsx
  16. 5 15
      public/app/core/components/sidemenu/SideMenu.tsx
  17. 3 3
      public/app/core/components/sidemenu/__snapshots__/SideMenu.test.tsx.snap
  18. 1 0
      public/app/core/config.ts
  19. 1 0
      public/app/core/controllers/login_ctrl.ts
  20. 3 2
      public/app/core/logs_model.ts
  21. 0 9
      public/app/core/services/context_srv.ts
  22. 28 0
      public/app/core/specs/logs_model.test.ts
  23. 14 3
      public/app/features/explore/LogLabels.tsx
  24. 1 1
      public/app/features/explore/LogRow.tsx
  25. 108 0
      public/app/features/explore/state/selectors.test.ts
  26. 1 1
      public/app/partials/login.html
  27. 3 19
      public/app/routes/GrafanaCtrl.ts
  28. 14 28
      public/sass/_variables.generated.scss
  29. 0 23
      public/sass/base/_type.scss
  30. 2 8
      public/sass/components/_navbar.scss
  31. 25 4
      public/sass/components/_panel_logs.scss
  32. 16 18
      public/sass/components/_sidemenu.scss
  33. 15 0
      public/sass/components/_view_states.scss
  34. 0 9
      public/sass/mixins/_mixins.scss
  35. 1 35
      public/sass/pages/_explore.scss

+ 1 - 0
conf/defaults.ini

@@ -231,6 +231,7 @@ verify_email_enabled = false
 
 # Background text for the user field on the login page
 login_hint = email or username
+password_hint = password
 
 # Default UI theme ("dark" or "light")
 default_theme = dark

+ 1 - 0
conf/sample.ini

@@ -211,6 +211,7 @@ log_queries =
 
 # Background text for the user field on the login page
 ;login_hint = email or username
+;password_hint = password
 
 # Default UI theme ("dark" or "light")
 ;default_theme = dark

+ 11 - 3
docs/sources/installation/configuration.md

@@ -162,9 +162,9 @@ executed with working directory set to the installation path.
 
 ### enable_gzip
 
-Set this option to `true` to enable HTTP compression, this can improve 
-transfer speed and bandwidth utilization. It is recommended that most 
-users set it to `true`. By default it is set to `false` for compatibility 
+Set this option to `true` to enable HTTP compression, this can improve
+transfer speed and bandwidth utilization. It is recommended that most
+users set it to `true`. By default it is set to `false` for compatibility
 reasons.
 
 ### cert_file
@@ -342,6 +342,14 @@ options are `Admin` and `Editor`. e.g. :
 Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard.
 Defaults to `false`.
 
+### login_hint
+
+Text used as placeholder text on login page for login/username input.
+
+### password_hint
+
+Text used as placeholder text on login page for password input.
+
 <hr>
 
 ## [auth]

+ 1 - 1
packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss

@@ -53,7 +53,7 @@
 }
 
 .panel-options-group__title {
-  font-size: 1.1rem;
+  font-size: 16px;
   position: relative;
   top: 1px;
 }

+ 33 - 47
packages/grafana-ui/src/themes/_variables.scss.tmpl.ts

@@ -17,7 +17,7 @@ $enable-hover-media-query: false !default;
 // Control the default styling of most Bootstrap elements by modifying these
 // variables. Mostly focused on spacing.
 
-$spacer: 1rem !default;
+$spacer: ${theme.spacing.m} !default;
 $spacer-x: $spacer !default;
 $spacer-y: $spacer !default;
 $spacers: (
@@ -46,7 +46,7 @@ $spacers: (
     ),
   ),
 ) !default;
-$border-width: 1px !default;
+$border-width: ${theme.border.width.s} !default;
 
 // Grid breakpoints
 //
@@ -54,11 +54,11 @@ $border-width: 1px !default;
 // adapting to different screen sizes, for use in media queries.
 
 $grid-breakpoints: (
-  xs: 0,
-  sm: 544px,
-  md: 768px,
-  lg: 992px,
-  xl: 1200px,
+  xs: ${theme.breakpoints.xs},
+  sm: ${theme.breakpoints.s},
+  md: ${theme.breakpoints.m},
+  lg: ${theme.breakpoints.l},
+  xl: ${theme.breakpoints.xl},
 ) !default;
 
 // Grid containers
@@ -84,46 +84,32 @@ $enable-flex: true;
 // Typography
 // -------------------------
 
-$font-family-sans-serif: 'Roboto', Helvetica, Arial, sans-serif;
-$font-family-serif: Georgia, 'Times New Roman', Times, serif;
-$font-family-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace;
+$font-family-sans-serif: ${theme.typography.fontFamily.sansSerif};
+$font-family-monospace: ${theme.typography.fontFamily.monospace};
 $font-family-base: $font-family-sans-serif !default;
 
-$font-size-root: 14px !default;
-$font-size-base: 13px !default;
+$font-size-root: ${theme.typography.size.root} !default;
+$font-size-base: ${theme.typography.size.base} !default;
 
-$font-size-lg: 18px !default;
-$font-size-md: 14px !default;
-$font-size-sm: 12px !default;
-$font-size-xs: 10px !default;
+$font-size-lg: ${theme.typography.size.l} !default;
+$font-size-md: ${theme.typography.size.m} !default;
+$font-size-sm: ${theme.typography.size.s} !default;
+$font-size-xs: ${theme.typography.size.xs} !default;
 
-$line-height-base: 1.5 !default;
-$font-weight-semi-bold: 500;
+$line-height-base: ${theme.typography.lineHeight.l} !default;
+$font-weight-semi-bold: ${theme.typography.weight.semibold};
 
-$font-size-h1: 2rem !default;
-$font-size-h2: 1.75rem !default;
-$font-size-h3: 1.5rem !default;
-$font-size-h4: 1.3rem !default;
-$font-size-h5: 1.2rem !default;
-$font-size-h6: 1rem !default;
-
-$display1-size: 6rem !default;
-$display2-size: 5.5rem !default;
-$display3-size: 4.5rem !default;
-$display4-size: 3.5rem !default;
-
-$display1-weight: 400 !default;
-$display2-weight: 400 !default;
-$display3-weight: 400 !default;
-$display4-weight: 400 !default;
-
-$lead-font-size: 1.25rem !default;
-$lead-font-weight: 300 !default;
+$font-size-h1: ${theme.typography.heading.h1} !default;
+$font-size-h2: ${theme.typography.heading.h2} !default;
+$font-size-h3: ${theme.typography.heading.h3} !default;
+$font-size-h4: ${theme.typography.heading.h4} !default;
+$font-size-h5: ${theme.typography.heading.h5} !default;
+$font-size-h6: ${theme.typography.heading.h6} !default;
 
 $headings-margin-bottom: ($spacer / 2) !default;
 $headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
-$headings-font-weight: 400 !default;
-$headings-line-height: 1.1 !default;
+$headings-font-weight: ${theme.typography.weight.normal} !default;
+$headings-line-height: ${theme.typography.lineHeight.s} !default;
 
 $hr-border-width: $border-width !default;
 $dt-font-weight: bold !default;
@@ -141,8 +127,8 @@ $border-radius-sm: 2px !default;
 
 // Page
 
-$page-sidebar-width: 11rem;
-$page-sidebar-margin: 4rem;
+$page-sidebar-width: 154px;
+$page-sidebar-margin: 56px;
 
 // Links
 // -------------------------
@@ -174,7 +160,7 @@ $input-padding-y-lg: 10px !default;
 
 $input-height: 35px !default;
 
-$gf-form-margin: 0.2rem;
+$gf-form-margin: 3px;
 $gf-form-input-height: 35px;
 
 $cursor-disabled: not-allowed !default;
@@ -199,13 +185,13 @@ $zindex-typeahead: 1060;
 // Buttons
 //
 
-$btn-padding-x: 1rem !default;
-$btn-padding-y: 0.7rem !default;
+$btn-padding-x: 14px !default;
+$btn-padding-y: 10px !default;
 $btn-line-height: 1 !default;
-$btn-font-weight: 500 !default;
+$btn-font-weight: ${theme.typography.weight.semibold} !default;
 
-$btn-padding-x-sm: 0.5rem !default;
-$btn-padding-y-sm: 0.25rem !default;
+$btn-padding-x-sm: 7px !default;
+$btn-padding-y-sm: 4px !default;
 
 $btn-padding-x-lg: 21px !default;
 $btn-padding-y-lg: 11px !default;

+ 14 - 11
packages/grafana-ui/src/themes/default.ts

@@ -5,10 +5,10 @@ const theme: GrafanaThemeCommons = {
   typography: {
     fontFamily: {
       sansSerif: "'Roboto', Helvetica, Arial, sans-serif",
-      serif: "Georgia, 'Times New Roman', Times, serif",
       monospace: "Menlo, Monaco, Consolas, 'Courier New', monospace",
     },
     size: {
+      root: '14px',
       base: '13px',
       xs: '10px',
       s: '12px',
@@ -16,12 +16,12 @@ const theme: GrafanaThemeCommons = {
       l: '18px',
     },
     heading: {
-      h1: '2rem',
-      h2: '1.75rem',
-      h3: '1.5rem',
-      h4: '1.3rem',
-      h5: '1.2rem',
-      h6: '1rem',
+      h1: '28px',
+      h2: '24px',
+      h3: '21px',
+      h4: '18px',
+      h5: '16px',
+      h6: '14px',
     },
     weight: {
       light: 300,
@@ -35,7 +35,7 @@ const theme: GrafanaThemeCommons = {
       l: 1.5,
     },
   },
-  brakpoints: {
+  breakpoints: {
     xs: '0',
     s: '544px',
     m: '768px',
@@ -44,9 +44,9 @@ const theme: GrafanaThemeCommons = {
   },
   spacing: {
     xs: '0',
-    s: '0.2rem',
-    m: '1rem',
-    l: '1.5rem',
+    s: '3px',
+    m: '14px',
+    l: '21px',
     gutter: '30px',
   },
   border: {
@@ -55,6 +55,9 @@ const theme: GrafanaThemeCommons = {
       s: '3px',
       m: '5px',
     },
+    width: {
+      s: '1px',
+    },
   },
 };
 

+ 5 - 2
packages/grafana-ui/src/types/theme.ts

@@ -6,7 +6,7 @@ export enum GrafanaThemeType {
 export interface GrafanaThemeCommons {
   name: string;
   // TODO: not sure if should be a part of theme
-  brakpoints: {
+  breakpoints: {
     xs: string;
     s: string;
     m: string;
@@ -16,10 +16,10 @@ export interface GrafanaThemeCommons {
   typography: {
     fontFamily: {
       sansSerif: string;
-      serif: string;
       monospace: string;
     };
     size: {
+      root: string;
       base: string;
       xs: string;
       s: string;
@@ -60,6 +60,9 @@ export interface GrafanaThemeCommons {
       s: string;
       m: string;
     };
+    width: {
+      s: string;
+    };
   };
 }
 

+ 1 - 0
pkg/api/login.go

@@ -36,6 +36,7 @@ func (hs *HTTPServer) LoginView(c *m.ReqContext) {
 	viewData.Settings["oauth"] = enabledOAuths
 	viewData.Settings["disableUserSignUp"] = !setting.AllowUserSignUp
 	viewData.Settings["loginHint"] = setting.LoginHint
+	viewData.Settings["passwordHint"] = setting.PasswordHint
 	viewData.Settings["disableLoginForm"] = setting.DisableLoginForm
 
 	if loginError, ok := tryGetEncryptedCookie(c, LoginErrorCookieName); ok {

+ 1 - 1
pkg/services/alerting/test_notification.go

@@ -56,7 +56,7 @@ func createTestEvalContext(cmd *NotificationTestCommand) *EvalContext {
 
 	ctx := NewEvalContext(context.Background(), testRule)
 	if cmd.Settings.Get("uploadImage").MustBool(true) {
-		ctx.ImagePublicUrl = "http://grafana.org/assets/img/blog/mixed_styles.png"
+		ctx.ImagePublicUrl = "https://grafana.com/assets/img/blog/mixed_styles.png"
 	}
 	ctx.IsTestRun = true
 	ctx.Firing = true

+ 6 - 1
pkg/services/sqlstore/datasource.go

@@ -174,6 +174,11 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
 			Version:           cmd.Version + 1,
 		}
 
+		sess.UseBool("is_default")
+		sess.UseBool("basic_auth")
+		sess.UseBool("with_credentials")
+		sess.UseBool("read_only")
+
 		var updateSession *xorm.Session
 		if cmd.Version != 0 {
 			// the reason we allow cmd.version > db.version is make it possible for people to force
@@ -185,7 +190,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
 			updateSession = sess.Where("id=? and org_id=?", ds.Id, ds.OrgId)
 		}
 
-		affected, err := updateSession.AllCols().Omit("created").Update(ds)
+		affected, err := updateSession.Update(ds)
 		if err != nil {
 			return err
 		}

+ 2 - 0
pkg/setting/setting.go

@@ -109,6 +109,7 @@ var (
 	AutoAssignOrgRole       string
 	VerifyEmailEnabled      bool
 	LoginHint               string
+	PasswordHint            string
 	DefaultTheme            string
 	DisableLoginForm        bool
 	DisableSignoutMenu      bool
@@ -656,6 +657,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
 	AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
 	VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
 	LoginHint = users.Key("login_hint").String()
+	PasswordHint = users.Key("password_hint").String()
 	DefaultTheme = users.Key("default_theme").String()
 	ExternalUserMngLinkUrl = users.Key("external_manage_link_url").String()
 	ExternalUserMngLinkName = users.Key("external_manage_link_name").String()

+ 5 - 4
pkg/tsdb/cloudwatch/cloudwatch.go

@@ -496,9 +496,6 @@ func parseQuery(model *simplejson.Json) (*CloudWatchQuery, error) {
 	}
 
 	alias := model.Get("alias").MustString()
-	if alias == "" {
-		alias = "{{metric}}_{{stat}}"
-	}
 
 	returnData := model.Get("returnData").MustBool(false)
 	highResolution := model.Get("highResolution").MustBool(false)
@@ -521,7 +518,11 @@ func parseQuery(model *simplejson.Json) (*CloudWatchQuery, error) {
 
 func formatAlias(query *CloudWatchQuery, stat string, dimensions map[string]string) string {
 	if len(query.Id) > 0 && len(query.Expression) > 0 {
-		return query.Id
+		if len(query.Alias) > 0 {
+			return query.Alias
+		} else {
+			return query.Id
+		}
 	}
 
 	data := map[string]string{}

+ 1 - 1
pkg/tsdb/elasticsearch/client/index_pattern.go

@@ -279,7 +279,7 @@ func formatDate(t time.Time, pattern string) string {
 		isoYearShort := fmt.Sprintf("%d", isoYear)[2:4]
 		formatted = strings.Replace(formatted, "<stdIsoYear>", fmt.Sprintf("%d", isoYear), -1)
 		formatted = strings.Replace(formatted, "<stdIsoYearShort>", isoYearShort, -1)
-		formatted = strings.Replace(formatted, "<stdWeekOfYear>", fmt.Sprintf("%d", isoWeek), -1)
+		formatted = strings.Replace(formatted, "<stdWeekOfYear>", fmt.Sprintf("%02d", isoWeek), -1)
 
 		formatted = strings.Replace(formatted, "<stdUnix>", fmt.Sprintf("%d", t.Unix()), -1)
 

+ 9 - 0
pkg/tsdb/elasticsearch/client/index_pattern_test.go

@@ -76,6 +76,15 @@ func TestIndexPattern(t *testing.T) {
 			So(indices, ShouldHaveLength, 1)
 			So(indices[0], ShouldEqual, "2018-data")
 		})
+
+		Convey("Should return 01 week", func() {
+			from = fmt.Sprintf("%d", time.Date(2018, 1, 15, 17, 50, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond))
+			to = fmt.Sprintf("%d", time.Date(2018, 1, 15, 17, 55, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond))
+			indexPatternScenario(intervalWeekly, "[data-]GGGG.WW", tsdb.NewTimeRange(from, to), func(indices []string) {
+				So(indices, ShouldHaveLength, 1)
+				So(indices[0], ShouldEqual, "data-2018.03")
+			})
+		})
 	})
 
 	Convey("Hourly interval", t, func() {

+ 0 - 16
public/app/core/components/sidemenu/SideMenu.test.tsx

@@ -2,7 +2,6 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import { SideMenu } from './SideMenu';
 import appEvents from '../../app_events';
-import { contextSrv } from 'app/core/services/context_srv';
 
 jest.mock('../../app_events', () => ({
   emit: jest.fn(),
@@ -26,7 +25,6 @@ jest.mock('app/core/services/context_srv', () => ({
     isGrafanaAdmin: false,
     isEditor: false,
     hasEditPermissionFolders: false,
-    toggleSideMenu: jest.fn(),
   },
 }));
 
@@ -54,20 +52,6 @@ describe('Render', () => {
 });
 
 describe('Functions', () => {
-  describe('toggle side menu', () => {
-    const wrapper = setup();
-    const instance = wrapper.instance() as SideMenu;
-    instance.toggleSideMenu();
-
-    it('should call contextSrv.toggleSideMenu', () => {
-      expect(contextSrv.toggleSideMenu).toHaveBeenCalled();
-    });
-
-    it('should emit toggle sidemenu event', () => {
-      expect(appEvents.emit).toHaveBeenCalledWith('toggle-sidemenu');
-    });
-  });
-
   describe('toggle side menu on mobile', () => {
     const wrapper = setup();
     const instance = wrapper.instance() as SideMenu;

+ 5 - 15
public/app/core/components/sidemenu/SideMenu.tsx

@@ -1,31 +1,21 @@
 import React, { PureComponent } from 'react';
 import appEvents from '../../app_events';
-import { contextSrv } from 'app/core/services/context_srv';
 import TopSection from './TopSection';
 import BottomSection from './BottomSection';
-import { store } from 'app/store/store';
+import config from 'app/core/config';
 
-export class SideMenu extends PureComponent {
-  toggleSideMenu = () => {
-    // ignore if we just made a location change, stops hiding sidemenu on double clicks of back button
-    const timeSinceLocationChanged = new Date().getTime() - store.getState().location.lastUpdated;
-    if (timeSinceLocationChanged < 1000) {
-      return;
-    }
-
-    contextSrv.toggleSideMenu();
-    appEvents.emit('toggle-sidemenu');
-  };
+const homeUrl = config.appSubUrl || '/';
 
+export class SideMenu extends PureComponent {
   toggleSideMenuSmallBreakpoint = () => {
     appEvents.emit('toggle-sidemenu-mobile');
   };
 
   render() {
     return [
-      <div className="sidemenu__logo" onClick={this.toggleSideMenu} key="logo">
+      <a href={homeUrl} className="sidemenu__logo" key="logo">
         <img src="public/img/grafana_icon.svg" alt="Grafana" />
-      </div>,
+      </a>,
       <div className="sidemenu__logo_small_breakpoint" onClick={this.toggleSideMenuSmallBreakpoint} key="hamburger">
         <i className="fa fa-bars" />
         <span className="sidemenu__close">

+ 3 - 3
public/app/core/components/sidemenu/__snapshots__/SideMenu.test.tsx.snap

@@ -2,16 +2,16 @@
 
 exports[`Render should render component 1`] = `
 Array [
-  <div
+  <a
     className="sidemenu__logo"
+    href="/"
     key="logo"
-    onClick={[Function]}
   >
     <img
       alt="Grafana"
       src="public/img/grafana_icon.svg"
     />
-  </div>,
+  </a>,
   <div
     className="sidemenu__logo_small_breakpoint"
     key="hamburger"

+ 1 - 0
public/app/core/config.ts

@@ -34,6 +34,7 @@ export class Settings {
   oauth: any;
   disableUserSignUp: boolean;
   loginHint: any;
+  passwordHint: any;
   loginError: any;
   viewersCanEdit: boolean;
   editorsCanOwn: boolean;

+ 1 - 0
public/app/core/controllers/login_ctrl.ts

@@ -25,6 +25,7 @@ export class LoginCtrl {
     $scope.disableLoginForm = config.disableLoginForm;
     $scope.disableUserSignUp = config.disableUserSignUp;
     $scope.loginHint = config.loginHint;
+    $scope.passwordHint = config.passwordHint;
 
     $scope.loginMode = true;
     $scope.submitBtnText = 'Log in';

+ 3 - 2
public/app/core/logs_model.ts

@@ -245,12 +245,13 @@ export function dedupLogRows(logs: LogsModel, strategy: LogsDedupStrategy): Logs
   }
 
   const dedupedRows = logs.rows.reduce((result: LogRowModel[], row: LogRowModel, index, list) => {
+    const rowCopy = { ...row };
     const previous = result[result.length - 1];
     if (index > 0 && isDuplicateRow(row, previous, strategy)) {
       previous.duplicates++;
     } else {
-      row.duplicates = 0;
-      result.push(row);
+      rowCopy.duplicates = 0;
+      result.push(rowCopy);
     }
     return result;
   }, []);

+ 0 - 9
public/app/core/services/context_srv.ts

@@ -1,7 +1,6 @@
 import config from 'app/core/config';
 import _ from 'lodash';
 import coreModule from 'app/core/core_module';
-import store from 'app/core/store';
 
 export class User {
   isGrafanaAdmin: any;
@@ -29,13 +28,10 @@ export class ContextSrv {
   isSignedIn: any;
   isGrafanaAdmin: any;
   isEditor: any;
-  sidemenu: any;
   sidemenuSmallBreakpoint = false;
   hasEditPermissionInFolders: boolean;
 
   constructor() {
-    this.sidemenu = store.getBool('grafana.sidemenu', true);
-
     if (!config.bootData) {
       config.bootData = { user: {}, settings: {} };
     }
@@ -55,11 +51,6 @@ export class ContextSrv {
     return !!(document.visibilityState === undefined || document.visibilityState === 'visible');
   }
 
-  toggleSideMenu() {
-    this.sidemenu = !this.sidemenu;
-    store.set('grafana.sidemenu', this.sidemenu);
-  }
-
   hasAccessToExplore() {
     return (this.isEditor || config.viewersCanEdit) && config.exploreEnabled;
   }

+ 28 - 0
public/app/core/specs/logs_model.test.ts

@@ -113,6 +113,34 @@ describe('dedupLogRows()', () => {
       },
     ]);
   });
+
+  test('should return to non-deduped state on same log result', () => {
+    const logs = {
+      rows: [
+        {
+          entry: 'INFO 123',
+        },
+        {
+          entry: 'WARN 123',
+        },
+        {
+          entry: 'WARN 123',
+        },
+      ],
+    };
+    expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.exact).rows).toEqual([
+      {
+        duplicates: 0,
+        entry: 'INFO 123',
+      },
+      {
+        duplicates: 1,
+        entry: 'WARN 123',
+      },
+    ]);
+
+    expect(dedupLogRows(logs as LogsModel, LogsDedupStrategy.none).rows).toEqual(logs.rows);
+  });
 });
 
 describe('calculateFieldStats()', () => {

+ 14 - 3
public/app/features/explore/LogLabels.tsx

@@ -13,8 +13,19 @@ interface Props {
 export class LogLabels extends PureComponent<Props> {
   render() {
     const { getRows, labels, onClickLabel, plain } = this.props;
-    return Object.keys(labels).map(key => (
-      <LogLabel key={key} getRows={getRows} label={key} value={labels[key]} plain={plain} onClickLabel={onClickLabel} />
-    ));
+    return (
+      <span className="logs-labels">
+        {Object.keys(labels).map(key => (
+          <LogLabel
+            key={key}
+            getRows={getRows}
+            label={key}
+            value={labels[key]}
+            plain={plain}
+            onClickLabel={onClickLabel}
+          />
+        ))}
+      </span>
+    );
   }
 }

+ 1 - 1
public/app/features/explore/LogRow.tsx

@@ -150,7 +150,7 @@ export class LogRow extends PureComponent<Props, State> {
           </div>
         )}
         {showLocalTime && (
-          <div className="logs-row__time" title={`${row.timestamp} (${row.timeFromNow})`}>
+          <div className="logs-row__localtime" title={`${row.timestamp} (${row.timeFromNow})`}>
             {row.timeLocal}
           </div>
         )}

+ 108 - 0
public/app/features/explore/state/selectors.test.ts

@@ -0,0 +1,108 @@
+import { deduplicatedLogsSelector } from './selectors';
+import { LogsDedupStrategy } from 'app/core/logs_model';
+import { ExploreItemState } from 'app/types';
+
+const state = {
+  logsResult: {
+    rows: [
+      {
+        entry: '2019-03-05T11:00:56Z sntpc sntpc[1]: offset=-0.033938, delay=0.000649',
+      },
+      {
+        entry: '2019-03-05T11:00:26Z sntpc sntpc[1]: offset=-0.033730, delay=0.000581',
+      },
+      {
+        entry: '2019-03-05T10:59:56Z sntpc sntpc[1]: offset=-0.034184, delay=0.001089',
+      },
+      {
+        entry: '2019-03-05T10:59:26Z sntpc sntpc[1]: offset=-0.033972, delay=0.000582',
+      },
+      {
+        entry: '2019-03-05T10:58:56Z sntpc sntpc[1]: offset=-0.033955, delay=0.000606',
+      },
+      {
+        entry: '2019-03-05T10:58:26Z sntpc sntpc[1]: offset=-0.034067, delay=0.000616',
+      },
+      {
+        entry: '2019-03-05T10:57:56Z sntpc sntpc[1]: offset=-0.034155, delay=0.001021',
+      },
+      {
+        entry: '2019-03-05T10:57:26Z sntpc sntpc[1]: offset=-0.035797, delay=0.000883',
+      },
+      {
+        entry: '2019-03-05T10:56:56Z sntpc sntpc[1]: offset=-0.046818, delay=0.000605',
+      },
+      {
+        entry: '2019-03-05T10:56:26Z sntpc sntpc[1]: offset=-0.049200, delay=0.000584',
+      },
+    ],
+  },
+  hiddenLogLevels: undefined,
+  dedupStrategy: LogsDedupStrategy.none,
+};
+
+describe('Deduplication selector', () => {
+  it('should correctly deduplicate log rows when changing strategy multiple times', () => {
+    // Simulating sequence of UI actions that was causing a problem with deduplication counter being visible when unnecessary.
+    // The sequence was changing dedup strategy: (none -> exact -> numbers -> signature -> none) *2 -> exact. After that the first
+    // row contained information that was deduped, while it shouldn't be.
+    // Problem was caused by mutating the log results entries in redux state. The memoisation hash for deduplicatedLogsSelector
+    // was changing depending on duplicates information from log row state, while should be dependand on log row only.
+
+    let dedups = deduplicatedLogsSelector(state as ExploreItemState);
+    expect(dedups.rows.length).toBe(10);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.none,
+    } as ExploreItemState);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.exact,
+    } as ExploreItemState);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.numbers,
+    } as ExploreItemState);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.signature,
+    } as ExploreItemState);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.none,
+    } as ExploreItemState);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.exact,
+    } as ExploreItemState);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.numbers,
+    } as ExploreItemState);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.signature,
+    } as ExploreItemState);
+
+    deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.none,
+    } as ExploreItemState);
+
+    dedups = deduplicatedLogsSelector({
+      ...state,
+      dedupStrategy: LogsDedupStrategy.exact,
+    } as ExploreItemState);
+
+    // Expecting that no row has duplicates now
+    expect(dedups.rows.reduce((acc, row) => acc + row.duplicates, 0)).toBe(0);
+  });
+});

+ 1 - 1
public/app/partials/login.html

@@ -13,7 +13,7 @@
           </div>
           <div class="login-form">
             <input type="password" name="password" class="gf-form-input login-form-input" required ng-model="formModel.password" id="inputPassword"
-              placeholder="password">
+              placeholder="{{passwordHint}}">
           </div>
           <div class="login-button-group">
             <button type="submit" class="btn btn-large p-x-2" ng-if="!loggingIn" ng-click="submit();" ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">

+ 3 - 19
public/app/routes/GrafanaCtrl.ts

@@ -75,27 +75,22 @@ export class GrafanaCtrl {
   }
 }
 
-function setViewModeBodyClass(body: JQuery, mode: KioskUrlValue, sidemenuOpen: boolean) {
+function setViewModeBodyClass(body: JQuery, mode: KioskUrlValue) {
   body.removeClass('view-mode--tv');
   body.removeClass('view-mode--kiosk');
   body.removeClass('view-mode--inactive');
 
   switch (mode) {
     case 'tv': {
-      body.removeClass('sidemenu-open');
       body.addClass('view-mode--tv');
       break;
     }
     // 1 & true for legacy states
     case '1':
     case true: {
-      body.removeClass('sidemenu-open');
       body.addClass('view-mode--kiosk');
       break;
     }
-    default: {
-      body.toggleClass('sidemenu-open', sidemenuOpen);
-    }
   }
 }
 
@@ -105,7 +100,6 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
     restrict: 'E',
     controller: GrafanaCtrl,
     link: (scope, elem) => {
-      let sidemenuOpen;
       const body = $('body');
 
       // see https://github.com/zenorocha/clipboard.js/issues/155
@@ -113,14 +107,6 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
 
       $('.preloader').remove();
 
-      sidemenuOpen = scope.contextSrv.sidemenu;
-      body.toggleClass('sidemenu-open', sidemenuOpen);
-
-      appEvents.on('toggle-sidemenu', () => {
-        sidemenuOpen = scope.contextSrv.sidemenu;
-        body.toggleClass('sidemenu-open');
-      });
-
       appEvents.on('toggle-sidemenu-mobile', () => {
         body.toggleClass('sidemenu-open--xs');
       });
@@ -163,7 +149,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
         $('#tooltip, .tooltip').remove();
 
         // check for kiosk url param
-        setViewModeBodyClass(body, data.params.kiosk, sidemenuOpen);
+        setViewModeBodyClass(body, data.params.kiosk);
 
         // close all drops
         for (const drop of Drop.drops) {
@@ -198,7 +184,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
         }
 
         $timeout(() => $location.search(search));
-        setViewModeBodyClass(body, search.kiosk, sidemenuOpen);
+        setViewModeBodyClass(body, search.kiosk);
       });
 
       // handle in active view state class
@@ -218,7 +204,6 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
         if (new Date().getTime() - lastActivity > inActiveTimeLimit) {
           activeUser = false;
           body.addClass('view-mode--inactive');
-          body.removeClass('sidemenu-open');
         }
       }
 
@@ -227,7 +212,6 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
         if (!activeUser) {
           activeUser = true;
           body.removeClass('view-mode--inactive');
-          body.toggleClass('sidemenu-open', sidemenuOpen);
         }
       }
 

+ 14 - 28
public/sass/_variables.generated.scss

@@ -20,7 +20,7 @@ $enable-hover-media-query: false !default;
 // Control the default styling of most Bootstrap elements by modifying these
 // variables. Mostly focused on spacing.
 
-$spacer: 1rem !default;
+$spacer: 14px !default;
 $spacer-x: $spacer !default;
 $spacer-y: $spacer !default;
 $spacers: (
@@ -88,7 +88,6 @@ $enable-flex: true;
 // -------------------------
 
 $font-family-sans-serif: 'Roboto', Helvetica, Arial, sans-serif;
-$font-family-serif: Georgia, 'Times New Roman', Times, serif;
 $font-family-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace;
 $font-family-base: $font-family-sans-serif !default;
 
@@ -103,25 +102,12 @@ $font-size-xs: 10px !default;
 $line-height-base: 1.5 !default;
 $font-weight-semi-bold: 500;
 
-$font-size-h1: 2rem !default;
-$font-size-h2: 1.75rem !default;
-$font-size-h3: 1.5rem !default;
-$font-size-h4: 1.3rem !default;
-$font-size-h5: 1.2rem !default;
-$font-size-h6: 1rem !default;
-
-$display1-size: 6rem !default;
-$display2-size: 5.5rem !default;
-$display3-size: 4.5rem !default;
-$display4-size: 3.5rem !default;
-
-$display1-weight: 400 !default;
-$display2-weight: 400 !default;
-$display3-weight: 400 !default;
-$display4-weight: 400 !default;
-
-$lead-font-size: 1.25rem !default;
-$lead-font-weight: 300 !default;
+$font-size-h1: 28px !default;
+$font-size-h2: 24px !default;
+$font-size-h3: 21px !default;
+$font-size-h4: 18px !default;
+$font-size-h5: 16px !default;
+$font-size-h6: 14px !default;
 
 $headings-margin-bottom: ($spacer / 2) !default;
 $headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
@@ -144,8 +130,8 @@ $border-radius-sm: 2px !default;
 
 // Page
 
-$page-sidebar-width: 11rem;
-$page-sidebar-margin: 4rem;
+$page-sidebar-width: 154px;
+$page-sidebar-margin: 56px;
 
 // Links
 // -------------------------
@@ -177,7 +163,7 @@ $input-padding-y-lg: 10px !default;
 
 $input-height: 35px !default;
 
-$gf-form-margin: 0.2rem;
+$gf-form-margin: 3px;
 $gf-form-input-height: 35px;
 
 $cursor-disabled: not-allowed !default;
@@ -202,13 +188,13 @@ $zindex-typeahead: 1060;
 // Buttons
 //
 
-$btn-padding-x: 1rem !default;
-$btn-padding-y: 0.7rem !default;
+$btn-padding-x: 14px !default;
+$btn-padding-y: 10px !default;
 $btn-line-height: 1 !default;
 $btn-font-weight: 500 !default;
 
-$btn-padding-x-sm: 0.5rem !default;
-$btn-padding-y-sm: 0.25rem !default;
+$btn-padding-x-sm: 7px !default;
+$btn-padding-y-sm: 4px !default;
 
 $btn-padding-x-lg: 21px !default;
 $btn-padding-y-lg: 11px !default;

+ 0 - 23
public/sass/base/_type.scss

@@ -141,29 +141,6 @@ h6,
   font-size: $font-size-h6;
 }
 
-.lead {
-  font-size: $lead-font-size;
-  font-weight: $lead-font-weight;
-}
-
-// Type display classes
-.display-1 {
-  font-size: $display1-size;
-  font-weight: $display1-weight;
-}
-.display-2 {
-  font-size: $display2-size;
-  font-weight: $display2-weight;
-}
-.display-3 {
-  font-size: $display3-size;
-  font-weight: $display3-weight;
-}
-.display-4 {
-  font-size: $display4-size;
-  font-weight: $display4-weight;
-}
-
 //
 // Horizontal rules
 //

+ 2 - 8
public/sass/components/_navbar.scss

@@ -157,14 +157,8 @@
 
 @include media-breakpoint-up(sm) {
   .navbar {
-    padding-left: 60px;
-  }
-
-  .sidemenu-open {
-    .navbar {
-      padding-left: 25px;
-      margin-left: 0;
-    }
+    padding-left: 20px;
+    margin-left: 0;
   }
 
   .navbar-page-btn {

+ 25 - 4
public/sass/components/_panel_logs.scss

@@ -63,6 +63,7 @@ $column-horizontal-spacing: 10px;
   font-size: $font-size-sm;
   display: table;
   table-layout: fixed;
+  width: 100%;
 }
 
 .logs-row {
@@ -83,16 +84,22 @@ $column-horizontal-spacing: 10px;
 
 .logs-row__time {
   white-space: nowrap;
+  width: 19em;
+}
+
+.logs-row__localtime {
+  white-space: nowrap;
+  width: 12.5em;
 }
 
 .logs-row__labels {
-  max-width: 20%;
+  width: 20%;
   line-height: 1.2;
+  position: relative;
 }
 
 .logs-row__message {
   word-break: break-all;
-  min-width: 80%;
 }
 
 .logs-row__match-highlight {
@@ -112,6 +119,7 @@ $column-horizontal-spacing: 10px;
 
 .logs-row__level {
   position: relative;
+  width: 10px;
 
   &::after {
     content: '';
@@ -165,6 +173,7 @@ $column-horizontal-spacing: 10px;
 
 .logs-row__duplicates {
   text-align: right;
+  width: 4.5em;
 }
 
 .logs-row__field-highlight {
@@ -193,15 +202,20 @@ $column-horizontal-spacing: 10px;
   }
 }
 
+.logs-labels {
+  display: flex;
+  flex-wrap: wrap;
+}
+
 .logs-label {
-  display: inline-block;
+  display: flex;
   padding: 0 2px;
   background-color: $btn-inverse-bg;
   border-radius: $border-radius;
   margin: 0 4px 2px 0;
   text-overflow: ellipsis;
   white-space: nowrap;
-  position: relative;
+  overflow: hidden;
 }
 
 .logs-label__icon {
@@ -211,6 +225,13 @@ $column-horizontal-spacing: 10px;
   margin-left: 2px;
 }
 
+.logs-label__value {
+  display: inline-block;
+  max-width: 20em;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+
 .logs-label__stats {
   position: absolute;
   top: 1.25em;

+ 16 - 18
public/sass/components/_sidemenu.scss

@@ -16,6 +16,14 @@
   .sidemenu__close {
     display: none;
   }
+
+  @include media-breakpoint-up(sm) {
+    background: $side-menu-bg;
+    height: auto;
+    box-shadow: $side-menu-shadow;
+    position: relative;
+    z-index: $zindex-sidemenu;
+  }
 }
 
 // body class that hides sidemenu
@@ -25,32 +33,22 @@
   }
 }
 
-@include media-breakpoint-up(sm) {
-  .sidemenu-open {
-    .sidemenu {
-      background: $side-menu-bg;
-      height: auto;
-      box-shadow: $side-menu-shadow;
-      position: relative;
-      z-index: $zindex-sidemenu;
-    }
-
-    .sidemenu__top,
-    .sidemenu__bottom {
-      display: block;
-    }
-  }
-}
-
 .sidemenu__top {
   padding-top: 3rem;
   flex-grow: 1;
-  display: none;
 }
 
 .sidemenu__bottom {
   padding-bottom: $spacer;
+}
+
+.sidemenu__top,
+.sidemenu__bottom {
   display: none;
+
+  @include media-breakpoint-up(sm) {
+    display: block;
+  }
 }
 
 .sidemenu-item {

+ 15 - 0
public/sass/components/_view_states.scss

@@ -29,6 +29,21 @@
 .view-mode--tv {
   @extend .view-mode--inactive;
 
+  .sidemenu {
+    position: fixed;
+    background-color: transparent;
+    box-shadow: none;
+
+    .sidemenu__top,
+    .sidemenu__bottom {
+      display: none;
+    }
+  }
+
+  .navbar {
+    padding-left: $side-menu-width;
+  }
+
   .submenu-controls {
     display: none;
   }

+ 0 - 9
public/sass/mixins/_mixins.scss

@@ -79,10 +79,6 @@
 // FONTS
 // --------------------------------------------------
 
-@mixin font-family-serif() {
-  font-family: $font-family-serif;
-}
-
 @mixin font-family-sans-serif() {
   font-family: $font-family-sans-serif;
 }
@@ -97,11 +93,6 @@
   line-height: $lineHeight;
 }
 
-@mixin font-serif($size: $font-size-base, $weight: normal, $lineHeight: $line-height-base) {
-  @include font-family-serif();
-  @include font-shorthand($size, $weight, $lineHeight);
-}
-
 @mixin font-sans-serif($size: $font-size-base, $weight: normal, $lineHeight: $line-height-base) {
   @include font-family-sans-serif();
   @include font-shorthand($size, $weight, $lineHeight);

+ 1 - 35
public/sass/pages/_explore.scss

@@ -25,20 +25,13 @@
   }
 }
 
-.sidemenu-open {
-  .explore-toolbar-header {
-    padding: 0;
-    margin-left: 0;
-  }
-}
-
 .explore-toolbar {
   background: inherit;
   display: flex;
   flex-flow: row wrap;
   justify-content: flex-start;
   height: auto;
-  padding: 0px $dashboard-padding 0 25px;
+  padding: 0 $dashboard-padding;
   border-bottom: 1px solid #0000;
   transition-duration: 0.35s;
   transition-timing-function: ease-in-out;
@@ -72,11 +65,6 @@
   font-size: 18px;
   min-height: 55px;
   line-height: 55px;
-  justify-content: space-between;
-  margin-left: $panel-margin * 3;
-}
-
-.explore-toolbar-header {
   justify-content: space-between;
   align-items: center;
 }
@@ -134,20 +122,6 @@
 }
 
 @media only screen and (max-width: 803px) {
-  .sidemenu-open {
-    .explore-toolbar-header-title {
-      .navbar-page-btn {
-        margin-left: 0;
-      }
-    }
-  }
-
-  .explore-toolbar-header-title {
-    .navbar-page-btn {
-      margin-left: $dashboard-padding;
-    }
-  }
-
   .btn-title {
     display: none;
   }
@@ -161,14 +135,6 @@
 }
 
 @media only screen and (max-width: 544px) {
-  .sidemenu-open {
-    .explore-toolbar-header-title {
-      .navbar-page-btn {
-        margin-left: $dashboard-padding;
-      }
-    }
-  }
-
   .explore-toolbar-header-title {
     .navbar-page-btn {
       margin-left: $dashboard-padding;