mirror of https://github.com/grafana/grafana
commit
08d6540cbb
@ -1,4 +1,4 @@ |
||||
{ |
||||
"stable": "6.0.1", |
||||
"testing": "6.0.1" |
||||
"stable": "6.0.2", |
||||
"testing": "6.0.2" |
||||
} |
||||
|
||||
@ -0,0 +1,16 @@ |
||||
import React from 'react'; |
||||
import { storiesOf } from '@storybook/react'; |
||||
import { action } from '@storybook/addon-actions'; |
||||
|
||||
import { ThresholdsEditor } from './ThresholdsEditor'; |
||||
|
||||
const ThresholdsEditorStories = storiesOf('UI/ThresholdsEditor', module); |
||||
const thresholds = [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 50, color: 'red' }]; |
||||
|
||||
ThresholdsEditorStories.add('default', () => { |
||||
return <ThresholdsEditor thresholds={[]} onChange={action('Thresholds changed')} />; |
||||
}); |
||||
|
||||
ThresholdsEditorStories.add('with thresholds', () => { |
||||
return <ThresholdsEditor thresholds={thresholds} onChange={action('Thresholds changed')} />; |
||||
}); |
||||
@ -1,7 +1,450 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render with base threshold 1`] = ` |
||||
<ContextConsumer> |
||||
<Component /> |
||||
</ContextConsumer> |
||||
<ThresholdsEditor |
||||
onChange={ |
||||
[MockFunction] { |
||||
"calls": Array [ |
||||
Array [ |
||||
Array [ |
||||
Object { |
||||
"color": "#7EB26D", |
||||
"index": 0, |
||||
"value": -Infinity, |
||||
}, |
||||
], |
||||
], |
||||
], |
||||
"results": Array [ |
||||
Object { |
||||
"isThrow": false, |
||||
"value": undefined, |
||||
}, |
||||
], |
||||
} |
||||
} |
||||
thresholds={Array []} |
||||
> |
||||
<Component |
||||
title="Thresholds" |
||||
> |
||||
<div |
||||
className="panel-options-group" |
||||
> |
||||
<div |
||||
className="panel-options-group__header" |
||||
> |
||||
<span |
||||
className="panel-options-group__title" |
||||
> |
||||
Thresholds |
||||
</span> |
||||
</div> |
||||
<div |
||||
className="panel-options-group__body" |
||||
> |
||||
<div |
||||
className="thresholds" |
||||
> |
||||
<div |
||||
className="thresholds-row" |
||||
key="0-0" |
||||
> |
||||
<div |
||||
className="thresholds-row-add-button" |
||||
onClick={[Function]} |
||||
> |
||||
<i |
||||
className="fa fa-plus" |
||||
/> |
||||
</div> |
||||
<div |
||||
className="thresholds-row-color-indicator" |
||||
style={ |
||||
Object { |
||||
"backgroundColor": "#7EB26D", |
||||
} |
||||
} |
||||
/> |
||||
<div |
||||
className="thresholds-row-input" |
||||
> |
||||
<div |
||||
className="thresholds-row-input-inner" |
||||
> |
||||
<span |
||||
className="thresholds-row-input-inner-arrow" |
||||
/> |
||||
<div |
||||
className="thresholds-row-input-inner-color" |
||||
> |
||||
<div |
||||
className="thresholds-row-input-inner-color-colorpicker" |
||||
> |
||||
<WithTheme(ColorPicker) |
||||
color="#7EB26D" |
||||
enableNamedColors={true} |
||||
onChange={[Function]} |
||||
> |
||||
<ColorPicker |
||||
color="#7EB26D" |
||||
enableNamedColors={true} |
||||
onChange={[Function]} |
||||
theme={ |
||||
Object { |
||||
"background": Object { |
||||
"dropdown": "#1f1f20", |
||||
"scrollbar": "#343436", |
||||
"scrollbar2": "#343436", |
||||
}, |
||||
"border": Object { |
||||
"radius": Object { |
||||
"lg": "5px", |
||||
"md": "3px", |
||||
"sm": "2px", |
||||
}, |
||||
"width": Object { |
||||
"sm": "1px", |
||||
}, |
||||
}, |
||||
"breakpoints": Object { |
||||
"lg": "992px", |
||||
"md": "768px", |
||||
"sm": "544px", |
||||
"xl": "1200px", |
||||
"xs": "0", |
||||
}, |
||||
"colors": Object { |
||||
"black": "#000000", |
||||
"blue": "#33b5e5", |
||||
"blueBase": "#3274d9", |
||||
"blueFaint": "#041126", |
||||
"blueLight": "#5794f2", |
||||
"blueShade": "#1f60c4", |
||||
"body": "#d8d9da", |
||||
"bodyBg": "#161719", |
||||
"brandDanger": "#e02f44", |
||||
"brandPrimary": "#eb7b18", |
||||
"brandSuccess": "#299c46", |
||||
"brandWarning": "#eb7b18", |
||||
"critical": "#e02f44", |
||||
"dark1": "#141414", |
||||
"dark10": "#424345", |
||||
"dark2": "#161719", |
||||
"dark3": "#1f1f20", |
||||
"dark4": "#212124", |
||||
"dark5": "#222426", |
||||
"dark6": "#262628", |
||||
"dark7": "#292a2d", |
||||
"dark8": "#2f2f32", |
||||
"dark9": "#343436", |
||||
"gray1": "#555555", |
||||
"gray2": "#8e8e8e", |
||||
"gray3": "#b3b3b3", |
||||
"gray4": "#d8d9da", |
||||
"gray5": "#ececec", |
||||
"gray6": "#f4f5f8", |
||||
"gray7": "#fbfbfb", |
||||
"grayBlue": "#212327", |
||||
"greenBase": "#299c46", |
||||
"greenShade": "#23843b", |
||||
"headingColor": "#e3e3e3", |
||||
"inputBlack": "#09090b", |
||||
"link": "#e3e3e3", |
||||
"linkDisabled": "#e3e3e3", |
||||
"linkExternal": "#33b5e5", |
||||
"linkHover": "#ffffff", |
||||
"online": "#299c46", |
||||
"orange": "#eb7b18", |
||||
"pageBg": "#161719", |
||||
"purple": "#9933cc", |
||||
"queryGreen": "#74e680", |
||||
"queryKeyword": "#66d9ef", |
||||
"queryOrange": "#eb7b18", |
||||
"queryPurple": "#fe85fc", |
||||
"queryRed": "#e02f44", |
||||
"red": "#d44a3a", |
||||
"redBase": "#e02f44", |
||||
"redShade": "#c4162a", |
||||
"text": "#d8d9da", |
||||
"textEmphasis": "#ececec", |
||||
"textFaint": "#222426", |
||||
"textStrong": "#ffffff", |
||||
"textWeak": "#8e8e8e", |
||||
"variable": "#32d1df", |
||||
"warn": "#f79520", |
||||
"white": "#ffffff", |
||||
"yellow": "#ecbb13", |
||||
}, |
||||
"name": "Grafana Dark", |
||||
"panelPadding": Object { |
||||
"horizontal": 10, |
||||
"vertical": 5, |
||||
}, |
||||
"spacing": Object { |
||||
"d": "14px", |
||||
"gutter": "30px", |
||||
"lg": "24px", |
||||
"md": "16px", |
||||
"sm": "8px", |
||||
"xl": "32px", |
||||
"xs": "4px", |
||||
"xxs": "2px", |
||||
}, |
||||
"type": "dark", |
||||
"typography": Object { |
||||
"fontFamily": Object { |
||||
"monospace": "Menlo, Monaco, Consolas, 'Courier New', monospace", |
||||
"sansSerif": "'Roboto', Helvetica, Arial, sans-serif", |
||||
}, |
||||
"heading": Object { |
||||
"h1": "28px", |
||||
"h2": "24px", |
||||
"h3": "21px", |
||||
"h4": "18px", |
||||
"h5": "16px", |
||||
"h6": "14px", |
||||
}, |
||||
"lineHeight": Object { |
||||
"lg": 1.5, |
||||
"md": 1.3333333333333333, |
||||
"sm": 1.1, |
||||
"xs": 1, |
||||
}, |
||||
"size": Object { |
||||
"base": "13px", |
||||
"lg": "18px", |
||||
"md": "14px", |
||||
"root": "14px", |
||||
"sm": "12px", |
||||
"xs": "10px", |
||||
}, |
||||
"weight": Object { |
||||
"light": 300, |
||||
"regular": 400, |
||||
"semibold": 500, |
||||
}, |
||||
}, |
||||
} |
||||
} |
||||
> |
||||
<PopperController |
||||
content={ |
||||
<ColorPickerPopover |
||||
color="#7EB26D" |
||||
enableNamedColors={true} |
||||
onChange={[Function]} |
||||
theme={ |
||||
Object { |
||||
"background": Object { |
||||
"dropdown": "#1f1f20", |
||||
"scrollbar": "#343436", |
||||
"scrollbar2": "#343436", |
||||
}, |
||||
"border": Object { |
||||
"radius": Object { |
||||
"lg": "5px", |
||||
"md": "3px", |
||||
"sm": "2px", |
||||
}, |
||||
"width": Object { |
||||
"sm": "1px", |
||||
}, |
||||
}, |
||||
"breakpoints": Object { |
||||
"lg": "992px", |
||||
"md": "768px", |
||||
"sm": "544px", |
||||
"xl": "1200px", |
||||
"xs": "0", |
||||
}, |
||||
"colors": Object { |
||||
"black": "#000000", |
||||
"blue": "#33b5e5", |
||||
"blueBase": "#3274d9", |
||||
"blueFaint": "#041126", |
||||
"blueLight": "#5794f2", |
||||
"blueShade": "#1f60c4", |
||||
"body": "#d8d9da", |
||||
"bodyBg": "#161719", |
||||
"brandDanger": "#e02f44", |
||||
"brandPrimary": "#eb7b18", |
||||
"brandSuccess": "#299c46", |
||||
"brandWarning": "#eb7b18", |
||||
"critical": "#e02f44", |
||||
"dark1": "#141414", |
||||
"dark10": "#424345", |
||||
"dark2": "#161719", |
||||
"dark3": "#1f1f20", |
||||
"dark4": "#212124", |
||||
"dark5": "#222426", |
||||
"dark6": "#262628", |
||||
"dark7": "#292a2d", |
||||
"dark8": "#2f2f32", |
||||
"dark9": "#343436", |
||||
"gray1": "#555555", |
||||
"gray2": "#8e8e8e", |
||||
"gray3": "#b3b3b3", |
||||
"gray4": "#d8d9da", |
||||
"gray5": "#ececec", |
||||
"gray6": "#f4f5f8", |
||||
"gray7": "#fbfbfb", |
||||
"grayBlue": "#212327", |
||||
"greenBase": "#299c46", |
||||
"greenShade": "#23843b", |
||||
"headingColor": "#e3e3e3", |
||||
"inputBlack": "#09090b", |
||||
"link": "#e3e3e3", |
||||
"linkDisabled": "#e3e3e3", |
||||
"linkExternal": "#33b5e5", |
||||
"linkHover": "#ffffff", |
||||
"online": "#299c46", |
||||
"orange": "#eb7b18", |
||||
"pageBg": "#161719", |
||||
"purple": "#9933cc", |
||||
"queryGreen": "#74e680", |
||||
"queryKeyword": "#66d9ef", |
||||
"queryOrange": "#eb7b18", |
||||
"queryPurple": "#fe85fc", |
||||
"queryRed": "#e02f44", |
||||
"red": "#d44a3a", |
||||
"redBase": "#e02f44", |
||||
"redShade": "#c4162a", |
||||
"text": "#d8d9da", |
||||
"textEmphasis": "#ececec", |
||||
"textFaint": "#222426", |
||||
"textStrong": "#ffffff", |
||||
"textWeak": "#8e8e8e", |
||||
"variable": "#32d1df", |
||||
"warn": "#f79520", |
||||
"white": "#ffffff", |
||||
"yellow": "#ecbb13", |
||||
}, |
||||
"name": "Grafana Dark", |
||||
"panelPadding": Object { |
||||
"horizontal": 10, |
||||
"vertical": 5, |
||||
}, |
||||
"spacing": Object { |
||||
"d": "14px", |
||||
"gutter": "30px", |
||||
"lg": "24px", |
||||
"md": "16px", |
||||
"sm": "8px", |
||||
"xl": "32px", |
||||
"xs": "4px", |
||||
"xxs": "2px", |
||||
}, |
||||
"type": "dark", |
||||
"typography": Object { |
||||
"fontFamily": Object { |
||||
"monospace": "Menlo, Monaco, Consolas, 'Courier New', monospace", |
||||
"sansSerif": "'Roboto', Helvetica, Arial, sans-serif", |
||||
}, |
||||
"heading": Object { |
||||
"h1": "28px", |
||||
"h2": "24px", |
||||
"h3": "21px", |
||||
"h4": "18px", |
||||
"h5": "16px", |
||||
"h6": "14px", |
||||
}, |
||||
"lineHeight": Object { |
||||
"lg": 1.5, |
||||
"md": 1.3333333333333333, |
||||
"sm": 1.1, |
||||
"xs": 1, |
||||
}, |
||||
"size": Object { |
||||
"base": "13px", |
||||
"lg": "18px", |
||||
"md": "14px", |
||||
"root": "14px", |
||||
"sm": "12px", |
||||
"xs": "10px", |
||||
}, |
||||
"weight": Object { |
||||
"light": 300, |
||||
"regular": 400, |
||||
"semibold": 500, |
||||
}, |
||||
}, |
||||
} |
||||
} |
||||
/> |
||||
} |
||||
hideAfter={300} |
||||
> |
||||
<ForwardRef(ColorPickerTrigger) |
||||
color="#7EB26D" |
||||
onClick={[Function]} |
||||
onMouseLeave={[Function]} |
||||
> |
||||
<div |
||||
onClick={[Function]} |
||||
onMouseLeave={[Function]} |
||||
style={ |
||||
Object { |
||||
"background": "inherit", |
||||
"border": "none", |
||||
"borderRadius": 10, |
||||
"color": "inherit", |
||||
"cursor": "pointer", |
||||
"overflow": "hidden", |
||||
"padding": 0, |
||||
} |
||||
} |
||||
> |
||||
<div |
||||
style={ |
||||
Object { |
||||
"backgroundImage": "url(data:image/png,base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)", |
||||
"border": "none", |
||||
"float": "left", |
||||
"height": 15, |
||||
"margin": 0, |
||||
"position": "relative", |
||||
"width": 15, |
||||
"zIndex": 0, |
||||
} |
||||
} |
||||
> |
||||
<div |
||||
style={ |
||||
Object { |
||||
"backgroundColor": "#7EB26D", |
||||
"bottom": 0, |
||||
"display": "block", |
||||
"left": 0, |
||||
"position": "absolute", |
||||
"right": 0, |
||||
"top": 0, |
||||
} |
||||
} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</ForwardRef(ColorPickerTrigger)> |
||||
</PopperController> |
||||
</ColorPicker> |
||||
</WithTheme(ColorPicker)> |
||||
</div> |
||||
</div> |
||||
<div |
||||
className="thresholds-row-input-inner-value" |
||||
> |
||||
<input |
||||
readOnly={true} |
||||
type="text" |
||||
value="Base" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</Component> |
||||
</ThresholdsEditor> |
||||
`; |
||||
|
||||
@ -0,0 +1,24 @@ |
||||
import { ValidationRule, ValidationEvents } from '../types/input'; |
||||
|
||||
export enum EventsWithValidation { |
||||
onBlur = 'onBlur', |
||||
onFocus = 'onFocus', |
||||
onChange = 'onChange', |
||||
} |
||||
|
||||
export const validate = (value: string, validationRules: ValidationRule[]) => { |
||||
const errors = validationRules.reduce( |
||||
(acc, currRule) => { |
||||
if (!currRule.rule(value)) { |
||||
return acc.concat(currRule.errorMessage); |
||||
} |
||||
return acc; |
||||
}, |
||||
[] as string[] |
||||
); |
||||
return errors.length > 0 ? errors : null; |
||||
}; |
||||
|
||||
export const hasValidationEvent = (event: EventsWithValidation, validationEvents: ValidationEvents | undefined) => { |
||||
return validationEvents && validationEvents[event]; |
||||
}; |
||||
@ -0,0 +1,55 @@ |
||||
package dashboards |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"time" |
||||
) |
||||
|
||||
func MakeUserAdmin(bus bus.Bus, orgId int64, userId int64, dashboardId int64, setViewAndEditPermissions bool) error { |
||||
rtEditor := models.ROLE_EDITOR |
||||
rtViewer := models.ROLE_VIEWER |
||||
|
||||
items := []*models.DashboardAcl{ |
||||
{ |
||||
OrgId: orgId, |
||||
DashboardId: dashboardId, |
||||
UserId: userId, |
||||
Permission: models.PERMISSION_ADMIN, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
}, |
||||
} |
||||
|
||||
if setViewAndEditPermissions { |
||||
items = append(items, |
||||
&models.DashboardAcl{ |
||||
OrgId: orgId, |
||||
DashboardId: dashboardId, |
||||
Role: &rtEditor, |
||||
Permission: models.PERMISSION_EDIT, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
}, |
||||
&models.DashboardAcl{ |
||||
OrgId: orgId, |
||||
DashboardId: dashboardId, |
||||
Role: &rtViewer, |
||||
Permission: models.PERMISSION_VIEW, |
||||
Created: time.Now(), |
||||
Updated: time.Now(), |
||||
}, |
||||
) |
||||
} |
||||
|
||||
aclCmd := &models.UpdateDashboardAclCommand{ |
||||
DashboardId: dashboardId, |
||||
Items: items, |
||||
} |
||||
|
||||
if err := bus.Dispatch(aclCmd); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
@ -0,0 +1,34 @@ |
||||
package teamguardian |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/bus" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
) |
||||
|
||||
func CanAdmin(bus bus.Bus, orgId int64, teamId int64, user *m.SignedInUser) error { |
||||
if user.OrgRole == m.ROLE_ADMIN { |
||||
return nil |
||||
} |
||||
|
||||
if user.OrgId != orgId { |
||||
return m.ErrNotAllowedToUpdateTeamInDifferentOrg |
||||
} |
||||
|
||||
cmd := m.GetTeamMembersQuery{ |
||||
OrgId: orgId, |
||||
TeamId: teamId, |
||||
UserId: user.UserId, |
||||
} |
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, member := range cmd.Result { |
||||
if member.UserId == user.UserId && member.Permission == m.PERMISSION_ADMIN { |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
return m.ErrNotAllowedToUpdateTeam |
||||
} |
||||
@ -0,0 +1,87 @@ |
||||
package teamguardian |
||||
|
||||
import ( |
||||
"github.com/grafana/grafana/pkg/bus" |
||||
m "github.com/grafana/grafana/pkg/models" |
||||
. "github.com/smartystreets/goconvey/convey" |
||||
"testing" |
||||
) |
||||
|
||||
func TestUpdateTeam(t *testing.T) { |
||||
Convey("Updating a team", t, func() { |
||||
bus.ClearBusHandlers() |
||||
|
||||
admin := m.SignedInUser{ |
||||
UserId: 1, |
||||
OrgId: 1, |
||||
OrgRole: m.ROLE_ADMIN, |
||||
} |
||||
editor := m.SignedInUser{ |
||||
UserId: 2, |
||||
OrgId: 1, |
||||
OrgRole: m.ROLE_EDITOR, |
||||
} |
||||
testTeam := m.Team{ |
||||
Id: 1, |
||||
OrgId: 1, |
||||
} |
||||
|
||||
Convey("Given an editor and a team he isn't a member of", func() { |
||||
Convey("Should not be able to update the team", func() { |
||||
bus.AddHandler("test", func(cmd *m.GetTeamMembersQuery) error { |
||||
cmd.Result = []*m.TeamMemberDTO{} |
||||
return nil |
||||
}) |
||||
|
||||
err := CanAdmin(bus.GetBus(), testTeam.OrgId, testTeam.Id, &editor) |
||||
So(err, ShouldEqual, m.ErrNotAllowedToUpdateTeam) |
||||
}) |
||||
}) |
||||
|
||||
Convey("Given an editor and a team he is an admin in", func() { |
||||
Convey("Should be able to update the team", func() { |
||||
bus.AddHandler("test", func(cmd *m.GetTeamMembersQuery) error { |
||||
cmd.Result = []*m.TeamMemberDTO{{ |
||||
OrgId: testTeam.OrgId, |
||||
TeamId: testTeam.Id, |
||||
UserId: editor.UserId, |
||||
Permission: m.PERMISSION_ADMIN, |
||||
}} |
||||
return nil |
||||
}) |
||||
|
||||
err := CanAdmin(bus.GetBus(), testTeam.OrgId, testTeam.Id, &editor) |
||||
So(err, ShouldBeNil) |
||||
}) |
||||
}) |
||||
|
||||
Convey("Given an editor and a team in another org", func() { |
||||
testTeamOtherOrg := m.Team{ |
||||
Id: 1, |
||||
OrgId: 2, |
||||
} |
||||
|
||||
Convey("Shouldn't be able to update the team", func() { |
||||
bus.AddHandler("test", func(cmd *m.GetTeamMembersQuery) error { |
||||
cmd.Result = []*m.TeamMemberDTO{{ |
||||
OrgId: testTeamOtherOrg.OrgId, |
||||
TeamId: testTeamOtherOrg.Id, |
||||
UserId: editor.UserId, |
||||
Permission: m.PERMISSION_ADMIN, |
||||
}} |
||||
return nil |
||||
}) |
||||
|
||||
err := CanAdmin(bus.GetBus(), testTeamOtherOrg.OrgId, testTeamOtherOrg.Id, &editor) |
||||
So(err, ShouldEqual, m.ErrNotAllowedToUpdateTeamInDifferentOrg) |
||||
}) |
||||
}) |
||||
|
||||
Convey("Given an org admin and a team", func() { |
||||
Convey("Should be able to update the team", func() { |
||||
err := CanAdmin(bus.GetBus(), testTeam.OrgId, testTeam.Id, &admin) |
||||
So(err, ShouldBeNil) |
||||
}) |
||||
}) |
||||
}) |
||||
} |
||||
@ -1 +0,0 @@ |
||||
export { Input } from './Input'; |
||||
@ -0,0 +1,13 @@ |
||||
import React, { FunctionComponent } from 'react'; |
||||
|
||||
export interface Props { |
||||
featureToggle: boolean; |
||||
} |
||||
|
||||
export const WithFeatureToggle: FunctionComponent<Props> = ({ featureToggle, children }) => { |
||||
if (featureToggle === true) { |
||||
return <>{children}</>; |
||||
} |
||||
|
||||
return null; |
||||
}; |
||||
@ -0,0 +1,30 @@ |
||||
import { DataQuery } from '@grafana/ui'; |
||||
import { getNextRefIdChar } from './query'; |
||||
|
||||
const dataQueries: DataQuery[] = [ |
||||
{ |
||||
refId: 'A', |
||||
}, |
||||
{ |
||||
refId: 'B', |
||||
}, |
||||
{ |
||||
refId: 'C', |
||||
}, |
||||
{ |
||||
refId: 'D', |
||||
}, |
||||
{ |
||||
refId: 'E', |
||||
}, |
||||
]; |
||||
|
||||
describe('Get next refId char', () => { |
||||
it('should return next char', () => { |
||||
expect(getNextRefIdChar(dataQueries)).toEqual('F'); |
||||
}); |
||||
|
||||
it('should get first char', () => { |
||||
expect(getNextRefIdChar([])).toEqual('A'); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,12 @@ |
||||
import _ from 'lodash'; |
||||
import { DataQuery } from '@grafana/ui/'; |
||||
|
||||
export const getNextRefIdChar = (queries: DataQuery[]): string => { |
||||
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
||||
|
||||
return _.find(letters, refId => { |
||||
return _.every(queries, other => { |
||||
return other.refId !== refId; |
||||
}); |
||||
}); |
||||
}; |
||||
@ -0,0 +1,90 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import { TeamMember, TeamPermissionLevel } from '../../types'; |
||||
import { getMockTeamMember } from './__mocks__/teamMocks'; |
||||
import { TeamMemberRow, Props } from './TeamMemberRow'; |
||||
import { SelectOptionItem } from '@grafana/ui'; |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const props: Props = { |
||||
member: getMockTeamMember(), |
||||
syncEnabled: false, |
||||
editorsCanAdmin: false, |
||||
signedInUserIsTeamAdmin: false, |
||||
updateTeamMember: jest.fn(), |
||||
removeTeamMember: jest.fn(), |
||||
}; |
||||
|
||||
Object.assign(props, propOverrides); |
||||
|
||||
const wrapper = shallow(<TeamMemberRow {...props} />); |
||||
const instance = wrapper.instance() as TeamMemberRow; |
||||
|
||||
return { |
||||
wrapper, |
||||
instance, |
||||
}; |
||||
}; |
||||
|
||||
describe('Render', () => { |
||||
it('should render team members when sync enabled', () => { |
||||
const member = getMockTeamMember(); |
||||
member.labels = ['LDAP']; |
||||
const { wrapper } = setup({ member, syncEnabled: true }); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
|
||||
describe('when feature toggle editorsCanAdmin is turned on', () => { |
||||
it('should render permissions select if user is team admin', () => { |
||||
const { wrapper } = setup({ editorsCanAdmin: true, signedInUserIsTeamAdmin: true }); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should render span and disable buttons if user is team member', () => { |
||||
const { wrapper } = setup({ editorsCanAdmin: true, signedInUserIsTeamAdmin: false }); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when feature toggle editorsCanAdmin is turned off', () => { |
||||
it('should not render permissions', () => { |
||||
const { wrapper } = setup({ editorsCanAdmin: false, signedInUserIsTeamAdmin: true }); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('Functions', () => { |
||||
describe('on remove member', () => { |
||||
const member = getMockTeamMember(); |
||||
const { instance } = setup({ member }); |
||||
|
||||
instance.onRemoveMember(member); |
||||
|
||||
expect(instance.props.removeTeamMember).toHaveBeenCalledWith(1); |
||||
}); |
||||
|
||||
describe('on update permision for user in team', () => { |
||||
const member: TeamMember = { |
||||
userId: 3, |
||||
teamId: 2, |
||||
avatarUrl: '', |
||||
email: 'user@user.org', |
||||
labels: [], |
||||
login: 'member', |
||||
permission: TeamPermissionLevel.Member, |
||||
}; |
||||
const { instance } = setup({ member }); |
||||
const permission = TeamPermissionLevel.Admin; |
||||
const item: SelectOptionItem = { value: permission }; |
||||
const expectedTeamMemeber = { ...member, permission }; |
||||
|
||||
instance.onPermissionChange(item, member); |
||||
|
||||
expect(instance.props.updateTeamMember).toHaveBeenCalledWith(expectedTeamMemeber); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,106 @@ |
||||
import React, { PureComponent } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
import { DeleteButton, Select, SelectOptionItem } from '@grafana/ui'; |
||||
|
||||
import { TeamMember, teamsPermissionLevels } from 'app/types'; |
||||
import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle'; |
||||
import { updateTeamMember, removeTeamMember } from './state/actions'; |
||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; |
||||
|
||||
export interface Props { |
||||
member: TeamMember; |
||||
syncEnabled: boolean; |
||||
editorsCanAdmin: boolean; |
||||
signedInUserIsTeamAdmin: boolean; |
||||
removeTeamMember?: typeof removeTeamMember; |
||||
updateTeamMember?: typeof updateTeamMember; |
||||
} |
||||
|
||||
export class TeamMemberRow extends PureComponent<Props> { |
||||
constructor(props: Props) { |
||||
super(props); |
||||
this.renderLabels = this.renderLabels.bind(this); |
||||
this.renderPermissions = this.renderPermissions.bind(this); |
||||
} |
||||
|
||||
onRemoveMember(member: TeamMember) { |
||||
this.props.removeTeamMember(member.userId); |
||||
} |
||||
|
||||
onPermissionChange = (item: SelectOptionItem, member: TeamMember) => { |
||||
const permission = item.value; |
||||
const updatedTeamMember = { ...member, permission }; |
||||
|
||||
this.props.updateTeamMember(updatedTeamMember); |
||||
}; |
||||
|
||||
renderPermissions(member: TeamMember) { |
||||
const { editorsCanAdmin, signedInUserIsTeamAdmin } = this.props; |
||||
const value = teamsPermissionLevels.find(dp => dp.value === member.permission); |
||||
|
||||
return ( |
||||
<WithFeatureToggle featureToggle={editorsCanAdmin}> |
||||
<td className="width-5 team-permissions"> |
||||
<div className="gf-form"> |
||||
{signedInUserIsTeamAdmin && ( |
||||
<Select |
||||
isSearchable={false} |
||||
options={teamsPermissionLevels} |
||||
onChange={item => this.onPermissionChange(item, member)} |
||||
className="gf-form-select-box__control--menu-right" |
||||
value={value} |
||||
/> |
||||
)} |
||||
{!signedInUserIsTeamAdmin && <span>{value.label}</span>} |
||||
</div> |
||||
</td> |
||||
</WithFeatureToggle> |
||||
); |
||||
} |
||||
|
||||
renderLabels(labels: string[]) { |
||||
if (!labels) { |
||||
return <td />; |
||||
} |
||||
|
||||
return ( |
||||
<td> |
||||
{labels.map(label => ( |
||||
<TagBadge key={label} label={label} removeIcon={false} count={0} onClick={() => {}} /> |
||||
))} |
||||
</td> |
||||
); |
||||
} |
||||
|
||||
render() { |
||||
const { member, syncEnabled, signedInUserIsTeamAdmin } = this.props; |
||||
return ( |
||||
<tr key={member.userId}> |
||||
<td className="width-4 text-center"> |
||||
<img className="filter-table__avatar" src={member.avatarUrl} /> |
||||
</td> |
||||
<td>{member.login}</td> |
||||
<td>{member.email}</td> |
||||
{this.renderPermissions(member)} |
||||
{syncEnabled && this.renderLabels(member.labels)} |
||||
<td className="text-right"> |
||||
<DeleteButton onConfirm={() => this.onRemoveMember(member)} disabled={!signedInUserIsTeamAdmin} /> |
||||
</td> |
||||
</tr> |
||||
); |
||||
} |
||||
} |
||||
|
||||
function mapStateToProps(state) { |
||||
return {}; |
||||
} |
||||
|
||||
const mapDispatchToProps = { |
||||
removeTeamMember, |
||||
updateTeamMember, |
||||
}; |
||||
|
||||
export default connect( |
||||
mapStateToProps, |
||||
mapDispatchToProps |
||||
)(TeamMemberRow); |
||||
@ -0,0 +1,250 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render team members when sync enabled 1`] = ` |
||||
<tr |
||||
key="1" |
||||
> |
||||
<td |
||||
className="width-4 text-center" |
||||
> |
||||
<img |
||||
className="filter-table__avatar" |
||||
src="some/url/" |
||||
/> |
||||
</td> |
||||
<td> |
||||
testUser |
||||
</td> |
||||
<td> |
||||
test@test.com |
||||
</td> |
||||
<Component |
||||
featureToggle={false} |
||||
> |
||||
<td |
||||
className="width-5 team-permissions" |
||||
> |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
<span> |
||||
Member |
||||
</span> |
||||
</div> |
||||
</td> |
||||
</Component> |
||||
<td> |
||||
<TagBadge |
||||
count={0} |
||||
key="LDAP" |
||||
label="LDAP" |
||||
onClick={[Function]} |
||||
removeIcon={false} |
||||
/> |
||||
</td> |
||||
<td |
||||
className="text-right" |
||||
> |
||||
<DeleteButton |
||||
disabled={true} |
||||
onConfirm={[Function]} |
||||
/> |
||||
</td> |
||||
</tr> |
||||
`; |
||||
|
||||
exports[`Render when feature toggle editorsCanAdmin is turned off should not render permissions 1`] = ` |
||||
<tr |
||||
key="1" |
||||
> |
||||
<td |
||||
className="width-4 text-center" |
||||
> |
||||
<img |
||||
className="filter-table__avatar" |
||||
src="some/url/" |
||||
/> |
||||
</td> |
||||
<td> |
||||
testUser |
||||
</td> |
||||
<td> |
||||
test@test.com |
||||
</td> |
||||
<Component |
||||
featureToggle={false} |
||||
> |
||||
<td |
||||
className="width-5 team-permissions" |
||||
> |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
<Select |
||||
autoFocus={false} |
||||
backspaceRemovesValue={true} |
||||
className="gf-form-select-box__control--menu-right" |
||||
isClearable={false} |
||||
isDisabled={false} |
||||
isLoading={false} |
||||
isMulti={false} |
||||
isSearchable={false} |
||||
maxMenuHeight={300} |
||||
onChange={[Function]} |
||||
openMenuOnFocus={false} |
||||
options={ |
||||
Array [ |
||||
Object { |
||||
"description": "Is team member", |
||||
"label": "Member", |
||||
"value": 0, |
||||
}, |
||||
Object { |
||||
"description": "Can add/remove permissions, members and delete team.", |
||||
"label": "Admin", |
||||
"value": 4, |
||||
}, |
||||
] |
||||
} |
||||
value={ |
||||
Object { |
||||
"description": "Is team member", |
||||
"label": "Member", |
||||
"value": 0, |
||||
} |
||||
} |
||||
width={null} |
||||
/> |
||||
</div> |
||||
</td> |
||||
</Component> |
||||
<td |
||||
className="text-right" |
||||
> |
||||
<DeleteButton |
||||
disabled={false} |
||||
onConfirm={[Function]} |
||||
/> |
||||
</td> |
||||
</tr> |
||||
`; |
||||
|
||||
exports[`Render when feature toggle editorsCanAdmin is turned on should render permissions select if user is team admin 1`] = ` |
||||
<tr |
||||
key="1" |
||||
> |
||||
<td |
||||
className="width-4 text-center" |
||||
> |
||||
<img |
||||
className="filter-table__avatar" |
||||
src="some/url/" |
||||
/> |
||||
</td> |
||||
<td> |
||||
testUser |
||||
</td> |
||||
<td> |
||||
test@test.com |
||||
</td> |
||||
<Component |
||||
featureToggle={true} |
||||
> |
||||
<td |
||||
className="width-5 team-permissions" |
||||
> |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
<Select |
||||
autoFocus={false} |
||||
backspaceRemovesValue={true} |
||||
className="gf-form-select-box__control--menu-right" |
||||
isClearable={false} |
||||
isDisabled={false} |
||||
isLoading={false} |
||||
isMulti={false} |
||||
isSearchable={false} |
||||
maxMenuHeight={300} |
||||
onChange={[Function]} |
||||
openMenuOnFocus={false} |
||||
options={ |
||||
Array [ |
||||
Object { |
||||
"description": "Is team member", |
||||
"label": "Member", |
||||
"value": 0, |
||||
}, |
||||
Object { |
||||
"description": "Can add/remove permissions, members and delete team.", |
||||
"label": "Admin", |
||||
"value": 4, |
||||
}, |
||||
] |
||||
} |
||||
value={ |
||||
Object { |
||||
"description": "Is team member", |
||||
"label": "Member", |
||||
"value": 0, |
||||
} |
||||
} |
||||
width={null} |
||||
/> |
||||
</div> |
||||
</td> |
||||
</Component> |
||||
<td |
||||
className="text-right" |
||||
> |
||||
<DeleteButton |
||||
disabled={false} |
||||
onConfirm={[Function]} |
||||
/> |
||||
</td> |
||||
</tr> |
||||
`; |
||||
|
||||
exports[`Render when feature toggle editorsCanAdmin is turned on should render span and disable buttons if user is team member 1`] = ` |
||||
<tr |
||||
key="1" |
||||
> |
||||
<td |
||||
className="width-4 text-center" |
||||
> |
||||
<img |
||||
className="filter-table__avatar" |
||||
src="some/url/" |
||||
/> |
||||
</td> |
||||
<td> |
||||
testUser |
||||
</td> |
||||
<td> |
||||
test@test.com |
||||
</td> |
||||
<Component |
||||
featureToggle={true} |
||||
> |
||||
<td |
||||
className="width-5 team-permissions" |
||||
> |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
<span> |
||||
Member |
||||
</span> |
||||
</div> |
||||
</td> |
||||
</Component> |
||||
<td |
||||
className="text-right" |
||||
> |
||||
<DeleteButton |
||||
disabled={true} |
||||
onConfirm={[Function]} |
||||
/> |
||||
</td> |
||||
</tr> |
||||
`; |
||||
@ -0,0 +1,114 @@ |
||||
FROM ubuntu:14.04 as toolchain |
||||
|
||||
ENV OSX_SDK_URL=https://s3.dockerproject.org/darwin/v2/ \ |
||||
OSX_SDK=MacOSX10.10.sdk \ |
||||
OSX_MIN=10.10 \ |
||||
CTNG=1.23.0 |
||||
|
||||
# FIRST PART |
||||
# build osx64 toolchain (stripped of man documentation) |
||||
# the toolchain produced is not self contained, it needs clang at runtime |
||||
# |
||||
# SECOND PART |
||||
# build gcc (no g++) centos6-x64 toolchain |
||||
# doc: https://crosstool-ng.github.io/docs/ |
||||
# apt-get should be all dep to build toolchain |
||||
# sed and 1st echo are for convenience to get the toolchain in /tmp/x86_64-centos6-linux-gnu |
||||
# other echo are to enable build by root (crosstool-NG refuse to do that by default) |
||||
# the last 2 rm are just to save some time and space writing docker layers |
||||
# |
||||
# THIRD PART |
||||
# build fpm and creates a set of deb from gem |
||||
# ruby2.0 depends on ruby1.9.3 which is install as default ruby |
||||
# rm/ln are here to change that |
||||
# created deb depends on rubygem-json but json gem is not build |
||||
# so do by hand |
||||
|
||||
|
||||
# might wanna make sure osx cross and the other tarball as well as the packages ends up somewhere other than tmp |
||||
# might also wanna put them as their own layer to not have to unpack them every time? |
||||
|
||||
RUN apt-get update && \ |
||||
apt-get install -y \ |
||||
clang-3.8 patch libxml2-dev \ |
||||
ca-certificates \ |
||||
curl \ |
||||
git \ |
||||
make \ |
||||
xz-utils && \ |
||||
git clone https://github.com/tpoechtrager/osxcross.git /tmp/osxcross && \ |
||||
curl -L ${OSX_SDK_URL}/${OSX_SDK}.tar.xz -o /tmp/osxcross/tarballs/${OSX_SDK}.tar.xz && \ |
||||
ln -s /usr/bin/clang-3.8 /usr/bin/clang && \ |
||||
ln -s /usr/bin/clang++-3.8 /usr/bin/clang++ && \ |
||||
ln -s /usr/bin/llvm-dsymutil-3.8 /usr/bin/dsymutil && \ |
||||
UNATTENDED=yes OSX_VERSION_MIN=${OSX_MIN} /tmp/osxcross/build.sh && \ |
||||
rm -rf /tmp/osxcross/target/SDK/${OSX_SDK}/usr/share && \ |
||||
cd /tmp && \ |
||||
tar cfJ osxcross.tar.xz osxcross/target && \ |
||||
rm -rf /tmp/osxcross && \ |
||||
apt-get install -y \ |
||||
bison curl flex gawk gcc g++ gperf help2man libncurses5-dev make patch python-dev texinfo xz-utils && \ |
||||
curl -L http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-${CTNG}.tar.xz \ |
||||
| tar -xJ -C /tmp/ && \ |
||||
cd /tmp/crosstool-ng-${CTNG} && \ |
||||
./configure --enable-local && \ |
||||
make && \ |
||||
./ct-ng x86_64-centos6-linux-gnu && \ |
||||
sed -i '/CT_PREFIX_DIR=/d' .config && \ |
||||
echo 'CT_PREFIX_DIR="/tmp/${CT_HOST:+HOST-${CT_HOST}/}${CT_TARGET}"' >> .config && \ |
||||
echo 'CT_EXPERIMENTAL=y' >> .config && \ |
||||
echo 'CT_ALLOW_BUILD_AS_ROOT=y' >> .config && \ |
||||
echo 'CT_ALLOW_BUILD_AS_ROOT_SURE=y' >> .config && \ |
||||
./ct-ng build && \ |
||||
cd /tmp && \ |
||||
rm /tmp/x86_64-centos6-linux-gnu/build.log.bz2 && \ |
||||
tar cfJ x86_64-centos6-linux-gnu.tar.xz x86_64-centos6-linux-gnu/ && \ |
||||
rm -rf /tmp/x86_64-centos6-linux-gnu/ && \ |
||||
rm -rf /tmp/crosstool-ng-${CTNG} |
||||
|
||||
# base image to crossbuild grafana |
||||
FROM ubuntu:14.04 |
||||
|
||||
ENV GOVERSION=1.11.5 \ |
||||
PATH=/usr/local/go/bin:$PATH \ |
||||
GOPATH=/go \ |
||||
NODEVERSION=10.14.2 |
||||
|
||||
COPY --from=toolchain /tmp/x86_64-centos6-linux-gnu.tar.xz /tmp/ |
||||
COPY --from=toolchain /tmp/osxcross.tar.xz /tmp/ |
||||
|
||||
RUN apt-get update && \ |
||||
apt-get install -y \ |
||||
clang-3.8 gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf gcc-mingw-w64-x86-64 \ |
||||
apt-transport-https \ |
||||
ca-certificates \ |
||||
curl \ |
||||
libfontconfig1 \ |
||||
gcc \ |
||||
g++ \ |
||||
git \ |
||||
make \ |
||||
rpm \ |
||||
xz-utils \ |
||||
expect \ |
||||
gnupg2 \ |
||||
unzip && \ |
||||
ln -s /usr/bin/clang-3.8 /usr/bin/clang && \ |
||||
ln -s /usr/bin/clang++-3.8 /usr/bin/clang++ && \ |
||||
ln -s /usr/bin/llvm-dsymutil-3.8 /usr/bin/dsymutil && \ |
||||
curl -L https://nodejs.org/dist/v${NODEVERSION}/node-v${NODEVERSION}-linux-x64.tar.xz \ |
||||
| tar -xJ --strip-components=1 -C /usr/local && \ |
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ |
||||
echo "deb [arch=amd64] https://dl.yarnpkg.com/debian/ stable main" \ |
||||
| tee /etc/apt/sources.list.d/yarn.list && \ |
||||
apt-get update && apt-get install --no-install-recommends yarn && \ |
||||
curl -L https://storage.googleapis.com/golang/go${GOVERSION}.linux-amd64.tar.gz \ |
||||
| tar -xz -C /usr/local |
||||
|
||||
RUN apt-get install -y \ |
||||
gcc libc-dev make && \ |
||||
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB && \ |
||||
curl -sSL https://get.rvm.io | bash -s stable && \ |
||||
/bin/bash -l -c "rvm requirements && rvm install 2.2 && gem install -N fpm" |
||||
|
||||
ADD ./bootstrap.sh /tmp/bootstrap.sh |
||||
@ -0,0 +1,54 @@ |
||||
VERSION="dev"
|
||||
TAG="grafana/build-container"
|
||||
USER_ID=$(shell id -u)
|
||||
GROUP_ID=$(shell id -g)
|
||||
|
||||
all: build deploy |
||||
|
||||
build: |
||||
docker build -t "${TAG}:${VERSION}" .
|
||||
|
||||
deploy: |
||||
docker push "${TAG}:${VERSION}"
|
||||
|
||||
run: |
||||
docker run -ti \
|
||||
-e "CIRCLE_BRANCH=local" \
|
||||
-e "CIRCLE_BUILD_NUM=472" \
|
||||
${TAG}:${VERSION} \
|
||||
bash
|
||||
|
||||
run-with-local-source-live: |
||||
docker run -d \
|
||||
-e "CIRCLE_BRANCH=local" \
|
||||
-e "CIRCLE_BUILD_NUM=472" \
|
||||
-w "/go/src/github.com/grafana/grafana" \
|
||||
--name grafana-build \
|
||||
-v "${GOPATH}/src/github.com/grafana/grafana:/go/src/github.com/grafana/grafana" \
|
||||
${TAG}:${VERSION} \
|
||||
bash -c "/tmp/bootstrap.sh; mkdir /.cache; chown "${USER_ID}:${GROUP_ID}" /.cache; tail -f /dev/null"
|
||||
docker exec -ti --user "${USER_ID}:${GROUP_ID}" grafana-build bash
|
||||
|
||||
run-with-local-source-copy: |
||||
docker run -d \
|
||||
-e "CIRCLE_BRANCH=local" \
|
||||
-e "CIRCLE_BUILD_NUM=472" \
|
||||
-w "/go/src/github.com/grafana/grafana" \
|
||||
--name grafana-build \
|
||||
${TAG}:${VERSION} \
|
||||
bash -c "/tmp/bootstrap.sh; tail -f /dev/null"
|
||||
docker cp "${GOPATH}/src/github.com/grafana/grafana" grafana-build:/go/src/github.com/grafana/
|
||||
docker exec -ti grafana-build bash
|
||||
|
||||
update-source: |
||||
docker cp "${GOPATH}/src/github.com/grafana/grafana" grafana-build:/go/src/github.com/grafana/
|
||||
|
||||
attach: |
||||
docker exec -ti grafana-build bash
|
||||
|
||||
attach-live: |
||||
docker exec -ti --user "${USER_ID}:${GROUP_ID}" grafana-build bash
|
||||
|
||||
stop: |
||||
docker kill grafana-build
|
||||
docker rm grafana-build
|
||||
@ -0,0 +1,20 @@ |
||||
# grafana-build-container |
||||
Grafana build container |
||||
|
||||
## Description |
||||
|
||||
This is a container for cross-platform builds of Grafana. You can run it locally using the Makefile. |
||||
|
||||
## Makefile targets |
||||
|
||||
* `make run-with-local-source-copy` |
||||
- Starts the container locally and copies your local sources into the container |
||||
* `make run-with-local-source-live` |
||||
- Starts the container (as your user) locally and maps your Grafana project dir into the container |
||||
* `make update-source` |
||||
- Updates the sources in the container from your local sources |
||||
* `make stop` |
||||
- Kills the container |
||||
* `make attach` |
||||
- Opens bash within the running container |
||||
|
||||
@ -0,0 +1,5 @@ |
||||
#!/bin/bash |
||||
|
||||
cd /tmp |
||||
tar xfJ x86_64-centos6-linux-gnu.tar.xz |
||||
tar xfJ osxcross.tar.xz |
||||
@ -0,0 +1,7 @@ |
||||
#!/bin/bash |
||||
|
||||
_version="1.2.3" |
||||
_tag="grafana/build-container:${_version}" |
||||
|
||||
docker build -t $_tag . |
||||
docker push $_tag |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue