Explorar o código

Added expire option to dashboard snapshots, #1623

Torkel Ödegaard %!s(int64=10) %!d(string=hai) anos
pai
achega
b1f85dc8f1

+ 20 - 35
pkg/api/dashboard_snapshot.go

@@ -1,10 +1,7 @@
 package api
 
 import (
-	"bytes"
-	"encoding/json"
-	"io/ioutil"
-	"net/http"
+	"strconv"
 	"time"
 
 	"github.com/grafana/grafana/pkg/api/dtos"
@@ -17,46 +14,24 @@ import (
 )
 
 func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapshotCommand) {
+	cmd.Key = util.GetRandomString(32)
+
 	if cmd.External {
-		createExternalSnapshot(c, cmd)
-		return
+		cmd.OrgId = -1
+		metrics.M_Api_Dashboard_Snapshot_External.Inc(1)
+	} else {
+		cmd.OrgId = c.OrgId
+		metrics.M_Api_Dashboard_Snapshot_Create.Inc(1)
 	}
 
-	cmd.Key = util.GetRandomString(32)
 	if err := bus.Dispatch(&cmd); err != nil {
 		c.JsonApiErr(500, "Failed to create snaphost", err)
 		return
 	}
 
-	metrics.M_Api_Dashboard_Snapshot_Create.Inc(1)
-
 	c.JSON(200, util.DynMap{"key": cmd.Key, "url": setting.ToAbsUrl("dashboard/snapshot/" + cmd.Key)})
 }
 
-func createExternalSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapshotCommand) {
-	metrics.M_Api_Dashboard_Snapshot_External.Inc(1)
-
-	cmd.External = false
-	json, _ := json.Marshal(cmd)
-	jsonData := bytes.NewBuffer(json)
-
-	client := http.Client{Timeout: time.Duration(5 * time.Second)}
-	resp, err := client.Post("http://snapshots-origin.raintank.io/api/snapshots", "application/json", jsonData)
-
-	if err != nil {
-		c.JsonApiErr(500, "Failed to publish external snapshot", err)
-		return
-	}
-
-	c.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
-	c.WriteHeader(resp.StatusCode)
-
-	if resp.ContentLength > 0 {
-		bytes, _ := ioutil.ReadAll(resp.Body)
-		c.Write(bytes)
-	}
-}
-
 func GetDashboardSnapshot(c *middleware.Context) {
 	key := c.Params(":key")
 
@@ -68,14 +43,24 @@ func GetDashboardSnapshot(c *middleware.Context) {
 		return
 	}
 
+	snapshot := query.Result
+
+	// expired snapshots should also be removed from db
+	if snapshot.Expires.Before(time.Now()) {
+		c.JsonApiErr(404, "Snapshot not found", err)
+		return
+	}
+
 	dto := dtos.Dashboard{
-		Model: query.Result.Dashboard,
+		Model: snapshot.Dashboard,
 		Meta:  dtos.DashboardMeta{IsSnapshot: true},
 	}
 
 	metrics.M_Api_Dashboard_Snapshot_Get.Inc(1)
 
-	c.Resp.Header().Set("Cache-Control", "public, max-age=31536000")
+	maxAge := int64(snapshot.Expires.Sub(time.Now()).Seconds())
+
+	c.Resp.Header().Set("Cache-Control", "public, max-age="+strconv.FormatInt(maxAge, 10))
 
 	c.JSON(200, dto)
 }

+ 8 - 5
pkg/models/dashboard_snapshot.go

