You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ha...@apache.org on 2022/11/08 14:31:41 UTC

[iotdb] branch master updated: Grafana plugin code optimization (#7934)

This is an automated email from the ASF dual-hosted git repository.

haonan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new ebad1dd5bf Grafana plugin code optimization (#7934)
ebad1dd5bf is described below

commit ebad1dd5bfd867a05540f4fda918318e51be16ad
Author: CloudWise-Lukemiao <76...@users.noreply.github.com>
AuthorDate: Tue Nov 8 22:31:34 2022 +0800

    Grafana plugin code optimization (#7934)
---
 grafana-plugin/README.md                           | 128 ----------------
 grafana-plugin/pkg/main.go                         |   2 +-
 .../pkg/plugin/iotdb_resource_handler.go           | 170 +++++++++++++++++++++
 grafana-plugin/pkg/plugin/plugin.go                | 119 +++++----------
 grafana-plugin/src/ConfigEditor.tsx                |  70 +++++----
 grafana-plugin/src/datasource.ts                   |  47 ++----
 grafana-plugin/src/img/addIoTDBDataSource.png      | Bin 0 -> 43760 bytes
 grafana-plugin/src/img/showData.png                | Bin 0 -> 170827 bytes
 grafana-plugin/src/plugin.json                     |   4 +-
 grafana-plugin/src/types.ts                        |   3 +-
 10 files changed, 269 insertions(+), 274 deletions(-)

diff --git a/grafana-plugin/README.md b/grafana-plugin/README.md
index eac176de15..9dfc4cc646 100644
--- a/grafana-plugin/README.md
+++ b/grafana-plugin/README.md
@@ -27,134 +27,6 @@ Grafana is an open source volume metrics monitoring and visualization tool, whic
 We developed the Grafana-Plugin for IoTDB, using the IoTDB REST service to present time series data and providing many visualization methods for time series data.
 Compared with previous IoTDB-Grafana-Connector, current Grafana-Plugin performs more efficiently and supports more query types. So, **we recommend using Grafana-Plugin instead of IoTDB-Grafana-Connector**.
 
-### Installation and deployment
-
-#### Install Grafana
-
-* Download url: https://grafana.com/grafana/download
-* Version >= 7.0.0
-
-
-#### Acquisition method of grafana plugin
-
-##### Method 1: grafana plugin binary Download
-
-Download url:https://iotdb.apache.org/zh/Download/
-
-##### Method 2: separate compilation of grafana plugin
-
-We need to compile the front-end project in the IoTDB `grafana-plugin` directory and then generate the `dist` directory. The specific execution process is as follows.
-
-Source download
-
-* Plugin name: grafana-plugin
-* Download url: https://github.com/apache/iotdb.git
-
-Execute the following command:
-
-```shell
-git clone https://github.com/apache/iotdb.git
-```
-
-* Option 1 (compile with maven): execute following command in the `grafana-plugin` directory:
-
-```shell
-mvn install package -P compile-grafana-plugin
-```
-
-* Option 2 (compile with yarn): execute following command in the `grafana-plugin` directory:
-
-```shell
-yarn install
-yarn build
-go get -u github.com/grafana/grafana-plugin-sdk-go
-go mod tidy
-mage -v
-```
-
-When using the go get -u command, the following error may be reported. In this case, we need to execute `go env -w GOPROXY=https://goproxy.cn`, and then execute `go get -u github.com/grafana/grafana -plugin-sdk-go`
-
-```
-go get: module github.com/grafana/grafana-plugin-sdk-go: Get "https://proxy.golang.org/github.com/grafana/grafana-plugin-sdk-go/@v/list": dial tcp 142.251.42.241:443: i/o timeout
-```
-
-If compiling successful, you can see the `dist` directory , which contains the compiled Grafana-Plugin:
-
-<img style="width:100%; max-width:333px; max-height:545px; margin-left:auto; margin-right:auto; display:block;" src="https://github.com/apache/iotdb-bin-resources/blob/main/docs/UserGuide/Ecosystem%20Integration/Grafana-plugin/grafana-plugin-build.png?raw=true">
-
-##### Method 3: The distribution package of IoTDB is fully compiled
-
-We can also obtain the front-end project of `grafana-plugin` and other IoTDB executable files by executing the **package instruction** of the IoTDB project.
-
-Execute following command in the IoTDB root directory:
-
-```shell
- mvn clean package -pl distribution -am -DskipTests -P compile-grafana-plugin
-```
-
-If compiling successful, you can see that the `distribution/target` directory contains the compiled Grafana-Plugin:
-
-<img style="width:100%; max-width:333px; max-height:545px; margin-left:auto; margin-right:auto; display:block;" src="https://github.com/apache/iotdb-bin-resources/blob/main/docs/UserGuide/Ecosystem%20Integration/Grafana-plugin/distribution.png?raw=true">
-
-
-#### Install Grafana-Plugin
-
-* Copy the front-end project target folder generated above to Grafana's plugin directory `${Grafana directory}\data\plugins\`。If there is no such directory, you can manually create it or start grafana and it will be created automatically. Of course, you can also modify the location of plugins. For details, please refer to the following instructions for modifying the location of Grafana's plugin directory.
-
-
-* Modify Grafana configuration file: the file is in(`${Grafana directory}\conf\defaults.ini`), and do the following modifications:
-
-  ```ini
-  allow_loading_unsigned_plugins = apache-iotdb-datasource
-  ```
-* Modify the location of Grafana's plugin directory: the file is in(`${Grafana directory}\conf\defaults.ini`), and do the following modifications:
-
-  ```ini
-  plugins = data/plugins
-  ```
-* Start Grafana (restart if the Grafana service is already started)
-
-For more details,please click [here](https://grafana.com/docs/grafana/latest/plugins/installation/)
-
-#### Start Grafana
-
-Start Grafana with the following command in the Grafana directory:
-
-* Windows:
-
-```shell
-bin\grafana-server.exe
-```
-* Linux:
-
-```shell
-sudo service grafana-server start
-```
-
-* MacOS:
-
-```shell
-brew services start grafana
-```
-
-For more details,please click [here](https://grafana.com/docs/grafana/latest/installation/)
-
-
-
-#### Configure IoTDB REST Service
-
-* Modify `{iotdb directory}/conf/iotdb-rest.properties` as following:
-
-```properties
-# Is the REST service enabled
-enable_rest_service=true
-
-# the binding port of the REST service
-rest_service_port=18080
-```
-
-Start IoTDB (restart if the IoTDB service is already started)
-
 
 ### How to use Grafana-Plugin
 
diff --git a/grafana-plugin/pkg/main.go b/grafana-plugin/pkg/main.go
index a0d3f13f8d..8f06e0d586 100644
--- a/grafana-plugin/pkg/main.go
+++ b/grafana-plugin/pkg/main.go
@@ -33,7 +33,7 @@ func main() {
 	// from Grafana to create different instances of SampleDatasource (per datasource
 	// ID). When datasource configuration changed Dispose method will be called and
 	// new datasource instance created using NewSampleDatasource factory.
-	if err := datasource.Manage("myorgid-simple-backend-datasource", plugin.NewSampleDatasource, datasource.ManageOpts{}); err != nil {
+	if err := datasource.Manage("apache-iotdb-backend-datasource", plugin.ApacheIoTDBDatasource, datasource.ManageOpts{}); err != nil {
 		log.DefaultLogger.Error(err.Error())
 		os.Exit(1)
 	}
diff --git a/grafana-plugin/pkg/plugin/iotdb_resource_handler.go b/grafana-plugin/pkg/plugin/iotdb_resource_handler.go
new file mode 100644
index 0000000000..74968370ba
--- /dev/null
+++ b/grafana-plugin/pkg/plugin/iotdb_resource_handler.go
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package plugin
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/grafana/grafana-plugin-sdk-go/backend"
+	"github.com/grafana/grafana-plugin-sdk-go/backend/log"
+	"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
+)
+
+func iotdbResourceHandler(authorization string) backend.CallResourceHandler {
+	mux := http.NewServeMux()
+
+	mux.Handle("/getVariables", getVariables(authorization))
+	mux.Handle("/getNodes", getNodes(authorization))
+
+	return httpadapter.New(mux)
+}
+
+type queryReq struct {
+	Sql string `json:"sql"`
+}
+type nodeReq struct {
+	Data []string `json:"data"`
+	Url string `json:"url"`
+}
+
+type queryResp struct {
+	Code    int    `json:"code"`
+	Message string `json:"message"`
+}
+
+func getVariables(authorization string) http.Handler{
+	fn := func(w http.ResponseWriter, r *http.Request) {
+		var url=r.FormValue("url")
+		var sql=r.FormValue("sql")
+		if r.Method != http.MethodGet {
+			http.NotFound(w, r)
+			return
+		}
+		var queryReq=&queryReq{Sql: sql}
+		qpJson, _ := json.Marshal(queryReq)
+		reader := bytes.NewReader(qpJson)
+		client := &http.Client{}
+		request, _ := http.NewRequest(http.MethodPost, url+"/grafana/v1/variable", reader)
+		request.Header.Set("Content-Type", "application/json")
+		request.Header.Add("Authorization", authorization)
+		rsp, _ := client.Do(request)
+		body, err := io.ReadAll(rsp.Body)
+		if err != nil {
+			log.DefaultLogger.Error("Data source is not working properly", err)
+		}
+
+		var dataResp []string
+		err = json.Unmarshal(body, &dataResp)
+		if err != nil {
+			log.DefaultLogger.Error("Parsing JSON error", err)
+			var resultResp queryResp
+			json.Unmarshal(body, &resultResp)
+			defer rsp.Body.Close()
+			j, err := json.Marshal(resultResp)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			_, err = w.Write(j)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+
+		}else{
+			defer rsp.Body.Close()
+			j, err := json.Marshal(dataResp)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			_, err = w.Write(j)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+		}
+
+	}
+	return http.HandlerFunc(fn)
+}
+
+func getNodes(authorization string) http.Handler{
+	fn := func(w http.ResponseWriter, r *http.Request) {
+		s, _:=ioutil.ReadAll(r.Body)
+		if r.Method != http.MethodPost {
+			http.NotFound(w, r)
+			return
+		}
+		var nodeReq nodeReq
+		err := json.Unmarshal(s, &nodeReq)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		qpJson, _ := json.Marshal(nodeReq.Data)
+		reader := bytes.NewReader(qpJson)
+		client := &http.Client{}
+		request, _ := http.NewRequest(http.MethodPost, nodeReq.Url+"/grafana/v1/node", reader)
+		request.Header.Set("Content-Type", "application/json")
+		request.Header.Add("Authorization", authorization)
+		rsp, _ := client.Do(request)
+		body, err := io.ReadAll(rsp.Body)
+		if err != nil {
+			log.DefaultLogger.Error("Data source is not working properly", err)
+		}
+
+		var dataResp []string
+		err = json.Unmarshal(body, &dataResp)
+		if err != nil {
+			log.DefaultLogger.Error("Parsing JSON error", err)
+			var resultResp queryResp
+			json.Unmarshal(body, &resultResp)
+			defer rsp.Body.Close()
+			j, err := json.Marshal(resultResp)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			_, err = w.Write(j)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+
+		}else{
+			defer rsp.Body.Close()
+			j, err := json.Marshal(dataResp)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			_, err = w.Write(j)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+		}
+
+	}
+	return http.HandlerFunc(fn)
+}
diff --git a/grafana-plugin/pkg/plugin/plugin.go b/grafana-plugin/pkg/plugin/plugin.go
index a0ab87a2c9..31db7b6dfa 100644
--- a/grafana-plugin/pkg/plugin/plugin.go
+++ b/grafana-plugin/pkg/plugin/plugin.go
@@ -46,30 +46,33 @@ import (
 var (
 	_ backend.QueryDataHandler      = (*IoTDBDataSource)(nil)
 	_ backend.CheckHealthHandler    = (*IoTDBDataSource)(nil)
-	_ backend.StreamHandler         = (*IoTDBDataSource)(nil)
-	_ instancemgmt.InstanceDisposer = (*IoTDBDataSource)(nil)
+	_ backend.CallResourceHandler   = (*IoTDBDataSource)(nil)
 )
 
-// NewSampleDatasource creates a new datasource instance.
-func NewSampleDatasource(d backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
+// ApacheIoTDBDatasource creates a new datasource instance.
+func ApacheIoTDBDatasource(d backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
 	var dm dataSourceModel
 	if err := json.Unmarshal(d.JSONData, &dm); err != nil {
 		return nil, err
 	}
-	return &IoTDBDataSource{Username: dm.Username, Password: dm.Password, Ulr: dm.Url}, nil
+	var authorization=""
+	if password, exists := d.DecryptedSecureJSONData["password"]; exists {
+		authorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(dm.Username+":"+password))
+	}
+	return &IoTDBDataSource{CallResourceHandler: iotdbResourceHandler(authorization),Username: dm.Username, Ulr: dm.Url}, nil
 }
 
 // SampleDatasource is an example datasource which can respond to data queries, reports
 // its health and has streaming skills.
 type IoTDBDataSource struct {
-	Password string
+	backend.CallResourceHandler
 	Username string
 	Ulr      string
 }
 
 // Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance
 // created. As soon as datasource settings change detected by SDK old datasource instance will
-// be disposed and a new one will be created using NewSampleDatasource factory function.
+// be disposed and a new one will be created using ApacheIoTDBDatasource factory function.
 func (d *IoTDBDataSource) Dispose() {
 	// Clean up datasource instance resources.
 }
@@ -97,7 +100,6 @@ func (d *IoTDBDataSource) QueryData(ctx context.Context, req *backend.QueryDataR
 
 type dataSourceModel struct {
 	Username string `json:"username"`
-	Password string `json:"password"`
 	Url      string `json:"url"`
 }
 
@@ -150,7 +152,13 @@ func NewQueryDataReq(expression []string, prefixPath []string, startTime int64,
 
 func (d *IoTDBDataSource) query(cxt context.Context, pCtx backend.PluginContext, query backend.DataQuery) backend.DataResponse {
 	response := backend.DataResponse{}
-	var authorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(d.Username+":"+d.Password))
+
+	instanceSettings := pCtx.DataSourceInstanceSettings
+	var authorization=""
+	if password, exists := instanceSettings.DecryptedSecureJSONData["password"]; exists {
+		// Use the decrypted API key.
+		authorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(d.Username+":"+password))
+	}
 
 	// Unmarshal the JSON into our queryModel.
 	var qp queryParam
@@ -220,12 +228,20 @@ func (d *IoTDBDataSource) query(cxt context.Context, pCtx backend.PluginContext,
 	// create data frame response.
 	frame := data.NewFrame("response")
 	for i := 0; i < len(queryDataResp.Expressions); i++ {
+		if queryDataResp.Timestamps==nil||len(queryDataResp.Timestamps)==0{
+			times := make([]time.Time, 1)
+			tmp := make([]float64, 1)
+			frame.Fields = append(frame.Fields,
+				data.NewField("time", nil, times),
+				data.NewField(queryDataResp.Expressions[i], nil, tmp),
+			)
+			continue
+		}
 		times := make([]time.Time, len(queryDataResp.Timestamps))
 		for c := 0; c < len(queryDataResp.Timestamps); c++ {
 			times[c] = time.Unix(queryDataResp.Timestamps[c]/1000, 0)
 		}
 		values :=  recoverType(queryDataResp.Values[i])
-
 		frame.Fields = append(frame.Fields,
 			data.NewField("time", nil, times),
 			data.NewField(queryDataResp.Expressions[i], nil, values),
@@ -242,7 +258,11 @@ func recoverType(m []interface{}) interface{} {
         case float64:
             tmp := make([]float64, len(m))
             for i := range m {
-                tmp[i] = m[i].(float64)
+				if m[i] == nil {
+					tmp[i] = 0
+				}else{
+					tmp[i] = m[i].(float64)
+				}
             }
             return tmp
         case string:
@@ -264,7 +284,11 @@ func recoverType(m []interface{}) interface{} {
         default:
             tmp := make([]float64, len(m))
             for i := range m {
-                tmp[i] = 0
+				if m[i] == nil {
+					tmp[i] = 0
+				}else{
+					tmp[i] = m[i].(float64)
+				}
             }
             return tmp
         }
@@ -288,7 +312,12 @@ func DataSourceUrlHandler(url string) string {
 // a datasource is working as expected.
 func (d *IoTDBDataSource) CheckHealth(_ context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
 	log.DefaultLogger.Info("CheckHealth called", "request", req)
-	var authorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(d.Username+":"+d.Password))
+	instanceSettings := req.PluginContext.DataSourceInstanceSettings
+	var authorization=""
+	if password, exists := instanceSettings.DecryptedSecureJSONData["password"]; exists {
+		authorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(d.Username+":"+password))
+	}
+
 	var status = backend.HealthStatusError
 	var message = "Data source is not working properly"
 
@@ -323,67 +352,3 @@ func (d *IoTDBDataSource) CheckHealth(_ context.Context, req *backend.CheckHealt
 		Message: message,
 	}, nil
 }
-
-// SubscribeStream is called when a client wants to connect to a stream. This callback
-// allows sending the first message.
-func (d *IoTDBDataSource) SubscribeStream(_ context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
-	log.DefaultLogger.Info("SubscribeStream called", "request", req)
-
-	status := backend.SubscribeStreamStatusPermissionDenied
-	if req.Path == "stream" {
-		// Allow subscribing only on expected path.
-		status = backend.SubscribeStreamStatusOK
-	}
-
-	return &backend.SubscribeStreamResponse{
-		Status: status,
-	}, nil
-}
-
-// RunStream is called once for any open channel.  Results are shared with everyone
-// subscribed to the same channel.
-func (d *IoTDBDataSource) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
-	log.DefaultLogger.Info("RunStream called", "request", req)
-
-	// Create the same data frame as for query data.
-	frame := data.NewFrame("response")
-
-	// Add fields (matching the same schema used in QueryData).
-	frame.Fields = append(frame.Fields,
-		data.NewField("time", nil, make([]time.Time, 1)),
-		data.NewField("values", nil, make([]int64, 1)),
-	)
-
-	counter := 0
-
-	// Stream data frames periodically till stream closed by Grafana.
-	for {
-		select {
-		case <-ctx.Done():
-			log.DefaultLogger.Info("Context done, finish streaming", "path", req.Path)
-			return nil
-		case <-time.After(time.Second):
-			// Send new data periodically.
-			frame.Fields[0].Set(0, time.Now())
-			frame.Fields[1].Set(0, int64(10*(counter%2+1)))
-
-			counter++
-
-			err := sender.SendFrame(frame, data.IncludeAll)
-			if err != nil {
-				log.DefaultLogger.Error("Error sending frame", "error", err)
-				continue
-			}
-		}
-	}
-}
-
-// PublishStream is called when a client sends a message to the stream.
-func (d *IoTDBDataSource) PublishStream(_ context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
-	log.DefaultLogger.Info("PublishStream called", "request", req)
-
-	// Do not allow publishing at all.
-	return &backend.PublishStreamResponse{
-		Status: backend.PublishStreamStatusPermissionDenied,
-	}, nil
-}
diff --git a/grafana-plugin/src/ConfigEditor.tsx b/grafana-plugin/src/ConfigEditor.tsx
index fb225c44f0..355047d72d 100644
--- a/grafana-plugin/src/ConfigEditor.tsx
+++ b/grafana-plugin/src/ConfigEditor.tsx
@@ -17,11 +17,11 @@
 import React, { ChangeEvent, PureComponent } from 'react';
 import { LegacyForms } from '@grafana/ui';
 import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
-import { IoTDBOptions } from './types';
+import { IoTDBOptions, IoTDBSecureJsonData } from './types';
 
-const { FormField } = LegacyForms;
+const { SecretFormField, FormField } = LegacyForms;
 
-interface Props extends DataSourcePluginOptionsEditorProps<IoTDBOptions> {}
+interface Props extends DataSourcePluginOptionsEditorProps<IoTDBOptions, IoTDBSecureJsonData> {}
 
 interface State {}
 
@@ -35,7 +35,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
     onOptionsChange({ ...options, jsonData });
   };
 
-  onUserChange = (event: ChangeEvent<HTMLInputElement>) => {
+  onUserNameChange = (event: ChangeEvent<HTMLInputElement>) => {
     const { onOptionsChange, options } = this.props;
     const jsonData = {
       ...options.jsonData,
@@ -44,18 +44,36 @@ export class ConfigEditor extends PureComponent<Props, State> {
     onOptionsChange({ ...options, jsonData });
   };
 
-  onPassWordChange = (event: ChangeEvent<HTMLInputElement>) => {
+  // Secure field (only sent to the backend)
+  onPasswordChange = (event: ChangeEvent<HTMLInputElement>) => {
     const { onOptionsChange, options } = this.props;
-    const jsonData = {
-      ...options.jsonData,
-      password: event.target.value,
-    };
-    onOptionsChange({ ...options, jsonData });
+    onOptionsChange({
+      ...options,
+      secureJsonData: {
+        password: event.target.value,
+      },
+    });
+  };
+
+  onResetPassword = () => {
+    const { onOptionsChange, options } = this.props;
+    onOptionsChange({
+      ...options,
+      secureJsonFields: {
+        ...options.secureJsonFields,
+        password: false,
+      },
+      secureJsonData: {
+        ...options.secureJsonData,
+        password: '',
+      },
+    });
   };
 
   render() {
     const { options } = this.props;
-    const { jsonData } = options;
+    const { secureJsonFields, jsonData } = options;
+    const secureJsonData = (options.secureJsonData || {}) as IoTDBSecureJsonData;
 
     return (
       <div className="gf-form-group">
@@ -69,30 +87,28 @@ export class ConfigEditor extends PureComponent<Props, State> {
             placeholder="please input URL"
           />
         </div>
-
-        <div className="gf-form-inline">
-          <div className="gf-form">
-            <FormField
-              value={jsonData.username || ''}
-              label="username"
-              placeholder="please input username"
-              labelWidth={6}
-              inputWidth={20}
-              onChange={this.onUserChange}
-            />
-          </div>
+        <div className="gf-form">
+          <FormField
+            label="username"
+            labelWidth={6}
+            inputWidth={20}
+            onChange={this.onUserNameChange}
+            value={jsonData.username || ''}
+            placeholder="please input username"
+          />
         </div>
 
         <div className="gf-form-inline">
           <div className="gf-form">
-            <FormField
-              value={jsonData.password || ''}
+            <SecretFormField
+              isConfigured={(secureJsonFields && secureJsonFields.password) as boolean}
+              value={secureJsonData.password || ''}
               label="password"
-              type="password"
               placeholder="please input password"
               labelWidth={6}
               inputWidth={20}
-              onChange={this.onPassWordChange}
+              onReset={this.onResetPassword}
+              onChange={this.onPasswordChange}
             />
           </div>
         </div>
diff --git a/grafana-plugin/src/datasource.ts b/grafana-plugin/src/datasource.ts
index c5ebe453df..88a0224a18 100644
--- a/grafana-plugin/src/datasource.ts
+++ b/grafana-plugin/src/datasource.ts
@@ -18,17 +18,15 @@ import { DataSourceInstanceSettings, MetricFindValue, ScopedVars } from '@grafan
 
 import { IoTDBOptions, IoTDBQuery } from './types';
 import { toMetricFindValue } from './functions';
-import { DataSourceWithBackend, getBackendSrv, getTemplateSrv } from '@grafana/runtime';
+import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
 
 export class DataSource extends DataSourceWithBackend<IoTDBQuery, IoTDBOptions> {
   username: string;
-  password: string;
   url: string;
 
   constructor(instanceSettings: DataSourceInstanceSettings<IoTDBOptions>) {
     super(instanceSettings);
     this.url = instanceSettings.jsonData.url;
-    this.password = instanceSettings.jsonData.password;
     this.username = instanceSettings.jsonData.username;
   }
   applyTemplateVariables(query: IoTDBQuery, scopedVars: ScopedVars) {
@@ -64,8 +62,7 @@ export class DataSource extends DataSourceWithBackend<IoTDBQuery, IoTDBOptions>
 
   metricFindQuery(query: any, options?: any): Promise<MetricFindValue[]> {
     query = getTemplateSrv().replace(query, options.scopedVars);
-    const sql = { sql: query };
-    return this.getVariablesResult(sql);
+    return this.getVariablesResult(query);
   }
 
   nodeQuery(query: any, options?: any): Promise<MetricFindValue[]> {
@@ -73,23 +70,13 @@ export class DataSource extends DataSourceWithBackend<IoTDBQuery, IoTDBOptions>
   }
 
   async getChildPaths(detachedPath: string[]) {
-    const myHeader = new Headers();
-    myHeader.append('Content-Type', 'application/json');
-    const Authorization = 'Basic ' + Buffer.from(this.username + ':' + this.password).toString('base64');
-    myHeader.append('Authorization', Authorization);
     if (this.url.substr(this.url.length - 1, 1) === '/') {
       this.url = this.url.substr(0, this.url.length - 1);
     }
-    return await getBackendSrv()
-      .datasourceRequest({
-        method: 'POST',
-        url: this.url + '/grafana/v1/node',
-        data: detachedPath,
-        headers: myHeader,
-      })
+    return this.postResource('getNodes', { url: this.url, data: detachedPath })
       .then((response) => {
-        if (response.data instanceof Array) {
-          return response.data;
+        if (response instanceof Array) {
+          return response;
         } else {
           throw 'the result is not array';
         }
@@ -97,30 +84,16 @@ export class DataSource extends DataSourceWithBackend<IoTDBQuery, IoTDBOptions>
       .then((data) => data.map(toMetricFindValue));
   }
 
-  async getVariablesResult(sql: object) {
-    const myHeader = new Headers();
-    myHeader.append('Content-Type', 'application/json');
-    const Authorization = 'Basic ' + Buffer.from(this.username + ':' + this.password).toString('base64');
-    myHeader.append('Authorization', Authorization);
+  async getVariablesResult(sql: string) {
     if (this.url.substr(this.url.length - 1, 1) === '/') {
       this.url = this.url.substr(0, this.url.length - 1);
     }
-    return await getBackendSrv()
-      .datasourceRequest({
-        method: 'POST',
-        url: this.url + '/grafana/v1/variable',
-        data: sql,
-        headers: myHeader,
-      })
+    return this.getResource('getVariables', { url: this.url, sql: sql })
       .then((response) => {
-        if (response.data instanceof Array) {
-          return response.data;
+        if (response instanceof Array) {
+          return response;
         } else {
-          if ((response.data.code = 400)) {
-            throw response.data.message;
-          } else {
-            throw 'the result is not array';
-          }
+          throw response.message;
         }
       })
       .then((data) => data.map(toMetricFindValue));
diff --git a/grafana-plugin/src/img/addIoTDBDataSource.png b/grafana-plugin/src/img/addIoTDBDataSource.png
new file mode 100644
index 0000000000..6efc3a4b21
Binary files /dev/null and b/grafana-plugin/src/img/addIoTDBDataSource.png differ
diff --git a/grafana-plugin/src/img/showData.png b/grafana-plugin/src/img/showData.png
new file mode 100644
index 0000000000..c8073568ff
Binary files /dev/null and b/grafana-plugin/src/img/showData.png differ
diff --git a/grafana-plugin/src/plugin.json b/grafana-plugin/src/plugin.json
index 749cab6e3e..a9e98929fc 100644
--- a/grafana-plugin/src/plugin.json
+++ b/grafana-plugin/src/plugin.json
@@ -6,7 +6,7 @@
   "metrics": true,
   "backend": true,
   "alerting": true,
-  "executable": "apache-iotdb-datasource",
+  "executable": "gpx_apache_iotdb_datasource",
   "info": {
     "description": "Apache IoTDB",
     "author": {
@@ -39,7 +39,7 @@
         "url": "https://github.com/apache/iotdb/blob/master/LICENSE"
       }
     ],
-    "screenshots": [],
+    "screenshots": [{ "name": "addDataSource", "path": "img/addIoTDBDataSource.png" },{ "name": "showData", "path": "img/showData.png" }],
     "version": "%VERSION%",
     "updated": "%TODAY%"
   },
diff --git a/grafana-plugin/src/types.ts b/grafana-plugin/src/types.ts
index 0d62f2f56b..5ce7162cd2 100644
--- a/grafana-plugin/src/types.ts
+++ b/grafana-plugin/src/types.ts
@@ -56,7 +56,6 @@ export interface LimitAll {
  */
 export interface IoTDBOptions extends DataSourceJsonData {
   url: string;
-  password: string;
   username: string;
 }
 
@@ -64,5 +63,5 @@ export interface IoTDBOptions extends DataSourceJsonData {
  * Value that is used in the backend, but never sent over HTTP to the frontend
  */
 export interface IoTDBSecureJsonData {
-  apiKey?: string;
+  password?: string;
 }