소스 검색

Panels: No title will no longer make panel header take up space (#16884)

* Panels: Initial poc of no panel titles

* moving panel-container to DashboardPanel

* Starting to work

* Gauge fix

* Panels: tweaked panel padding and title z-index stuff

* Panels: changed panel padding a bit and simplified it by having same padding in vertical and horizontal

* Lots of tweaks to panel padding related stuff

* Updated snapshot

* Added test dashboard

* Final refactorings

* Trim title befgor applying panel-container--no-title class

* Remove unnecessary type annotation

* Panels: hasTitle no need to trim

* Gauge: fixed font family
Torkel Ödegaard 6 년 전
부모
커밋
48ae93048b

+ 904 - 0
devenv/dev-dashboards/panel-common/panels_without_title.json

@@ -0,0 +1,904 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "gnetId": null,
+  "graphTooltip": 0,
+  "links": [],
+  "panels": [
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": ["#299c46", "#5794F2", "#d44a3a"],
+      "description": "asdasdas",
+      "format": "ms",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": false,
+        "thresholdLabels": false,
+        "thresholdMarkers": true
+      },
+      "gridPos": {
+        "h": 4,
+        "w": 11,
+        "x": 0,
+        "y": 0
+      },
+      "id": 8,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "100%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": true,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": true
+      },
+      "tableColumn": "",
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "thresholds": "",
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "Title",
+      "type": "singlestat",
+      "valueFontSize": "120%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "N/A",
+          "value": "null"
+        }
+      ],
+      "valueName": "avg"
+    },
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": ["#299c46", "#5794F2", "#d44a3a"],
+      "description": "asdasdas",
+      "format": "ms",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": false,
+        "thresholdLabels": false,
+        "thresholdMarkers": true
+      },
+      "gridPos": {
+        "h": 4,
+        "w": 13,
+        "x": 11,
+        "y": 0
+      },
+      "id": 2,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "100%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": true,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": true
+      },
+      "tableColumn": "",
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "thresholds": "",
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "",
+      "type": "singlestat",
+      "valueFontSize": "120%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "N/A",
+          "value": "null"
+        }
+      ],
+      "valueName": "avg"
+    },
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": ["#299c46", "#5794F2", "#d44a3a"],
+      "description": "asdasdas",
+      "format": "ms",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": false,
+        "thresholdLabels": false,
+        "thresholdMarkers": true
+      },
+      "gridPos": {
+        "h": 4,
+        "w": 11,
+        "x": 0,
+        "y": 4
+      },
+      "id": 4,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "100%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": true,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": true
+      },
+      "tableColumn": "",
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "thresholds": "",
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "Panel Title",
+      "type": "singlestat",
+      "valueFontSize": "120%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "N/A",
+          "value": "null"
+        }
+      ],
+      "valueName": "avg"
+    },
+    {
+      "cacheTimeout": null,
+      "description": "asdasdas",
+      "gridPos": {
+        "h": 4,
+        "w": 6,
+        "x": 11,
+        "y": 4
+      },
+      "id": 9,
+      "links": [],
+      "options": {
+        "maxValue": 100,
+        "minValue": 0,
+        "orientation": "auto",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "thresholds": [
+          {
+            "color": "green",
+            "index": 0,
+            "value": null
+          },
+          {
+            "color": "red",
+            "index": 1,
+            "value": 80
+          }
+        ],
+        "valueMappings": [],
+        "valueOptions": {
+          "decimals": null,
+          "prefix": "",
+          "stat": "mean",
+          "suffix": "",
+          "unit": "none"
+        }
+      },
+      "pluginVersion": "6.2.0-pre",
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "Panel Title",
+      "type": "gauge"
+    },
+    {
+      "cacheTimeout": null,
+      "description": "asdasdas",
+      "gridPos": {
+        "h": 4,
+        "w": 7,
+        "x": 17,
+        "y": 4
+      },
+      "id": 11,
+      "links": [],
+      "options": {
+        "maxValue": 100,
+        "minValue": 0,
+        "orientation": "auto",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "thresholds": [
+          {
+            "color": "green",
+            "index": 0,
+            "value": null
+          },
+          {
+            "color": "red",
+            "index": 1,
+            "value": 80
+          }
+        ],
+        "valueMappings": [],
+        "valueOptions": {
+          "decimals": null,
+          "prefix": "",
+          "stat": "mean",
+          "suffix": "",
+          "unit": "none"
+        }
+      },
+      "pluginVersion": "6.2.0-pre",
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "Panel Title",
+      "type": "gauge"
+    },
+    {
+      "gridPos": {
+        "h": 4,
+        "w": 11,
+        "x": 0,
+        "y": 8
+      },
+      "id": 15,
+      "links": [],
+      "options": {
+        "content": "# Title\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n\n",
+        "mode": "markdown"
+      },
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "",
+      "type": "text2"
+    },
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
+      "description": "asdasdas",
+      "format": "none",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": true,
+        "thresholdLabels": false,
+        "thresholdMarkers": true
+      },
+      "gridPos": {
+        "h": 4,
+        "w": 6,
+        "x": 11,
+        "y": 8
+      },
+      "id": 10,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "pluginVersion": "6.2.0-pre",
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "50%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": false,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": false
+      },
+      "tableColumn": "",
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "thresholds": "",
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "",
+      "type": "singlestat",
+      "valueFontSize": "80%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "N/A",
+          "value": "null"
+        }
+      ],
+      "valueName": "avg"
+    },
+    {
+      "cacheTimeout": null,
+      "description": "asdasdas",
+      "gridPos": {
+        "h": 4,
+        "w": 7,
+        "x": 17,
+        "y": 8
+      },
+      "id": 12,
+      "links": [],
+      "options": {
+        "maxValue": 100,
+        "minValue": 0,
+        "orientation": "auto",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "thresholds": [
+          {
+            "color": "green",
+            "index": 0,
+            "value": null
+          },
+          {
+            "color": "red",
+            "index": 1,
+            "value": 80
+          }
+        ],
+        "valueMappings": [],
+        "valueOptions": {
+          "decimals": null,
+          "prefix": "",
+          "stat": "mean",
+          "suffix": "",
+          "unit": "none"
+        }
+      },
+      "pluginVersion": "6.2.0-pre",
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "cacheTimeout": null,
+      "description": "",
+      "gridPos": {
+        "h": 6,
+        "w": 11,
+        "x": 0,
+        "y": 12
+      },
+      "id": 3,
+      "links": [],
+      "options": {
+        "displayMode": "gradient",
+        "fieldOptions": {
+          "defaults": {
+            "max": 100,
+            "min": 0
+          },
+          "mappings": [],
+          "override": {},
+          "stats": ["mean"],
+          "thresholds": [
+            {
+              "color": "green",
+              "index": 0,
+              "value": null
+            },
+            {
+              "color": "red",
+              "index": 1,
+              "value": 80
+            }
+          ],
+          "title": "Testing",
+          "values": false
+        },
+        "maxValue": 100,
+        "minValue": 0,
+        "orientation": "horizontal",
+        "thresholds": [
+          {
+            "color": "green",
+            "index": 0,
+            "value": null
+          },
+          {
+            "color": "red",
+            "index": 1,
+            "value": 80
+          }
+        ],
+        "valueMappings": [],
+        "valueOptions": {
+          "decimals": null,
+          "prefix": "",
+          "stat": "mean",
+          "suffix": "",
+          "unit": "none"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "random_walk"
+        },
+        {
+          "refId": "B",
+          "scenarioId": "random_walk"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "",
+      "type": "bargauge"
+    },
+    {
+      "cacheTimeout": null,
+      "description": "asdasdas",
+      "gridPos": {
+        "h": 6,
+        "w": 13,
+        "x": 11,
+        "y": 12
+      },
+      "id": 5,
+      "links": [],
+      "options": {
+        "displayMode": "gradient",
+        "fieldOptions": {
+          "defaults": {
+            "max": 100,
+            "min": 0
+          },
+          "mappings": [],
+          "override": {},
+          "stats": ["mean"],
+          "thresholds": [
+            {
+              "color": "green",
+              "index": 0,
+              "value": null
+            },
+            {
+              "color": "red",
+              "index": 1,
+              "value": 80
+            }
+          ],
+          "title": "Testing",
+          "values": false
+        },
+        "maxValue": 100,
+        "minValue": 0,
+        "orientation": "horizontal",
+        "thresholds": [
+          {
+            "color": "green",
+            "index": 0,
+            "value": null
+          },
+          {
+            "color": "red",
+            "index": 1,
+            "value": 80
+          }
+        ],
+        "valueMappings": [],
+        "valueOptions": {
+          "decimals": null,
+          "prefix": "",
+          "stat": "mean",
+          "suffix": "",
+          "unit": "none"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "slow_query",
+          "stringInput": "1s"
+        },
+        {
+          "refId": "B",
+          "scenarioId": "slow_query",
+          "stringInput": "1s"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": "2h",
+      "title": "My cool pane title",
+      "type": "bargauge"
+    },
+    {
+      "cacheTimeout": null,
+      "description": "asdasdas",
+      "gridPos": {
+        "h": 4,
+        "w": 24,
+        "x": 0,
+        "y": 18
+      },
+      "id": 7,
+      "links": [],
+      "options": {
+        "displayMode": "gradient",
+        "fieldOptions": {
+          "defaults": {
+            "max": 100,
+            "min": 0
+          },
+          "mappings": [],
+          "override": {},
+          "stats": ["mean"],
+          "thresholds": [
+            {
+              "color": "green",
+              "index": 0,
+              "value": null
+            },
+            {
+              "color": "red",
+              "index": 1,
+              "value": 80
+            }
+          ],
+          "title": "Testing",
+          "values": false
+        },
+        "maxValue": 100,
+        "minValue": 0,
+        "orientation": "horizontal",
+        "thresholds": [
+          {
+            "color": "green",
+            "index": 0,
+            "value": null
+          },
+          {
+            "color": "red",
+            "index": 1,
+            "value": 80
+          }
+        ],
+        "valueMappings": [],
+        "valueOptions": {
+          "decimals": null,
+          "prefix": "",
+          "stat": "mean",
+          "suffix": "",
+          "unit": "none"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "slow_query",
+          "stringInput": "10s"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "My cool pane title",
+      "type": "bargauge"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "cacheTimeout": null,
+      "dashLength": 10,
+      "dashes": false,
+      "description": "asdasdas",
+      "fill": 1,
+      "gridPos": {
+        "h": 6,
+        "w": 11,
+        "x": 0,
+        "y": 22
+      },
+      "id": 6,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 2,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "slow_query",
+          "stringInput": "5s"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "My cool pane title",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "cacheTimeout": null,
+      "dashLength": 10,
+      "dashes": false,
+      "description": "",
+      "fill": 1,
+      "gridPos": {
+        "h": 6,
+        "w": 13,
+        "x": 11,
+        "y": 22
+      },
+      "id": 13,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "percentage": false,
+      "pointradius": 2,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "slow_query",
+          "stringInput": "5s"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    }
+  ],
+  "schemaVersion": 18,
+  "style": "dark",
+  "tags": ["gdev", "panel-tests"],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-6h",
+    "to": "now"
+  },
+  "timepicker": {
+    "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
+    "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
+  },
+  "timezone": "",
+  "title": "Panel Tests - With & Without title",
+  "uid": "2WaUpZmWk",
+  "version": 23
+}

+ 4 - 8
packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap

@@ -182,10 +182,8 @@ exports[`Render should render with base threshold 1`] = `
                               "sm": "24px",
                             },
                             "name": "Grafana Dark",
-                            "panelPadding": Object {
-                              "horizontal": 16,
-                              "vertical": 8,
-                            },
+                            "panelHeaderHeight": 28,
+                            "panelPadding": 8,
                             "spacing": Object {
                               "d": "14px",
                               "gutter": "30px",
@@ -345,10 +343,8 @@ exports[`Render should render with base threshold 1`] = `
                                     "sm": "24px",
                                   },
                                   "name": "Grafana Dark",
-                                  "panelPadding": Object {
-                                    "horizontal": 16,
-                                    "vertical": 8,
-                                  },
+                                  "panelHeaderHeight": 28,
+                                  "panelPadding": 8,
                                   "spacing": Object {
                                     "d": "14px",
                                     "gutter": "30px",

+ 2 - 2
packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx

@@ -74,12 +74,12 @@ export class VizRepeater<T> extends PureComponent<Props<T>, State<T>> {
 
     if (orientation === VizOrientation.Horizontal) {
       repeaterStyle.flexDirection = 'column';
-      itemStyles.margin = `${itemSpacing / 2}px 0`;
+      itemStyles.marginBottom = `${itemSpacing}px`;
       vizWidth = width;
       vizHeight = height / values.length - itemSpacing;
     } else {
       repeaterStyle.flexDirection = 'row';
-      itemStyles.margin = `0 ${itemSpacing / 2}px`;
+      itemStyles.marginRight = `${itemSpacing}px`;
       vizHeight = height;
       vizWidth = width / values.length - itemSpacing;
     }

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

@@ -197,9 +197,9 @@ $side-menu-width: 60px;
 
 // dashboard
 $dashboard-padding: $space-md;
-$panel-padding: 0 ${theme.panelPadding.horizontal}px ${theme.panelPadding.vertical}px ${
-    theme.panelPadding.horizontal
-  }px;
+$panel-padding: ${theme.panelPadding}px;
+$panel-header-height: ${theme.panelHeaderHeight}px;
+$panel-header-z-index: 10;
 
 // tabs
 $tabs-padding: 10px 15px 9px;

+ 2 - 4
packages/grafana-ui/src/themes/default.ts

@@ -72,10 +72,8 @@ const theme: GrafanaThemeCommons = {
     md: '32px',
     lg: '48px',
   },
-  panelPadding: {
-    horizontal: 16,
-    vertical: 8,
-  },
+  panelPadding: 8,
+  panelHeaderHeight: 28,
   zIndex: {
     dropdown: '1000',
     navbarFixed: '1020',

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

@@ -77,10 +77,8 @@ export interface GrafanaThemeCommons {
     md: string;
     lg: string;
   };
-  panelPadding: {
-    horizontal: number;
-    vertical: number;
-  };
+  panelPadding: number;
+  panelHeaderHeight: number;
   zIndex: {
     dropdown: string;
     navbarFixed: string;

+ 0 - 4
public/app/core/constants.ts

@@ -9,8 +9,4 @@ export const MIN_PANEL_HEIGHT = GRID_CELL_HEIGHT * 3;
 
 export const LS_PANEL_COPY_KEY = 'panel-copy';
 
-export const DASHBOARD_TOOLBAR_HEIGHT = 55;
-export const DASHBOARD_TOP_PADDING = 20;
-
-export const PANEL_HEADER_HEIGHT = 27;
 export const PANEL_BORDER = 2;

+ 13 - 8
public/app/features/dashboard/dashgrid/DashboardPanel.tsx

@@ -129,16 +129,21 @@ export class DashboardPanel extends PureComponent<Props, State> {
     this.props.dashboard.setPanelFocus(0);
   };
 
-  renderReactPanel() {
+  renderPanel() {
     const { dashboard, panel, isFullscreen, isInView } = this.props;
     const { plugin } = this.state;
 
+    if (plugin.angularPanelCtrl) {
+      return <div ref={element => (this.element = element)} className="panel-height-helper" />;
+    }
+
     return (
       <AutoSizer>
         {({ width, height }) => {
           if (width === 0) {
             return null;
           }
+
           return (
             <PanelChrome
               plugin={plugin}
@@ -155,10 +160,6 @@ export class DashboardPanel extends PureComponent<Props, State> {
     );
   }
 
-  renderAngularPanel() {
-    return <div ref={element => (this.element = element)} className="panel-height-helper" />;
-  }
-
   render() {
     const { panel, dashboard, isFullscreen, isEditing } = this.props;
     const { plugin, angularPanel, isLazy } = this.state;
@@ -177,7 +178,11 @@ export class DashboardPanel extends PureComponent<Props, State> {
       return null;
     }
 
-    const containerClass = classNames({ 'panel-editor-container': isEditing, 'panel-height-helper': !isEditing });
+    const editorContainerClasses = classNames({
+      'panel-editor-container': isEditing,
+      'panel-height-helper': !isEditing,
+    });
+
     const panelWrapperClass = classNames({
       'panel-wrapper': true,
       'panel-wrapper--edit': isEditing,
@@ -185,7 +190,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
     });
 
     return (
-      <div className={containerClass}>
+      <div className={editorContainerClasses}>
         <PanelResizer
           isEditing={isEditing}
           panel={panel}
@@ -196,7 +201,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
               onMouseLeave={this.onMouseLeave}
               style={styles}
             >
-              {plugin.angularPanelCtrl ? this.renderAngularPanel() : this.renderReactPanel()}
+              {this.renderPanel()}
             </div>
           )}
         />

+ 20 - 15
public/app/features/dashboard/dashgrid/PanelChrome.tsx

@@ -1,17 +1,18 @@
 // Libraries
 import React, { PureComponent } from 'react';
-
-// Services
-import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
+import classNames from 'classnames';
+import { Unsubscribable } from 'rxjs';
 
 // Components
 import { PanelHeader } from './PanelHeader/PanelHeader';
 import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
 
-// Utils
-import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
-import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
+// Utils & Services
+import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
+import { applyPanelTimeOverrides, calculateInnerPanelHeight } from 'app/features/dashboard/utils/panel';
 import { profiler } from 'app/core/profiler';
+import { getProcessedSeriesData } from '../state/PanelQueryState';
+import templateSrv from 'app/features/templating/template_srv';
 import config from 'app/core/config';
 
 // Types
@@ -19,11 +20,6 @@ import { DashboardModel, PanelModel } from '../state';
 import { LoadingState, PanelData, PanelPlugin } from '@grafana/ui';
 import { ScopedVars } from '@grafana/ui';
 
-import templateSrv from 'app/features/templating/template_srv';
-
-import { getProcessedSeriesData } from '../state/PanelQueryState';
-import { Unsubscribable } from 'rxjs';
-
 const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
 
 export interface Props {
@@ -232,7 +228,7 @@ export class PanelChrome extends PureComponent<Props, State> {
   renderPanel(width: number, height: number): JSX.Element {
     const { panel, plugin } = this.props;
     const { renderCounter, data, isFirstLoad } = this.state;
-    const PanelComponent = plugin.panel;
+    const { theme } = config;
 
     // This is only done to increase a counter that is used by backend
     // image rendering (phantomjs/headless chrome) to know when to capture image
@@ -246,6 +242,9 @@ export class PanelChrome extends PureComponent<Props, State> {
       return this.renderLoadingState();
     }
 
+    const PanelComponent = plugin.panel;
+    const innerPanelHeight = calculateInnerPanelHeight(panel, height);
+
     return (
       <>
         {loading === LoadingState.Loading && this.renderLoadingState()}
@@ -254,8 +253,8 @@ export class PanelChrome extends PureComponent<Props, State> {
             data={data}
             timeRange={data.request ? data.request.range : this.timeSrv.timeRange()}
             options={panel.getOptions(plugin.defaults)}
-            width={width - 2 * config.theme.panelPadding.horizontal}
-            height={height - PANEL_HEADER_HEIGHT - config.theme.panelPadding.vertical}
+            width={width - theme.panelPadding * 2}
+            height={innerPanelHeight}
             renderCounter={renderCounter}
             replaceVariables={this.replaceVariables}
             onOptionsChange={this.onOptionsChange}
@@ -278,7 +277,13 @@ export class PanelChrome extends PureComponent<Props, State> {
     const { errorMessage, data } = this.state;
     const { transparent } = panel;
 
-    const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
+    const containerClassNames = classNames({
+      'panel-container': true,
+      'panel-container--absolute': true,
+      'panel-container--no-title': !panel.hasTitle(),
+      'panel-transparent': transparent,
+    });
+
     return (
       <div className={containerClassNames}>
         <PanelHeader

+ 5 - 2
public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx

@@ -72,10 +72,13 @@ export class PanelHeader extends Component<Props, State> {
 
   render() {
     const { panel, dashboard, timeInfo, scopedVars, error, isFullscreen } = this.props;
-
-    const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen });
     const title = templateSrv.replaceWithText(panel.title, scopedVars);
 
+    const panelHeaderClass = classNames({
+      'panel-header': true,
+      'grid-drag-handle': !isFullscreen,
+    });
+
     return (
       <>
         <PanelHeaderCorner

+ 4 - 0
public/app/features/dashboard/state/PanelModel.ts

@@ -326,6 +326,10 @@ export class PanelModel {
     return this.queryRunner;
   }
 
+  hasTitle() {
+    return !!this.title.length;
+  }
+
   destroy() {
     this.events.emit('panel-teardown');
     this.events.removeAllListeners();

+ 11 - 1
public/app/features/dashboard/utils/panel.ts

@@ -11,12 +11,13 @@ import { isString as _isString } from 'lodash';
 import * as rangeUtil from '@grafana/ui/src/utils/rangeutil';
 import * as dateMath from '@grafana/ui/src/utils/datemath';
 import appEvents from 'app/core/app_events';
+import config from 'app/core/config';
 
 // Services
 import templateSrv from 'app/features/templating/template_srv';
 
 // Constants
-import { LS_PANEL_COPY_KEY } from 'app/core/constants';
+import { LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants';
 
 export const removePanel = (dashboard: DashboardModel, panel: PanelModel, ask: boolean) => {
   // confirm deletion
@@ -169,3 +170,12 @@ export function getResolution(panel: PanelModel): number {
 
   return panel.maxDataPoints ? panel.maxDataPoints : Math.ceil(width * (panel.gridPos.w / 24));
 }
+
+export function calculateInnerPanelHeight(panel: PanelModel, containerHeight: number): number {
+  return (
+    containerHeight -
+    (panel.hasTitle() ? config.theme.panelHeaderHeight : 0) -
+    config.theme.panelPadding * 2 -
+    PANEL_BORDER
+  );
+}

+ 3 - 2
public/app/features/panel/panel_ctrl.ts

@@ -11,9 +11,10 @@ import {
   copyPanel as copyPanelUtil,
   editPanelJson as editPanelJsonUtil,
   sharePanel as sharePanelUtil,
+  calculateInnerPanelHeight,
 } from 'app/features/dashboard/utils/panel';
 
-import { GRID_COLUMN_COUNT, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
+import { GRID_COLUMN_COUNT } from 'app/core/constants';
 
 export class PanelCtrl {
   panel: any;
@@ -202,7 +203,7 @@ export class PanelCtrl {
 
   calculatePanelHeight(containerHeight) {
     this.containerHeight = containerHeight;
-    this.height = this.containerHeight - (PANEL_BORDER + PANEL_HEADER_HEIGHT);
+    this.height = calculateInnerPanelHeight(this.panel, containerHeight);
   }
 
   render(payload?) {

+ 1 - 1
public/app/features/panel/panel_directive.ts

@@ -6,7 +6,7 @@ import baron from 'baron';
 const module = angular.module('grafana.directives');
 
 const panelTemplate = `
-  <div class="panel-container">
+  <div class="panel-container" ng-class="{'panel-container--no-title': !ctrl.panel.title.length}">
       <div class="panel-header" ng-class="{'grid-drag-handle': !ctrl.panel.fullscreen}">
         <span class="panel-info-corner">
           <i class="fa"></i>

+ 8 - 9
public/app/plugins/panel/singlestat/module.ts

@@ -430,7 +430,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
 
       const plotCanvas = $('<div></div>');
       const plotCss = {
-        top: '10px',
+        top: '5px',
         margin: 'auto',
         position: 'relative',
         height: height * 0.9 + 'px',
@@ -494,7 +494,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
               },
               font: {
                 size: fontSize,
-                family: '"Helvetica Neue", Helvetica, Arial, sans-serif',
+                family: config.theme.typography.fontFamily.sansSerif,
               },
             },
             show: true,
@@ -512,7 +512,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
     }
 
     function addSparkline() {
-      const width = elem.width() + 20;
+      const width = elem.width();
       if (width < 30) {
         // element has not gotten it's width yet
         // delay sparkline render
@@ -524,17 +524,16 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       const plotCanvas = $('<div></div>');
       const plotCss: any = {};
       plotCss.position = 'absolute';
+      plotCss.bottom = '0px';
 
       if (panel.sparkline.full) {
-        plotCss.bottom = '5px';
-        plotCss.left = '-5px';
-        plotCss.width = width - 10 + 'px';
+        plotCss.left = '0px';
+        plotCss.width = width + 'px';
         const dynamicHeightMargin = height <= 100 ? 5 : Math.round(height / 100) * 15 + 5;
         plotCss.height = height - dynamicHeightMargin + 'px';
       } else {
-        plotCss.bottom = '0px';
-        plotCss.left = '-5px';
-        plotCss.width = width - 10 + 'px';
+        plotCss.left = '0px';
+        plotCss.width = width + 'px';
         plotCss.height = Math.floor(height * 0.25) + 'px';
       }
 

+ 1 - 0
public/sass/_grafana.scss

@@ -99,6 +99,7 @@
 @import 'components/page_loader';
 @import 'components/toggle_button_group';
 @import 'components/popover-box';
+@import 'components/panel_header';
 
 // LOAD @grafana/ui components
 @import '../../packages/grafana-ui/src/index';

+ 3 - 1
public/sass/_variables.generated.scss

@@ -200,7 +200,9 @@ $side-menu-width: 60px;
 
 // dashboard
 $dashboard-padding: $space-md;
-$panel-padding: 0 16px 8px 16px;
+$panel-padding: 8px;
+$panel-header-height: 28px;
+$panel-header-z-index: 10;
 
 // tabs
 $tabs-padding: 10px 15px 9px;

+ 1 - 0
public/sass/components/_panel_editor.scss

@@ -6,6 +6,7 @@
 
 .panel-wrapper {
   height: 100%;
+  position: relative;
 
   &--edit {
     height: 40%;

+ 2 - 2
public/sass/components/_panel_graph.scss

@@ -352,7 +352,7 @@
 
 .left-yaxis-label {
   top: 50%;
-  left: 2px;
+  left: 8px;
   transform: translateX(-50%) translateY(-50%) rotate(-90deg);
   // this is needed for phantomsjs 2.1
   -webkit-transform: translateX(-50%) translateY(-50%) rotate(-90deg);
@@ -360,7 +360,7 @@
 
 .right-yaxis-label {
   top: 50%;
-  right: 2px;
+  right: 8px;
   transform: translateX(50%) translateY(-50%) rotate(90deg);
   // this is needed for phantomsjs 2.1
   -webkit-transform: translateX(50%) translateY(-50%) rotate(90deg);

+ 176 - 0
public/sass/components/_panel_header.scss

@@ -0,0 +1,176 @@
+$panel-header-no-title-zindex: 1;
+
+.panel-header {
+  &:hover {
+    transition: background-color 0.1s ease-in-out;
+    background-color: $panel-header-hover-bg;
+  }
+}
+
+.panel-container--no-title {
+  .panel-header {
+    position: absolute;
+    width: 100%;
+    z-index: $panel-header-z-index;
+  }
+  .panel-content {
+    height: 100%;
+  }
+}
+
+.panel-title-container {
+  cursor: move;
+  word-wrap: break-word;
+  display: block;
+}
+
+.panel-title {
+  border: 0px;
+  font-weight: $font-weight-semi-bold;
+  position: relative;
+  width: 100%;
+  display: flex;
+  flex-wrap: nowrap;
+  justify-content: center;
+  height: $panel-header-height;
+  align-items: center;
+}
+
+.panel-title-text {
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+  max-width: calc(100% - 38px);
+  cursor: pointer;
+  font-weight: $font-weight-semi-bold;
+
+  &:hover {
+    color: $link-hover-color;
+  }
+  .panel-has-alert & {
+    max-width: calc(100% - 54px);
+  }
+}
+
+.panel-menu-container {
+  width: 1px;
+  height: 19px;
+  display: inline-block;
+}
+
+.panel-menu-toggle {
+  color: $text-color-weak;
+  cursor: pointer;
+  padding: 3px 5px;
+  visibility: hidden;
+  opacity: 0;
+  width: 16px;
+  height: 16px;
+  left: 1px;
+  top: 2px;
+
+  &:hover {
+    color: $link-hover-color;
+  }
+}
+
+.panel-loading {
+  position: absolute;
+  top: 4px;
+  right: 4px;
+  z-index: $panel-header-z-index + 1;
+  font-size: $font-size-sm;
+  color: $text-color-weak;
+}
+
+.panel-empty {
+  display: flex;
+  align-items: center;
+  height: 100%;
+  width: 100%;
+
+  p {
+    text-align: center;
+    color: $text-muted;
+    font-size: $font-size-lg;
+    width: 100%;
+  }
+}
+
+.panel-menu {
+  top: 25px;
+  left: -100px;
+}
+
+.panel-info-corner-inner {
+  width: 0;
+  height: 0;
+  position: absolute;
+  left: 0;
+  bottom: 0;
+}
+
+@mixin panel-corner-color($corner-bg) {
+  .panel-info-corner-inner {
+    border-left: $panel-header-height solid $corner-bg;
+    border-right: none;
+    border-bottom: $panel-header-height solid transparent;
+  }
+}
+
+.panel-info-corner {
+  color: $text-muted;
+  cursor: pointer;
+  position: absolute;
+  display: none;
+  left: 0;
+  width: $panel-header-height;
+  height: $panel-header-height;
+  top: 0;
+
+  .fa {
+    position: relative;
+    top: -2px;
+    left: 6px;
+    font-size: 75%;
+    z-index: $panel-header-no-title-zindex + 2;
+  }
+
+  &--info {
+    display: block;
+    @include panel-corner-color(lighten($panel-corner, 4%));
+    .fa:before {
+      content: '\f129';
+    }
+  }
+
+  &--links {
+    display: block;
+    @include panel-corner-color(lighten($panel-corner, 4%));
+    .fa {
+      left: 4px;
+    }
+    .fa:before {
+      content: '\f08e';
+    }
+  }
+
+  &--error {
+    display: block;
+    color: $white;
+    @include panel-corner-color($popover-error-bg);
+    .fa:before {
+      content: '\f12a';
+    }
+  }
+}
+
+.panel-time-info {
+  font-weight: $font-weight-semi-bold;
+  float: right;
+  margin-right: 8px;
+  color: $blue;
+  font-size: 85%;
+  position: absolute;
+  right: 0;
+}

+ 10 - 1
public/sass/components/_panel_singlestat.scss

@@ -14,6 +14,15 @@
   z-index: 1;
   font-size: 3em;
   font-weight: $font-weight-semi-bold;
+  // helps make the title feel more centered when there is a panel title
+  padding-bottom: $panel-padding;
+}
+
+// Helps
+.panel-container--no-title {
+  .singlestat-panel-value-container {
+    padding-bottom: 0;
+  }
 }
 
 .singlestat-panel-prefix {
@@ -21,5 +30,5 @@
 }
 
 #flotGagueValue0 {
-  font-weight: bold; //please dont hurt me for this!
+  font-weight: $font-weight-semi-bold; //please dont hurt me for this!
 }

+ 2 - 168
public/sass/pages/_dashboard.scss

@@ -79,9 +79,9 @@ div.flot-text {
 
 .panel-content {
   padding: $panel-padding;
-  height: calc(100% - 27px);
+  height: calc(100% - #{$panel-header-height});
+  width: 100%;
   position: relative;
-
   // Fixes scrolling on mobile devices
   overflow: auto;
 }
@@ -93,172 +93,6 @@ div.flot-text {
   }
 }
 
-.panel-title-container {
-  min-height: 9px;
-  cursor: move;
-  word-wrap: break-word;
-  display: block;
-}
-
-.panel-title {
-  border: 0px;
-  font-weight: $font-weight-semi-bold;
-  position: relative;
-  width: 100%;
-  display: flex;
-  flex-wrap: nowrap;
-  justify-content: center;
-  padding: 4px 0 4px;
-}
-
-.panel-title-text {
-  text-overflow: ellipsis;
-  overflow: hidden;
-  white-space: nowrap;
-  max-width: calc(100% - 38px);
-  cursor: pointer;
-  font-weight: $font-weight-semi-bold;
-
-  &:hover {
-    color: $link-hover-color;
-  }
-  .panel-has-alert & {
-    max-width: calc(100% - 54px);
-  }
-}
-
-.panel-menu-container {
-  width: 1px;
-  height: 19px;
-  display: inline-block;
-}
-
-.panel-menu-toggle {
-  color: $text-color-weak;
-  cursor: pointer;
-  padding: 3px 5px;
-  visibility: hidden;
-  opacity: 0;
-  width: 16px;
-  height: 16px;
-  left: 1px;
-  top: 2px;
-
-  &:hover {
-    color: $link-hover-color;
-  }
-}
-
-.panel-loading {
-  position: absolute;
-  top: -3px;
-  right: 0px;
-  z-index: 800;
-  font-size: $font-size-sm;
-  color: $text-color-weak;
-}
-
-.panel-empty {
-  display: flex;
-  align-items: center;
-  height: 100%;
-  width: 100%;
-
-  p {
-    text-align: center;
-    color: $text-muted;
-    font-size: $font-size-lg;
-    width: 100%;
-  }
-}
-
-.panel-header {
-  &:hover {
-    transition: background-color 0.1s ease-in-out;
-    background-color: $panel-header-hover-bg;
-  }
-}
-
-.panel-menu {
-  top: 25px;
-  left: -100px;
-}
-
-.panel-info-corner-inner {
-  width: 0;
-  height: 0;
-  position: absolute;
-  left: 0;
-  bottom: 0;
-}
-
-@mixin panel-corner-color($corner-bg) {
-  .panel-info-corner-inner {
-    border-left: 27px solid $corner-bg;
-    border-right: none;
-    border-bottom: 27px solid transparent;
-  }
-}
-
-.panel-info-corner {
-  color: $text-muted;
-  cursor: pointer;
-  position: absolute;
-  display: none;
-  left: 0;
-  width: 27px;
-  height: 27px;
-  top: 0;
-  z-index: 1;
-
-  .fa {
-    position: relative;
-    top: -2px;
-    left: 6px;
-    font-size: 75%;
-    z-index: 1;
-  }
-
-  &--info {
-    display: block;
-    @include panel-corner-color(lighten($panel-corner, 4%));
-    .fa:before {
-      content: '\f129';
-    }
-  }
-
-  &--links {
-    display: block;
-    @include panel-corner-color(lighten($panel-corner, 4%));
-    .fa {
-      left: 4px;
-    }
-    .fa:before {
-      content: '\f08e';
-    }
-  }
-
-  &--error {
-    display: block;
-    color: $white;
-    @include panel-corner-color($popover-error-bg);
-    .fa:before {
-      content: '\f12a';
-    }
-  }
-}
-
-.panel-time-info {
-  font-weight: bold;
-  float: right;
-  margin-right: 15px;
-  color: $blue;
-  font-size: 85%;
-  position: absolute;
-  top: 4px;
-  right: 0;
-}
-
 .dashboard-header {
   font-size: $font-size-h3;
   text-align: center;

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

@@ -164,7 +164,6 @@
   padding: $space-sm $space-md 0 $space-md;
   display: flex;
   cursor: pointer;
-  margin-bottom: $space-sm;
   transition: all 0.1s linear;
 }