@@ -4,9 +4,10 @@ import "time"
 
 // DashboardSnapshot model
 type DashboardSnapshot struct {
-	Id   int64
-	Name string
-	Key  string
+	Id    int64
+	Name  string
+	Key   string
+	OrgId int64
 
 	Expires time.Time
 	Created time.Time
@@ -20,9 +21,11 @@ type DashboardSnapshot struct {
 
 type CreateDashboardSnapshotCommand struct {
 	Dashboard map[string]interface{} `json:"dashboard" binding:"Required"`
-	External  bool
+	External  bool                   `json:"external"`
+	Expires   int64                  `json:"expires"`
 
-	Key string `json:"-"`
+	OrgId int64  `json:"-"`
+	Key   string `json:"-"`
 
 	Result *DashboardSnapshot
 }

+ 1 - 1
pkg/services/sqlstore/apikey_test.go

@@ -21,8 +21,8 @@ func TestApiKeyDataAccess(t *testing.T) {
 			Convey("Should be able to get key by name", func() {
 				query := m.GetApiKeyByNameQuery{KeyName: "hello", OrgId: 1}
 				err = GetApiKeyByName(&query)
-				So(err, ShouldBeNil)
 
+				So(err, ShouldBeNil)
 				So(query.Result, ShouldNotBeNil)
 			})
 

+ 10 - 3
pkg/services/sqlstore/dashboard_snapshot.go

@@ -16,10 +16,17 @@ func init() {
 func CreateDashboardSnapshot(cmd *m.CreateDashboardSnapshotCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 
+		// never
+		var expires = time.Now().Add(time.Hour * 24 * 365 * 50)
+		if cmd.Expires > 0 {
+			expires = time.Now().Add(time.Second * time.Duration(cmd.Expires))
+		}
+
 		snapshot := &m.DashboardSnapshot{
 			Key:       cmd.Key,
+			OrgId:     cmd.OrgId,
 			Dashboard: cmd.Dashboard,
-			Expires:   time.Unix(0, 0),
+			Expires:   expires,
 			Created:   time.Now(),
 			Updated:   time.Now(),
 		}
@@ -32,8 +39,8 @@ func CreateDashboardSnapshot(cmd *m.CreateDashboardSnapshotCommand) error {
 }
 
 func GetDashboardSnapshot(query *m.GetDashboardSnapshotQuery) error {
-	var snapshot m.DashboardSnapshot
-	has, err := x.Where("key=?", query.Key).Get(&snapshot)
+	snapshot := m.DashboardSnapshot{Key: query.Key}
+	has, err := x.Get(&snapshot)
 
 	if err != nil {
 		return err

+ 6 - 0
pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go

@@ -21,4 +21,10 @@ func addDashboardSnapshotMigrations(mg *Migrator) {
 
 	mg.AddMigration("create dashboard_snapshot table v4", NewAddTableMigration(snapshotV4))
 	addTableIndicesMigrations(mg, "v4", snapshotV4)
+
+	mg.AddMigration("add org_id to dashboard_snapshot", new(AddColumnMigration).
+		Table("dashboard_snapshot").Column(&Column{Name: "org_id", Type: DB_BigInt, Nullable: true}))
+
+	mg.AddMigration("add index org_id to dashboard_snapshot",
+		NewAddIndexMigration(snapshotV4, &Index{Cols: []string{"org_id"}}))
 }

+ 8 - 11
pkg/services/sqlstore/sqlstore.go

@@ -22,7 +22,6 @@ import (
 var (
 	x       *xorm.Engine
 	dialect migrator.Dialect
-	tables  []interface{}
 
 	HasEngine bool
 
@@ -80,10 +79,6 @@ func SetEngine(engine *xorm.Engine, enableLog bool) (err error) {
 		return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)
 	}
 
-	if err := x.Sync2(tables...); err != nil {
-		return fmt.Errorf("sync database struct error: %v\n", err)
-	}
-
 	if enableLog {
 		logPath := path.Join(setting.LogRootPath, "xorm.log")
 		os.MkdirAll(path.Dir(logPath), os.ModePerm)
@@ -94,11 +89,13 @@ func SetEngine(engine *xorm.Engine, enableLog bool) (err error) {
 		}
 		x.Logger = xorm.NewSimpleLogger(f)
 
-		x.ShowSQL = true
-		x.ShowInfo = true
-		x.ShowDebug = true
-		x.ShowErr = true
-		x.ShowWarn = true
+		if setting.Env == setting.DEV {
+			x.ShowSQL = false
+			x.ShowInfo = false
+			x.ShowDebug = false
+			x.ShowErr = true
+			x.ShowWarn = true
+		}
 	}
 
 	return nil
@@ -125,7 +122,7 @@ func getEngine() (*xorm.Engine, error) {
 			DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode)
 	case "sqlite3":
 		os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm)
-		cnnstr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc"
+		cnnstr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc&_loc=Local"
 	default:
 		return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
 	}

+ 1 - 0
pkg/services/sqlstore/sqlstore.goconvey

@@ -0,0 +1 @@
+-timeout=10s

+ 1 - 1
pkg/services/sqlstore/sqlutil/sqlutil.go

@@ -11,7 +11,7 @@ type TestDB struct {
 	ConnStr    string
 }
 
-var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:"}
+var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:?_loc=Local"}
 var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?charset=utf8"}
 var TestDB_Postgres = TestDB{DriverName: "postgres", ConnStr: "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"}
 

+ 0 - 0
pkg/services/sqlstore/xorm.log


+ 1 - 0
src/app/features/dashboard/dashboardCtrl.js

@@ -79,6 +79,7 @@ function (angular, $, config) {
         meta.canEdit = false;
         meta.canSave = false;
         meta.canStar = false;
+        meta.canShare = false;
       }
 
       $scope.dashboardMeta = meta;

+ 13 - 2
src/app/features/dashboard/partials/shareDashboard.html

@@ -55,7 +55,7 @@
 				leaving only the visible metric data and series names embedded into your dashboard.
 			</p>
 			<p class="share-snapshot-info-text">
-				Keep in mind, your <strong>snapshot can be viewed any anyone</strong> that has the link and can reach the URL.
+				Keep in mind, your <strong>snapshot can be viewed by anyone</strong> that has the link and can reach the URL.
 				Share wisely.
 			</p>
 		</div>
@@ -79,11 +79,22 @@
 							Expire
 						</li>
 						<li>
-							<select class="input-small tight-form-input last" style="width: 211px" ng-model="snapshot.expire" ng-options="f.value as f.text for f in expireOptions"></select>
+							<select class="input-small tight-form-input last" style="width: 211px" ng-model="snapshot.expires" ng-options="f.value as f.text for f in expireOptions"></select>
 						</li>
 					</ul>
 					<div class="clearfix"></div>
 				</div>
+				<!-- <div class="tight&#45;form"> -->
+				<!-- 	<ul class="tight&#45;form&#45;list"> -->
+				<!-- 		<li class="tight&#45;form&#45;item" style="width: 110px"> -->
+				<!-- 			Access -->
+				<!-- 		</li> -->
+				<!-- 		<li> -->
+				<!-- 			<select class="input&#45;small tight&#45;form&#45;input last" style="width: 211px" ng&#45;model="snapshot.access" ng&#45;options="f.value as f.text for f in accessOptions"></select> -->
+				<!-- 		</li> -->
+				<!-- 	</ul> -->
+				<!-- 	<div class="clearfix"></div> -->
+				<!-- </div> -->
 			</div>
 
 			<div class="gf-form" ng-if="step === 2" style="margin-top: 55px">

+ 17 - 7
src/app/features/dashboard/shareSnapshotCtrl.js

@@ -11,8 +11,7 @@ function (angular, _) {
 
     $scope.snapshot = {
       name: $scope.dashboard.title,
-      expire: 0,
-      external: false,
+      expires: 0,
     };
 
     $scope.step = 1;
@@ -24,6 +23,12 @@ function (angular, _) {
       {text: 'Never',  value: 0},
     ];
 
+    $scope.accessOptions = [
+      {text: 'Anyone with the link', value: 1},
+      {text: 'Organization users',  value: 2},
+      {text: 'Public on the web', value: 3},
+    ];
+
     $scope.createSnapshot = function(external) {
       $scope.dashboard.snapshot = {
         timestamp: new Date()
@@ -35,11 +40,11 @@ function (angular, _) {
       $rootScope.$broadcast('refresh');
 
       $timeout(function() {
-        $scope.saveSnapshot();
+        $scope.saveSnapshot(external);
       }, 3000);
     };
 
-    $scope.saveSnapshot = function() {
+    $scope.saveSnapshot = function(external) {
       var dash = angular.copy($scope.dashboard);
       // change title
       dash.title = $scope.snapshot.name;
@@ -66,14 +71,19 @@ function (angular, _) {
 
       var cmdData = {
         dashboard: dash,
-        external: $scope.snapshot.external,
+        external: external === true,
         expires: $scope.snapshot.expires,
       };
 
-      backendSrv.post('/api/snapshots', cmdData).then(function(results) {
+      var apiUrl = '/api/snapshots/';
+      if (external) {
+        apiUrl = "http://snapshots-origin.raintank.io/api/snapshots";
+      }
+
+      backendSrv.post(apiUrl, cmdData).then(function(results) {
         $scope.loading = false;
 
-        if ($scope.snapshot.external) {
+        if (external) {
           $scope.snapshotUrl = results.url;
         } else {
           var baseUrl = $location.absUrl().replace($location.url(), "");