Compare commits

...

53 Commits

Author SHA1 Message Date
Torkel Ödegaard 43ebc172cb fix(profile): fixed change password link and page, fixes #4542 2016-04-04 13:52:41 -04:00
Torkel Ödegaard 892e79a982 fix(table): another fix for table panel sorting, fixes #4544 2016-04-04 13:43:13 -04:00
Torkel Ödegaard 542e080f0a fix(graph): fixed issue with graph legend when in table mode and series on righ-y axis, also fixed so that you can see what series are on right-y axis when legend is in table mode, fixes #4551, fixes #1145 2016-04-04 13:13:25 -04:00
Torkel Ödegaard f35eb0f148 fix(metrics tab): Fix for missing datasource name in datasource selector, fixes #4540 2016-04-03 16:19:14 -04:00
Torkel Ödegaard c764e7fd97 fix(elasticsearch): add a better default terms size limit of 10, fixes #4013 2016-04-03 16:11:42 -04:00
Torkel Ödegaard 1c97f10d8a fix(table panel): fixed column sort issue with table panel, #4532 2016-04-03 16:01:48 -04:00
Torkel Ödegaard d8499e6941 fix(influxdb): added regex start string char to templating suggestion, fixes #4405 2016-04-03 15:51:54 -04:00
Torkel Ödegaard 105a678d64 fix(): another test fix 2016-04-03 10:28:31 -04:00
Torkel Ödegaard a12de09533 changelog(): updated with preferences features 2016-04-03 10:21:11 -04:00
Torkel Ödegaard 431a610f00 fix(): fixed failing unit test 2016-04-03 10:12:43 -04:00
Torkel Ödegaard 6e6ebc5947 trying to fix karma issue 2016-04-03 09:43:33 -04:00
Torkel Ödegaard b30b78e442 feat(preferences): got timezone option to work on org and profile level, as well as dashboard 2016-04-03 06:05:43 -07:00
Torkel Ödegaard cab859a0e4 Merge branch 'master' into preferences 2016-04-03 05:33:11 -07:00
Torkel Ödegaard a2c6469d41 fix(): A Datasource with null jsonData would make Grafana fail to load page, fixes #4536 2016-04-03 05:32:15 -07:00
Torkel Ödegaard 55d95f9009 feat(prefs): moved context srv to typescript 2016-04-03 05:27:35 -07:00
Torkel Ödegaard ab1048b7ee feat(preferences): theme and home dashbord settings now work work on profile and org settings page 2016-04-02 13:54:06 -07:00
Torkel Ödegaard 38a10f8be4 progress 2016-04-01 17:34:30 -07:00
Torkel Ödegaard f165ba6480 Merge branch 'master' into preferences 2016-04-01 16:02:02 -07:00
Torkel Ödegaard bcc875a39c Merge branch 'master' of github.com:grafana/grafana 2016-04-01 10:57:17 -07:00
Torkel Ödegaard 2f2029a5c1 fix(dashboard): fixed dashboard layout for mobile devices, fixes #4529 2016-04-01 10:56:56 -07:00
Torkel Ödegaard 8dd509c31b Merge pull request #4491 from grafana/code-snippets
Code snippets
2016-04-01 10:45:10 -07:00
Torkel Ödegaard 171f6422b1 docs(): corrected version 2016-03-31 07:05:44 -07:00
Torkel Ödegaard 5e6c3f314c build(): trying to fix windows build on appveyor 2016-03-31 06:47:50 -07:00
Torkel Ödegaard efc97d177a Merge branch 'master' of github.com:grafana/grafana 2016-03-31 06:24:04 -07:00
Torkel Ödegaard 601e90f5fe docs(): updated install instructions 2016-03-31 06:23:52 -07:00
Torkel Ödegaard decede394a Merge pull request #4517 from pborreli/patch-1
doc(plugins): typo
2016-03-31 05:54:34 -07:00
Pascal Borreli eb850ddd36 doc(plugins): typo 2016-03-31 11:43:04 +01:00
Torkel Ödegaard 4a807b6373 feat(preferences): still a lot more work needed in prefernces branch 2016-03-30 14:10:32 -07:00
Torkel Ödegaard 6ed9164c9d Merge branch 'master' of github.com:grafana/grafana into preferences 2016-03-30 13:35:17 -07:00
Matt Toback ef89dabb61 Go away extra line 2016-03-28 18:37:52 -04:00
Matt Toback 9be1ffd633 Fixed code blocks in Grafana 2016-03-28 18:37:02 -04:00
Torkel Ödegaard 5e431149cd feat(preferences): removed unused api prefernce methods, these need to be abstracted more from an http api perpsective, they are too low level for the http api, #4399 2016-03-20 11:54:24 +01:00
Torkel Ödegaard 7023dedc05 feat(preferences): refactoring PR #4399 2016-03-20 11:52:19 +01:00
Torkel Ödegaard 9e98c86ae1 Merge branch 'master' into preferences 2016-03-20 11:27:49 +01:00
Torkel Ödegaard 071237d322 Merge pull request #4399 from utkarshcmu/preferences
Set home dashboard per user per org #3214
2016-03-20 11:25:03 +01:00
utkarshcmu cb42cfc6af Removed unwanted api, moved logic into backend 2016-03-17 02:29:34 -07:00
utkarshcmu 4fbe954a79 Home dashboard per user per org works 2016-03-17 01:38:18 -07:00
utkarshcmu 66621d762e Added getDashboardSlugById api 2016-03-17 01:01:58 -07:00
utkarshcmu 9dd6aefcec Minor corrections 2016-03-16 23:35:06 -07:00
utkarshcmu 749fd618a9 Saving home dashboard id in table 2016-03-16 23:22:27 -07:00
Torkel Ödegaard a88176e060 feat(preferences): lots of refactoring and changes to #3214 2016-03-15 22:49:52 +01:00
Torkel Ödegaard e06756bcab Merge branch 'preferences' of github.com:grafana/grafana into preferences 2016-03-15 22:15:27 +01:00
Torkel Ödegaard 9c222a445f Merge branch 'master' into preferences 2016-03-15 22:14:35 +01:00
Torkel Ödegaard 51bde36dee Merge pull request #4354 from utkarshcmu/preferences
Ability to save Preferences
2016-03-15 22:04:10 +01:00
utkarshcmu e371e03696 Able to save preference version, created, updated fields 2016-03-14 03:12:52 -07:00
utkarshcmu 0bf721a74c Able to save home dashboard 2016-03-13 23:57:38 -07:00
utkarshcmu 43b474143c Preferences model updated 2016-03-11 06:30:05 -08:00
Torkel Ödegaard 02221c993e Merge pull request #4268 from utkarshcmu/save-pref
Added APIs for Get and Save User Preferences
2016-03-07 08:06:32 +01:00
utkarshcmu 9c8d508247 Made API handling better, removed unused components 2016-03-06 12:32:22 -08:00
utkarshcmu 8f42bec270 Implemented GetUserPreferences API 2016-03-06 11:42:15 -08:00
utkarshcmu 660d3fa1e9 Implemented savePreferences API 2016-03-06 03:47:39 -08:00
utkarshcmu 1ef332e82c preferences UI poc 2016-03-05 23:44:56 -08:00
utkarshcmu cf926134ef Added savePreferencesAPI 2016-03-05 13:15:49 -08:00
62 changed files with 812 additions and 245 deletions
+16 -1
View File
@@ -1,4 +1,19 @@
# 3.0.0 (unrelased master branch)
# 3.0.0-beta2 (unreleased)
### New Features (introduces since 3.0-beta1)
* **Preferences**: Set home dashboard on user and org level, closes [#1678](https://github.com/grafana/grafana/issues/1678)
* **Preferences**: Set timezone on user and org level, closes [#3214](https://github.com/grafana/grafana/issues/3214), [#1200](https://github.com/grafana/grafana/issues/1200)
* **Preferences**: Set theme on user and org level, closes [#3214](https://github.com/grafana/grafana/issues/3214), [#1917](https://github.com/grafana/grafana/issues/1917)
### Bug fixes
* **Dashboard**: Fixed dashboard panel layout for mobile devices, fixes [#4529](https://github.com/grafana/grafana/issues/4529)
* **Table Panel**: Fixed issue with table panel sort, fixes [#4532](https://github.com/grafana/grafana/issues/4532)
* **Page Load Crash**: A Datasource with null jsonData would make Grafana fail to load page, fixes [#4536](https://github.com/grafana/grafana/issues/4536)
* **Metrics tab**: Fix for missing datasource name in datasource selector, fixes [#4540](https://github.com/grafana/grafana/issues/4540)
* **Graph**: Fix legend in table mode with series on right-y axis, fixes [#4551](https://github.com/grafana/grafana/issues/4551), [#1145](https://github.com/grafana/grafana/issues/1145)
* **Password**: Password reset link/page did not work, fixes [#4542](https://github.com/grafana/grafana/issues/4542)
# 3.0.0-beta1 (2016-03-31)
### New Features
* **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/issues/3655)
+1 -1
View File
@@ -5,7 +5,7 @@ os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\grafana\grafana
environment:
nodejs_version: "4"
nodejs_version: "5"
GOPATH: c:\gopath
install:
+1 -1
View File
@@ -1 +1 @@
4.0.0
3.0.0
+9 -2
View File
@@ -10,14 +10,21 @@ page_keywords: grafana, installation, debian, ubuntu, guide
Description | Download
------------ | -------------
.deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
Beta .deb for Debian-based Linux | [grafana_3.0.0-beta11459429091_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta11459429091_amd64.deb)
## Install
## Install Stable
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb
$ sudo apt-get install -y adduser libfontconfig
$ sudo dpkg -i grafana_2.6.0_amd64.deb
## Install 3.0 Beta
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta11459429091_amd64.deb
$ sudo apt-get install -y adduser libfontconfig
$ sudo dpkg -i grafana_3.0.0-beta11459429091_amd64.deb
## APT Repository
Add the following line to your `/etc/apt/sources.list` file.
+21 -2
View File
@@ -10,9 +10,10 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
Description | Download
------------ | -------------
.RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta11459429091.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta11459429091.x86_64.rpm)
## Install from package file
## Install Stable Release from package file
You can install Grafana using Yum directly.
@@ -29,6 +30,24 @@ Or install manually using `rpm`.
$ sudo rpm -i --nodeps grafana-2.6.0-1.x86_64.rpm
## Install Beta Release from package file
You can install Grafana using Yum directly.
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta11459429091.x86_64.rpm
Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
$ sudo yum install initscripts fontconfig
$ sudo rpm -Uvh grafana-3.0.0-beta11459429091.x86_64.rpm
#### On OpenSuse:
$ sudo rpm -i --nodeps grafana-3.0.0-beta11459429091.x86_64.rpm
## Install via YUM Repository
Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
+1 -1
View File
@@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
Description | Download
------------ | -------------
Zip package for Windows | [grafana.2.5.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
Stable Zip package for Windows | [grafana.2.5.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
## Configure
-1
View File
@@ -9,7 +9,6 @@ module.exports = function(config) {
// list of files / patterns to load in the browser
files: [
'vendor/npm/es6-shim/es6-shim.js',
'vendor/npm/es6-promise/dist/es6-promise.js',
'vendor/npm/systemjs/dist/system.src.js',
'test/test-main.js',
+3 -3
View File
@@ -4,7 +4,7 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
"version": "3.0.0-beta1",
"version": "3.0.0-beta2",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"
@@ -32,7 +32,7 @@
"grunt-contrib-watch": "^0.6.1",
"grunt-filerev": "^0.2.1",
"grunt-git-describe": "~2.3.2",
"grunt-karma": "~0.12.1",
"grunt-karma": "~0.12.2",
"grunt-ng-annotate": "^1.0.1",
"grunt-notify": "^0.4.3",
"grunt-postcss": "^0.8.0",
@@ -43,7 +43,7 @@
"grunt-typescript": "^0.8.0",
"grunt-usemin": "3.0.0",
"jshint-stylish": "~2.1.0",
"karma": "~0.13.15",
"karma": "0.13.22",
"karma-chrome-launcher": "~0.2.2",
"karma-coverage": "0.5.3",
"karma-coveralls": "1.1.2",
+3 -3
View File
@@ -1,11 +1,11 @@
#! /usr/bin/env bash
deb_ver=3.0.0-pre1459399258
rpm_ver=3.0.0-pre1459399258
deb_ver=3.0.0-beta11459429091
rpm_ver=3.0.0-beta11459429091
#rpm_ver=3.0.0-1
#wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
# package_cloud push grafana/stable/debian/jessie grafana_${deb_ver}_amd64.deb
# package_cloud push grafana/stable/debian/wheezy grafana_${deb_ver}_amd64.deb
+14
View File
@@ -29,6 +29,7 @@ func Register(r *macaron.Macaron) {
// authed views
r.Get("/profile/", reqSignedIn, Index)
r.Get("/profile/password", reqSignedIn, Index)
r.Get("/org/", reqSignedIn, Index)
r.Get("/org/new", reqSignedIn, Index)
r.Get("/datasources/", reqSignedIn, Index)
@@ -96,10 +97,15 @@ func Register(r *macaron.Macaron) {
r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
r.Post("/using/:id", wrap(UserSetUsingOrg))
r.Get("/orgs", wrap(GetSignedInUserOrgList))
r.Post("/stars/dashboard/:id", wrap(StarDashboard))
r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
r.Get("/quotas", wrap(GetUserQuotas))
r.Get("/preferences", wrap(GetUserPreferences))
r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences))
})
// users (admin permission required)
@@ -130,6 +136,9 @@ func Register(r *macaron.Macaron) {
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
// prefs
r.Get("/preferences", wrap(GetOrgPreferences))
r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
}, reqOrgAdmin)
// create new org
@@ -164,6 +173,11 @@ func Register(r *macaron.Macaron) {
r.Delete("/:id", wrap(DeleteApiKey))
}, reqOrgAdmin)
// Preferences
r.Group("/preferences", func() {
r.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), wrap(SetHomeDashboard))
})
// Data sources
r.Group("/datasources", func() {
r.Get("/", GetDataSources)
+18
View File
@@ -159,6 +159,24 @@ func canEditDashboard(role m.RoleType) bool {
}
func GetHomeDashboard(c *middleware.Context) {
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
if err := bus.Dispatch(&prefsQuery); err != nil {
c.JsonApiErr(500, "Failed to get preferences", err)
}
if prefsQuery.Result.HomeDashboardId != 0 {
slugQuery := m.GetDashboardSlugByIdQuery{Id: prefsQuery.Result.HomeDashboardId}
err := bus.Dispatch(&slugQuery)
if err != nil {
c.JsonApiErr(500, "Failed to get slug from database", err)
return
}
dashRedirect := dtos.DashboardRedirect{RedirectUri: "db/" + slugQuery.Result}
c.JSON(200, &dashRedirect)
return
}
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
file, err := os.Open(filePath)
if err != nil {
+5
View File
@@ -33,6 +33,7 @@ type CurrentUser struct {
OrgRole m.RoleType `json:"orgRole"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
GravatarUrl string `json:"gravatarUrl"`
Timezone string `json:"timezone"`
}
type DashboardMeta struct {
@@ -57,6 +58,10 @@ type DashboardFullWithMeta struct {
Dashboard *simplejson.Json `json:"dashboard"`
}
type DashboardRedirect struct {
RedirectUri string `json:"redirectUri"`
}
type DataSource struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
+13
View File
@@ -0,0 +1,13 @@
package dtos
type Prefs struct {
Theme string `json:"theme"`
HomeDashboardId int64 `json:"homeDashboardId"`
Timezone string `json:"timezone"`
}
type UpdatePrefsCmd struct {
Theme string `json:"theme"`
HomeDashboardId int64 `json:"homeDashboardId"`
Timezone string `json:"timezone"`
}
+1 -1
View File
@@ -59,7 +59,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
defaultDatasource = ds.Name
}
if len(ds.JsonData.MustMap()) > 0 {
if ds.JsonData != nil {
dsMap["jsonData"] = ds.JsonData
}
+9 -1
View File
@@ -2,6 +2,7 @@ package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
@@ -14,6 +15,12 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
return nil, err
}
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
if err := bus.Dispatch(&prefsQuery); err != nil {
return nil, err
}
prefs := prefsQuery.Result
var data = dtos.IndexViewData{
User: &dtos.CurrentUser{
Id: c.UserId,
@@ -21,12 +28,13 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
Login: c.Login,
Email: c.Email,
Name: c.Name,
LightTheme: c.Theme == "light",
OrgId: c.OrgId,
OrgName: c.OrgName,
OrgRole: c.OrgRole,
GravatarUrl: dtos.GetGravatarUrl(c.Email),
IsGrafanaAdmin: c.IsGrafanaAdmin,
LightTheme: prefs.Theme == "light",
Timezone: prefs.Timezone,
},
Settings: settings,
AppUrl: setting.AppUrl,
+73
View File
@@ -0,0 +1,73 @@
package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
)
// POST /api/preferences/set-home-dash
func SetHomeDashboard(c *middleware.Context, cmd m.SavePreferencesCommand) Response {
cmd.UserId = c.UserId
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to set home dashboard", err)
}
return ApiSuccess("Home dashboard set")
}
// GET /api/user/preferences
func GetUserPreferences(c *middleware.Context) Response {
return getPreferencesFor(c.OrgId, c.UserId)
}
func getPreferencesFor(orgId int64, userId int64) Response {
prefsQuery := m.GetPreferencesQuery{UserId: userId, OrgId: orgId}
if err := bus.Dispatch(&prefsQuery); err != nil {
return ApiError(500, "Failed to get preferences", err)
}
dto := dtos.Prefs{
Theme: prefsQuery.Result.Theme,
HomeDashboardId: prefsQuery.Result.HomeDashboardId,
Timezone: prefsQuery.Result.Timezone,
}
return Json(200, &dto)
}
// PUT /api/user/preferences
func UpdateUserPreferences(c *middleware.Context, dtoCmd dtos.UpdatePrefsCmd) Response {
return updatePreferencesFor(c.OrgId, c.UserId, &dtoCmd)
}
func updatePreferencesFor(orgId int64, userId int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
saveCmd := m.SavePreferencesCommand{
UserId: userId,
OrgId: orgId,
Theme: dtoCmd.Theme,
Timezone: dtoCmd.Timezone,
HomeDashboardId: dtoCmd.HomeDashboardId,
}
if err := bus.Dispatch(&saveCmd); err != nil {
return ApiError(500, "Failed to save preferences", err)
}
return ApiSuccess("Preferences updated")
}
// GET /api/org/preferences
func GetOrgPreferences(c *middleware.Context) Response {
return getPreferencesFor(c.OrgId, 0)
}
// PUT /api/org/preferences
func UpdateOrgPreferences(c *middleware.Context, dtoCmd dtos.UpdatePrefsCmd) Response {
return updatePreferencesFor(c.OrgId, 0, &dtoCmd)
}
+5
View File
@@ -148,3 +148,8 @@ type GetDashboardsQuery struct {
DashboardIds []int64
Result *[]Dashboard
}
type GetDashboardSlugByIdQuery struct {
Id int64
Result string
}
+53
View File
@@ -0,0 +1,53 @@
package models
import (
"errors"
"time"
)
// Typed errors
var (
ErrPreferencesNotFound = errors.New("Preferences not found")
)
type Preferences struct {
Id int64
OrgId int64
UserId int64
Version int
HomeDashboardId int64
Timezone string
Theme string
Created time.Time
Updated time.Time
}
// ---------------------
// QUERIES
type GetPreferencesQuery struct {
Id int64
OrgId int64
UserId int64
Result *Preferences
}
type GetPreferencesWithDefaultsQuery struct {
Id int64
OrgId int64
UserId int64
Result *Preferences
}
// ---------------------
// COMMANDS
type SavePreferencesCommand struct {
UserId int64
OrgId int64
HomeDashboardId int64 `json:"homeDashboardId"`
Timezone string `json:"timezone"`
Theme string `json:"theme"`
}
-1
View File
@@ -136,7 +136,6 @@ type SignedInUser struct {
Login string
Name string
Email string
Theme string
ApiKeyId int64
IsGrafanaAdmin bool
}
+21
View File
@@ -18,6 +18,7 @@ func init() {
bus.AddHandler("sql", DeleteDashboard)
bus.AddHandler("sql", SearchDashboards)
bus.AddHandler("sql", GetDashboardTags)
bus.AddHandler("sql", GetDashboardSlugById)
}
func SaveDashboard(cmd *m.SaveDashboardCommand) error {
@@ -255,3 +256,23 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
return nil
}
type DashboardSlugDTO struct {
Slug string
}
func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error {
var rawSql = `SELECT slug from dashboard WHERE Id=?`
var slug = DashboardSlugDTO{}
exists, err := x.Sql(rawSql, query.Id).Get(&slug)
if err != nil {
return err
} else if exists == false {
return m.ErrDashboardNotFound
}
query.Result = slug.Slug
return nil
}
@@ -4,17 +4,27 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addPreferencesMigrations(mg *Migrator) {
preferencesV1 := Table{
mg.AddMigration("drop preferences table v2", NewDropTableMigration("preferences"))
preferencesV2 := Table{
Name: "preferences",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "pref_id", Type: DB_Int, Nullable: false},
{Name: "pref_type", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "pref_data", Type: DB_Text, Nullable: false},
{Name: "org_id", Type: DB_Int, Nullable: true},
{Name: "user_id", Type: DB_NVarchar, Length: 255, Nullable: true},
{Name: "version", Type: DB_Int, Nullable: false},
{Name: "home_dashboard_id", Type: DB_BigInt, Nullable: true},
{Name: "timezone", Type: DB_NVarchar, Length: 50, Nullable: true},
{Name: "theme", Type: DB_NVarchar, Length: 20, Nullable: true},
{Name: "created", Type: DB_DateTime, Nullable: false},
{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"org_id"}},
{Cols: []string{"user_id"}},
},
}
// create table
mg.AddMigration("create preferences table v1", NewAddTableMigration(preferencesV1))
mg.AddMigration("create preferences table v2", NewAddTableMigration(preferencesV2))
}
+96
View File
@@ -0,0 +1,96 @@
package sqlstore
import (
"time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", GetPreferences)
bus.AddHandler("sql", GetPreferencesWithDefaults)
bus.AddHandler("sql", SavePreferences)
}
func GetPreferencesWithDefaults(query *m.GetPreferencesWithDefaultsQuery) error {
prefs := make([]*m.Preferences, 0)
filter := "(org_id=? AND user_id=?) OR (org_id=? AND user_id=0)"
err := x.Where(filter, query.OrgId, query.UserId, query.OrgId).
OrderBy("user_id ASC").
Find(&prefs)
if err != nil {
return err
}
res := &m.Preferences{
Theme: "dark",
Timezone: "browser",
HomeDashboardId: 0,
}
for _, p := range prefs {
if p.Theme != "" {
res.Theme = p.Theme
}
if p.Timezone != "" {
res.Timezone = p.Timezone
}
if p.HomeDashboardId != 0 {
res.HomeDashboardId = p.HomeDashboardId
}
}
query.Result = res
return nil
}
func GetPreferences(query *m.GetPreferencesQuery) error {
var prefs m.Preferences
exists, err := x.Where("org_id=? AND user_id=?", query.OrgId, query.UserId).Get(&prefs)
if err != nil {
return err
}
if exists {
query.Result = &prefs
} else {
query.Result = new(m.Preferences)
}
return nil
}
func SavePreferences(cmd *m.SavePreferencesCommand) error {
return inTransaction2(func(sess *session) error {
var prefs m.Preferences
exists, err := sess.Where("org_id=? AND user_id=?", cmd.OrgId, cmd.UserId).Get(&prefs)
if !exists {
prefs = m.Preferences{
UserId: cmd.UserId,
OrgId: cmd.OrgId,
HomeDashboardId: cmd.HomeDashboardId,
Timezone: cmd.Timezone,
Theme: cmd.Theme,
Created: time.Now(),
Updated: time.Now(),
}
_, err = sess.Insert(&prefs)
return err
} else {
prefs.HomeDashboardId = cmd.HomeDashboardId
prefs.Timezone = cmd.Timezone
prefs.Theme = cmd.Theme
prefs.Updated = time.Now()
prefs.Version += 1
_, err := sess.Id(prefs.Id).AllCols().Update(&prefs)
return err
}
})
}
-1
View File
@@ -273,7 +273,6 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
u.email as email,
u.login as login,
u.name as name,
u.theme as theme,
org.name as org_name,
org_user.role as org_role,
org.id as org_id
@@ -0,0 +1,44 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import _ from 'lodash';
import $ from 'jquery';
import coreModule from 'app/core/core_module';
var template = `
<select class="gf-form-input" ng-model="ctrl.model" ng-options="f.value as f.text for f in ctrl.options"></select>
`;
export class DashboardSelectorCtrl {
model: any;
options: any;
/** @ngInject */
constructor(private backendSrv) {
}
$onInit() {
this.options = [{value: 0, text: 'Default'}];
return this.backendSrv.search({starred: true}).then(res => {
res.forEach(dash => {
this.options.push({value: dash.id, text: dash.title});
});
});
}
}
export function dashboardSelector() {
return {
restrict: 'E',
controller: DashboardSelectorCtrl,
bindToController: true,
controllerAs: 'ctrl',
template: template,
scope: {
model: '='
}
};
}
coreModule.directive('dashboardSelector', dashboardSelector);
+2
View File
@@ -32,6 +32,7 @@ import {liveSrv} from './live/live_srv';
import {Emitter} from './utils/emitter';
import {layoutSelector} from './components/layout_selector/layout_selector';
import {switchDirective} from './components/switch';
import {dashboardSelector} from './components/dashboard_selector';
import 'app/core/controllers/all';
import 'app/core/services/all';
import 'app/core/routes/routes';
@@ -54,4 +55,5 @@ export {
infoPopover,
Emitter,
appEvents,
dashboardSelector,
};
+9 -5
View File
@@ -4,13 +4,17 @@ define([
function (coreModule) {
"use strict";
coreModule.default.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv) {
coreModule.default.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) {
if (!$routeParams.slug) {
backendSrv.get('/api/dashboards/home').then(function(result) {
var meta = result.meta;
meta.canSave = meta.canShare = meta.canStar = false;
$scope.initDashboard(result, $scope);
backendSrv.get('/api/dashboards/home').then(function(homeDash) {
if (homeDash.redirectUri) {
$location.path('dashboard/' + homeDash.redirectUri);
} else {
var meta = homeDash.meta;
meta.canSave = meta.canShare = meta.canStar = false;
$scope.initDashboard(homeDash, $scope);
}
});
return;
}
+7 -3
View File
@@ -88,16 +88,20 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
resolve: loadOrgBundle,
})
.when('/profile', {
templateUrl: 'public/app/features/profile/partials/profile.html',
templateUrl: 'public/app/features/org/partials/profile.html',
controller : 'ProfileCtrl',
controllerAs: 'ctrl',
resolve: loadOrgBundle,
})
.when('/profile/password', {
templateUrl: 'public/app/features/profile/partials/password.html',
templateUrl: 'public/app/features/org/partials/change_password.html',
controller : 'ChangePasswordCtrl',
resolve: loadOrgBundle,
})
.when('/profile/select-org', {
templateUrl: 'public/app/features/profile/partials/select_org.html',
templateUrl: 'public/app/features/org/partials/select_org.html',
controller : 'SelectOrgCtrl',
resolve: loadOrgBundle,
})
// ADMIN
.when('/admin', {
-47
View File
@@ -1,47 +0,0 @@
define([
'angular',
'lodash',
'../core_module',
'app/core/store',
'app/core/config',
],
function (angular, _, coreModule, store, config) {
'use strict';
coreModule.default.service('contextSrv', function() {
function User() {
if (config.bootData.user) {
_.extend(this, config.bootData.user);
}
}
this.hasRole = function(role) {
return this.user.orgRole === role;
};
this.setPinnedState = function(val) {
this.pinned = val;
store.set('grafana.sidemenu.pinned', val);
};
this.toggleSideMenu = function() {
this.sidemenu = !this.sidemenu;
if (!this.sidemenu) {
this.setPinnedState(false);
}
};
this.pinned = store.getBool('grafana.sidemenu.pinned', false);
if (this.pinned) {
this.sidemenu = true;
}
this.version = config.buildInfo.version;
this.lightTheme = false;
this.user = new User();
this.isSignedIn = this.user.isSignedIn;
this.isGrafanaAdmin = this.user.isGrafanaAdmin;
this.isEditor = this.hasRole('Editor') || this.hasRole('Admin');
});
});
+73
View File
@@ -0,0 +1,73 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import _ from 'lodash';
import coreModule from 'app/core/core_module';
import store from 'app/core/store';
export class User {
isGrafanaAdmin: any;
isSignedIn: any;
orgRole: any;
constructor() {
if (config.bootData.user) {
_.extend(this, config.bootData.user);
}
}
}
export class ContextSrv {
pinned: any;
version: any;
user: User;
isSignedIn: any;
isGrafanaAdmin: any;
isEditor: any;
sidemenu: any;
lightTheme: any;
constructor() {
this.pinned = store.getBool('grafana.sidemenu.pinned', false);
if (this.pinned) {
this.sidemenu = true;
}
if (!config.buildInfo) {
config.buildInfo = {};
}
if (!config.bootData) {
config.bootData = {user: {}, settings: {}};
}
this.version = config.buildInfo.version;
this.lightTheme = false;
this.user = new User();
this.isSignedIn = this.user.isSignedIn;
this.isGrafanaAdmin = this.user.isGrafanaAdmin;
this.isEditor = this.hasRole('Editor') || this.hasRole('Admin');
}
hasRole(role) {
return this.user.orgRole === role;
}
setPinnedState(val) {
this.pinned = val;
store.set('grafana.sidemenu.pinned', val);
}
toggleSideMenu() {
this.sidemenu = !this.sidemenu;
if (!this.sidemenu) {
this.setPinnedState(false);
}
}
}
var contextSrv = new ContextSrv();
export {contextSrv};
coreModule.factory('contextSrv', function() {
return contextSrv;
});
-3
View File
@@ -7,8 +7,5 @@ define([
'./playlist/all',
'./snapshot/all',
'./panel/all',
'./profile/profileCtrl',
'./profile/changePasswordCtrl',
'./profile/selectOrgCtrl',
'./styleguide/styleguide',
], function () {});
@@ -134,6 +134,10 @@ function (angular, $, config, moment) {
});
};
$scope.timezoneChanged = function() {
$rootScope.$broadcast("refresh");
};
$scope.formatDate = function(date) {
return moment(date).format('MMM Do YYYY, h:mm:ss a');
};
+10 -2
View File
@@ -9,7 +9,7 @@ function (angular, $, _, moment) {
var module = angular.module('grafana.services');
module.factory('dashboardSrv', function() {
module.factory('dashboardSrv', function(contextSrv) {
function DashboardModel (data, meta) {
if (!data) {
@@ -25,7 +25,7 @@ function (angular, $, _, moment) {
this.originalTitle = this.title;
this.tags = data.tags || [];
this.style = data.style || "dark";
this.timezone = data.timezone || 'browser';
this.timezone = data.timezone || '';
this.editable = data.editable !== false;
this.hideControls = data.hideControls || false;
this.sharedCrosshair = data.sharedCrosshair || false;
@@ -208,6 +208,14 @@ function (angular, $, _, moment) {
});
};
p.isTimezoneUtc = function() {
return this.getTimezone() === 'utc';
};
p.getTimezone = function() {
return this.timezone ? this.timezone : contextSrv.user.timezone;
};
p._updateSchema = function(old) {
var i, j, k;
var oldVersion = this.schemaVersion;
@@ -48,6 +48,7 @@
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="editJson();">View JSON</a></li>
<li ng-if="contextSrv.isEditor && !dashboard.editable"><a class="pointer" ng-click="makeEditable();">Make Editable</a></li>
<li ng-if="contextSrv.isEditor"><a class="pointer" ng-click="saveDashboardAs();">Save As...</a></li>
<li ng-if="dashboard.id"><a class="pointer" ng-click="saveDashboardAsHome();">Set As Home</a></li>
<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
</ul>
</li>
@@ -34,7 +34,7 @@
<div class="gf-form">
<label class="gf-form-label width-7">Timezone</label>
<div class="gf-form-select-wrapper">
<select ng-model="dashboard.timezone" class='gf-form-input' ng-options="f for f in ['browser','utc']"></select>
<select ng-model="dashboard.timezone" class='gf-form-input' ng-options="f.value as f.text for f in [{value: '', text: 'Default'}, {value: 'browser', text: 'Local browser time'},{value: 'utc', text: 'UTC'}]" ng-change="timezoneChanged()"></select>
</div>
</div>
</div>
@@ -62,66 +62,67 @@
</gf-form-switch>
</div>
</div>
</div>
<div ng-if="editor.index == 1">
<h5 class="section-heading">Rows settings</h5>
</div>
<div class="gf-form-group">
<div class="gf-form-inline" ng-repeat="row in dashboard.rows">
<div class="gf-form">
<span class="gf-form-label">Title</span>
<input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
</div>
<gf-form-switch class="gf-form" label="Show title" checked="row.showTitle" switch-class="max-width-6"></gf-form-switch>
<div ng-if="editor.index == 1">
<h5 class="section-heading">Rows settings</h5>
<div class="gf-form">
<button class="btn btn-inverse gf-form-btn" ng-click="_.move(dashboard.rows,$index,$index-1)">
<i ng-class="{'invisible': $first}" class="fa fa-arrow-up"></i>
</button>
<button class="btn btn-inverse gf-from-btn" ng-click="_.move(dashboard.rows,$index,$index+1)">
<i ng-class="{'invisible': $last}" class="fa fa-arrow-down"></i>
</button>
<button class="btn btn-inverse gf-form-btn" click="dashboard.rows = _.without(dashboard.rows,row)">
<i class="fa fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
<div class="gf-form-group">
<div class="gf-form-inline" ng-repeat="row in dashboard.rows">
<div class="gf-form">
<span class="gf-form-label">Title</span>
<input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
</div>
<gf-form-switch class="gf-form" label="Show title" checked="row.showTitle" switch-class="max-width-6"></gf-form-switch>
<div ng-if="editor.index == 2">
<dash-links-editor></dash-links-editor>
</div>
<div class="gf-form">
<button class="btn btn-inverse gf-form-btn" ng-click="_.move(dashboard.rows,$index,$index-1)">
<i ng-class="{'invisible': $first}" class="fa fa-arrow-up"></i>
</button>
<button class="btn btn-inverse gf-from-btn" ng-click="_.move(dashboard.rows,$index,$index+1)">
<i ng-class="{'invisible': $last}" class="fa fa-arrow-down"></i>
</button>
<button class="btn btn-inverse gf-form-btn" click="dashboard.rows = _.without(dashboard.rows,row)">
<i class="fa fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
<div ng-if="editor.index == 3">
<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
</div>
<div ng-if="editor.index == 2">
<dash-links-editor></dash-links-editor>
</div>
<div ng-if="editor.index == 4">
<h5 class="section-heading">Dashboard info</h5>
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Last updated at:</span>
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.updated)}}</span>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Last updated by:</span>
<span class="gf-form-label width-18">{{dashboardMeta.updatedBy}}&nbsp;</span>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Created at:</span>
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.created)}}&nbsp;</span>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Created by:</span>
<span class="gf-form-label width-18">{{dashboardMeta.createdBy}}&nbsp;</span>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Current version:</span>
<span class="gf-form-label width-18">{{dashboardMeta.version}}&nbsp;</span>
</div>
</div>
</div>
<div ng-if="editor.index == 3">
<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
</div>
<div ng-if="editor.index == 4">
<h5 class="section-heading">Dashboard info</h5>
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Last updated at:</span>
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.updated)}}</span>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Last updated by:</span>
<span class="gf-form-label width-18">{{dashboardMeta.updatedBy}}&nbsp;</span>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Created at:</span>
<span class="gf-form-label width-18">{{formatDate(dashboardMeta.created)}}&nbsp;</span>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Created by:</span>
<span class="gf-form-label width-18">{{dashboardMeta.createdBy}}&nbsp;</span>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Current version:</span>
<span class="gf-form-label width-18">{{dashboardMeta.version}}&nbsp;</span>
</div>
</div>
</div>
</div>
@@ -44,7 +44,7 @@ export class TimePickerCtrl {
var time = angular.copy(this.timeSrv.timeRange());
var timeRaw = angular.copy(this.timeSrv.timeRange(false));
if (this.dashboard.timezone === 'browser') {
if (!this.dashboard.isTimezoneUtc()) {
time.from.local();
time.to.local();
if (moment.isMoment(timeRaw.from)) {
@@ -125,7 +125,7 @@ export class TimePickerCtrl {
}
getAbsoluteMomentForTimezone(jsDate) {
return this.dashboard.timezone === 'browser' ? moment(jsDate) : moment(jsDate).utc();
return this.dashboard.isTimezoneUtc() ? moment(jsDate).utc() : moment(jsDate);
}
setRelativeFilter(timespan) {
+5
View File
@@ -1,7 +1,12 @@
define([
'./org_users_ctrl',
'./profile_ctrl',
'./org_users_ctrl',
'./select_org_ctrl',
'./change_password_ctrl',
'./newOrgCtrl',
'./userInviteCtrl',
'./orgApiKeysCtrl',
'./orgDetailsCtrl',
'./prefs_control',
], function () {});
@@ -19,6 +19,7 @@
</div>
</form>
<prefs-control mode="org"></prefs-control>
<h3 class="page-heading">Address</h3>
@@ -6,31 +6,28 @@
<h1>Profile</h1>
</div>
<form name="userForm" class="gf-form-group">
<h3 class="page-heading">Preferences</h3>
<form name="ctrl.userForm" class="gf-form-group">
<h3 class="page-heading">Information</h3>
<div class="gf-form">
<span class="gf-form-label width-7">Name</span>
<input class="gf-form-input max-width-21" type="text" required ng-model="user.name" >
<span class="gf-form-label width-9">Name</span>
<input class="gf-form-input max-width-21" type="text" required ng-model="ctrl.user.name" >
</div>
<div class="gf-form">
<span class="gf-form-label width-7">Email</span>
<input class="gf-form-input max-width-21" type="email" required ng-model="user.email">
<span class="gf-form-label width-9">Email</span>
<input class="gf-form-input max-width-21" type="email" required ng-model="ctrl.user.email">
</div>
<div class="gf-form">
<span class="gf-form-label width-7">Username</span>
<input class="gf-form-input max-width-21" type="text" required ng-model="user.login">
<span class="gf-form-label width-9">Username</span>
<input class="gf-form-input max-width-21" type="text" required ng-model="ctrl.user.login">
</div>
<div class="gf-form">
<span class="gf-form-label width-7">UI Theme</span>
<select class="gf-form-input gf-size-auto" ng-model="user.theme" ng-options="f for f in ['dark', 'light']"></select>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="update()">Update</button>
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Update</button>
</div>
</form>
<prefs-control mode="user"></prefs-control>
<h3 class="page-heading">Password</h3>
<div class="gf-form-group">
<a href="profile/password" class="btn btn-inverse">Change Password</a>
@@ -47,7 +44,7 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="org in orgs">
<tr ng-repeat="org in ctrl.orgs">
<td>{{org.name}}</td>
<td>{{org.role}}</td>
<td class="text-right">
+98
View File
@@ -0,0 +1,98 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import _ from 'lodash';
import coreModule from 'app/core/core_module';
export class PrefsControlCtrl {
prefs: any;
oldTheme: any;
prefsForm: any;
mode: string;
timezones: any = [
{value: '', text: 'Default'},
{value: 'browser', text: 'Local browser time'},
{value: 'utc', text: 'UTC'},
];
themes: any = [
{value: '', text: 'Default'},
{value: 'dark', text: 'Dark'},
{value: 'light', text: 'Light'},
];
/** @ngInject **/
constructor(private backendSrv, private $location) {
}
$onInit() {
return this.backendSrv.get(`/api/${this.mode}/preferences`).then(prefs => {
this.prefs = prefs;
this.oldTheme = prefs.theme;
});
}
updatePrefs() {
if (!this.prefsForm.$valid) { return; }
var cmd = {
theme: this.prefs.theme,
timezone: this.prefs.timezone,
homeDashboardId: this.prefs.homeDashboardId
};
this.backendSrv.put(`/api/${this.mode}/preferences`, cmd).then(() => {
window.location.href = config.appSubUrl + this.$location.path();
});
}
}
var template = `
<form name="ctrl.prefsForm" class="gf-form-group">
<h3 class="page-heading">Preferences</h3>
<div class="gf-form">
<span class="gf-form-label width-9">UI Theme</span>
<div class="gf-form-select-wrapper max-width-20">
<select class="gf-form-input" ng-model="ctrl.prefs.theme" ng-options="f.value as f.text for f in ctrl.themes"></select>
</div>
</div>
<div class="gf-form">
<span class="gf-form-label width-9">Home Dashboard</span>
<dashboard-selector
class="gf-form-select-wrapper max-width-20"
model="ctrl.prefs.homeDashboardId">
</dashboard-selector>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">Timezone</label>
<div class="gf-form-select-wrapper max-width-20">
<select class="gf-form-input" ng-model="ctrl.prefs.timezone" ng-options="f.value as f.text for f in ctrl.timezones"></select>
</div>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="ctrl.updatePrefs()">Update</button>
</div>
</form>
`;
export function prefsControlDirective() {
return {
restrict: 'E',
controller: PrefsControlCtrl,
bindToController: true,
controllerAs: 'ctrl',
template: template,
scope: {
mode: "@"
}
};
}
coreModule.directive('prefsControl', prefsControlDirective);
+51
View File
@@ -0,0 +1,51 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import {coreModule} from 'app/core/core';
import _ from 'lodash';
export class ProfileCtrl {
user: any;
old_theme: any;
orgs: any;
userForm: any;
/** @ngInject **/
constructor(private backendSrv, private contextSrv, private $location) {
this.getUser();
this.getUserOrgs();
}
getUser() {
this.backendSrv.get('/api/user').then(user => {
this.user = user;
this.user.theme = user.theme || 'dark';
});
}
getUserOrgs() {
this.backendSrv.get('/api/user/orgs').then(orgs => {
this.orgs = orgs;
});
}
setUsingOrg(org) {
this.backendSrv.post('/api/user/using/' + org.orgId).then(() => {
window.location.href = config.appSubUrl + '/profile';
});
}
update() {
if (!this.userForm.$valid) { return; }
this.backendSrv.put('/api/user/', this.user).then(() => {
this.contextSrv.user.name = this.user.name || this.user.login;
if (this.old_theme !== this.user.theme) {
window.location.href = config.appSubUrl + this.$location.path();
}
});
}
}
coreModule.controller('ProfileCtrl', ProfileCtrl);
@@ -165,6 +165,7 @@ class MetricsPanelCtrl extends PanelCtrl {
issueQueries(datasource) {
this.updateTimeRange();
this.datasource = datasource;
if (!this.panel.targets || this.panel.targets.length === 0) {
return this.$q.when([]);
@@ -251,7 +252,6 @@ class MetricsPanelCtrl extends PanelCtrl {
addDataQuery(datasource) {
var target = {
datasource: datasource ? datasource.name : undefined
};
this.panel.targets.push(target);
}
@@ -1,51 +0,0 @@
define([
'angular',
'app/core/config',
],
function (angular, config) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('ProfileCtrl', function($scope, backendSrv, contextSrv, $location) {
$scope.init = function() {
$scope.getUser();
$scope.getUserOrgs();
};
$scope.getUser = function() {
backendSrv.get('/api/user').then(function(user) {
$scope.user = user;
$scope.user.theme = user.theme || 'dark';
$scope.old_theme = $scope.user.theme;
});
};
$scope.getUserOrgs = function() {
backendSrv.get('/api/user/orgs').then(function(orgs) {
$scope.orgs = orgs;
});
};
$scope.setUsingOrg = function(org) {
backendSrv.post('/api/user/using/' + org.orgId).then(function() {
window.location.href = config.appSubUrl + '/profile';
});
};
$scope.update = function() {
if (!$scope.userForm.$valid) { return; }
backendSrv.put('/api/user/', $scope.user).then(function() {
contextSrv.user.name = $scope.user.name || $scope.user.login;
if ($scope.old_theme !== $scope.user.theme) {
window.location.href = config.appSubUrl + $location.path();
}
});
};
$scope.init();
});
});
@@ -77,7 +77,7 @@ function (angular, _, queryDef) {
switch($scope.agg.type) {
case 'terms': {
settings.order = settings.order || "asc";
settings.size = settings.size || "0";
settings.size = settings.size || "10";
settings.orderBy = settings.orderBy || "_term";
if (settings.size !== '0') {
@@ -193,7 +193,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
if (addTemplateVars) {
for (let variable of this.templateSrv.variables) {
segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '/$' + variable.name + '$/', expandable: true }));
segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '/^$' + variable.name + '$/', expandable: true }));
}
}
@@ -1,7 +1,7 @@
# CloudWatch Datasource - Native Plugin
# Prometheus Datasource - Native Plugin
Grafana ships with **built in** support for Prometheus, the open-source service monitoring system and time series database.
Read more about it here:
[http://docs.grafana.org/datasources/prometheus/](http://docs.grafana.org/datasources/prometheus/)
[http://docs.grafana.org/datasources/prometheus/](http://docs.grafana.org/datasources/prometheus/)
+1 -1
View File
@@ -279,7 +279,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
options.xaxis = {
timezone: dashboard.timezone,
timezone: dashboard.getTimezone(),
show: panel['x-axis'],
mode: "time",
min: min,
+1 -1
View File
@@ -157,7 +157,7 @@ function (angular, _, $) {
}
var html = '<div class="graph-legend-series';
if (series.yaxis === 2) { html += ' pull-right'; }
if (series.yaxis === 2) { html += ' graph-legend-series--right-y'; }
if (ctrl.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
html += '" data-series-index="' + i + '">';
html += '<div class="graph-legend-icon">';
@@ -53,7 +53,9 @@ describe('grafanaGraph', function() {
},
renderingCompleted: sinon.spy(),
hiddenSeries: {},
dashboard: {timezone: 'browser'},
dashboard: {
getTimezone: sinon.stub().returns('browser')
},
range: {
from: moment([2015, 1, 1, 10]),
to: moment([2015, 1, 1, 22]),
+6 -3
View File
@@ -114,9 +114,13 @@ class TablePanelCtrl extends MetricsPanelCtrl {
}
}
this.render();
}
render() {
this.table = transformDataToTable(this.dataRaw, this.panel);
this.table.sort(this.panel.sort);
this.render(this.table);
return super.render(this.table);
}
toggleColumnSort(col, colIndex) {
@@ -130,7 +134,6 @@ class TablePanelCtrl extends MetricsPanelCtrl {
this.panel.sort.col = colIndex;
this.panel.sort.desc = true;
}
this.render();
}
@@ -155,7 +158,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
}
function appendTableRows(tbodyElem) {
var renderer = new TableRenderer(panel, data, ctrl.dashboard.timezone);
var renderer = new TableRenderer(panel, data, ctrl.dashboard.isTimezoneUtc());
tbodyElem.empty();
tbodyElem.html(renderer.render(ctrl.pageIndex));
}
+2 -2
View File
@@ -8,7 +8,7 @@ export class TableRenderer {
formaters: any[];
colorState: any;
constructor(private panel, private table, private timezone) {
constructor(private panel, private table, private isUtc) {
this.formaters = [];
this.colorState = {};
}
@@ -45,7 +45,7 @@ export class TableRenderer {
return v => {
if (_.isArray(v)) { v = v[0]; }
var date = moment(v);
if (this.timezone === 'utc') {
if (this.isUtc) {
date = date.utc();
}
return date.format(style.dateFormat);
+1
View File
@@ -17,6 +17,7 @@
@import "base/grid";
@import "base/font_awesome";
@import "base/grafana_icons";
@import "base/code";
// UTILS
@import "utils/utils";
+2 -2
View File
@@ -7,8 +7,8 @@
// ---------------------
@include media-breakpoint-down(sm) {
div.panel {
width: 100%;
padding: 0px;
width: 100% !important;
padding: 0px !important;
}
.panel-margin {
margin-right: 0;
+7 -7
View File
@@ -11,28 +11,28 @@ pre {
background-color: $code-tag-bg;
color: $text-color;
border: 1px solid darken($code-tag-bg, 15%);
padding: 2px;
padding: 10px;
border-radius: 4px;
}
// Inline code
code {
color: #d14;
background-color: #f7f7f9;
border: 1px solid #e1e1e8;
color: $text-color;
background-color: $code-tag-bg;
border: 1px solid darken($code-tag-bg, 15%);
white-space: nowrap;
}
// Blocks of code
pre {
display: block;
margin: 0 0 $line-height-base / 2;
font-size: $font-size-base - 1; // 14px to 13px
margin: 0 0 $line-height-base;
line-height: $line-height-base;
word-break: break-all;
word-wrap: break-word;
white-space: pre;
white-space: pre-wrap;
background-color: #f5f5f5;
background-color: $code-tag-bg;
// Make prettyprint styles more spaced out for readability
&.prettyprint {
-6
View File
@@ -66,12 +66,6 @@
border: solid 0.08em #eeeeee;
border-radius: .1em;
}
.pull-right {
float: right;
}
.pull-left {
float: left;
}
.fa.pull-left {
margin-right: .3em;
}
+14 -2
View File
@@ -74,6 +74,10 @@
float: left;
white-space: nowrap;
padding-left: 10px;
&--right-y {
float: right;
}
}
.graph-legend-value {
@@ -83,14 +87,22 @@
.graph-legend-table {
overflow-y: scroll;
.graph-legend-series { display: table-row;
.graph-legend-series {
display: table-row;
float: none;
padding-left: 0;
&.pull-right {
&--right-y {
float: none;
.graph-legend-alias:after {
content: '(right-y)';
padding: 0 5px;
color: $text-color-weak;
}
}
}
td, .graph-legend-alias, .graph-legend-icon, .graph-legend-value {
float: none;
display: table-cell;
+4
View File
@@ -7,6 +7,10 @@ define([
var _dashboardSrv;
beforeEach(module('grafana.services'));
beforeEach(module(function($provide) {
$provide.value('contextSrv', {
});
}));
beforeEach(inject(function(dashboardSrv) {
_dashboardSrv = dashboardSrv;
+10 -5
View File
@@ -12,6 +12,11 @@ define([
ctx.setup = function (setupFunc) {
beforeEach(module('grafana.services'));
beforeEach(module(function($provide) {
$provide.value('contextSrv', {
user: { timezone: 'utc'}
});
}));
beforeEach(inject(function(dynamicDashboardSrv, dashboardSrv) {
ctx.dynamicDashboardSrv = dynamicDashboardSrv;
@@ -45,10 +50,10 @@ define([
value: ['se1', 'se2', 'se3']
},
options: [
{text: 'se1', value: 'se1', selected: true},
{text: 'se2', value: 'se2', selected: true},
{text: 'se3', value: 'se3', selected: true},
{text: 'se4', value: 'se4', selected: false}
{text: 'se1', value: 'se1', selected: true},
{text: 'se2', value: 'se2', selected: true},
{text: 'se3', value: 'se3', selected: true},
{text: 'se4', value: 'se4', selected: false}
]
});
});
@@ -93,7 +98,7 @@ define([
describe('After a second iteration and selected values reduced', function() {
beforeEach(function() {
ctx.dash.templating.list[0].options[1].selected = false;
ctx.dynamicDashboardSrv.update(ctx.dash);
});
+1 -1
View File
@@ -13,6 +13,6 @@
"commit": "5a07c5107b4d24f41744a02b07717d55bad88e70"
},
"_source": "git://github.com/angular/bower-angular.git",
"_target": "1.5.3",
"_target": "~1.5.3",
"_originalSource": "angular"
}