Commit f3980504 by Torkel Ödegaard

Merge branch 'master' into walmartlabs-master

parents 77c046aa 1efdd92a
......@@ -3,14 +3,19 @@
## Enhancements
* **Elasticsearch**: Added filter aggregation label [#8420](https://github.com/grafana/grafana/pull/8420), thx [@tianzk](github.com/tianzk)
* **Sensu**: Added option for source and handler [#8405](https://github.com/grafana/grafana/pull/8405), thx [@joemiller](github.com/joemiller)
* **CSV**: Configurable csv export datetime format [#8058](https://github.com/grafana/grafana/issues/8058), thx [@cederigo](github.com/cederigo)
# 4.3.2 (upcoming patch release)
# 4.3.2 (2017-05-31)
## Bug fixes
* **InfluxDB**: Fixed issue with query editor not showing ALIAS BY input field when in text editor mode [#8459](https://github.com/grafana/grafana/issues/8459)
* **Graph Log Scale**: Fixed issue with log scale going below x-axis [#8244](https://github.com/grafana/grafana/issues/8244)
* **Playlist**: Fixed dashboard play order issue [#7688](https://github.com/grafana/grafana/issues/7688)
* **Elasticsearch**: Fixed table query issue with ES 2.x [#8467](https://github.com/grafana/grafana/issues/8467), thx [@goldeelox](https://github.com/goldeelox)
## Changes
* **Lazy Loading Of Panels**: Panels are no longer loaded as they are scrolled into view, this was reverted due to Chrome bug, might be reintroduced when Chrome fixes it's JS blocking behavior on scroll. [#8500](https://github.com/grafana/grafana/issues/8500)
# 4.3.1 (2017-05-23)
......
......@@ -52,12 +52,22 @@ Here you can specify the name of the alert rule and how often the scheduler shou
### Conditions
Currently the only condition type that exists is a `Query` condition that allows you to
specify a query letter, time range and an aggregation function. The letter refers to
a query you already have added in the **Metrics** tab. The result from the query and the aggregation function is
a single value that is then used in the threshold check. The query used in an alert rule cannot
contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially.
specify a query letter, time range and an aggregation function.
### Query condition example
```sql
avg() OF query(A, 5m, now) IS BELOW 14
```
- `avg()` Controls how the values for **each** serie should be reduced to a value that can be compared against the threshold. Click on the function to change it to another aggregation function.
- `query(A, 5m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters defines the time range, `5m, now` means 5 minutes from now to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes from now to 2 minutes from now. This is useful if you want to ignore the last 2 minutes of data.
- `IS BELOW 14` Defines the type of threshold and the threshold value. You can click on `IS BELOW` to change the type of threshold.
The query used in an alert rule cannot contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially.
For example, we have 3 conditions in the following order:
`condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)`
*condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)*
so the result will be calculated as ((TRUE OR FALSE) AND TRUE) = TRUE.
We plan to add other condition types in the future, like `Other Alert`, where you can include the state
......
......@@ -92,9 +92,10 @@ The Elasticsearch data source supports two types of queries you can use in the *
Query | Description
------------ | -------------
*{"find": "fields", "type": "keyword"} | Returns a list of field names with the index type `keyword`.
*{"find": "terms", "field": "@hostname"}* | Returns a list of values for a field using term aggregation. Query will user current dashboard time range as time range for query.
*{"find": "terms", "field": "@hostname", "size": 1000}* | Returns a list of values for a field using term aggregation. Query will user current dashboard time range as time range for query.
*{"find": "terms", "field": "@hostname", "query": '<lucene query>'}* | Returns a list of values for a field using term aggregation & and a specified lucene query filter. Query will use current dashboard time range as time range for query.
There is a default size limit of 500 on terms queries. Set the size property in your query to set a custom limit.
You can use other variables inside the query. Example query definition for a variable named `$host`.
```
......
......@@ -65,7 +65,7 @@ Each field in the dashboard JSON is explained below with its usage:
| **timezone** | timezone of dashboard, i.e. `utc` or `browser` |
| **editable** | whether a dashboard is editable or not |
| **hideControls** | whether row controls on the left in green are hidden or not |
| **graphTooltip** | TODO |
| **graphTooltip** | 0 for no shared crosshair or tooltip (default), 1 for shared crosshair, 2 for shared crosshair AND shared tooltip |
| **rows** | row metadata, see [rows section](#rows) for details |
| **time** | time range for dashboard, i.e. last 6 hours, last 7 days, etc |
| **timepicker** | timepicker metadata, see [timepicker section](#timepicker) for details |
......
......@@ -261,6 +261,7 @@ func (hs *HttpServer) registerRoutes() {
r.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics))
r.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios))
r.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSqlTestData))
r.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk))
// metrics
r.Get("/metrics", wrap(GetInternalMetrics))
......
......@@ -90,6 +90,7 @@ func init() {
"CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "ReadBytes", "ReadTime", "WriteBytes", "WriteTime", "QueuedWrites"},
"AWS/SWF": {"DecisionTaskScheduleToStartTime", "DecisionTaskStartToCloseTime", "DecisionTasksCompleted", "StartedDecisionTasksTimedOutOnClose", "WorkflowStartToCloseTime", "WorkflowsCanceled", "WorkflowsCompleted", "WorkflowsContinuedAsNew", "WorkflowsFailed", "WorkflowsTerminated", "WorkflowsTimedOut",
"ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat"},
"AWS/VPN": {"TunnelState", "TunnelDataIn", "TunnelDataOut"},
"AWS/WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"},
"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
"KMS": {"SecondsUntilKeyMaterialExpiration"},
......@@ -131,6 +132,7 @@ func init() {
"AWS/SQS": {"QueueName"},
"AWS/StorageGateway": {"GatewayId", "GatewayName", "VolumeId"},
"AWS/SWF": {"Domain", "WorkflowTypeName", "WorkflowTypeVersion", "ActivityTypeName", "ActivityTypeVersion"},
"AWS/VPN": {"VpnId", "TunnelIpAddress"},
"AWS/WAF": {"Rule", "WebACL"},
"AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"},
"KMS": {"KeyId"},
......
......@@ -28,6 +28,7 @@ var (
ErrEmailNotAllowed = errors.New("Required email domain not fulfilled")
ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter")
ErrUsersQuotaReached = errors.New("Users quota reached")
ErrNoEmail = errors.New("Login provider didn't return an email address")
)
func GenStateString() string {
......@@ -63,7 +64,7 @@ func OAuthLogin(ctx *middleware.Context) {
if setting.OAuthService.OAuthInfos[name].HostedDomain == "" {
ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
} else {
ctx.Redirect(connect.AuthCodeURL(state, oauth2.SetParam("hd", setting.OAuthService.OAuthInfos[name].HostedDomain), oauth2.AccessTypeOnline))
ctx.Redirect(connect.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", setting.OAuthService.OAuthInfos[name].HostedDomain), oauth2.AccessTypeOnline))
}
return
}
......@@ -134,6 +135,12 @@ func OAuthLogin(ctx *middleware.Context) {
ctx.Logger.Debug("OAuthLogin got user info", "userInfo", userInfo)
// validate that we got at least an email address
if userInfo.Email == "" {
redirectWithError(ctx, ErrNoEmail)
return
}
// validate that the email is allowed to login to grafana
if !connect.IsEmailAllowed(userInfo.Email) {
redirectWithError(ctx, ErrEmailNotAllowed)
......
......@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models"
......@@ -144,3 +145,29 @@ func GenerateSqlTestData(c *middleware.Context) Response {
return Json(200, &util.DynMap{"message": "OK"})
}
// GET /api/tsdb/testdata/random-walk
func GetTestDataRandomWalk(c *middleware.Context) Response {
from := c.Query("from")
to := c.Query("to")
intervalMs := c.QueryInt64("intervalMs")
timeRange := tsdb.NewTimeRange(from, to)
request := &tsdb.Request{TimeRange: timeRange}
request.Queries = append(request.Queries, &tsdb.Query{
RefId: "A",
IntervalMs: intervalMs,
Model: simplejson.NewFromAny(&util.DynMap{
"scenario": "random_walk",
}),
DataSource: &models.DataSource{Type: "grafana-testdata-datasource"},
})
resp, err := tsdb.HandleRequest(context.Background(), request)
if err != nil {
return ApiError(500, "Metric request error", err)
}
return Json(200, &resp)
}
......@@ -16,7 +16,7 @@ func addAlertMigrations(mg *Migrator) {
{Name: "org_id", Type: DB_BigInt, Nullable: false},
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "message", Type: DB_Text, Nullable: false},
{Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false},
{Name: "settings", Type: DB_Text, Nullable: false},
{Name: "frequency", Type: DB_BigInt, Nullable: false},
{Name: "handler", Type: DB_BigInt, Nullable: false},
......@@ -70,7 +70,7 @@ func addAlertMigrations(mg *Migrator) {
mg.AddMigration("Update alert table charset", NewTableCharsetMigration("alert", []*Column{
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "message", Type: DB_Text, Nullable: false},
{Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false},
{Name: "settings", Type: DB_Text, Nullable: false},
{Name: "severity", Type: DB_Text, Nullable: false},
{Name: "execution_error", Type: DB_Text, Nullable: false},
......
......@@ -8,7 +8,7 @@ func addDashboardMigration(mg *Migrator) {
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "version", Type: DB_Int, Nullable: false},
{Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false},
{Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false},
{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "data", Type: DB_Text, Nullable: false},
{Name: "account_id", Type: DB_BigInt, Nullable: false},
......@@ -56,7 +56,7 @@ func addDashboardMigration(mg *Migrator) {
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "version", Type: DB_Int, Nullable: false},
{Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false},
{Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false},
{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "data", Type: DB_Text, Nullable: false},
{Name: "org_id", Type: DB_BigInt, Nullable: false},
......@@ -114,7 +114,7 @@ func addDashboardMigration(mg *Migrator) {
// add column to store plugin_id
mg.AddMigration("Add column plugin_id in dashboard", NewAddColumnMigration(dashboardV2, &Column{
Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255,
Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 189,
}))
mg.AddMigration("Add index for plugin_id in dashboard", NewAddIndexMigration(dashboardV2, &Index{
......@@ -127,9 +127,9 @@ func addDashboardMigration(mg *Migrator) {
}))
mg.AddMigration("Update dashboard table charset", NewTableCharsetMigration("dashboard", []*Column{
{Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false},
{Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false},
{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255},
{Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 189},
{Name: "data", Type: DB_MediumText, Nullable: false},
}))
......
......@@ -9,10 +9,10 @@ func addTempUserMigrations(mg *Migrator) {
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: DB_BigInt, Nullable: false},
{Name: "version", Type: DB_Int, Nullable: false},
{Name: "email", Type: DB_NVarchar, Length: 255},
{Name: "email", Type: DB_NVarchar, Length: 190},
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
{Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true},
{Name: "code", Type: DB_NVarchar, Length: 255},
{Name: "code", Type: DB_NVarchar, Length: 190},
{Name: "status", Type: DB_Varchar, Length: 20},
{Name: "invited_by_user_id", Type: DB_BigInt, Nullable: true},
{Name: "email_sent", Type: DB_Bool},
......@@ -37,10 +37,10 @@ func addTempUserMigrations(mg *Migrator) {
addTableIndicesMigrations(mg, "v1-7", tempUserV1)
mg.AddMigration("Update temp_user table charset", NewTableCharsetMigration("temp_user", []*Column{
{Name: "email", Type: DB_NVarchar, Length: 255},
{Name: "email", Type: DB_NVarchar, Length: 190},
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
{Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true},
{Name: "code", Type: DB_NVarchar, Length: 255},
{Name: "code", Type: DB_NVarchar, Length: 190},
{Name: "status", Type: DB_Varchar, Length: 20},
{Name: "remote_addr", Type: DB_Varchar, Length: 255, Nullable: true},
}))
......
......@@ -2,7 +2,11 @@ package social
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/grafana/grafana/pkg/log"
)
func isEmailAllowed(email string, allowedDomains []string) bool {
......@@ -18,3 +22,25 @@ func isEmailAllowed(email string, allowedDomains []string) bool {
return valid
}
func HttpGet(client *http.Client, url string) ([]byte, error) {
r, err := client.Get(url)
if err != nil {
return nil, err
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
if r.StatusCode >= 300 {
return nil, fmt.Errorf(string(body))
}
log.Trace("HTTP GET %s: %s %s", url, r.Status, string(body))
return body, nil
}
......@@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"github.com/grafana/grafana/pkg/models"
......@@ -84,22 +83,14 @@ func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
IsConfirmed bool `json:"is_confirmed"`
}
emailsUrl := fmt.Sprintf(s.apiUrl + "/emails")
r, err := client.Get(emailsUrl)
body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/emails"))
if err != nil {
return "", err
return "", fmt.Errorf("Error getting email address: %s", err)
}
defer r.Body.Close()
var records []Record
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return "", err
}
err = json.Unmarshal(body, records)
err = json.Unmarshal(body, &records)
if err != nil {
var data struct {
Values []Record `json:"values"`
......@@ -107,7 +98,7 @@ func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
err = json.Unmarshal(body, &data)
if err != nil {
return "", err
return "", fmt.Errorf("Error getting email address: %s", err)
}
records = data.Values
......@@ -129,18 +120,16 @@ func (s *GenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error)
Id int `json:"id"`
}
membershipUrl := fmt.Sprintf(s.apiUrl + "/teams")
r, err := client.Get(membershipUrl)
body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/teams"))
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting team memberships: %s", err)
}
defer r.Body.Close()
var records []Record
if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
return nil, err
err = json.Unmarshal(body, &records)
if err != nil {
return nil, fmt.Errorf("Error getting team memberships: %s", err)
}
var ids = make([]int, len(records))
......@@ -156,18 +145,16 @@ func (s *GenericOAuth) FetchOrganizations(client *http.Client) ([]string, error)
Login string `json:"login"`
}
url := fmt.Sprintf(s.apiUrl + "/orgs")
r, err := client.Get(url)
body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs"))
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting organizations: %s", err)
}
defer r.Body.Close()
var records []Record
if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
return nil, err
err = json.Unmarshal(body, &records)
if err != nil {
return nil, fmt.Errorf("Error getting organizations: %s", err)
}
var logins = make([]string, len(records))
......@@ -188,16 +175,14 @@ func (s *GenericOAuth) UserInfo(client *http.Client) (*BasicUserInfo, error) {
Attributes map[string][]string `json:"attributes"`
}
var err error
r, err := client.Get(s.apiUrl)
body, err := HttpGet(client, s.apiUrl)
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting user info: %s", err)
}
defer r.Body.Close()
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
return nil, err
err = json.Unmarshal(body, &data)
if err != nil {
return nil, fmt.Errorf("Error getting user info: %s", err)
}
userInfo := &BasicUserInfo{
......
......@@ -85,18 +85,16 @@ func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) {
Verified bool `json:"verified"`
}
emailsUrl := fmt.Sprintf(s.apiUrl + "/emails")
r, err := client.Get(emailsUrl)
body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/emails"))
if err != nil {
return "", err
return "", fmt.Errorf("Error getting email address: %s", err)
}
defer r.Body.Close()
var records []Record
if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
return "", err
err = json.Unmarshal(body, &records)
if err != nil {
return "", fmt.Errorf("Error getting email address: %s", err)
}
var email = ""
......@@ -114,18 +112,16 @@ func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error)
Id int `json:"id"`
}
membershipUrl := fmt.Sprintf(s.apiUrl + "/teams")
r, err := client.Get(membershipUrl)
body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/teams"))
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting team memberships: %s", err)
}
defer r.Body.Close()
var records []Record
if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
return nil, err
err = json.Unmarshal(body, &records)
if err != nil {
return nil, fmt.Errorf("Error getting team memberships: %s", err)
}
var ids = make([]int, len(records))
......@@ -141,18 +137,16 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error)
Login string `json:"login"`
}
url := fmt.Sprintf(s.apiUrl + "/orgs")
r, err := client.Get(url)
body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs"))
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting organizations: %s", err)
}
defer r.Body.Close()
var records []Record
if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
return nil, err
err = json.Unmarshal(body, &records)
if err != nil {
return nil, fmt.Errorf("Error getting organizations: %s", err)
}
var logins = make([]string, len(records))
......@@ -170,16 +164,14 @@ func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
Email string `json:"email"`
}
var err error
r, err := client.Get(s.apiUrl)
body, err := HttpGet(client, s.apiUrl)
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting user info: %s", err)
}
defer r.Body.Close()
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
return nil, err
err = json.Unmarshal(body, &data)
if err != nil {
return nil, fmt.Errorf("Error getting user info: %s", err)
}
userInfo := &BasicUserInfo{
......
......@@ -2,6 +2,7 @@ package social
import (
"encoding/json"
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/models"
......@@ -34,16 +35,17 @@ func (s *SocialGoogle) UserInfo(client *http.Client) (*BasicUserInfo, error) {
Name string `json:"name"`
Email string `json:"email"`
}
var err error
r, err := client.Get(s.apiUrl)
body, err := HttpGet(client, s.apiUrl)
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting user info: %s", err)
}
defer r.Body.Close()
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
return nil, err
err = json.Unmarshal(body, &data)
if err != nil {
return nil, fmt.Errorf("Error getting user info: %s", err)
}
return &BasicUserInfo{
Name: data.Name,
Email: data.Email,
......
......@@ -2,6 +2,7 @@ package social
import (
"encoding/json"
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/models"
......@@ -57,16 +58,14 @@ func (s *SocialGrafanaCom) UserInfo(client *http.Client) (*BasicUserInfo, error)
Orgs []OrgRecord `json:"orgs"`
}
var err error
r, err := client.Get(s.url + "/api/oauth2/user")
body, err := HttpGet(client, s.url+"/api/oauth2/user")
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting user info: %s", err)
}
defer r.Body.Close()
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
return nil, err
err = json.Unmarshal(body, &data)
if err != nil {
return nil, fmt.Errorf("Error getting user info: %s", err)
}
userInfo := &BasicUserInfo{
......
......@@ -105,10 +105,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
if (pageClass) {
body.removeClass(pageClass);
}
pageClass = data.$$route.pageClass;
if (pageClass) {
body.addClass(pageClass);
if (data.$$route) {
pageClass = data.$$route.pageClass;
if (pageClass) {
body.addClass(pageClass);
}
}
$("#tooltip, .tooltip").remove();
// check for kiosk url param
......@@ -194,6 +198,15 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
});
}
}
// hide menus
var openMenus = body.find('.navbar-page-btn--open');
if (openMenus.length > 0) {
if (target.parents('.navbar-page-btn--open').length === 0) {
openMenus.removeClass('navbar-page-btn--open');
}
}
// hide sidemenu
if (!ignoreSideMenuHide && !contextSrv.pinned && body.find('.sidemenu').length > 0) {
if (target.parents('.sidemenu').length === 0) {
......
<div class="modal-body">
<div class="modal-header">
<h2 class="modal-header-title">
<i class="fa fa-keyboard"></i>
<i class="fa fa-keyboard-o"></i>
<span class="p-l-1">Shortcuts</span>
</h2>
......@@ -20,7 +20,7 @@
<div class="modal-content help-modal">
<p class="small" style="position: absolute; top: 48px; right: 10px">
<p class="small" style="position: absolute; top: 13px; right: 44px">
<span class="shortcut-table-key">mod</span> =
<span class="muted">CTRL on windows or linux and CMD key on Mac</span>
</p>
......
......@@ -8,11 +8,36 @@
<i class="fa fa-chevron-left"></i>
</a>
<a href="{{ctrl.titleUrl}}" class="navbar-page-btn" ng-show="ctrl.title">
<i class="{{ctrl.icon}}" ng-show="ctrl.icon"></i>
<img ng-src="{{ctrl.iconUrl}}" ng-show="ctrl.iconUrl"></i>
{{ctrl.title}}
<a class="navbar-page-btn" ng-click="ctrl.showSearch()">
<i class="fa fa-search"></i>
</a>
<div ng-transclude></div>
<div ng-if="::!ctrl.hasMenu">
<a href="{{::ctrl.section.url}}" class="navbar-page-btn">
<i class="{{::ctrl.section.icon}}" ng-show="::ctrl.section.icon"></i>
<img ng-src="{{::ctrl.section.iconUrl}}" ng-show="::ctrl.section.iconUrl"></i>
{{::ctrl.section.title}}
</a>
</div>
<div class="dropdown navbar-section-wrapper" ng-if="::ctrl.hasMenu">
<a href="{{::ctrl.section.url}}" class="navbar-page-btn" data-toggle="dropdown">
<i class="{{::ctrl.section.icon}}" ng-show="::ctrl.section.icon"></i>
<img ng-src="{{::ctrl.section.iconUrl}}" ng-show="::ctrl.section.iconUrl"></i>
{{::ctrl.section.title}}
<i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu dropdown-menu--navbar">
<li ng-repeat="navItem in ::ctrl.model.menu" ng-class="{active: navItem.active}">
<a class="pointer" ng-href="{{::navItem.url}}" ng-click="ctrl.navItemClicked(navItem, $event)">
<i class="{{::navItem.icon}}" ng-show="::navItem.icon"></i>
{{::navItem.title}}
</a>
</li>
</ul>
</div>
<div ng-transclude></div>
</div>
<dashboard-search></dashboard-search>
......@@ -4,10 +4,28 @@ import config from 'app/core/config';
import _ from 'lodash';
import $ from 'jquery';
import coreModule from '../../core_module';
import {NavModel, NavModelItem} from '../../nav_model_srv';
export class NavbarCtrl {
model: NavModel;
section: NavModelItem;
hasMenu: boolean;
/** @ngInject */
constructor(private $scope, private contextSrv) {
constructor(private $scope, private $rootScope, private contextSrv) {
this.section = this.model.section;
this.hasMenu = this.model.menu.length > 0;
}
showSearch() {
this.$rootScope.appEvent('show-dash-search');
}
navItemClicked(navItem, evt) {
if (navItem.clickHandler) {
navItem.clickHandler();
evt.preventDefault();
}
}
}
......@@ -20,12 +38,9 @@ export function navbarDirective() {
transclude: true,
controllerAs: 'ctrl',
scope: {
title: "@",
titleUrl: "@",
iconUrl: "@",
model: "=",
},
link: function(scope, elem, attrs, ctrl) {
ctrl.icon = attrs.icon;
link: function(scope, elem) {
elem.addClass('navbar');
}
};
......
<div class="search-backdrop" ng-if="ctrl.isOpen"></div>
<div class="search-container" ng-if="ctrl.isOpen">
<div class="search-field-wrapper">
<span style="position: relative;">
<input type="text" placeholder="Find dashboards by name" give-focus="ctrl.giveSearchFocus" tabindex="1"
ng-keydown="ctrl.keyDown($event)" ng-model="ctrl.query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.search()" />
</span>
<div class="search-field-icon pointer" ng-click="ctrl.closeSearch()">
<i class="fa fa-search"></i>
</div>
<input type="text" placeholder="Find dashboards by name" give-focus="ctrl.giveSearchFocus" tabindex="1"
ng-keydown="ctrl.keyDown($event)"
ng-model="ctrl.query.query"
ng-model-options="{ debounce: 500 }"
spellcheck='false'
ng-change="ctrl.search()"
ng-blur="ctrl.searchInputBlur()"
/>
<div class="search-switches">
<i class="fa fa-filter"></i>
<a class="pointer" href="javascript:void 0;" ng-click="ctrl.showStarred()" tabindex="2">
......@@ -24,54 +37,55 @@
</span>
</span>
</div>
<div class="search-field-spacer"></div>
</div>
<div class="search-results-container" ng-if="ctrl.tagsMode">
<div ng-repeat="tag in ctrl.results" class="pointer" style="width: 180px; float: left;"
ng-class="{'selected': $index === ctrl.selectedIndex }"
ng-click="ctrl.filterByTag(tag.term, $event)">
<a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
<i class="fa fa-tag"></i>
<span>{{tag.term}} &nbsp;({{tag.count}})</span>
</a>
<div class="search-dropdown" ng-class="{'search-dropdown--fade-in': ctrl.openCompleted}">
<div class="search-results-container" ng-if="ctrl.tagsMode">
<div ng-repeat="tag in ctrl.results" class="pointer" style="width: 180px; float: left;"
ng-class="{'selected': $index === ctrl.selectedIndex }"
ng-click="ctrl.filterByTag(tag.term, $event)">
<a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
<i class="fa fa-tag"></i>
<span>{{tag.term}} &nbsp;({{tag.count}})</span>
</a>
</div>
</div>
</div>
<div class="search-results-container" ng-if="!ctrl.tagsMode">
<h6 ng-hide="ctrl.results.length">No dashboards matching your query were found.</h6>
<div class="search-results-container" ng-if="!ctrl.tagsMode">
<h6 ng-hide="ctrl.results.length">No dashboards matching your query were found.</h6>
<a class="search-item pointer search-item-{{row.type}}" bindonce ng-repeat="row in ctrl.results"
ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{row.url}}">
<a class="search-item pointer search-item-{{row.type}}" bindonce ng-repeat="row in ctrl.results"
ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{row.url}}">
<span class="search-result-tags">
<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name="tag" class="label label-tag">
{{tag}}
<span class="search-result-tags">
<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name="tag" class="label label-tag">
{{tag}}
</span>
<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
</span>
<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
</span>
<span class="search-result-link">
<i class="fa search-result-icon"></i>
<span bo-text="row.title"></span>
</span>
</a>
</div>
<div class="search-button-row">
<a class="btn btn-inverse pull-left" href="dashboard/new" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
<i class="fa fa-plus"></i>
Create New
</a>
<span class="search-result-link">
<i class="fa search-result-icon"></i>
<span bo-text="row.title"></span>
</span>
</a>
</div>
<a class="btn btn-inverse pull-left" href="dashboard/new/?editview=import" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
<i class="fa fa-upload"></i>
Import
</a>
<div class="search-button-row">
<a class="btn btn-secondary" href="dashboard/new" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
<i class="fa fa-plus"></i>&nbsp; New Dashboard
</a>
<a class="pull-right search-button-row-explore-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search">
Find <img src="public/img/icn-dashboard-tiny.svg" width="14" /> dashboards on Grafana.com
</a>
<a class="btn btn-inverse" href="dashboard/new/?editview=import" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
<i class="fa fa-upload"></i>&nbsp; Import Dashboard
</a>
<div class="clearfix"></div>
<a class="search-button-row-explore-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search">
Find <img src="public/img/icn-dashboard-tiny.svg" width="14" /> dashboards on Grafana.com
</a>
</div>
</div>
</div>
......@@ -18,6 +18,8 @@ export class SearchCtrl {
showImport: boolean;
dismiss: any;
ignoreClose: any;
// triggers fade animation class
openCompleted: boolean;
/** @ngInject */
constructor(private $scope, private $location, private $timeout, private backendSrv, private contextSrv, private $rootScope) {
......@@ -27,6 +29,7 @@ export class SearchCtrl {
closeSearch() {
this.isOpen = this.ignoreClose;
this.openCompleted = false;
}
openSearch(evt, payload) {
......@@ -56,6 +59,7 @@ export class SearchCtrl {
}
this.$timeout(() => {
this.openCompleted = true;
this.ignoreClose = false;
this.giveSearchFocus = this.giveSearchFocus + 1;
this.search();
......@@ -169,6 +173,7 @@ export function searchDirective() {
controller: SearchCtrl,
bindToController: true,
controllerAs: 'ctrl',
scope: {},
};
}
......
......@@ -5,7 +5,9 @@ define([
function (angular, coreModule) {
'use strict';
coreModule.default.controller('ErrorCtrl', function($scope, contextSrv) {
coreModule.default.controller('ErrorCtrl', function($scope, contextSrv, navModelSrv) {
$scope.navModel = navModelSrv.getNotFoundNav();
var showSideMenu = contextSrv.sidemenu;
contextSrv.sidemenu = false;
......
......@@ -45,6 +45,7 @@ import {assignModelProperties} from './utils/model_utils';
import {contextSrv} from './services/context_srv';
import {KeybindingSrv} from './services/keybindingSrv';
import {helpModal} from './components/help/help';
import {NavModelSrv, NavModel} from './nav_model_srv';
export {
......@@ -69,4 +70,6 @@ export {
contextSrv,
KeybindingSrv,
helpModal,
NavModelSrv,
NavModel,
};
define([
'jquery',
'angular',
'../core_module',
],
function ($, coreModule) {
function ($, angular, coreModule) {
'use strict';
var editViewMap = {
'settings': { src: 'public/app/features/dashboard/partials/settings.html'},
'annotations': { src: 'public/app/features/annotations/partials/editor.html'},
'history': { src: 'public/app/features/dashboard/history/partials/history.html'},
'templating': { src: 'public/app/features/templating/partials/editor.html'},
'import': { src: '<dash-import></dash-import>' }
'history': { html: '<gf-dashboard-history dashboard="dashboard"></gf-dashboard-history>'},
'timepicker': { src: 'public/app/features/dashboard/timepicker/dropdown.html' },
'import': { html: '<dash-import></dash-import>' }
};
coreModule.default.directive('dashEditorView', function($compile, $location, $rootScope) {
......@@ -18,47 +20,53 @@ function ($, coreModule) {
restrict: 'A',
link: function(scope, elem) {
var editorScope;
var lastEditor;
var lastEditView;
function hideEditorPane() {
function hideEditorPane(hideToShowOtherView) {
if (editorScope) {
scope.appEvent('dash-editor-hidden', lastEditor);
editorScope.dismiss();
editorScope.dismiss(hideToShowOtherView);
scope.appEvent('dash-editor-hidden');
}
}
function showEditorPane(evt, payload, editview) {
if (editview) {
scope.contextSrv.editview = editViewMap[editview];
payload.src = scope.contextSrv.editview.src;
function showEditorPane(evt, options) {
if (options.editview) {
options.src = editViewMap[options.editview].src;
options.html = editViewMap[options.editview].html;
}
if (lastEditor === payload.src) {
hideEditorPane();
if (lastEditView === options.editview) {
hideEditorPane(false);
return;
}
hideEditorPane();
hideEditorPane(true);
lastEditor = payload.src;
editorScope = payload.scope ? payload.scope.$new() : scope.$new();
lastEditView = options.editview;
editorScope = options.scope ? options.scope.$new() : scope.$new();
editorScope.dismiss = function() {
editorScope.dismiss = function(hideToShowOtherView) {
editorScope.$destroy();
elem.empty();
lastEditor = null;
lastEditView = null;
editorScope = null;
elem.removeClass('dash-edit-view--open');
if (!hideToShowOtherView) {
setTimeout(function() {
elem.empty();
}, 250);
}
if (editview) {
if (options.editview) {
var urlParams = $location.search();
if (editview === urlParams.editview) {
if (options.editview === urlParams.editview) {
delete urlParams.editview;
$location.search(urlParams);
}
}
};
if (editview === 'import') {
if (options.editview === 'import') {
var modalScope = $rootScope.$new();
modalScope.$on("$destroy", function() {
editorScope.dismiss();
......@@ -73,29 +81,42 @@ function ($, coreModule) {
return;
}
var view = payload.src;
if (view.indexOf('.html') > 0) {
view = $('<div class="tabbed-view" ng-include="' + "'" + view + "'" + '"></div>');
var view;
if (options.src) {
view = angular.element(document.createElement('div'));
view.html('<div class="tabbed-view" ng-include="' + "'" + options.src + "'" + '"></div>');
} else {
view = angular.element(document.createElement('div'));
view.addClass('tabbed-view');
view.html(options.html);
}
elem.append(view);
$compile(elem.contents())(editorScope);
$compile(view)(editorScope);
setTimeout(function() {
elem.empty();
elem.append(view);
setTimeout(function() {
elem.addClass('dash-edit-view--open');
}, 10);
}, 10);
}
scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) {
if (newValue) {
showEditorPane(null, {}, newValue);
showEditorPane(null, {editview: newValue});
} else if (oldValue) {
scope.contextSrv.editview = null;
if (lastEditor === editViewMap[oldValue]) {
if (lastEditView === oldValue) {
hideEditorPane();
}
}
});
scope.contextSrv.editview = null;
scope.$on("$destroy", hideEditorPane);
scope.onAppEvent('hide-dash-editor', hideEditorPane);
scope.onAppEvent('hide-dash-editor', function() {
hideEditorPane(false);
});
scope.onAppEvent('show-dash-editor', showEditorPane);
}
};
......
///<reference path="../headers/common.d.ts" />
import coreModule from 'app/core/core_module';
export interface NavModelItem {
title: string;
url: string;
icon?: string;
iconUrl?: string;
}
export interface NavModel {
section: NavModelItem;
menu: NavModelItem[];
}
export class NavModelSrv {
/** @ngInject */
constructor(private contextSrv) {
}
getAlertingNav(subPage) {
return {
section: {
title: 'Alerting',
url: 'plugins',
icon: 'icon-gf icon-gf-alert'
},
menu: [
{title: 'Alert List', active: subPage === 0, url: 'alerting/list', icon: 'fa fa-list-ul'},
{title: 'Notification channels', active: subPage === 1, url: 'alerting/notifications', icon: 'fa fa-bell-o'},
]
};
}
getDatasourceNav(subPage) {
return {
section: {
title: 'Data Sources',
url: 'datasources',
icon: 'icon-gf icon-gf-datasources'
},
menu: [
{title: 'List view', active: subPage === 0, url: 'datasources', icon: 'fa fa-list-ul'},
{title: 'Add data source', active: subPage === 1, url: 'datasources/new', icon: 'fa fa-plus'},
]
};
}
getPlaylistsNav(subPage) {
return {
section: {
title: 'Playlists',
url: 'playlists',
icon: 'fa fa-fw fa-film'
},
menu: [
{title: 'List view', active: subPage === 0, url: 'playlists', icon: 'fa fa-list-ul'},
{title: 'Add Playlist', active: subPage === 1, url: 'playlists/create', icon: 'fa fa-plus'},
]
};
}
getProfileNav() {
return {
section: {
title: 'User Profile',
url: 'profile',
icon: 'fa fa-fw fa-user'
},
menu: []
};
}
getNotFoundNav() {
return {
section: {
title: 'Page',
url: '',
icon: 'fa fa-fw fa-warning'
},
menu: []
};
}
getOrgNav(subPage) {
return {
section: {
title: 'Organization',
url: 'org',
icon: 'icon-gf icon-gf-users'
},
menu: [
{title: 'Preferences', active: subPage === 0, url: 'org', icon: 'fa fa-fw fa-cog'},
{title: 'Org Users', active: subPage === 1, url: 'org/users', icon: 'fa fa-fw fa-users'},
{title: 'API Keys', active: subPage === 2, url: 'org/apikeys', icon: 'fa fa-fw fa-key'},
]
};
}
getAdminNav(subPage) {
return {
section: {
title: 'Admin',
url: 'admin',
icon: 'fa fa-fw fa-cogs'
},
menu: [
{title: 'Users', active: subPage === 0, url: 'admin/users', icon: 'fa fa-fw fa-user'},
{title: 'Orgs', active: subPage === 1, url: 'admin/orgs', icon: 'fa fa-fw fa-users'},
{title: 'Server Settings', active: subPage === 2, url: 'admin/settings', icon: 'fa fa-fw fa-cogs'},
{title: 'Server Stats', active: subPage === 2, url: 'admin/stats', icon: 'fa fa-fw fa-line-chart'},
{title: 'Style Guide', active: subPage === 2, url: 'styleguide', icon: 'fa fa-fw fa-key'},
]
};
}
getPluginsNav() {
return {
section: {
title: 'Plugins',
url: 'plugins',
icon: 'icon-gf icon-gf-apps'
},
menu: []
};
}
getDashboardNav(dashboard, dashNavCtrl) {
// special handling for snapshots
if (dashboard.meta.isSnapshot) {
return {
section: {
title: dashboard.title,
icon: 'icon-gf icon-gf-snapshot'
},
menu: [
{
title: 'Go to original dashboard',
icon: 'fa fa-fw fa-external-link',
url: dashboard.snapshot.originalUrl,
}
]
};
}
var menu = [];
if (dashboard.meta.canEdit) {
menu.push({
title: 'Settings',
icon: 'fa fa-fw fa-cog',
clickHandler: () => dashNavCtrl.openEditView('settings')
});
menu.push({
title: 'Templating',
icon: 'fa fa-fw fa-code',
clickHandler: () => dashNavCtrl.openEditView('templating')
});
menu.push({
title: 'Annotations',
icon: 'fa fa-fw fa-bolt',
clickHandler: () => dashNavCtrl.openEditView('annotations')
});
if (dashboard.version > 0) {
menu.push({
title: 'Version History',
icon: 'fa fa-fw fa-history',
clickHandler: () => dashNavCtrl.openEditView('history')
});
}
menu.push({
title: 'View JSON',
icon: 'fa fa-fw fa-eye',
clickHandler: () => dashNavCtrl.viewJson()
});
}
if (this.contextSrv.isEditor && !dashboard.editable) {
menu.push({
title: 'Make Editable',
icon: 'fa fa-fw fa-edit',
clickHandler: () => dashNavCtrl.makeEditable()
});
}
menu.push({
title: 'Shortcuts',
icon: 'fa fa-fw fa-keyboard-o',
clickHandler: () => dashNavCtrl.showHelpModal()
});
if (this.contextSrv.isEditor) {
menu.push({
title: 'Save As ...',
icon: 'fa fa-fw fa-save',
clickHandler: () => dashNavCtrl.saveDashboardAs()
});
}
if (dashboard.meta.canSave) {
menu.push({
title: 'Delete',
icon: 'fa fa-fw fa-trash',
clickHandler: () => dashNavCtrl.deleteDashboard()
});
}
return {
section: {
title: dashboard.title,
icon: 'icon-gf icon-gf-dashboard'
},
menu: menu
};
}
}
coreModule.service('navModelSrv', NavModelSrv);
......@@ -103,11 +103,13 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
.when('/admin', {
templateUrl: 'public/app/features/admin/partials/admin_home.html',
controller : 'AdminHomeCtrl',
controllerAs: 'ctrl',
resolve: loadAdminBundle,
})
.when('/admin/settings', {
templateUrl: 'public/app/features/admin/partials/settings.html',
controller : 'AdminSettingsCtrl',
controllerAs: 'ctrl',
resolve: loadAdminBundle,
})
.when('/admin/users', {
......@@ -129,11 +131,13 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
.when('/admin/orgs', {
templateUrl: 'public/app/features/admin/partials/orgs.html',
controller : 'AdminListOrgsCtrl',
controllerAs: 'ctrl',
resolve: loadAdminBundle,
})
.when('/admin/orgs/edit/:id', {
templateUrl: 'public/app/features/admin/partials/edit_org.html',
controller : 'AdminEditOrgCtrl',
controllerAs: 'ctrl',
resolve: loadAdminBundle,
})
.when('/admin/stats', {
......
......@@ -113,10 +113,6 @@ export class KeybindingSrv {
scope.appEvent('shift-time-forward');
});
this.bind('mod+i', () => {
scope.appEvent('quick-snapshot');
});
// edit panel
this.bind('e', () => {
if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
......@@ -225,7 +221,7 @@ export class KeybindingSrv {
}
scope.appEvent('hide-dash-editor');
scope.exitFullscreen();
scope.appEvent('panel-change-view', {fullscreen: false, edit: false});
});
}
}
......
......@@ -87,9 +87,11 @@ export default class TimeSeries {
if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); }
if (override.stack !== void 0) { this.stack = override.stack; }
if (override.linewidth !== void 0) {
this.lines.lineWidth = override.linewidth;
this.lines.lineWidth = this.dashes.show ? 0: override.linewidth;
this.dashes.lineWidth = override.linewidth;
}
if (override.dashLength !== void 0) { this.dashes.dashLength[0] = override.dashLength; }
if (override.spaceLength !== void 0) { this.dashes.dashLength[1] = override.spaceLength; }
if (override.nullPointMode !== void 0) { this.nullPointMode = override.nullPointMode; }
if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
......
///<reference path="../../headers/common.d.ts" />
import _ from 'lodash';
import moment from 'moment';
declare var window: any;
export function exportSeriesListToCsv(seriesList) {
var text = 'sep=;\nSeries;Time;Value\n';
const DEFAULT_DATETIME_FORMAT: String = 'YYYY-MM-DDTHH:mm:ssZ';
export function exportSeriesListToCsv(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT) {
var text = 'Series;Time;Value\n';
_.each(seriesList, function(series) {
_.each(series.datapoints, function(dp) {
text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n';
text += series.alias + ';' + moment(dp[1]).format(dateTimeFormat) + ';' + dp[0] + '\n';
});
});
saveSaveBlob(text, 'grafana_data_export.csv');
}
export function exportSeriesListToCsvColumns(seriesList) {
var text = 'sep=;\nTime;';
export function exportSeriesListToCsvColumns(seriesList, dateTimeFormat = DEFAULT_DATETIME_FORMAT) {
var text = 'Time;';
// add header
_.each(seriesList, function(series) {
text += series.alias + ';';
......@@ -30,7 +33,7 @@ export function exportSeriesListToCsvColumns(seriesList) {
var cIndex = 0;
dataArr.push([]);
_.each(series.datapoints, function(dp) {
dataArr[0][cIndex] = new Date(dp[1]).toISOString();
dataArr[0][cIndex] = moment(dp[1]).format(dateTimeFormat);
dataArr[sIndex][cIndex] = dp[0];
cIndex++;
});
......@@ -50,7 +53,7 @@ export function exportSeriesListToCsvColumns(seriesList) {
}
export function exportTableDataToCsv(table) {
var text = 'sep=;\n';
var text = '';
// add header
_.each(table.columns, function(column) {
text += (column.title || column.text) + ';';
......
......@@ -6,9 +6,11 @@ import './adminEditUserCtrl';
import coreModule from 'app/core/core_module';
class AdminSettingsCtrl {
navModel: any;
/** @ngInject **/
constructor($scope, backendSrv) {
constructor($scope, backendSrv, navModelSrv) {
this.navModel = navModelSrv.getAdminNav();
backendSrv.get('/api/admin/settings').then(function(settings) {
$scope.settings = settings;
......@@ -18,16 +20,22 @@ class AdminSettingsCtrl {
}
class AdminHomeCtrl {
navModel: any;
/** @ngInject **/
constructor() {
constructor(navModelSrv) {
this.navModel = navModelSrv.getAdminNav();
}
}
export class AdminStatsCtrl {
stats: any;
navModel: any;
/** @ngInject */
constructor(backendSrv: any) {
constructor(backendSrv: any, navModelSrv) {
this.navModel = navModelSrv.getAdminNav();
backendSrv.get('/api/admin/stats').then(stats => {
this.stats = stats;
});
......
......@@ -6,9 +6,11 @@ function (angular) {
var module = angular.module('grafana.controllers');
module.controller('AdminEditOrgCtrl', function($scope, $routeParams, backendSrv, $location) {
module.controller('AdminEditOrgCtrl', function($scope, $routeParams, backendSrv, $location, navModelSrv) {
$scope.init = function() {
$scope.navModel = navModelSrv.getAdminNav();
if ($routeParams.id) {
$scope.getOrg($routeParams.id);
$scope.getOrgUsers($routeParams.id);
......
......@@ -7,10 +7,11 @@ function (angular, _) {
var module = angular.module('grafana.controllers');
module.controller('AdminEditUserCtrl', function($scope, $routeParams, backendSrv, $location) {
module.controller('AdminEditUserCtrl', function($scope, $routeParams, backendSrv, $location, navModelSrv) {
$scope.user = {};
$scope.newOrg = { name: '', role: 'Editor' };
$scope.permissions = {};
$scope.navModel = navModelSrv.getAdminNav();
$scope.init = function() {
if ($routeParams.id) {
......
......@@ -6,9 +6,10 @@ function (angular) {
var module = angular.module('grafana.controllers');
module.controller('AdminListOrgsCtrl', function($scope, backendSrv) {
module.controller('AdminListOrgsCtrl', function($scope, backendSrv, navModelSrv) {
$scope.init = function() {
$scope.navModel = navModelSrv.getAdminNav();
$scope.getOrgs();
};
......
......@@ -8,9 +8,11 @@ export default class AdminListUsersCtrl {
totalPages: number;
showPaging = false;
query: any;
navModel: any;
/** @ngInject */
constructor(private $scope, private backendSrv) {
constructor(private $scope, private backendSrv, private navModelSrv) {
this.navModel = navModelSrv.getAdminNav();
this.query = '';
this.getUsers();
}
......
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<a href="admin/orgs" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Orgs
</a>
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<a href="admin/users" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Users
</a>
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<a href="admin/users" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Users
</a>
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<a href="admin/orgs" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Orgs
</a>
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
<a href="admin/users" class="navbar-page-btn">
<i class="icon-gf icon-gf-users"></i>
Users
</a>
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......@@ -14,12 +9,14 @@
Add new user
</a>
</div>
<div class="search-field-wrapper pull-right width-18">
<div class="gf-form pull-right gf-form-group">
<label class="gf-form-label">Search</label>
<span style="position: relative;">
<input type="text" placeholder="Find user by name/login/email" tabindex="1" give-focus="true"
ng-model="ctrl.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.getUsers()" />
<input class="gf-form-input width-15" type="text" placeholder="Find user by name/login/email" tabindex="1" give-focus="true" ng-model="ctrl.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.getUsers()" />
</span>
</div>
<div class="admin-list-table">
<table class="filter-table form-inline">
<thead>
......
......@@ -2,13 +2,12 @@
import angular from 'angular';
import _ from 'lodash';
import coreModule from '../../core/core_module';
import appEvents from '../../core/app_events';
import moment from 'moment';
import {coreModule, appEvents} from 'app/core/core';
import alertDef from './alert_def';
export class AlertListCtrl {
alerts: any;
stateFilters = [
{text: 'All', value: null},
......@@ -17,13 +16,15 @@ export class AlertListCtrl {
{text: 'No Data', value: 'no_data'},
{text: 'Paused', value: 'paused'},
];
filters = {
state: 'ALL'
};
navModel: any;
/** @ngInject */
constructor(private backendSrv, private $location, private $scope) {
constructor(private backendSrv, private $location, private $scope, navModelSrv) {
this.navModel = navModelSrv.getAlertingNav(0);
var params = $location.search();
this.filters.state = params.state || null;
this.loadAlerts();
......
......@@ -7,6 +7,7 @@ import {appEvents, coreModule} from 'app/core/core';
export class AlertNotificationEditCtrl {
theForm: any;
navModel: any;
testSeverity = "critical";
notifiers: any;
notifierTemplateId: string;
......@@ -23,7 +24,9 @@ export class AlertNotificationEditCtrl {
};
/** @ngInject */
constructor(private $routeParams, private backendSrv, private $location, private $templateCache) {
constructor(private $routeParams, private backendSrv, private $location, private $templateCache, navModelSrv) {
this.navModel = navModelSrv.getAlertingNav();
this.backendSrv.get(`/api/alert-notifiers`).then(notifiers => {
this.notifiers = notifiers;
......
......@@ -2,16 +2,19 @@
import angular from 'angular';
import _ from 'lodash';
import coreModule from '../../core/core_module';
import config from 'app/core/config';
export class AlertNotificationsListCtrl {
import {coreModule} from 'app/core/core';
export class AlertNotificationsListCtrl {
notifications: any;
navModel: any;
/** @ngInject */
constructor(private backendSrv, private $scope) {
constructor(private backendSrv, private $scope, navModelSrv) {
this.loadNotifications();
this.navModel = navModelSrv.getAlertingNav(1);
}
loadNotifications() {
......
<navbar icon="icon-gf icon-gf-alert" title="Alerting" title-url="alerting">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container" >
<div class="page-header">
......
<navbar icon="icon-gf icon-gf-alert" title="Alerting" title-url="alerting">
<a href="alerting/notifications" class="navbar-page-btn">
<i class="fa fa-fw fa-rss"></i>
Notification channels
</a>
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="icon-gf icon-gf-alert" title="Alerting" title-url="alerting">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container" >
<div class="page-header">
......
......@@ -19,6 +19,7 @@ define([
'./upload',
'./import/dash_import',
'./export/export_modal',
'./export_data/export_data_modal',
'./dash_list_ctrl',
'./ad_hoc_filters',
'./row/row_ctrl',
......
<navbar>
<navbar model="ctrl.navModel">
<a class="pointer navbar-page-btn" ng-if="::!dashboardMeta.isSnapshot" ng-click="openSearch()">
<i class="icon-gf icon-gf-dashboard"></i>
<span>{{dashboard.title}}</span>
<i class="fa fa-caret-down"></i>
</a>
<a class="pointer navbar-page-btn" ng-if="::dashboardMeta.isSnapshot" bs-tooltip="titleTooltip" data-placement="bottom" ng-click="openSearch()">
<i class="icon-gf icon-gf-snapshot"></i>
<span>
{{dashboard.title}}
<em class="small">&nbsp;&nbsp;(snapshot)</em>
</span>
</a>
<ul class="nav dash-playlist-actions" ng-if="playlistSrv">
<ul class="nav dash-playlist-actions" ng-if="ctrl.playlistSrv.isPlaying">
<li>
<a ng-click="playlistSrv.prev()"><i class="fa fa-step-backward"></i></a>
<a ng-click="ctrl.playlistSrv.prev()"><i class="fa fa-step-backward"></i></a>
</li>
<li>
<a ng-click="playlistSrv.stop()"><i class="fa fa-stop"></i></a>
<a ng-click="ctrl.playlistSrv.stop()"><i class="fa fa-stop"></i></a>
</li>
<li>
<a ng-click="playlistSrv.next()"><i class="fa fa-step-forward"></i></a>
<a ng-click="ctrl.playlistSrv.next()"><i class="fa fa-step-forward"></i></a>
</li>
</ul>
<ul class="nav pull-left dashnav-action-icons">
<li ng-show="::dashboardMeta.canStar">
<a class="pointer" ng-click="starDashboard()">
<i class="fa" ng-class="{'fa-star-o': !dashboardMeta.isStarred, 'fa-star': dashboardMeta.isStarred}" style="color: orange;"></i>
<li ng-show="::ctrl.dashboard.meta.canStar">
<a class="pointer" ng-click="ctrl.starDashboard()">
<i class="fa" ng-class="{'fa-star-o': !ctrl.dashboard.meta.isStarred, 'fa-star': ctrl.dashboard.meta.isStarred}" style="color: orange;"></i>
</a>
</li>
<li ng-show="::dashboardMeta.canShare" class="dropdown">
<a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a>
<li ng-show="::ctrl.dashboard.meta.canShare" class="dropdown">
<a class="pointer" ng-click="ctrl.hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a>
<ul class="dropdown-menu">
<li>
<a class="pointer" ng-click="shareDashboard(0)">
<a class="pointer" ng-click="ctrl.shareDashboard(0)">
<i class="fa fa-link"></i> Link to Dashboard
<div class="dropdown-desc">Share an internal link to the current dashboard. Some configuration options available.</div>
</a>
</li>
<li>
<a class="pointer" ng-click="shareDashboard(1)">
<a class="pointer" ng-click="ctrl.shareDashboard(1)">
<i class="icon-gf icon-gf-snapshot"></i>Snapshot
<div class="dropdown-desc">Interactive, publically accessible dashboard. Sensitive data is stripped out.</div>
</a>
</li>
<li>
<a class="pointer" ng-click="shareDashboard(2)">
<li>
<a class="pointer" ng-click="ctrl.shareDashboard(2)">
<i class="fa fa-cloud-upload"></i>Export
<div class="dropdown-desc">Export the dashboard to a JSON file for others and to share on Grafana.com</div>
</a>
</li>
</ul>
</li>
<li ng-show="::dashboardMeta.canSave">
<a ng-show="dashboard.version === 0" ng-click="saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
<a ng-show="dashboard.version > 0" ng-click="saveDashboard()" bs-tooltip="'Save to changelog <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
<li ng-show="::ctrl.dashboard.meta.canSave">
<a ng-click="ctrl.saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
</li>
<li ng-if="dashboard.snapshot.originalUrl">
<a ng-href="{{dashboard.snapshot.originalUrl}}" bs-tooltip="'Open original dashboard'" data-placement="bottom"><i class="fa fa-link"></i></a>
</li>
<li ng-if="::showSettingsMenu" class="dropdown">
<a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Manage dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-cog"></i></a>
<ul class="dropdown-menu">
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('settings');">Settings</a></li>
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('annotations');">Annotations</a></li>
<li ng-if="dashboardMeta.canEdit && dashboard.version > 0 && !dashboardMeta.isHome"><a class="pointer" ng-click="openEditView('history');">Changelog</a></li>
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('templating');">Templating</a></li>
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="viewJson();">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 class="dropdown-menu-item-with-shortcut">
<a class="pointer" ng-click="showHelpModal();">
Shortcuts <span class="dropdown-menu-item-shortcut">?</span>
</a>
</li>
<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
</ul>
<li ng-if="::ctrl.dashboard.snapshot.originalUrl">
<a ng-href="{{ctrl.dashboard.snapshot.originalUrl}}" bs-tooltip="'Open original dashboard'" data-placement="bottom"><i class="fa fa-link"></i></a>
</li>
</ul>
<ul class="nav pull-right">
<li ng-show="dashboard.meta.fullscreen" class="dashnav-back-to-dashboard">
<a ng-click="exitFullscreen()">
<li ng-show="ctrl.dashboard.meta.fullscreen" class="dashnav-back-to-dashboard">
<a ng-click="ctrl.exitFullscreen()">
Back to dashboard
</a>
</li>
<li ng-if="dashboard">
<gf-time-picker dashboard="dashboard"></gf-time-picker>
<li>
<gf-time-picker dashboard="ctrl.dashboard"></gf-time-picker>
</li>
</ul>
......
......@@ -3,92 +3,98 @@
import _ from 'lodash';
import moment from 'moment';
import angular from 'angular';
import {appEvents, NavModel} from 'app/core/core';
import {DashboardModel} from '../model';
import {DashboardExporter} from '../export/exporter';
export class DashNavCtrl {
dashboard: DashboardModel;
navModel: NavModel;
titleTooltip: string;
/** @ngInject */
constructor($scope, $rootScope, dashboardSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) {
$scope.init = function() {
$scope.onAppEvent('save-dashboard', $scope.saveDashboard);
$scope.onAppEvent('delete-dashboard', $scope.deleteDashboard);
$scope.onAppEvent('quick-snapshot', $scope.quickSnapshot);
$scope.showSettingsMenu = $scope.dashboardMeta.canEdit || $scope.contextSrv.isEditor;
if ($scope.dashboardMeta.isSnapshot) {
$scope.showSettingsMenu = false;
var meta = $scope.dashboardMeta;
$scope.titleTooltip = 'Created: &nbsp;' + moment(meta.created).calendar();
constructor(
private $scope,
private $rootScope,
private dashboardSrv,
private $location,
private playlistSrv,
private backendSrv,
private $timeout,
private datasourceSrv,
private navModelSrv) {
this.navModel = navModelSrv.getDashboardNav(this.dashboard, this);
appEvents.on('save-dashboard', this.saveDashboard.bind(this), $scope);
appEvents.on('delete-dashboard', this.deleteDashboard.bind(this), $scope);
if (this.dashboard.meta.isSnapshot) {
var meta = this.dashboard.meta;
this.titleTooltip = 'Created: &nbsp;' + moment(meta.created).calendar();
if (meta.expires) {
$scope.titleTooltip += '<br>Expires: &nbsp;' + moment(meta.expires).fromNow() + '<br>';
this.titleTooltip += '<br>Expires: &nbsp;' + moment(meta.expires).fromNow() + '<br>';
}
}
};
}
$scope.openEditView = function(editview) {
var search = _.extend($location.search(), {editview: editview});
$location.search(search);
};
openEditView(editview) {
var search = _.extend(this.$location.search(), {editview: editview});
this.$location.search(search);
}
$scope.showHelpModal = function() {
$scope.appEvent('show-modal', {templateHtml: '<help-modal></help-modal>'});
};
showHelpModal() {
appEvents.emit('show-modal', {templateHtml: '<help-modal></help-modal>'});
}
$scope.starDashboard = function() {
if ($scope.dashboardMeta.isStarred) {
backendSrv.delete('/api/user/stars/dashboard/' + $scope.dashboard.id).then(function() {
$scope.dashboardMeta.isStarred = false;
});
} else {
backendSrv.post('/api/user/stars/dashboard/' + $scope.dashboard.id).then(function() {
$scope.dashboardMeta.isStarred = true;
starDashboard() {
if (this.dashboard.meta.isStarred) {
return this.backendSrv.delete('/api/user/stars/dashboard/' + this.dashboard.id).then(() => {
this.dashboard.meta.isStarred = false;
});
}
};
$scope.shareDashboard = function(tabIndex) {
var modalScope = $scope.$new();
this.backendSrv.post('/api/user/stars/dashboard/' + this.dashboard.id).then(() => {
this.dashboard.meta.isStarred = true;
});
}
shareDashboard(tabIndex) {
var modalScope = this.$scope.$new();
modalScope.tabIndex = tabIndex;
modalScope.dashboard = this.dashboard;
$scope.appEvent('show-modal', {
appEvents.emit('show-modal', {
src: 'public/app/features/dashboard/partials/shareModal.html',
scope: modalScope
});
};
$scope.quickSnapshot = function() {
$scope.shareDashboard(1);
};
}
$scope.openSearch = function() {
$scope.appEvent('show-dash-search');
};
$scope.hideTooltip = function(evt) {
hideTooltip(evt) {
angular.element(evt.currentTarget).tooltip('hide');
$scope.appEvent('hide-dash-search');
};
}
$scope.makeEditable = function() {
$scope.dashboard.editable = true;
makeEditable() {
this.dashboard.editable = true;
return dashboardSrv.saveDashboard({makeEditable: true, overwrite: false}).then(function() {
return this.dashboardSrv.saveDashboard({makeEditable: true, overwrite: false}).then(() => {
// force refresh whole page
window.location.href = window.location.href;
});
};
}
exitFullscreen() {
this.$rootScope.appEvent('panel-change-view', {fullscreen: false, edit: false});
}
$scope.saveDashboard = function(options) {
return dashboardSrv.saveDashboard(options);
};
saveDashboard(options) {
return this.dashboardSrv.saveDashboard(options);
}
$scope.deleteDashboard = function() {
deleteDashboard() {
var confirmText = "";
var text2 = $scope.dashboard.title;
var alerts = $scope.dashboard.rows.reduce((memo, row) => {
var text2 = this.dashboard.title;
var alerts = this.dashboard.rows.reduce((memo, row) => {
memo += row.panels.filter(panel => panel.alert).length;
return memo;
}, 0);
......@@ -98,60 +104,37 @@ export class DashNavCtrl {
text2 = `This dashboad contains ${alerts} alerts. Deleting this dashboad will also delete those alerts`;
}
$scope.appEvent('confirm-modal', {
appEvents.emit('confirm-modal', {
title: 'Delete',
text: 'Do you want to delete this dashboard?',
text2: text2,
icon: 'fa-trash',
confirmText: confirmText,
yesText: 'Delete',
onConfirm: function() {
$scope.dashboardMeta.canSave = false;
$scope.deleteDashboardConfirmed();
onConfirm: () => {
this.dashboard.meta.canSave = false;
this.deleteDashboardConfirmed();
}
});
};
}
$scope.deleteDashboardConfirmed = function() {
backendSrv.delete('/api/dashboards/db/' + $scope.dashboardMeta.slug).then(function() {
$scope.appEvent('alert-success', ['Dashboard Deleted', $scope.dashboard.title + ' has been deleted']);
$location.url('/');
deleteDashboardConfirmed() {
this.backendSrv.delete('/api/dashboards/db/' + this.dashboard.meta.slug).then(() => {
appEvents.emit('alert-success', ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
this.$location.url('/');
});
};
}
$scope.saveDashboardAs = function() {
return dashboardSrv.saveDashboardAs();
};
saveDashboardAs() {
return this.dashboardSrv.saveDashboardAs();
}
$scope.viewJson = function() {
var clone = $scope.dashboard.getSaveModelClone();
viewJson() {
var clone = this.dashboard.getSaveModelClone();
var html = angular.toJson(clone, true);
var uri = "data:application/json;charset=utf-8," + encodeURIComponent(html);
var newWindow = window.open(uri);
};
$scope.snapshot = function() {
$scope.dashboard.snapshot = true;
$rootScope.$broadcast('refresh');
$timeout(function() {
$scope.dashboard.snapshot = false;
$scope.appEvent('dashboard-snapshot-cleanup');
}, 1000);
};
$scope.editJson = function() {
var clone = $scope.dashboard.getSaveModelClone();
$scope.appEvent('show-json-editor', { object: clone });
};
$scope.stopPlaylist = function() {
playlistSrv.stop(1);
};
$scope.init();
}
}
}
export function dashNavDirective() {
......@@ -159,7 +142,10 @@ export function dashNavDirective() {
restrict: 'E',
templateUrl: 'public/app/features/dashboard/dashnav/dashnav.html',
controller: DashNavCtrl,
bindToController: true,
controllerAs: 'ctrl',
transclude: true,
scope: { dashboard: "=" }
};
}
......
<div class="modal-body">
<div class="modal-header">
<h2 class="modal-header-title">
Export CSV
</h2>
<a class="modal-header-close" ng-click="ctrl.dismiss();">
<i class="fa fa-remove"></i>
</a>
</div>
<div class="modal-content">
<div class="p-t-2">
<div class="gf-form">
<label class="gf-form-label width-10">Mode</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="ctrl.asRows" ng-options="f.value as f.text for f in [{text: 'Series as rows', value: true}, {text: 'Series as columns', value: false}]">
</select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-10">Date Time Format</label>
<input type="text" class="gf-form-input" ng-model="ctrl.dateTimeFormat">
</div>
</div>
<div class="gf-form-button-row text-center">
<a class="btn btn-success" ng-click="ctrl.export();">Export</a>
<a class="btn-text" ng-click="ctrl.dismiss();">Cancel</a>
</div>
</div>
</div>
///<reference path="../../../headers/common.d.ts" />
import angular from 'angular';
import * as fileExport from 'app/core/utils/file_export';
import appEvents from 'app/core/app_events';
export class ExportDataModalCtrl {
private data: any[];
asRows: Boolean = true;
dateTimeFormat: String = 'YYYY-MM-DDTHH:mm:ssZ';
/** @ngInject */
constructor(private $scope) { }
export() {
if (this.asRows) {
fileExport.exportSeriesListToCsv(this.data, this.dateTimeFormat);
} else {
fileExport.exportSeriesListToCsvColumns(this.data, this.dateTimeFormat);
}
this.dismiss();
}
dismiss() {
appEvents.emit('hide-modal');
}
}
export function exportDataModal() {
return {
restrict: 'E',
templateUrl: 'public/app/features/dashboard/export_data/export_data_modal.html',
controller: ExportDataModalCtrl,
controllerAs: 'ctrl',
scope: {
data: '<' // The difference to '=' is that the bound properties are not watched
},
bindToController: true
};
}
angular.module('grafana.directives').directive('exportDataModal', exportDataModal);
......@@ -54,6 +54,10 @@ export class HistoryListCtrl {
$rootScope.onAppEvent('dashboard-saved', this.onDashboardSaved.bind(this));
}
dismiss() {
this.$rootScope.appEvent('hide-dash-editor');
}
addToLog() {
this.start = this.start + this.limit;
this.getLog(true);
......
<gf-dashboard-history dashboard="dashboard"></gf-dashboard-history>
......@@ -2,7 +2,7 @@
<div class="modal-header">
<h2 class="modal-header-title">
<i class="fa fa-save"></i>
<span class="p-l-1">Save to changelog</span>
<span class="p-l-1">Save Dashboard</span>
</h2>
<a class="modal-header-close" ng-click="dismiss();">
......@@ -39,7 +39,7 @@
<div class="gf-form-button-row text-center">
<button type="submit" class="btn btn-success" ng-disabled="saveMessage.$invalid">
Save to changelog
Save
</button>
<button class="btn btn-inverse" ng-click="dismiss();">Cancel</button>
</div>
......
......@@ -34,7 +34,6 @@ export class TimePickerCtrl {
$rootScope.onAppEvent('shift-time-backward', () => this.move(-1), $scope);
$rootScope.onAppEvent('refresh', () => this.init(), $scope);
$rootScope.onAppEvent('dash-editor-hidden', () => this.isOpen = false, $scope);
this.init();
}
......@@ -114,7 +113,7 @@ export class TimePickerCtrl {
this.refresh.options.unshift({text: 'off'});
this.$rootScope.appEvent('show-dash-editor', {
src: 'public/app/features/dashboard/timepicker/dropdown.html',
editview: 'timepicker',
scope: this.$scope,
cssClass: 'gf-timepicker-dropdown',
});
......
......@@ -20,12 +20,6 @@ function (angular, _, $, config) {
self.$scope = $scope;
self.dashboard = $scope.dashboard;
$scope.exitFullscreen = function() {
if (self.state.fullscreen) {
self.update({ fullscreen: false });
}
};
$scope.onAppEvent('$routeUpdate', function() {
var urlState = self.getQueryStringState();
if (self.needsSync(urlState)) {
......@@ -41,6 +35,9 @@ function (angular, _, $, config) {
self.registerPanel(payload.scope);
});
// this marks changes to location during this digest cycle as not to add history item
// dont want url changes like adding orgId to add browser history
$location.replace();
this.update(this.getQueryStringState());
this.expandRowForPanel();
}
......
......@@ -7,11 +7,12 @@ function (angular, config) {
var module = angular.module('grafana.controllers');
module.controller('ChangePasswordCtrl', function($scope, backendSrv, $location) {
module.controller('ChangePasswordCtrl', function($scope, backendSrv, $location, navModelSrv) {
$scope.command = {};
$scope.authProxyEnabled = config.authProxyEnabled;
$scope.ldapEnabled = config.ldapEnabled;
$scope.navModel = navModelSrv.getProfileNav();
$scope.changePassword = function() {
if (!$scope.userForm.$valid) { return; }
......
......@@ -7,8 +7,9 @@ function (angular, config) {
var module = angular.module('grafana.controllers');
module.controller('NewOrgCtrl', function($scope, $http, backendSrv) {
module.controller('NewOrgCtrl', function($scope, $http, backendSrv, navModelSrv) {
$scope.navModel = navModelSrv.getOrgNav(0);
$scope.newOrg = {name: ''};
$scope.createOrg = function() {
......
......@@ -6,8 +6,9 @@ function (angular) {
var module = angular.module('grafana.controllers');
module.controller('OrgApiKeysCtrl', function($scope, $http, backendSrv) {
module.controller('OrgApiKeysCtrl', function($scope, $http, backendSrv, navModelSrv) {
$scope.navModel = navModelSrv.getOrgNav(0);
$scope.roleTypes = ['Viewer', 'Editor', 'Admin'];
$scope.token = { role: 'Viewer' };
......
......@@ -6,10 +6,11 @@ function (angular) {
var module = angular.module('grafana.controllers');
module.controller('OrgDetailsCtrl', function($scope, $http, backendSrv, contextSrv) {
module.controller('OrgDetailsCtrl', function($scope, $http, backendSrv, contextSrv, navModelSrv) {
$scope.init = function() {
$scope.getOrgInfo();
$scope.navModel = navModelSrv.getOrgNav(0);
};
$scope.getOrgInfo = function() {
......
......@@ -11,13 +11,15 @@ export class OrgUsersCtrl {
pendingInvites: any;
editor: any;
showInviteUI: boolean;
navModel: any;
/** @ngInject */
constructor(private $scope, private $http, private backendSrv) {
constructor(private $scope, private $http, private backendSrv, navModelSrv) {
this.user = {
loginOrEmail: '',
role: 'Viewer',
};
this.navModel = navModelSrv.getOrgNav(0);
this.get();
this.editor = { index: 0 };
......
<navbar icon="icon-gf icon-gf-users" title="Profile" title-url="profile">
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar title="Organization" icon="icon-gf icon-gf-users">
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container" ng-form="playlistEditForm">
<div class="page-header">
......
<navbar icon="icon-gf icon-gf-users" title="Organization" title-url="org">
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="icon-gf icon-gf-users" title="Organization" title-url="org">
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="icon-gf icon-gf-users" title="Organization Users" title-url="org/users">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon="icon-gf icon-gf-users" title="Profile" title-url="profile">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
<h1>Profile</h1>
<h1>User Profile</h1>
</div>
<form name="ctrl.userForm" class="gf-form-group">
......
......@@ -11,11 +11,13 @@ export class ProfileCtrl {
userForm: any;
showOrgsList = false;
readonlyLoginFields = config.disableLoginForm;
navModel: any;
/** @ngInject **/
constructor(private backendSrv, private contextSrv, private $location) {
constructor(private backendSrv, private contextSrv, private $location, navModelSrv) {
this.getUser();
this.getUserOrgs();
this.navModel = navModelSrv.getProfileNav();
}
getUser() {
......
......@@ -35,8 +35,6 @@ export class PanelCtrl {
containerHeight: any;
events: Emitter;
timing: any;
skippedLastRefresh: boolean;
isPanelVisible: any;
constructor($scope, $injector) {
this.$injector = $injector;
......@@ -77,13 +75,6 @@ export class PanelCtrl {
}
refresh() {
if (!this.isPanelVisible() && !this.dashboard.meta.soloMode && !this.dashboard.snapshot) {
this.skippedLastRefresh = true;
return;
}
this.skippedLastRefresh = false;
this.events.emit('refresh', null);
}
......
......@@ -4,6 +4,7 @@ import angular from 'angular';
import $ from 'jquery';
import _ from 'lodash';
import Drop from 'tether-drop';
import {appEvents} from 'app/core/core';
var module = angular.module('grafana.directives');
......@@ -185,23 +186,9 @@ module.directive('grafanaPanel', function($rootScope, $document) {
elem.on('mouseenter', mouseEnter);
elem.on('mouseleave', mouseLeave);
ctrl.isPanelVisible = function () {
var position = panelContainer[0].getBoundingClientRect();
return (0 < position.top) && (position.top < window.innerHeight);
};
const refreshOnScroll = function () {
if (ctrl.skippedLastRefresh) {
ctrl.refresh();
}
};
$document.on('scroll', refreshOnScroll);
scope.$on('$destroy', function() {
elem.off();
cornerInfoElem.off();
$document.off('scroll', refreshOnScroll);
if (infoDrop) {
infoDrop.destroy();
......
<navbar icon="fa fa-fw fa-list" title="Playlists" title-url="playlists">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container" ng-form="playlistEditForm">
<div class="page-header">
......
<navbar icon="fa fa-fw fa-list" title="Playlists" title-url="playlists">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
......@@ -16,21 +16,30 @@ export class PlaylistEditCtrl {
playlistItems: any = [];
dashboardresult: any = [];
tagresult: any = [];
navModel: any;
/** @ngInject */
constructor(private $scope, private playlistSrv, private backendSrv, private $location, private $route) {
constructor(
private $scope,
private playlistSrv,
private backendSrv,
private $location,
private $route,
private navModelSrv
) {
this.navModel = navModelSrv.getPlaylistsNav(0);
if ($route.current.params.id) {
var playlistId = $route.current.params.id;
backendSrv.get('/api/playlists/' + playlistId)
.then((result) => {
this.playlist = result;
});
backendSrv.get('/api/playlists/' + playlistId).then(result => {
this.playlist = result;
});
backendSrv.get('/api/playlists/' + playlistId + '/items')
.then((result) => {
this.playlistItems = result;
});
backendSrv.get('/api/playlists/' + playlistId + '/items').then(result => {
this.playlistItems = result;
});
}
}
......@@ -85,7 +94,7 @@ export class PlaylistEditCtrl {
? this.backendSrv.put('/api/playlists/' + playlist.id, playlist)
: this.backendSrv.post('/api/playlists', playlist);
savePromise
savePromise
.then(() => {
this.$scope.appEvent('alert-success', ['Playlist saved', '']);
this.$location.path('/playlists');
......
......@@ -11,6 +11,7 @@ class PlaylistSrv {
private interval: any;
private playlistId: number;
private startUrl: string;
public isPlaying: boolean;
/** @ngInject */
constructor(private $rootScope: any, private $location: any, private $timeout: any, private backendSrv: any) { }
......@@ -42,7 +43,7 @@ class PlaylistSrv {
this.startUrl = window.location.href;
this.index = 0;
this.playlistId = playlistId;
this.$rootScope.playlistSrv = this;
this.isPlaying = true;
this.backendSrv.get(`/api/playlists/${playlistId}`).then(playlist => {
this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then(dashboards => {
......@@ -55,13 +56,12 @@ class PlaylistSrv {
stop() {
this.index = 0;
this.isPlaying = false;
this.playlistId = 0;
if (this.cancelPromise) {
this.$timeout.cancel(this.cancelPromise);
}
this.$rootScope.playlistSrv = null;
}
}
......
......@@ -6,20 +6,22 @@ import coreModule from '../../core/core_module';
export class PlaylistsCtrl {
playlists: any;
navModel: any;
/** @ngInject */
constructor(private $scope, private $location, private backendSrv) {
backendSrv.get('/api/playlists')
.then((result) => {
this.playlists = result;
});
constructor(private $scope, private $location, private backendSrv, private navModelSrv) {
this.navModel = navModelSrv.getPlaylistsNav(0);
backendSrv.get('/api/playlists').then(result => {
this.playlists = result;
});
}
removePlaylistConfirmed(playlist) {
_.remove(this.playlists, { id: playlist.id });
this.backendSrv.delete('/api/playlists/' + playlist.id)
.then(() => {
.then(() => {
this.$scope.appEvent('alert-success', ['Playlist deleted', '']);
}, () => {
this.$scope.appEvent('alert-error', ['Unable to delete playlist', '']);
......
......@@ -5,7 +5,11 @@ import {PlaylistEditCtrl} from '../playlist_edit_ctrl';
describe('PlaylistEditCtrl', () => {
var ctx: any;
beforeEach(() => {
ctx = new PlaylistEditCtrl(null, null, null, null, { current: { params: {} } });
let navModelSrv = {
getPlaylistsNav: page => {},
};
ctx = new PlaylistEditCtrl(null, null, null, null, { current: { params: {} } }, navModelSrv);
ctx.dashboardresult = [
{ id: 2, title: 'dashboard: 2' },
......
......@@ -30,6 +30,7 @@ export class DataSourceEditCtrl {
hasDashboards: boolean;
editForm: any;
gettingStarted: boolean;
navModel: any;
/** @ngInject */
constructor(
......@@ -38,141 +39,144 @@ export class DataSourceEditCtrl {
private backendSrv,
private $routeParams,
private $location,
private datasourceSrv) {
this.isNew = true;
this.datasources = [];
this.tabIndex = 0;
this.loadDatasourceTypes().then(() => {
if (this.$routeParams.id) {
this.getDatasourceById(this.$routeParams.id);
} else {
this.initNewDatasourceModel();
}
});
}
initNewDatasourceModel() {
this.current = angular.copy(defaults);
// We are coming from getting started
if (this.$location.search().gettingstarted) {
this.gettingStarted = true;
this.current.isDefault = true;
}
this.typeChanged();
}
loadDatasourceTypes() {
if (datasourceTypes.length > 0) {
this.types = datasourceTypes;
return this.$q.when(null);
private datasourceSrv,
private navModelSrv,
) {
this.navModel = navModelSrv.getDatasourceNav(0);
this.isNew = true;
this.datasources = [];
this.tabIndex = 0;
this.loadDatasourceTypes().then(() => {
if (this.$routeParams.id) {
this.getDatasourceById(this.$routeParams.id);
} else {
this.initNewDatasourceModel();
}
});
}
return this.backendSrv.get('/api/plugins', {enabled: 1, type: 'datasource'}).then(plugins => {
datasourceTypes = plugins;
this.types = plugins;
});
}
initNewDatasourceModel() {
this.current = angular.copy(defaults);
getDatasourceById(id) {
this.backendSrv.get('/api/datasources/' + id).then(ds => {
this.isNew = false;
this.current = ds;
if (datasourceCreated) {
datasourceCreated = false;
this.testDatasource();
}
return this.typeChanged();
});
// We are coming from getting started
if (this.$location.search().gettingstarted) {
this.gettingStarted = true;
this.current.isDefault = true;
}
typeChanged() {
this.hasDashboards = false;
return this.backendSrv.get('/api/plugins/' + this.current.type + '/settings').then(pluginInfo => {
this.datasourceMeta = pluginInfo;
console.log(this.datasourceMeta) ;
this.hasDashboards = _.find(pluginInfo.includes, {type: 'dashboard'});
});
}
this.typeChanged();
}
updateFrontendSettings() {
return this.backendSrv.get('/api/frontend/settings').then(settings => {
config.datasources = settings.datasources;
config.defaultDatasource = settings.defaultDatasource;
this.datasourceSrv.init();
});
loadDatasourceTypes() {
if (datasourceTypes.length > 0) {
this.types = datasourceTypes;
return this.$q.when(null);
}
testDatasource() {
this.testing = { done: false };
this.datasourceSrv.get(this.current.name).then(datasource => {
if (!datasource.testDatasource) {
delete this.testing;
return;
}
return this.backendSrv.get('/api/plugins', {enabled: 1, type: 'datasource'}).then(plugins => {
datasourceTypes = plugins;
this.types = plugins;
});
}
getDatasourceById(id) {
this.backendSrv.get('/api/datasources/' + id).then(ds => {
this.isNew = false;
this.current = ds;
if (datasourceCreated) {
datasourceCreated = false;
this.testDatasource();
}
return this.typeChanged();
});
}
typeChanged() {
this.hasDashboards = false;
return this.backendSrv.get('/api/plugins/' + this.current.type + '/settings').then(pluginInfo => {
this.datasourceMeta = pluginInfo;
console.log(this.datasourceMeta) ;
this.hasDashboards = _.find(pluginInfo.includes, {type: 'dashboard'});
});
}
updateFrontendSettings() {
return this.backendSrv.get('/api/frontend/settings').then(settings => {
config.datasources = settings.datasources;
config.defaultDatasource = settings.defaultDatasource;
this.datasourceSrv.init();
});
}
testDatasource() {
this.testing = { done: false };
this.datasourceSrv.get(this.current.name).then(datasource => {
if (!datasource.testDatasource) {
delete this.testing;
return;
}
return datasource.testDatasource().then(result => {
this.testing.message = result.message;
this.testing.status = result.status;
this.testing.title = result.title;
}).catch(err => {
if (err.statusText) {
this.testing.message = err.statusText;
this.testing.title = "HTTP Error";
} else {
this.testing.message = err.message;
this.testing.title = "Unknown error";
}
});
}).finally(() => {
if (this.testing) {
this.testing.done = true;
return datasource.testDatasource().then(result => {
this.testing.message = result.message;
this.testing.status = result.status;
this.testing.title = result.title;
}).catch(err => {
if (err.statusText) {
this.testing.message = err.statusText;
this.testing.title = "HTTP Error";
} else {
this.testing.message = err.message;
this.testing.title = "Unknown error";
}
});
}
saveChanges() {
if (!this.editForm.$valid) {
return;
}).finally(() => {
if (this.testing) {
this.testing.done = true;
}
});
}
if (this.current.id) {
return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => {
this.updateFrontendSettings().then(() => {
this.testDatasource();
});
});
} else {
return this.backendSrv.post('/api/datasources', this.current).then(result => {
this.updateFrontendSettings();
datasourceCreated = true;
this.$location.path('datasources/edit/' + result.id);
});
}
saveChanges() {
if (!this.editForm.$valid) {
return;
}
confirmDelete() {
this.backendSrv.delete('/api/datasources/' + this.current.id).then(() => {
this.$location.path('datasources');
if (this.current.id) {
return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => {
this.updateFrontendSettings().then(() => {
this.testDatasource();
});
});
}
} else {
return this.backendSrv.post('/api/datasources', this.current).then(result => {
this.updateFrontendSettings();
delete(s) {
appEvents.emit('confirm-modal', {
title: 'Delete',
text: 'Are you sure you want to delete this datasource?',
yesText: "Delete",
icon: "fa-trash",
onConfirm: () => {
this.confirmDelete();
}
datasourceCreated = true;
this.$location.path('datasources/edit/' + result.id);
});
}
}
confirmDelete() {
this.backendSrv.delete('/api/datasources/' + this.current.id).then(() => {
this.$location.path('datasources');
});
}
delete(s) {
appEvents.emit('confirm-modal', {
title: 'Delete',
text: 'Are you sure you want to delete this datasource?',
yesText: "Delete",
icon: "fa-trash",
onConfirm: () => {
this.confirmDelete();
}
});
}
}
coreModule.controller('DataSourceEditCtrl', DataSourceEditCtrl);
......
......@@ -6,31 +6,41 @@ import coreModule from '../../core/core_module';
export class DataSourcesCtrl {
datasources: any;
navModel: any;
/** @ngInject */
constructor(private $scope, private $location, private $http, private backendSrv, private datasourceSrv) {
backendSrv.get('/api/datasources')
.then((result) => {
this.datasources = result;
});
constructor(
private $scope,
private $location,
private $http,
private backendSrv,
private datasourceSrv,
private navModelSrv
) {
this.navModel = this.navModelSrv.getDatasourceNav(0);
backendSrv.get('/api/datasources').then(result => {
this.datasources = result;
});
}
removeDataSourceConfirmed(ds) {
this.backendSrv.delete('/api/datasources/' + ds.id)
.then(() => {
this.$scope.appEvent('alert-success', ['Datasource deleted', '']);
}, () => {
this.$scope.appEvent('alert-error', ['Unable to delete datasource', '']);
}).then(() => {
.then(() => {
this.$scope.appEvent('alert-success', ['Datasource deleted', '']);
}, () => {
this.$scope.appEvent('alert-error', ['Unable to delete datasource', '']);
}).then(() => {
this.backendSrv.get('/api/datasources')
.then((result) => {
this.datasources = result;
});
.then((result) => {
this.datasources = result;
});
this.backendSrv.get('/api/frontend/settings')
.then((settings) => {
this.datasourceSrv.init(settings.datasources);
});
.then((settings) => {
this.datasourceSrv.init(settings.datasources);
});
});
}
......
<navbar title="Data Sources" title-url="datasources" icon="icon-gf icon-gf-datasources">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
......
<navbar
title="Data Sources"
title-url="datasources"
icon="icon-gf icon-gf-datasources">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar title="Plugins" title-url="plugins" icon="icon-gf icon-gf-apps">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container" ng-init="ctrl.init()">
<div class="page-header">
......
<navbar title="Plugins" icon="icon-gf icon-gf-apps" title-url="plugins">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
<navbar icon-url="{{ctrl.appLogoUrl}}" title="{{ctrl.appModel.name}}" title-url="{{ctrl.appModel.defaultNavUrl}}">
</navbar>
<navbar model="ctrl.navModel" ng-if="ctrl.navModel"></navbar>
<div class="page-container" >
<div ng-if="ctrl.page">
......
......@@ -13,17 +13,22 @@ export class PluginEditCtrl {
includedDatasources: any;
tabIndex: number;
tabs: any;
navModel: any;
hasDashboards: any;
preUpdateHook: () => any;
postUpdateHook: () => any;
/** @ngInject */
constructor(private $scope,
private $rootScope,
private backendSrv,
private $routeParams,
private $sce,
private $http) {
constructor(
private $scope,
private $rootScope,
private backendSrv,
private $routeParams,
private $sce,
private $http,
private navModelSrv,
) {
this.navModel = navModelSrv.getPluginsNav();
this.model = {};
this.pluginId = $routeParams.pluginId;
this.tabIndex = 0;
......@@ -31,7 +36,7 @@ export class PluginEditCtrl {
this.preUpdateHook = () => Promise.resolve();
this.postUpdateHook = () => Promise.resolve();
}
}
init() {
return this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(result => {
......
......@@ -5,10 +5,12 @@ import angular from 'angular';
export class PluginListCtrl {
plugins: any[];
tabIndex: number;
navModel: any;
/** @ngInject */
constructor(private backendSrv: any, $location) {
constructor(private backendSrv: any, $location, navModelSrv) {
this.tabIndex = 0;
this.navModel = navModelSrv.getPluginsNav();
var pluginType = $location.search().type || 'panel';
switch (pluginType) {
......
......@@ -2,6 +2,7 @@
import angular from 'angular';
import _ from 'lodash';
import {NavModel} from 'app/core/core';
var pluginInfoCache = {};
......@@ -9,7 +10,7 @@ export class AppPageCtrl {
page: any;
pluginId: any;
appModel: any;
appLogoUrl: any;
navModel: NavModel;
/** @ngInject */
constructor(private backendSrv, private $routeParams: any, private $rootScope) {
......@@ -25,13 +26,53 @@ export class AppPageCtrl {
initPage(app) {
this.appModel = app;
this.page = _.find(app.includes, {slug: this.$routeParams.slug});
this.appLogoUrl = app.info.logos.small;
pluginInfoCache[this.pluginId] = app;
if (!this.page) {
this.$rootScope.appEvent('alert-error', ['App Page Not Found', '']);
this.navModel = {
section: {
title: "Page not found",
url: app.defaultNavUrl,
icon: 'icon-gf icon-gf-sadface',
},
menu: [],
};
return;
}
let menu = [];
for (let item of app.includes) {
if (item.addToNav) {
if (item.type === 'dashboard') {
menu.push({
title: item.name,
url: 'dashboard/db/' + item.slug,
icon: 'fa fa-fw fa-dot-circle-o',
});
}
if (item.type === 'page') {
menu.push({
title: item.name,
url: `plugins/${app.id}/page/${item.slug}`,
icon: 'fa fa-fw fa-dot-circle-o',
});
}
}
}
this.navModel = {
section: {
title: app.name,
url: app.defaultNavUrl,
iconUrl: app.info.logos.small,
},
menu: menu,
};
}
loadPluginInfo() {
......
<navbar icon="icon-gf icon-gf-snapshot" title="Snapshots" title-url="dashboard/snapshots">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
......@@ -4,11 +4,20 @@ import angular from 'angular';
import _ from 'lodash';
export class SnapshotsCtrl {
navModel: any;
snapshots: any;
/** @ngInject */
constructor(private $rootScope, private backendSrv) {
this.navModel = {
section: {
title: 'Snapshots',
icon: 'icon-gf icon-gf-snapshot',
url: 'dashboard/snapshots',
},
menu: [],
};
this.backendSrv.get('/api/dashboard/snapshots').then(result => {
this.snapshots = result;
});
......
<navbar icon="fa fa-fw fa-adjust" title="Style Guide" title-url="styleguide">
</navbar>
<navbar model="ctrl.navModel"></navbar>
<div class="page-container">
<div class="page-header">
......
......@@ -12,9 +12,11 @@ class StyleGuideCtrl {
icons: any = [];
page: any;
pages = ['colors', 'buttons', 'icons', 'plugins'];
navModel: any;
/** @ngInject **/
constructor(private $http, private $routeParams, private $location, private backendSrv) {
constructor(private $http, private $routeParams, private $location, private backendSrv, navModelSrv) {
this.navModel = navModelSrv.getAdminNav();
this.theme = config.bootData.user.lightTheme ? 'light': 'dark';
this.page = {};
......
......@@ -72,3 +72,4 @@ declare module 'd3' {
var d3: any;
export default d3;
}
<div dash-class ng-if="dashboard">
<dashnav></dashnav>
<dashnav dashboard="dashboard"></dashnav>
<div class="dashboard-container">
<div dash-editor-view></div>
<dashboard-search></dashboard-search>
<div dash-editor-view class="dash-edit-view"></div>
<div class="clearfix"></div>
<dashboard-submenu ng-if="dashboard.meta.submenuEnabled" dashboard="dashboard"></dashboard-submenu>
......
<navbar title="404" icon="fa fa-fw fa-question" title-url="/">
</navbar>
<navbar model="navModel"></navbar>
<div class="page-container">
......
......@@ -119,8 +119,12 @@ function (queryDef) {
query.fields = ["*", "_source"];
}
query.script_fields = {},
query.docvalue_fields = [this.timeField];
query.script_fields = {};
if (this.esVersion < 5) {
query.fielddata_fields = [this.timeField];
} else {
query.docvalue_fields = [this.timeField];
}
return query;
};
......
......@@ -8,11 +8,31 @@ class GrafanaDatasource {
constructor(private backendSrv, private $q) {}
query(options) {
return this.$q.when({data: []});
return this.backendSrv.get('/api/tsdb/testdata/random-walk', {
from: options.range.from.valueOf(),
to: options.range.to.valueOf(),
intervalMs: options.intervalMs,
maxDataPoints: options.maxDataPoints,
}).then(res => {
var data = [];
if (res.results) {
_.forEach(res.results, queryRes => {
for (let series of queryRes.series) {
data.push({
target: series.name,
datapoints: series.points
});
}
});
}
return {data: data};
});
}
metricFindQuery() {
return this.$q.when([]);
metricFindQuery(options) {
return this.$q.when({data: []});
}
annotationQuery(options) {
......
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">Test metric (fake data source)</label>
<label class="gf-form-label">Test data: random walk</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment