diff --git a/.circleci/config.yml b/.circleci/config.yml index 7b5a9e7923b..7e89ffe7a1f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -147,9 +147,6 @@ jobs: - run: name: sha-sum packages command: 'go run build.go sha-dist' - - run: - name: Build Grafana.com master publisher - command: 'go build -o scripts/publish scripts/build/publish.go' - run: name: Test and build Grafana.com release publisher command: 'cd scripts/build/release_publisher && go test . && go build -o release_publisher .' @@ -158,7 +155,6 @@ jobs: paths: - dist/grafana* - scripts/*.sh - - scripts/publish - scripts/build/release_publisher/release_publisher - scripts/build/publish.sh @@ -393,7 +389,7 @@ jobs: name: Publish to Grafana.com command: | rm dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz - ./scripts/publish -apiKey ${GRAFANA_COM_API_KEY} + cd dist && ../scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -from-local deploy-release: docker: diff --git a/conf/defaults.ini b/conf/defaults.ini index 6fc4cf2e4de..4da3588bef2 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -504,7 +504,7 @@ concurrent_render_limit = 5 #################################### Explore ############################# [explore] # Enable the Explore section -enabled = false +enabled = true #################################### Internal Grafana Metrics ############ # Metrics available at HTTP API Url /metrics diff --git a/conf/sample.ini b/conf/sample.ini index 0f1c02dc231..8b731e43bd9 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -429,7 +429,7 @@ log_queries = #################################### Explore ############################# [explore] # Enable the Explore section -;enabled = false +;enabled = true #################################### Internal Grafana Metrics ########################## # Metrics available at HTTP API Url /metrics diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 5959c230fb9..2789b0bf51e 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -336,7 +336,7 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) { "id": 123123, "gridPos": map[string]interface{}{ "x": 0, - "y": 0, + "y": 3, "w": 24, "h": 4, }, diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 660a00ba41d..d1eca777004 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -718,7 +718,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { AlertingNoDataOrNullValues = alerting.Key("nodata_or_nullvalues").MustString("no_data") explore := iniFile.Section("explore") - ExploreEnabled = explore.Key("enabled").MustBool(false) + ExploreEnabled = explore.Key("enabled").MustBool(true) panels := iniFile.Section("panels") cfg.EnableAlphaPanels = panels.Key("enable_alpha").MustBool(false) diff --git a/public/app/core/actions/location.ts b/public/app/core/actions/location.ts index 6f7ac67363e..8669788fa16 100644 --- a/public/app/core/actions/location.ts +++ b/public/app/core/actions/location.ts @@ -1,13 +1,17 @@ import { LocationUpdate } from 'app/types'; +export enum CoreActionTypes { + UpdateLocation = 'UPDATE_LOCATION', +} + export type Action = UpdateLocationAction; export interface UpdateLocationAction { - type: 'UPDATE_LOCATION'; + type: CoreActionTypes.UpdateLocation; payload: LocationUpdate; } export const updateLocation = (location: LocationUpdate): UpdateLocationAction => ({ - type: 'UPDATE_LOCATION', + type: CoreActionTypes.UpdateLocation, payload: location, }); diff --git a/public/app/core/reducers/location.ts b/public/app/core/reducers/location.ts index a42bd813782..6b39710dcca 100644 --- a/public/app/core/reducers/location.ts +++ b/public/app/core/reducers/location.ts @@ -1,4 +1,4 @@ -import { Action } from 'app/core/actions/location'; +import { Action, CoreActionTypes } from 'app/core/actions/location'; import { LocationState } from 'app/types'; import { renderUrl } from 'app/core/utils/url'; import _ from 'lodash'; @@ -12,7 +12,7 @@ export const initialState: LocationState = { export const locationReducer = (state = initialState, action: Action): LocationState => { switch (action.type) { - case 'UPDATE_LOCATION': { + case CoreActionTypes.UpdateLocation: { const { path, routeParams } = action.payload; let query = action.payload.query || state.query; @@ -24,9 +24,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS return { url: renderUrl(path || state.path, query), path: path || state.path, - query: { - ...query, - }, + query: { ...query }, routeParams: routeParams || state.routeParams, }; } diff --git a/public/app/features/dashboard/services/DashboardViewStateSrv.test.ts b/public/app/features/dashboard/services/DashboardViewStateSrv.test.ts index aee6746ff36..20215017e1d 100644 --- a/public/app/features/dashboard/services/DashboardViewStateSrv.test.ts +++ b/public/app/features/dashboard/services/DashboardViewStateSrv.test.ts @@ -58,7 +58,6 @@ describe('when updating view state', () => { it('should remove params from query string', () => { viewState.update({ fullscreen: true, panelId: 1, edit: true }); viewState.update({ fullscreen: false }); - expect(viewState.dashboard.meta.fullscreen).toBe(false); expect(viewState.state.fullscreen).toBe(null); }); }); diff --git a/public/app/features/dashboard/services/DashboardViewStateSrv.ts b/public/app/features/dashboard/services/DashboardViewStateSrv.ts index cb9794d6abb..816b6d8bd2d 100644 --- a/public/app/features/dashboard/services/DashboardViewStateSrv.ts +++ b/public/app/features/dashboard/services/DashboardViewStateSrv.ts @@ -72,7 +72,6 @@ export class DashboardViewStateSrv { } _.extend(this.state, state); - this.dashboard.meta.fullscreen = this.state.fullscreen; if (!this.state.fullscreen) { this.state.fullscreen = null; @@ -117,10 +116,20 @@ export class DashboardViewStateSrv { } syncState() { - if (this.dashboard.meta.fullscreen) { + if (this.state.fullscreen) { const panel = this.dashboard.getPanelById(this.state.panelId); if (!panel) { + this.state.fullscreen = null; + this.state.panelId = null; + this.state.edit = null; + + this.update(this.state); + + setTimeout(() => { + appEvents.emit('alert-error', ['Error', 'Panel not found']); + }, 100); + return; } diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index b6f57a76004..20ab8ee67b9 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -9,8 +9,6 @@ import { AutoSizer } from 'react-virtualized'; import store from 'app/core/store'; // Components -import { DataSourceSelectItem } from '@grafana/ui/src/types'; -import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { Alert } from './Error'; import ErrorBoundary from './ErrorBoundary'; import GraphContainer from './GraphContainer'; @@ -21,18 +19,13 @@ import TimePicker, { parseTime } from './TimePicker'; // Actions import { - changeDatasource, changeSize, changeTime, - clearQueries, initializeExplore, modifyQueries, - runQueries, scanStart, scanStop, setQueries, - splitClose, - splitOpen, } from './state/actions'; // Types @@ -41,27 +34,23 @@ import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/ import { StoreState } from 'app/types'; import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore'; import { Emitter } from 'app/core/utils/emitter'; +import { ExploreToolbar } from './ExploreToolbar'; interface ExploreProps { StartPage?: any; - changeDatasource: typeof changeDatasource; changeSize: typeof changeSize; changeTime: typeof changeTime; - clearQueries: typeof clearQueries; datasourceError: string; datasourceInstance: any; datasourceLoading: boolean | null; datasourceMissing: boolean; - exploreDatasources: DataSourceSelectItem[]; exploreId: ExploreId; initialDatasource?: string; initialQueries: DataQuery[]; initializeExplore: typeof initializeExplore; initialized: boolean; - loading: boolean; modifyQueries: typeof modifyQueries; range: RawTimeRange; - runQueries: typeof runQueries; scanner?: RangeScanner; scanning?: boolean; scanRange?: RawTimeRange; @@ -69,8 +58,6 @@ interface ExploreProps { scanStop: typeof scanStop; setQueries: typeof setQueries; split: boolean; - splitClose: typeof splitClose; - splitOpen: typeof splitOpen; showingStartPage?: boolean; supportsGraph: boolean | null; supportsLogs: boolean | null; @@ -145,10 +132,6 @@ export class Explore extends React.PureComponent { this.el = el; }; - onChangeDatasource = async option => { - this.props.changeDatasource(this.props.exploreId, option.value); - }; - onChangeTime = (range: TimeRange, changedByScanner?: boolean) => { if (this.props.scanning && !changedByScanner) { this.onStopScanning(); @@ -156,23 +139,11 @@ export class Explore extends React.PureComponent { this.props.changeTime(this.props.exploreId, range); }; - onClickClear = () => { - this.props.clearQueries(this.props.exploreId); - }; - - onClickCloseSplit = () => { - this.props.splitClose(); - }; - // Use this in help pages to set page to a single query onClickExample = (query: DataQuery) => { this.props.setQueries(this.props.exploreId, [query]); }; - onClickSplit = () => { - this.props.splitOpen(); - }; - onClickLabel = (key: string, value: string) => { this.onModifyQueries({ type: 'ADD_FILTER', key, value }); }; @@ -204,10 +175,6 @@ export class Explore extends React.PureComponent { this.props.scanStop(this.props.exploreId); }; - onSubmit = () => { - this.props.runQueries(this.props.exploreId); - }; - render() { const { StartPage, @@ -215,11 +182,8 @@ export class Explore extends React.PureComponent { datasourceError, datasourceLoading, datasourceMissing, - exploreDatasources, exploreId, - loading, initialQueries, - range, showingStartPage, split, supportsGraph, @@ -227,64 +191,10 @@ export class Explore extends React.PureComponent { supportsTable, } = this.props; const exploreClass = split ? 'explore explore-split' : 'explore'; - const selectedDatasource = datasourceInstance - ? exploreDatasources.find(d => d.name === datasourceInstance.name) - : undefined; return (
-
- {exploreId === 'left' ? ( - - ) : ( - <> -
-
- -
- - )} - {!datasourceMissing ? ( -
- -
- ) : null} -
- {exploreId === 'left' && !split ? ( -
- -
- ) : null} - -
- -
-
- -
-
+ {datasourceLoading ?
Loading datasource...
: null} {datasourceMissing ? (
Please add a datasource that supports Explore (e.g., Prometheus).
@@ -341,30 +251,24 @@ function mapStateToProps(state: StoreState, { exploreId }) { datasourceInstance, datasourceLoading, datasourceMissing, - exploreDatasources, initialDatasource, initialQueries, initialized, - queryTransactions, range, showingStartPage, supportsGraph, supportsLogs, supportsTable, } = item; - const loading = queryTransactions.some(qt => !qt.done); return { StartPage, datasourceError, datasourceInstance, datasourceLoading, datasourceMissing, - exploreDatasources, initialDatasource, initialQueries, initialized, - loading, - queryTransactions, range, showingStartPage, split, @@ -375,18 +279,13 @@ function mapStateToProps(state: StoreState, { exploreId }) { } const mapDispatchToProps = { - changeDatasource, changeSize, changeTime, - clearQueries, initializeExplore, modifyQueries, - runQueries, scanStart, scanStop, setQueries, - splitClose, - splitOpen, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Explore)); diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx new file mode 100644 index 00000000000..35f06d11c81 --- /dev/null +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -0,0 +1,191 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'react-redux'; +import { hot } from 'react-hot-loader'; + +import { ExploreId } from 'app/types/explore'; +import { DataSourceSelectItem, RawTimeRange, TimeRange } from '@grafana/ui'; +import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; +import { StoreState } from 'app/types/store'; +import { changeDatasource, clearQueries, splitClose, runQueries, splitOpen } from './state/actions'; +import TimePicker from './TimePicker'; + +enum IconSide { + left = 'left', + right = 'right', +} + +const createResponsiveButton = (options: { + splitted: boolean; + title: string; + onClick: () => void; + buttonClassName?: string; + iconClassName?: string; + iconSide?: IconSide; +}) => { + const defaultOptions = { + iconSide: IconSide.left, + }; + const props = { ...options, defaultOptions }; + const { title, onClick, buttonClassName, iconClassName, splitted, iconSide } = props; + + return ( + + ); +}; + +interface OwnProps { + exploreId: ExploreId; + timepickerRef: React.RefObject; + onChangeTime: (range: TimeRange, changedByScanner?: boolean) => void; +} + +interface StateProps { + datasourceMissing: boolean; + exploreDatasources: DataSourceSelectItem[]; + loading: boolean; + range: RawTimeRange; + selectedDatasource: DataSourceSelectItem; + splitted: boolean; +} + +interface DispatchProps { + changeDatasource: typeof changeDatasource; + clearAll: typeof clearQueries; + runQuery: typeof runQueries; + closeSplit: typeof splitClose; + split: typeof splitOpen; +} + +type Props = StateProps & DispatchProps & OwnProps; + +export class UnConnectedExploreToolbar extends PureComponent { + constructor(props) { + super(props); + } + + onChangeDatasource = async option => { + this.props.changeDatasource(this.props.exploreId, option.value); + }; + + onClearAll = () => { + this.props.clearAll(this.props.exploreId); + }; + + onRunQuery = () => { + this.props.runQuery(this.props.exploreId); + }; + + render() { + const { + datasourceMissing, + exploreDatasources, + exploreId, + loading, + range, + selectedDatasource, + splitted, + timepickerRef, + } = this.props; + + return ( +
+
+
+
+ {exploreId === 'left' && ( + + + Explore + + )} +
+
+ {exploreId === 'right' && ( + + + + )} +
+
+
+
+
+ {!datasourceMissing ? ( +
+
+ +
+
+ ) : null} + {exploreId === 'left' && !splitted ? ( +
+ {createResponsiveButton({ + splitted, + title: 'Split', + onClick: this.props.split, + iconClassName: 'fa fa-fw fa-columns icon-margin-right', + iconSide: IconSide.left, + })} +
+ ) : null} +
+ +
+
+ +
+
+ {createResponsiveButton({ + splitted, + title: 'Run Query', + onClick: this.onRunQuery, + buttonClassName: 'navbar-button--primary', + iconClassName: loading ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-level-down fa-fw run-icon', + iconSide: IconSide.right, + })} +
+
+
+
+ ); + } +} + +const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => { + const splitted = state.explore.split; + const exploreItem = state.explore[exploreId]; + const { datasourceInstance, datasourceMissing, exploreDatasources, queryTransactions, range } = exploreItem; + const selectedDatasource = datasourceInstance + ? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name) + : undefined; + const loading = queryTransactions.some(qt => !qt.done); + + return { + datasourceMissing, + exploreDatasources, + loading, + range, + selectedDatasource, + splitted, + }; +}; + +const mapDispatchToProps: DispatchProps = { + changeDatasource, + clearAll: clearQueries, + runQuery: runQueries, + closeSplit: splitClose, + split: splitOpen, +}; + +export const ExploreToolbar = hot(module)(connect(mapStateToProps, mapDispatchToProps)(UnConnectedExploreToolbar)); diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index 24b8b8f5b16..d27213cea34 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -73,6 +73,7 @@ export class QueryField extends React.PureComponent { - this.setState({ - suggestions: [], - typeaheadIndex: 0, - typeaheadPrefix: '', - typeaheadContext: null, - }); - this.resetTimer = null; + if (this.mounted) { + this.setState({ + suggestions: [], + typeaheadIndex: 0, + typeaheadPrefix: '', + typeaheadContext: null, + }); + this.resetTimer = null; + } }; handleBlur = () => { diff --git a/public/app/features/explore/TimePicker.tsx b/public/app/features/explore/TimePicker.tsx index 8476c6b2b27..38c3f2e7498 100644 --- a/public/app/features/explore/TimePicker.tsx +++ b/public/app/features/explore/TimePicker.tsx @@ -293,6 +293,7 @@ export default class TimePicker extends PureComponent
diff --git a/public/app/features/explore/Wrapper.tsx b/public/app/features/explore/Wrapper.tsx index 770b6bd6588..aca2e6d8cbd 100644 --- a/public/app/features/explore/Wrapper.tsx +++ b/public/app/features/explore/Wrapper.tsx @@ -7,7 +7,7 @@ import { StoreState } from 'app/types'; import { ExploreId, ExploreUrlState } from 'app/types/explore'; import { parseUrlState } from 'app/core/utils/explore'; -import { initializeExploreSplit } from './state/actions'; +import { initializeExploreSplit, resetExplore } from './state/actions'; import ErrorBoundary from './ErrorBoundary'; import Explore from './Explore'; import { CustomScrollbar } from '@grafana/ui'; @@ -16,6 +16,7 @@ interface WrapperProps { initializeExploreSplit: typeof initializeExploreSplit; split: boolean; updateLocation: typeof updateLocation; + resetExplore: typeof resetExplore; urlStates: { [key: string]: string }; } @@ -42,6 +43,10 @@ export class Wrapper extends Component { } } + componentWillUnmount() { + this.props.resetExplore(); + } + render() { const { split } = this.props; const { leftState, rightState } = this.urlStates; @@ -74,6 +79,7 @@ const mapStateToProps = (state: StoreState) => { const mapDispatchToProps = { initializeExploreSplit, updateLocation, + resetExplore, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Wrapper)); diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts index 4e1d658f072..21918e1c013 100644 --- a/public/app/features/explore/state/actionTypes.ts +++ b/public/app/features/explore/state/actionTypes.ts @@ -1,6 +1,6 @@ // Types import { Emitter } from 'app/core/core'; -import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem } from '@grafana/ui/src/types'; +import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem } from '@grafana/ui/src/types'; import { ExploreId, ExploreItemState, @@ -41,6 +41,7 @@ export enum ActionTypes { ToggleGraph = 'explore/TOGGLE_GRAPH', ToggleLogs = 'explore/TOGGLE_LOGS', ToggleTable = 'explore/TOGGLE_TABLE', + ResetExplore = 'explore/RESET_EXPLORE', } export interface AddQueryRowAction { @@ -270,6 +271,11 @@ export interface ToggleLogsAction { }; } +export interface ResetExploreAction { + type: ActionTypes.ResetExplore; + payload: {}; +} + export type Action = | AddQueryRowAction | ChangeQueryAction @@ -297,4 +303,5 @@ export type Action = | SplitOpenAction | ToggleGraphAction | ToggleLogsAction - | ToggleTableAction; + | ToggleTableAction + | ResetExploreAction; diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index d4c42ffa9c7..59df0c47ef9 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -21,7 +21,7 @@ import { updateLocation } from 'app/core/actions'; // Types import { StoreState } from 'app/types'; -import { DataQuery, DataSourceSelectItem, QueryHint } from '@grafana/ui/src/types'; +import { DataQuery, DataSourceSelectItem, QueryHint } from '@grafana/ui/src/types'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { ExploreId, @@ -48,7 +48,6 @@ import { ScanStopAction, } from './actionTypes'; - type ThunkResult = ThunkAction; /** @@ -539,6 +538,7 @@ export function runQueries(exploreId: ExploreId) { if (!hasNonEmptyQuery(modifiedQueries)) { dispatch({ type: ActionTypes.RunQueriesEmpty, payload: { exploreId } }); + dispatch(stateSave()); // Remember to saves to state and update location return; } @@ -766,3 +766,12 @@ export function toggleTable(exploreId: ExploreId): ThunkResult { } }; } + +/** + * Resets state for explore. + */ +export function resetExplore(): ThunkResult { + return dispatch => { + dispatch({ type: ActionTypes.ResetExplore, payload: {} }); + }; +} diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 0331234d07d..5e9cc23c64f 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -422,25 +422,19 @@ export const itemReducer = (state, action: Action): ExploreItemState => { export const exploreReducer = (state = initialExploreState, action: Action): ExploreState => { switch (action.type) { case ActionTypes.SplitClose: { - return { - ...state, - split: false, - }; + return { ...state, split: false }; } case ActionTypes.SplitOpen: { - return { - ...state, - split: true, - right: action.payload.itemState, - }; + return { ...state, split: true, right: action.payload.itemState }; } case ActionTypes.InitializeExploreSplit: { - return { - ...state, - split: true, - }; + return { ...state, split: true }; + } + + case ActionTypes.ResetExplore: { + return initialExploreState; } } diff --git a/public/app/plugins/datasource/loki/language_provider.ts b/public/app/plugins/datasource/loki/language_provider.ts index 115a0a5f11f..631e61277b8 100644 --- a/public/app/plugins/datasource/loki/language_provider.ts +++ b/public/app/plugins/datasource/loki/language_provider.ts @@ -173,8 +173,9 @@ export default class LokiLanguageProvider extends LanguageProvider { }) ); } + // Return a cleaned LokiQuery return queries.map(query => ({ - ...query, + refId: query.refId, expr: '', })); } diff --git a/public/dashboards/home.json b/public/dashboards/home.json index 17795d64aa3..f2c441053bb 100644 --- a/public/dashboards/home.json +++ b/public/dashboards/home.json @@ -10,6 +10,23 @@ "id": null, "links": [], "panels": [ + { + "content": "
\n Home Dashboard\n
", + "editable": true, + "id": 1, + "links": [], + "mode": "html", + "style": {}, + "title": "", + "transparent": true, + "type": "text", + "gridPos": { + "w": 24, + "h": 3, + "x": 0, + "y": 0 + } + }, { "folderId": 0, "headings": true, @@ -28,7 +45,7 @@ "w": 12, "h": 17, "x": 0, - "y": 1 + "y": 6 } }, { @@ -43,7 +60,7 @@ "w": 12, "h": 17, "x": 12, - "y": 1 + "y": 6 } } ], diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 0ad72a78d6c..dce944dc7b2 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -1,180 +1,324 @@ -.explore { - flex: 1 1 auto; +.icon-margin-right { + margin-right: 0.25em; +} - &-container { - padding: $dashboard-padding; +.icon-margin-left { + margin-left: 0.25em; +} + +.run-icon { + transform: rotate(90deg); +} + +.timepicker { + display: flex; +} + +.timepicker-rangestring { + margin-left: 0.5em; +} + +.datasource-picker { + .ds-picker { + min-width: 200px; + max-width: 200px; + } +} + +.sidemenu-open { + .explore-toolbar-header { + padding: 0; + margin-left: 0; } - &-wrapper { - display: flex; - - > .explore-split { - width: 50%; + .explore-toolbar-header-title { + .navbar-page-btn { + padding-left: 0; } } +} - // Push split button a bit - .explore-first-button { - margin-left: 15px; +.explore-toolbar { + background: inherit; + display: flex; + flex-flow: row wrap; + justify-content: flex-start; + height: auto; + padding: 0px $dashboard-padding; + border-bottom: 1px solid #0000; + transition-duration: 0.35s; + transition-timing-function: ease-in-out; + transition-property: box-shadow, border-bottom; +} + +.explore-toolbar-item { + position: relative; + align-self: center; +} + +.explore-toolbar.splitted { + .explore-toolbar-item { + flex: 1 1 100%; } - .explore-panel { - margin-top: $panel-margin; + .explore-toolbar-content-item:first-child { + padding-left: 0; + margin-right: auto; } +} - .explore-panel__body { - padding: $panel-padding; - } +.explore-toolbar-item:last-child { + flex: auto; +} - .explore-panel__header { - padding: $panel-padding; - padding-top: 5px; - padding-bottom: 0; - display: flex; - cursor: pointer; - margin-bottom: 5px; - transition: all 0.1s linear; - } +.explore-toolbar-header { + display: flex; + flex: 1 1 0; + flex-flow: row nowrap; + font-size: 18px; + min-height: 55px; + line-height: 55px; + justify-content: space-between; + margin-left: $panel-margin * 3; +} - .explore-panel__header-label { - font-weight: 500; - margin-right: $panel-margin; - font-size: $font-size-h6; - box-shadow: $text-shadow-faint; - } +.explore-toolbar-header { + justify-content: space-between; + align-items: center; +} - .explore-panel__header-buttons { - margin-right: $panel-margin; - font-size: $font-size-lg; - line-height: $font-size-h6; - } - - // Make sure wrap buttons around on small screens - .navbar { - flex-wrap: wrap; - height: auto; - } +.explore-toolbar-header-title { + color: darken($link-color, 5%); .navbar-page-btn { - margin-right: 1rem; + padding-left: $dashboard-padding; + } - // Explore icon in header - .fa { - font-size: 100%; - opacity: 0.75; - margin-right: 0.5em; + .fa { + font-size: 100%; + opacity: 0.75; + margin-right: 0.5em; + } +} + +.explore-toolbar-header-close { + margin-left: auto; +} + +.explore-toolbar-content { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: space-between; +} + +.explore-toolbar-content-item { + padding: 10px 2px; +} + +.explore-toolbar-content-item:first-child { + padding-left: $dashboard-padding; + margin-right: auto; +} + +@media only screen and (max-width: 1545px) { + .explore-toolbar.splitted { + .timepicker-rangestring { + display: none; } } +} - // Toggle mode - .navbar-button.active { - color: $btn-active-text-color; - background-color: $btn-active-bg; - } - - .navbar-button--no-icon { - line-height: 18px; - } - - .result-options { - margin: 2 * $panel-margin 0; - } - - .time-series-disclaimer { - width: 300px; - margin: $panel-margin auto; - padding: 10px 0; - border-radius: $border-radius; - text-align: center; - background-color: $panel-bg; - - .disclaimer-icon { - color: $yellow; - margin-right: $panel-margin/2; - } - - .show-all-time-series { - cursor: pointer; - color: $external-link-color; - } - } - - .navbar .elapsed-time { - position: absolute; - left: 0; - right: 0; - top: 3.5rem; - text-align: center; - font-size: 0.8rem; - } - - .graph-legend { - flex-wrap: wrap; - } - - .explore-panel__loader { - height: 2px; - position: relative; - overflow: hidden; - background: none; - margin: $panel-margin / 2; - } - - .explore-panel__loader--active:after { - content: ' '; - display: block; - width: 25%; - top: 0; - top: -50%; - height: 250%; - position: absolute; - animation: loader 2s cubic-bezier(0.17, 0.67, 0.83, 0.67) 500ms; - animation-iteration-count: 100; - left: -25%; - background: $blue; - } - - @keyframes loader { - from { - left: -25%; - opacity: .1; - } - to { - opacity: 1; - left: 100%; - } - } - - .datasource-picker { - min-width: 200px; - } - +@media only screen and (max-width: 1070px) { .timepicker { - display: flex; - - &-rangestring { - margin-left: 0.5em; + .timepicker-rangestring { + display: none; } } - .run-icon { - margin-left: 0.25em; - transform: rotate(90deg); + .explore-toolbar-content { + justify-content: flex-start; } - .relative { - position: relative; + .explore-toolbar.splitted { + .explore-toolbar-content-item { + padding: 2px 0; + margin: 0; + } } - .link { - text-decoration: underline; + .explore-toolbar-content-item { + padding: 2px 2px; } } +@media only screen and (max-width: 803px) { + .sidemenu-open { + .explore-toolbar-header-title { + .navbar-page-btn { + padding-left: 0; + margin-left: 0; + } + } + } + + .explore-toolbar-header-title { + .navbar-page-btn { + padding-left: 0; + margin-left: $dashboard-padding; + } + } + + .btn-title { + display: none; + } +} + +@media only screen and (max-width: 702px) { + .explore-toolbar-content-item:first-child { + padding-left: 2px; + margin-right: 0; + } +} + +@media only screen and (max-width: 544px) { + .sidemenu-open { + .explore-toolbar-header-title { + .navbar-page-btn { + padding-left: 0; + margin-left: $dashboard-padding; + } + } + } + + .explore-toolbar-header-title { + .navbar-page-btn { + padding-left: 0; + margin-left: $dashboard-padding; + } + } +} + +.explore { + flex: 1 1 auto; +} + .explore + .explore { border-left: 1px dotted $table-border; } +.explore-container { + padding: $dashboard-padding; +} + +.explore-wrapper { + display: flex; + + > .explore-split { + width: 50%; + } +} + +.explore-panel { + margin-top: $panel-margin; +} + +.explore-panel__body { + padding: $panel-padding; +} + +.explore-panel__header { + padding: $panel-padding; + padding-top: 5px; + padding-bottom: 0; + display: flex; + cursor: pointer; + margin-bottom: 5px; + transition: all 0.1s linear; +} + +.explore-panel__header-label { + font-weight: 500; + margin-right: $panel-margin; + font-size: $font-size-h6; + box-shadow: $text-shadow-faint; +} + +.explore-panel__header-buttons { + margin-right: $panel-margin; + font-size: $font-size-lg; + line-height: $font-size-h6; +} + +.result-options { + margin: 2 * $panel-margin 0; +} + +.time-series-disclaimer { + width: 300px; + margin: $panel-margin auto; + padding: 10px 0; + border-radius: $border-radius; + text-align: center; + background-color: $panel-bg; + + .disclaimer-icon { + color: $yellow; + margin-right: $panel-margin/2; + } + + .show-all-time-series { + cursor: pointer; + color: $external-link-color; + } +} + +.navbar .elapsed-time { + position: absolute; + left: 0; + right: 0; + top: 3.5rem; + text-align: center; + font-size: 0.8rem; +} + +.graph-legend { + flex-wrap: wrap; +} + +.explore-panel__loader { + height: 2px; + position: relative; + overflow: hidden; + background: none; + margin: $panel-margin / 2; +} + +.explore-panel__loader--active:after { + content: ' '; + display: block; + width: 25%; + top: 0; + top: -50%; + height: 250%; + position: absolute; + animation: loader 2s cubic-bezier(0.17, 0.67, 0.83, 0.67) 500ms; + animation-iteration-count: 100; + left: -25%; + background: $blue; +} + +@keyframes loader { + from { + left: -25%; + opacity: 0.1; + } + to { + left: 100%; + opacity: 1; + } +} + .query-row { display: flex; position: relative; diff --git a/scripts/build/publish.go b/scripts/build/publish.go deleted file mode 100644 index 0cb776e2b99..00000000000 --- a/scripts/build/publish.go +++ /dev/null @@ -1,194 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "log" - "net/http" - "os" - "path/filepath" - "regexp" - "strings" - "time" -) - -var apiURL = flag.String("apiUrl", "https://grafana.com/api", "api url") -var apiKey = flag.String("apiKey", "", "api key") -var version = "" -var versionRe = regexp.MustCompile(`grafana-(.*)(\.|_)(arm64|armhfp|aarch64|armv7|darwin|linux|windows|x86_64)`) -var debVersionRe = regexp.MustCompile(`grafana_(.*)_(arm64|armv7|armhf|amd64)\.deb`) -var builds = []build{} -var architectureMapping = map[string]string{ - "armv7": "armv7", - "armhfp": "armv7", - "armhf": "armv7", - "arm64": "arm64", - "aarch64": "arm64", - "amd64": "amd64", - "x86_64": "amd64", -} - -func main() { - flag.Parse() - if *apiKey == "" { - log.Fatalf("Require apiKey command line parameters") - } - - err := filepath.Walk("dist", packageWalker) - if err != nil { - log.Fatalf("Cannot find any packages to publish, %v", err) - } - - if version == "" { - log.Fatalf("No version found") - } - - if len(builds) == 0 { - log.Fatalf("No builds found") - } - - nightly := release{ - Version: version, - ReleaseDate: time.Now(), - Stable: false, - Nightly: true, - Beta: false, - WhatsNewURL: "", - ReleaseNotesURL: "", - Builds: builds, - } - - postRequest("/grafana/versions", nightly, fmt.Sprintf("Create Release %s", nightly.Version)) - postRequest("/grafana/versions/"+nightly.Version, nightly, fmt.Sprintf("Update Release %s", nightly.Version)) - - for _, b := range nightly.Builds { - postRequest(fmt.Sprintf("/grafana/versions/%s/packages", nightly.Version), b, fmt.Sprintf("Create Build %s %s", b.Os, b.Arch)) - postRequest(fmt.Sprintf("/grafana/versions/%s/packages/%s/%s", nightly.Version, b.Arch, b.Os), b, fmt.Sprintf("Update Build %s %s", b.Os, b.Arch)) - } -} - -func mapPackage(path string, name string, shaBytes []byte) (build, error) { - log.Printf("Finding package file %s", name) - result := versionRe.FindSubmatch([]byte(name)) - debResult := debVersionRe.FindSubmatch([]byte(name)) - - if len(result) > 0 { - version = string(result[1]) - log.Printf("Version detected: %v", version) - } else if len(debResult) > 0 { - version = string(debResult[1]) - } else { - return build{}, fmt.Errorf("Unable to figure out version from '%v'", name) - } - - os := "" - if strings.Contains(name, "linux") { - os = "linux" - } - if strings.HasSuffix(name, "windows-amd64.zip") { - os = "win" - } - if strings.HasSuffix(name, "darwin-amd64.tar.gz") { - os = "darwin" - } - if strings.HasSuffix(name, ".rpm") { - os = "rhel" - } - if strings.HasSuffix(name, ".deb") { - os = "deb" - } - if os == "" { - return build{}, fmt.Errorf("Unable to figure out os from '%v'", name) - } - - arch := "" - for archListed, archReal := range architectureMapping { - if strings.Contains(name, archListed) { - arch = archReal - break - } - } - if arch == "" { - return build{}, fmt.Errorf("Unable to figure out arch from '%v'", name) - } - - return build{ - Os: os, - Arch: arch, - URL: "https://s3-us-west-2.amazonaws.com/grafana-releases/master/" + name, - Sha256: string(shaBytes), - }, nil -} - -func packageWalker(path string, f os.FileInfo, err error) error { - if err != nil { - log.Printf("error: %v", err) - } - if f.Name() == "dist" || strings.Contains(f.Name(), "sha256") || strings.Contains(f.Name(), "latest") { - return nil - } - - shaBytes, err := ioutil.ReadFile(path + ".sha256") - if err != nil { - log.Fatalf("Failed to read sha256 file %v", err) - } - - build, err := mapPackage(path, f.Name(), shaBytes) - if err != nil { - log.Printf("Could not map metadata from package: %v", err) - return nil - } - - builds = append(builds, build) - return nil -} - -func postRequest(url string, obj interface{}, desc string) { - jsonBytes, _ := json.Marshal(obj) - req, _ := http.NewRequest(http.MethodPost, (*apiURL)+url, bytes.NewReader(jsonBytes)) - req.Header.Add("Authorization", "Bearer "+(*apiKey)) - req.Header.Add("Content-Type", "application/json") - - res, err := http.DefaultClient.Do(req) - if err != nil { - log.Fatalf("error: %v", err) - } - - if res.StatusCode == http.StatusOK { - log.Printf("Action: %s \t OK", desc) - } else { - - if res.Body != nil { - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - if strings.Contains(string(body), "already exists") || strings.Contains(string(body), "Nothing to update") { - log.Printf("Action: %s \t Already exists", desc) - } else { - log.Printf("Action: %s \t Failed - Status: %v", desc, res.Status) - log.Printf("Resp: %s", body) - log.Fatalf("Quitting") - } - } - } -} - -type release struct { - Version string `json:"version"` - ReleaseDate time.Time `json:"releaseDate"` - Stable bool `json:"stable"` - Beta bool `json:"beta"` - Nightly bool `json:"nightly"` - WhatsNewURL string `json:"whatsNewUrl"` - ReleaseNotesURL string `json:"releaseNotesUrl"` - Builds []build `json:"-"` -} - -type build struct { - Os string `json:"os"` - URL string `json:"url"` - Sha256 string `json:"sha256"` - Arch string `json:"arch"` -} diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index 77b7e18b724..8fd139c2638 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -127,11 +127,21 @@ var completeBuildArtifactConfigurations = []buildArtifact{ arch: "armv7", urlPostfix: "_armhf.deb", }, + { + os: "deb", + arch: "armv6", + urlPostfix: "_armel.deb", + }, { os: "rhel", arch: "armv7", urlPostfix: ".armhfp.rpm", }, + { + os: "linux", + arch: "armv6", + urlPostfix: ".linux-armv6.tar.gz", + }, { os: "linux", arch: "armv7",