├── Gruntfile.js ├── LICENSE ├── Magefile.go ├── README.md ├── go.mod ├── package.json ├── pkg ├── datasource.go └── plugin.go ├── spec ├── datasource_spec.js └── test-main.js └── src ├── .DS_Store ├── css └── query-editor.css ├── datasource.js ├── img └── yunjiankong.png ├── json ├── 2019-10-30_ecs_test.json └── 2019-10-30_rds_test.json ├── module.js ├── partials ├── annotations.editor.html ├── config.html ├── query.editor.html └── query.options.html ├── plugin.json ├── query_ctrl.js ├── sha1.js ├── signer.js └── util.js /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | require('load-grunt-tasks')(grunt); 4 | 5 | grunt.loadNpmTasks('grunt-execute'); 6 | grunt.loadNpmTasks('grunt-contrib-clean'); 7 | 8 | grunt.initConfig({ 9 | 10 | clean: ["dist"], 11 | 12 | copy: { 13 | src_to_dist: { 14 | cwd: 'src', 15 | expand: true, 16 | src: ['**/*', '!**/*.js', '!**/*.scss'], 17 | dest: 'dist' 18 | }, 19 | pluginDef: { 20 | expand: true, 21 | src: ['README.md'], 22 | dest: 'dist' 23 | } 24 | }, 25 | 26 | watch: { 27 | rebuild_all: { 28 | files: ['src/**/*'], 29 | tasks: ['default'], 30 | options: {spawn: false} 31 | } 32 | }, 33 | 34 | babel: { 35 | options: { 36 | sourceMap: true, 37 | presets: ['es2015'], 38 | plugins: ['transform-es2015-modules-systemjs', 'transform-es2015-for-of'] 39 | }, 40 | dist: { 41 | files: [{ 42 | cwd: 'src', 43 | expand: true, 44 | src: ['**/*.js'], 45 | dest: 'dist', 46 | ext:'.js' 47 | }] 48 | }, 49 | distTestNoSystemJs: { 50 | files: [{ 51 | cwd: 'src', 52 | expand: true, 53 | src: ['**/*.js'], 54 | dest: 'dist/test', 55 | ext:'.js' 56 | }] 57 | }, 58 | distTestsSpecsNoSystemJs: { 59 | files: [{ 60 | expand: true, 61 | cwd: 'spec', 62 | src: ['**/*.js'], 63 | dest: 'dist/test/spec', 64 | ext:'.js' 65 | }] 66 | } 67 | }, 68 | 69 | // mochaTest: { 70 | // test: { 71 | // options: { 72 | // reporter: 'spec' 73 | // }, 74 | // src: ['dist/test/spec/test-main.js', 'dist/test/spec/*_spec.js'] 75 | // } 76 | // } 77 | }); 78 | 79 | grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:pluginDef', 'babel']); 80 | }; 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Grafana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Magefile.go: -------------------------------------------------------------------------------- 1 | //+build mage 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | // mage:import 8 | build "github.com/grafana/grafana-plugin-sdk-go/build" 9 | ) 10 | 11 | // Hello prints a message (shows that you can define custom Mage targets). 12 | func Hello() { 13 | fmt.Println("hello plugin developer!") 14 | } 15 | 16 | // Default configures the default target. 17 | var Default = build.BuildAll 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aliyun-cms-grafana 2.0 服务端数据源安装使用说明文档 2 | 3 | ## 安装依赖 4 | 2.0 服务端版本需要 Grafana 版本 7+ 5 | 如果是旧版本 Grafana,只能安装 1.0 版本 https://github.com/aliyun/aliyun-cms-grafana/tree/v1.0 6 | ## 1、直接安装云监控grafana数据源 7 | a. 直接 从release 页面 https://github.com/aliyun/aliyun-cms-grafana/releases 里面下载 aliyun_cms_grafana_datasource_v2.0.tar.gz 8 | b. 下载到 grafan的plugin目录中,解压缩 tar -xzf aliyun_cms_grafana_datasource_v2.0.tar.gz 9 | c. 修改 conf/defaults.ini 允许未签名插件运行 10 | allow_loading_unsigned_plugins = aliyun_cms_grafana_datasource 11 | d. 重启grafana 12 | 13 | ## 2、源代码安装 14 | a. 前端编译 15 | 进入aliyun-cms-grafana目录下,执行grunt命令(需要安装nodejs和npm),则会按照Gruntfile.js里面的配置将项目里面的文件打包到指定的目录, 16 | 当前配置是将项目文件打包到dist目录下,发布的时候打包发布整个插件目录下的文件,dist目录下一定是经源文件编译后的。 17 | b. 服务端编译 18 | 需要安装 19 | go 1.14 20 | mage 21 | 之后在目录中运行 mage -v, 会自动在 dist目录下生成 相应的二进制包。之后跟随前端代码统一发布 22 | 23 | c. 部署 24 | 1)按照上面顺序编译完成后,代码都会到dist下面。包括前端文件和二进制可执行文件 cms-datasource*。 25 | 2)保证 cms-datasource* 都具有可执行权限。chmod +x cms-datasource* 26 | 3) 在grafana 的plugin目录中,创建 aliyun_cms_grafana_datasource 目录,把编译出来的dist目录拷贝到此 27 | 4) 修改 conf/defaults.ini 允许未签名插件运行 28 | allow_loading_unsigned_plugins = aliyun_cms_grafana_datasource 29 | 5) 重启grafana 30 | 31 | 32 | ## 3、配置云监控grafana数据源 33 | a.进入grafana的数据源配置页面(Data Sources),点击Add data source进入配置表单页面,填入数据源名称(Name), 34 | 在数据源类型(Type)对应的下拉列表中选择CMS Grafana Service。 35 | b. 配置你的AK 和阿里云ID 36 | 如果显示Success Data source is working,说明数据源配置成功,可以开始在grafana中访问阿里云监控的数据了。 37 | 具体可参考:https://help.aliyun.com/document_detail/109434.html?spm=a2c4g.11186623.6.565.70d048adQpRZsT 38 | 39 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gitlab.alibaba-inc.com/cms/metricstore4grafana 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/aliyun/alibaba-cloud-sdk-go v1.61.752 7 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 8 | github.com/gocql/gocql v0.0.0-20200815110948-5378c8f664e9 9 | github.com/grafana/grafana-plugin-sdk-go v0.77.0 10 | github.com/magefile/mage v1.10.0 // indirect 11 | gopkg.in/inf.v0 v0.9.1 12 | ) 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aliyun-cms-grafana-datasource", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "watch": "grunt watch", 9 | "build": "./node_modules/grunt-cli/bin/grunt --force", 10 | "test": "./node_modules/grunt-cli/bin/grunt mochaTest" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "http://gitlab.alibaba-inc.com/cms/aliyun-cms-grafana.git" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "http://gitlab.alibaba-inc.com/cms/aliyun-cms-grafana.git" 20 | }, 21 | "engines": { 22 | "node": "14.2.0" 23 | }, 24 | "engineStrict": true, 25 | "devDependencies": { 26 | "chai": "~3.5.0", 27 | "grunt": "^1.1.0", 28 | "grunt-babel": "~6.0.0", 29 | "grunt-cli": "^1.2.0", 30 | "grunt-contrib-clean": "^1.1.0", 31 | "grunt-contrib-copy": "^1.0.0", 32 | "grunt-contrib-requirejs": "^1.0.0", 33 | "grunt-contrib-uglify": "^2.3.0", 34 | "grunt-contrib-watch": "^1.0.0", 35 | "grunt-execute": "~0.2.2", 36 | "grunt-mocha-test": "^0.13.2", 37 | "grunt-systemjs-builder": "^1.0.0", 38 | "jsdom": "~9.12.0", 39 | "load-grunt-tasks": "^3.5.2", 40 | "prunk": "^1.3.0", 41 | "q": "^1.5.0" 42 | }, 43 | "dependencies": { 44 | "babel-plugin-transform-es2015-for-of": "^6.23.0", 45 | "babel-preset-es2015": "^6.24.1", 46 | "lodash": "^4.17.4", 47 | "mocha": "^8.0.1", 48 | "package.json": "^2.0.1" 49 | }, 50 | "homepage": "http://gitlab.alibaba-inc.com/cms/aliyun-cms-grafana.git" 51 | } 52 | -------------------------------------------------------------------------------- /pkg/datasource.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 11 | "github.com/aliyun/alibaba-cloud-sdk-go/services/cms" 12 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 13 | "github.com/aliyun/alibaba-cloud-sdk-go/services/rds" 14 | "github.com/grafana/grafana-plugin-sdk-go/backend" 15 | "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" 16 | "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" 17 | "github.com/grafana/grafana-plugin-sdk-go/backend/log" 18 | "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" 19 | ) 20 | 21 | // newDatasource returns datasource.ServeOpts. 22 | func newDatasource() *CmsDatasource { 23 | log.DefaultLogger.Debug("new_cms_datasource") 24 | im := datasource.NewInstanceManager(newDataSourceInstance) 25 | ds := &CmsDatasource{ 26 | im: im, 27 | logger: log.New(), 28 | } 29 | return ds 30 | } 31 | 32 | type CmsSetting struct { 33 | CmsAccessKey string `json:"cmsAccessKey"` 34 | CmsAcessSecret string `json:"cmsSecretKey"` 35 | } 36 | 37 | // CmsDatasource is an example datasource used to scaffold 38 | // new datasource plugins with an backend. 39 | type CmsDatasource struct { 40 | im instancemgmt.InstanceManager 41 | logger log.Logger 42 | } 43 | 44 | // QueryData 非直接proxy代码,目前无须实现 45 | func (td *CmsDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { 46 | return nil, nil 47 | } 48 | 49 | // 参数都是唯一的,转换为map 50 | func parseRequestParams(req *http.Request) (map[string]string, error) { 51 | result := map[string]string{} 52 | for k, values := range req.URL.Query() { 53 | if len(values) > 0 { 54 | result[k] = values[0] 55 | } 56 | } 57 | data, _ := json.Marshal(result) 58 | log.DefaultLogger.Debug("request_params: ", string(data)) 59 | _, hasAction := result["Action"] 60 | if !hasAction { 61 | return result, errors.New("Action parameter is missing") 62 | } 63 | return result, nil 64 | } 65 | 66 | func (ds *CmsDatasource) getDataSourceSetting(req *http.Request) (*instanceSettings, error) { 67 | pluginCxt := httpadapter.PluginConfigFromContext(req.Context()) 68 | 69 | instance, err := ds.im.Get(pluginCxt) 70 | if err != nil { 71 | log.DefaultLogger.Info("Failed getting connection", "error", err) 72 | return nil, errors.New("Failed getting connection") 73 | } 74 | // create response struct 75 | instSetting, ok := instance.(*instanceSettings) 76 | log.DefaultLogger.Debug("setting", instSetting) 77 | 78 | if !ok { 79 | log.DefaultLogger.Info("Failed load setting connection !", "error", err) 80 | return nil, errors.New("Failed getting connection") 81 | } 82 | return instSetting, nil 83 | } 84 | 85 | func proxyListMetricMeta(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 86 | client, err := cms.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 87 | request := cms.CreateDescribeMetricMetaListRequest() 88 | request.Scheme = "https" 89 | if project, ok := params["Project"]; ok { 90 | request.Namespace = project 91 | } 92 | if metric, ok := params["Metric"]; ok { 93 | request.MetricName = metric 94 | } 95 | if pageSize, ok := params["PageSize"]; ok { 96 | size, err := strconv.ParseInt(pageSize, 10, 32) 97 | if err != nil { 98 | request.PageSize = requests.NewInteger(int(size)) 99 | } else { 100 | request.PageSize = requests.NewInteger(1000) 101 | } 102 | } else { 103 | request.PageSize = requests.NewInteger(1000) 104 | } 105 | 106 | response, err := client.DescribeMetricMetaList(request) 107 | if err != nil { 108 | log.DefaultLogger.Error(err.Error()) 109 | handleResponse(rw, nil, err) 110 | } else { 111 | data, err := json.Marshal(response) 112 | log.DefaultLogger.Debug(string(data)) 113 | handleResponse(rw, data, err) 114 | } 115 | } 116 | 117 | func proxyListProjectMeta(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 118 | client, err := cms.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 119 | request := cms.CreateDescribeProjectMetaRequest() 120 | request.Scheme = "https" 121 | if pageSize, ok := params["PageSize"]; ok { 122 | size, err := strconv.ParseInt(pageSize, 10, 32) 123 | if err != nil { 124 | request.PageSize = requests.NewInteger(int(size)) 125 | } else { 126 | request.PageSize = requests.NewInteger(1000) 127 | } 128 | } else { 129 | request.PageSize = requests.NewInteger(1000) 130 | } 131 | 132 | response, err := client.DescribeProjectMeta(request) 133 | if err != nil { 134 | log.DefaultLogger.Error(err.Error()) 135 | handleResponse(rw, nil, err) 136 | } else { 137 | data, err := json.Marshal(response) 138 | log.DefaultLogger.Debug(string(data)) 139 | handleResponse(rw, data, err) 140 | } 141 | } 142 | 143 | func proxyListMyGroups(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 144 | client, err := cms.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 145 | request := cms.CreateDescribeMonitorGroupsRequest() 146 | request.Scheme = "https" 147 | if pageSize, ok := params["PageSize"]; ok { 148 | size, err := strconv.ParseInt(pageSize, 10, 32) 149 | if err != nil { 150 | request.PageSize = requests.NewInteger(int(size)) 151 | } else { 152 | request.PageSize = requests.NewInteger(1000) 153 | } 154 | } else { 155 | request.PageSize = requests.NewInteger(1000) 156 | } 157 | if pageNumber, ok := params["PageNumber"]; ok { 158 | num, err := strconv.ParseInt(pageNumber, 10, 32) 159 | if err != nil { 160 | request.PageNumber = requests.NewInteger(int(num)) 161 | } else { 162 | request.PageNumber = requests.NewInteger(1) 163 | } 164 | } else { 165 | request.PageNumber = requests.NewInteger(1) 166 | } 167 | 168 | response, err := client.DescribeMonitorGroups(request) 169 | if err != nil { 170 | log.DefaultLogger.Error(err.Error()) 171 | handleResponse(rw, nil, err) 172 | } else { 173 | data, err := json.Marshal(response) 174 | log.DefaultLogger.Debug(string(data)) 175 | handleResponse(rw, data, err) 176 | } 177 | } 178 | 179 | func proxyAccessKeyGet(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 180 | client, err := cms.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 181 | request := cms.CreateDescribeMonitoringAgentAccessKeyRequest() 182 | request.Scheme = "https" 183 | 184 | response, err := client.DescribeMonitoringAgentAccessKey(request) 185 | if err != nil { 186 | log.DefaultLogger.Error(err.Error()) 187 | handleResponse(rw, nil, err) 188 | } else { 189 | data, err := json.Marshal(response) 190 | log.DefaultLogger.Debug(string(data)) 191 | handleResponse(rw, data, err) 192 | } 193 | } 194 | 195 | func proxyQueryMetricData(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 196 | client, err := cms.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 197 | request := cms.CreateDescribeMetricListRequest() 198 | request.Scheme = "https" 199 | if project, ok := params["Project"]; ok { 200 | request.Namespace = project 201 | } 202 | if metric, ok := params["Metric"]; ok { 203 | request.MetricName = metric 204 | } 205 | if dims, ok := params["Dimensions"]; ok { 206 | request.Dimensions = dims 207 | } else { 208 | request.Dimensions = "{}" 209 | } 210 | if period, ok := params["Period"]; ok { 211 | if period != "auto" { 212 | request.Period = period 213 | } 214 | } 215 | if size, ok := params["Length"]; ok { 216 | request.Length = size 217 | } else { 218 | request.Length = "1440" 219 | } 220 | if startTime, ok := params["StartTime"]; ok { 221 | request.StartTime = startTime 222 | } else { 223 | request.StartTime = "60" 224 | } 225 | if endTime, ok := params["EndTime"]; ok { 226 | request.EndTime = endTime 227 | } else { 228 | request.EndTime = "60" 229 | } 230 | 231 | response, err := client.DescribeMetricList(request) 232 | if err != nil { 233 | log.DefaultLogger.Error(err.Error()) 234 | handleResponse(rw, nil, err) 235 | } else { 236 | data, err := json.Marshal(response) 237 | log.DefaultLogger.Debug(string(data)) 238 | handleResponse(rw, data, err) 239 | } 240 | } 241 | 242 | func proxyQueryMetricLast(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 243 | client, err := cms.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 244 | request := cms.CreateDescribeMetricLastRequest() 245 | request.Scheme = "https" 246 | 247 | if project, ok := params["Project"]; ok { 248 | request.Namespace = project 249 | } 250 | if metric, ok := params["Metric"]; ok { 251 | request.MetricName = metric 252 | } 253 | if dims, ok := params["Dimensions"]; ok { 254 | request.Dimensions = dims 255 | } else { 256 | request.Dimensions = "{}" 257 | } 258 | if period, ok := params["Period"]; ok { 259 | if period != "auto" { 260 | request.Period = period 261 | } 262 | } 263 | if size, ok := params["Length"]; ok { 264 | request.Length = size 265 | } else { 266 | request.Length = "1440" 267 | } 268 | if startTime, ok := params["StartTime"]; ok { 269 | request.StartTime = startTime 270 | } else { 271 | request.StartTime = "60" 272 | } 273 | if endTime, ok := params["EndTime"]; ok { 274 | request.EndTime = endTime 275 | } else { 276 | request.EndTime = "60" 277 | } 278 | 279 | response, err := client.DescribeMetricLast(request) 280 | if err != nil { 281 | log.DefaultLogger.Error(err.Error()) 282 | handleResponse(rw, nil, err) 283 | } else { 284 | data, err := json.Marshal(response) 285 | log.DefaultLogger.Debug(string(data)) 286 | handleResponse(rw, data, err) 287 | } 288 | } 289 | 290 | func proxyRdsDescribeTags(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 291 | client, err := rds.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 292 | 293 | request := rds.CreateDescribeTagsRequest() 294 | request.Scheme = "https" 295 | 296 | if regionID, ok := params["RegionId"]; ok { 297 | request.RegionId = regionID 298 | } 299 | 300 | if tags, ok := params["Tags"]; ok { 301 | request.Tags = tags 302 | } 303 | 304 | if tags, ok := params["Tags"]; ok { 305 | request.Tags = tags 306 | } 307 | 308 | if instanceID, ok := params["DBInstanceId"]; ok { 309 | request.DBInstanceId = instanceID 310 | } 311 | 312 | response, err := client.DescribeTags(request) 313 | if err != nil { 314 | log.DefaultLogger.Error(err.Error()) 315 | handleResponse(rw, nil, err) 316 | } else { 317 | data, err := json.Marshal(response) 318 | log.DefaultLogger.Debug(string(data)) 319 | handleResponse(rw, data, err) 320 | } 321 | } 322 | 323 | func proxyRdsListTagResources(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 324 | client, err := rds.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 325 | 326 | request := rds.CreateListTagResourcesRequest() 327 | request.Scheme = "https" 328 | if regionID, ok := params["RegionId"]; ok { 329 | request.RegionId = regionID 330 | } else { 331 | request.RegionId = "cn-hangzhou" 332 | } 333 | 334 | if resourceType, ok := params["ResourceType"]; ok { 335 | request.ResourceType = resourceType 336 | } else { 337 | request.ResourceType = "INSTANCE" 338 | } 339 | 340 | tags := []rds.ListTagResourcesTag{} 341 | // only support 20 tags at most 342 | for i := 1; i < 21; i++ { 343 | tagkey := "Tag." + strconv.Itoa(i) + ".Key" 344 | tagValue := "Tag." + strconv.Itoa(i) + ".Value" 345 | tagObj := rds.ListTagResourcesTag{} 346 | if val, ok := params[tagkey]; ok { 347 | tagObj.Key = val 348 | } else { 349 | continue 350 | } 351 | if val, ok := params[tagValue]; ok { 352 | tagObj.Value = val 353 | } 354 | tags = append(tags, tagObj) 355 | } 356 | if len(tags) > 0 { 357 | request.Tag = &tags 358 | } 359 | 360 | resourceIDs := []string{} 361 | // only support 50 tags at most 362 | for i := 1; i < 51; i++ { 363 | tagkey := "ResourceId." + strconv.Itoa(i) 364 | if val, ok := params[tagkey]; ok { 365 | resourceIDs = append(resourceIDs, val) 366 | } else { 367 | continue 368 | } 369 | } 370 | if len(resourceIDs) > 0 { 371 | request.ResourceId = &resourceIDs 372 | } 373 | 374 | if nextToken, ok := params["NextToken"]; ok { 375 | request.NextToken = nextToken 376 | } 377 | 378 | response, err := client.ListTagResources(request) 379 | if err != nil { 380 | log.DefaultLogger.Error(err.Error()) 381 | handleResponse(rw, nil, err) 382 | } else { 383 | data, err := json.Marshal(response) 384 | log.DefaultLogger.Debug(string(data)) 385 | handleResponse(rw, data, err) 386 | } 387 | } 388 | 389 | func proxyEcsDescribeTags(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 390 | client, err := ecs.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 391 | 392 | request := ecs.CreateDescribeTagsRequest() 393 | request.Scheme = "https" 394 | if pageSize, ok := params["PageSize"]; ok { 395 | size, err := strconv.ParseInt(pageSize, 10, 32) 396 | if err != nil { 397 | request.PageSize = requests.NewInteger(int(size)) 398 | } else { 399 | request.PageSize = requests.NewInteger(100) 400 | } 401 | } else { 402 | request.PageSize = requests.NewInteger(100) 403 | } 404 | 405 | if pageNumber, ok := params["PageNumber"]; ok { 406 | num, err := strconv.ParseInt(pageNumber, 10, 32) 407 | if err != nil { 408 | request.PageNumber = requests.NewInteger(int(num)) 409 | } else { 410 | request.PageNumber = requests.NewInteger(1) 411 | } 412 | } else { 413 | request.PageNumber = requests.NewInteger(1) 414 | } 415 | 416 | if resourceType, ok := params["ResourceType"]; ok { 417 | request.ResourceType = resourceType 418 | } 419 | 420 | if resourceID, ok := params["ResourceId"]; ok { 421 | request.ResourceId = resourceID 422 | } 423 | 424 | if category, ok := params["Category"]; ok { 425 | request.Category = category 426 | } 427 | 428 | tags := []ecs.DescribeTagsTag{} 429 | // only support 5 tags at most 430 | for i := 1; i < 6; i++ { 431 | tagkey := "Tag." + strconv.Itoa(i) + ".Key" 432 | tagValue := "Tag." + strconv.Itoa(i) + ".Value" 433 | tagObj := ecs.DescribeTagsTag{} 434 | if val, ok := params[tagkey]; ok { 435 | tagObj.Key = val 436 | } else { 437 | continue 438 | } 439 | if val, ok := params[tagValue]; ok { 440 | tagObj.Value = val 441 | } 442 | tags = append(tags, tagObj) 443 | } 444 | log.DefaultLogger.Debug("tags_length:" + strconv.Itoa(len(tags))) 445 | if len(tags) > 0 { 446 | log.DefaultLogger.Debug("tags_key:" + tags[0].Key) 447 | request.Tag = &tags 448 | } 449 | 450 | response, err := client.DescribeTags(request) 451 | if err != nil { 452 | log.DefaultLogger.Error(err.Error()) 453 | handleResponse(rw, nil, err) 454 | } else { 455 | data, err := json.Marshal(response) 456 | log.DefaultLogger.Debug(string(data)) 457 | handleResponse(rw, data, err) 458 | } 459 | } 460 | 461 | func proxyEcsListTagResources(instSetting *instanceSettings, params map[string]string, rw http.ResponseWriter) { 462 | client, err := ecs.NewClientWithAccessKey("cn-hangzhou", instSetting.accessKey, instSetting.accessSecret) 463 | 464 | request := ecs.CreateListTagResourcesRequest() 465 | request.Scheme = "https" 466 | 467 | if regionID, ok := params["RegionId"]; ok { 468 | request.RegionId = regionID 469 | } 470 | 471 | if regionID, ok := params["RegionId"]; ok { 472 | request.RegionId = regionID 473 | } else { 474 | request.RegionId = "cn-hangzhou" 475 | } 476 | 477 | if resourceType, ok := params["ResourceType"]; ok { 478 | request.ResourceType = resourceType 479 | } else { 480 | request.ResourceType = "instance" 481 | } 482 | 483 | if nextToken, ok := params["NextToken"]; ok { 484 | request.NextToken = nextToken 485 | } 486 | 487 | tags := []ecs.ListTagResourcesTag{} 488 | // only support 5 tags at most 489 | for i := 1; i < 6; i++ { 490 | tagkey := "Tag." + strconv.Itoa(i) + ".Key" 491 | tagValue := "Tag." + strconv.Itoa(i) + ".Value" 492 | tagObj := ecs.ListTagResourcesTag{} 493 | if val, ok := params[tagkey]; ok { 494 | tagObj.Key = val 495 | } else { 496 | continue 497 | } 498 | if val, ok := params[tagValue]; ok { 499 | tagObj.Value = val 500 | } 501 | tags = append(tags, tagObj) 502 | } 503 | if len(tags) > 0 { 504 | request.Tag = &tags 505 | } 506 | 507 | tagFilters := []ecs.ListTagResourcesTagFilter{} 508 | // only support 5 tags at most 509 | for i := 1; i < 6; i++ { 510 | tagObj := ecs.ListTagResourcesTagFilter{} 511 | tagkey := "TagFilter." + strconv.Itoa(i) + ".TagKey" 512 | if val, ok := params[tagkey]; ok { 513 | tagObj.TagKey = val 514 | } else { 515 | continue 516 | } 517 | tagFilterValues := []string{} 518 | for ii := 1; ii < 6; ii++ { 519 | tagValue := "TagFilter." + strconv.Itoa(i) + ".TagValues." + strconv.Itoa(ii) 520 | if val, ok := params[tagValue]; ok { 521 | tagFilterValues = append(tagFilterValues, val) 522 | } 523 | } 524 | if len(tagFilterValues) > 0 { 525 | tagObj.TagValues = &tagFilterValues 526 | } 527 | tagFilters = append(tagFilters, tagObj) 528 | } 529 | if len(tagFilters) > 0 { 530 | request.TagFilter = &tagFilters 531 | } 532 | 533 | response, err := client.ListTagResources(request) 534 | if err != nil { 535 | log.DefaultLogger.Error(err.Error()) 536 | handleResponse(rw, nil, err) 537 | } else { 538 | data, err := json.Marshal(response) 539 | log.DefaultLogger.Debug(string(data)) 540 | handleResponse(rw, data, err) 541 | } 542 | } 543 | 544 | func handleResponse(rw http.ResponseWriter, data []byte, err error) { 545 | if err != nil { 546 | rw.Write([]byte(err.Error())) 547 | rw.WriteHeader(http.StatusInternalServerError) 548 | } else { 549 | rw.Header().Add("Content-Type", "application/json") 550 | rw.WriteHeader(http.StatusOK) 551 | rw.Write(data) 552 | } 553 | } 554 | 555 | func (ds *CmsDatasource) ProxyCmsPopApi(rw http.ResponseWriter, req *http.Request) { 556 | //parse param map 557 | params, err := parseRequestParams(req) 558 | if err != nil { 559 | handleResponse(rw, nil, err) 560 | return 561 | } 562 | // 获取AK 配置 563 | instSetting, err := ds.getDataSourceSetting(req) 564 | if err != nil { 565 | handleResponse(rw, nil, err) 566 | return 567 | } 568 | // redirect by action 569 | switch params["Action"] { 570 | case "QueryMetricLast": 571 | proxyQueryMetricLast(instSetting, params, rw) 572 | break 573 | case "QueryMetricList": 574 | proxyQueryMetricData(instSetting, params, rw) 575 | break 576 | case "QueryMetricMeta": 577 | proxyListMetricMeta(instSetting, params, rw) 578 | break 579 | case "QueryProjectMeta": 580 | proxyListProjectMeta(instSetting, params, rw) 581 | break 582 | case "ListMyGroups": 583 | proxyListMyGroups(instSetting, params, rw) 584 | break 585 | //disable for security reason 586 | // case "AccessKeyGet": 587 | // proxyAccessKeyGet(instSetting, params, rw) 588 | // break 589 | } 590 | } 591 | 592 | func (ds *CmsDatasource) ProxyEcsPopApi(rw http.ResponseWriter, req *http.Request) { 593 | //parse param map 594 | params, err := parseRequestParams(req) 595 | if err != nil { 596 | handleResponse(rw, nil, err) 597 | return 598 | } 599 | // 获取AK 配置 600 | instSetting, err := ds.getDataSourceSetting(req) 601 | if err != nil { 602 | handleResponse(rw, nil, err) 603 | return 604 | } 605 | // redirect by action 606 | switch params["Action"] { 607 | case "DescribeTags": 608 | proxyEcsDescribeTags(instSetting, params, rw) 609 | break 610 | case "ListTagResources": 611 | proxyEcsListTagResources(instSetting, params, rw) 612 | break 613 | } 614 | } 615 | 616 | func (ds *CmsDatasource) ProxyRdsPopApi(rw http.ResponseWriter, req *http.Request) { 617 | //parse param map 618 | params, err := parseRequestParams(req) 619 | if err != nil { 620 | handleResponse(rw, nil, err) 621 | return 622 | } 623 | // 获取AK 配置 624 | instSetting, err := ds.getDataSourceSetting(req) 625 | if err != nil { 626 | handleResponse(rw, nil, err) 627 | return 628 | } 629 | // redirect by action 630 | switch params["Action"] { 631 | case "DescribeTags": 632 | proxyRdsDescribeTags(instSetting, params, rw) 633 | break 634 | case "ListTagResources": 635 | proxyRdsListTagResources(instSetting, params, rw) 636 | break 637 | } 638 | } 639 | 640 | // 保存datasource时检查datasource是否工作正常 641 | func (td *CmsDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { 642 | var status = backend.HealthStatusOk 643 | var message = "Data source is working" 644 | 645 | return &backend.CheckHealthResult{ 646 | Status: status, 647 | Message: message, 648 | }, nil 649 | } 650 | 651 | type instanceSettings struct { 652 | endpoint string 653 | name string 654 | accessKey string 655 | accessSecret string 656 | } 657 | 658 | func newDataSourceInstance(setting backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { 659 | type editModel struct { 660 | Host string `json:"host"` 661 | } 662 | 663 | log.DefaultLogger.Debug("newDataSourceInstance ak: ", setting.DecryptedSecureJSONData["cmsAccessKey"]+"/"+setting.DecryptedSecureJSONData["cmsSecretKey"]) 664 | 665 | cmsConfigObj := &CmsSetting{} 666 | 667 | if accessKey, hasIt := setting.DecryptedSecureJSONData["cmsAccessKey"]; hasIt { 668 | cmsConfigObj.CmsAccessKey = accessKey 669 | cmsConfigObj.CmsAcessSecret = setting.DecryptedSecureJSONData["cmsSecretKey"] 670 | } else { 671 | json.Unmarshal(setting.JSONData, &cmsConfigObj) 672 | } 673 | 674 | log.DefaultLogger.Debug("newDataSourceInstance" + "/" + cmsConfigObj.CmsAccessKey + "/" + cmsConfigObj.CmsAcessSecret) 675 | 676 | return &instanceSettings{ 677 | // cluster: newCluster, 678 | // authenticator: authenticator, 679 | endpoint: setting.URL, 680 | accessKey: cmsConfigObj.CmsAccessKey, 681 | accessSecret: cmsConfigObj.CmsAcessSecret, 682 | }, nil 683 | } 684 | -------------------------------------------------------------------------------- /pkg/plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | 7 | "github.com/grafana/grafana-plugin-sdk-go/backend" 8 | "github.com/grafana/grafana-plugin-sdk-go/backend/log" 9 | "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" 10 | ) 11 | 12 | func main() { 13 | 14 | logger := log.New() 15 | 16 | mux := http.NewServeMux() 17 | 18 | ds := newDatasource() 19 | 20 | mux.HandleFunc("/proxy_aliyun_cms_pop", ds.ProxyCmsPopApi) 21 | mux.HandleFunc("/proxy_aliyun_ecs_pop", ds.ProxyEcsPopApi) 22 | mux.HandleFunc("/proxy_aliyun_rds_pop", ds.ProxyRdsPopApi) 23 | 24 | httpResourceHandler := httpadapter.New(mux) 25 | 26 | logger.Debug("Aliyun_CloudMonitor_Datasource") 27 | 28 | err := backend.Serve(backend.ServeOpts{ 29 | CallResourceHandler: httpResourceHandler, 30 | // QueryDataHandler: ds, 31 | CheckHealthHandler: ds, 32 | }) 33 | 34 | if err != nil { 35 | logger.Error(err.Error()) 36 | os.Exit(1) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spec/datasource_spec.js: -------------------------------------------------------------------------------- 1 | import {Datasource} from "../module"; 2 | import Q from "q"; 3 | 4 | describe('GenericDatasource', function() { 5 | var ctx = {}; 6 | 7 | beforeEach(function() { 8 | ctx.$q = Q; 9 | ctx.backendSrv = {}; 10 | ctx.templateSrv = {}; 11 | ctx.ds = new Datasource({}, ctx.$q, ctx.backendSrv, ctx.templateSrv); 12 | }); 13 | 14 | it('should return an empty array when no targets are set', function(done) { 15 | ctx.ds.query({targets: []}).then(function(result) { 16 | expect(result.data).to.have.length(0); 17 | done(); 18 | }); 19 | }); 20 | 21 | it('should return the server results when a target is set', function(done) { 22 | ctx.backendSrv.datasourceRequest = function(request) { 23 | return ctx.$q.when({ 24 | _request: request, 25 | data: [ 26 | { 27 | target: 'X', 28 | datapoints: [1, 2, 3] 29 | } 30 | ] 31 | }); 32 | }; 33 | 34 | ctx.templateSrv.replace = function(data) { 35 | return data; 36 | } 37 | 38 | ctx.ds.query({targets: ['hits']}).then(function(result) { 39 | expect(result._request.data.targets).to.have.length(1); 40 | 41 | var series = result.data[0]; 42 | expect(series.target).to.equal('X'); 43 | expect(series.datapoints).to.have.length(3); 44 | done(); 45 | }); 46 | }); 47 | 48 | it ('should return the metric results when a target is null', function(done) { 49 | ctx.backendSrv.datasourceRequest = function(request) { 50 | return ctx.$q.when({ 51 | _request: request, 52 | data: [ 53 | "metric_0", 54 | "metric_1", 55 | "metric_2", 56 | ] 57 | }); 58 | }; 59 | 60 | ctx.templateSrv.replace = function(data) { 61 | return data; 62 | } 63 | 64 | ctx.ds.metricFindQuery({target: null}).then(function(result) { 65 | expect(result).to.have.length(3); 66 | expect(result[0].text).to.equal('metric_0'); 67 | expect(result[0].value).to.equal('metric_0'); 68 | expect(result[1].text).to.equal('metric_1'); 69 | expect(result[1].value).to.equal('metric_1'); 70 | expect(result[2].text).to.equal('metric_2'); 71 | expect(result[2].value).to.equal('metric_2'); 72 | done(); 73 | }); 74 | }); 75 | 76 | it ('should return the metric target results when a target is set', function(done) { 77 | ctx.backendSrv.datasourceRequest = function(request) { 78 | var target = request.data.target; 79 | var result = [target + "_0", target + "_1", target + "_2"]; 80 | 81 | return ctx.$q.when({ 82 | _request: request, 83 | data: result 84 | }); 85 | }; 86 | 87 | ctx.templateSrv.replace = function(data) { 88 | return data; 89 | } 90 | 91 | ctx.ds.metricFindQuery('search').then(function(result) { 92 | expect(result).to.have.length(3); 93 | expect(result[0].text).to.equal('search_0'); 94 | expect(result[0].value).to.equal('search_0'); 95 | expect(result[1].text).to.equal('search_1'); 96 | expect(result[1].value).to.equal('search_1'); 97 | expect(result[2].text).to.equal('search_2'); 98 | expect(result[2].value).to.equal('search_2'); 99 | done(); 100 | }); 101 | }); 102 | 103 | it ('should return the metric results when the target is an empty string', function(done) { 104 | ctx.backendSrv.datasourceRequest = function(request) { 105 | return ctx.$q.when({ 106 | _request: request, 107 | data: [ 108 | "metric_0", 109 | "metric_1", 110 | "metric_2", 111 | ] 112 | }); 113 | }; 114 | 115 | ctx.templateSrv.replace = function(data) { 116 | return data; 117 | } 118 | 119 | ctx.ds.metricFindQuery('').then(function(result) { 120 | expect(result).to.have.length(3); 121 | expect(result[0].text).to.equal('metric_0'); 122 | expect(result[0].value).to.equal('metric_0'); 123 | expect(result[1].text).to.equal('metric_1'); 124 | expect(result[1].value).to.equal('metric_1'); 125 | expect(result[2].text).to.equal('metric_2'); 126 | expect(result[2].value).to.equal('metric_2'); 127 | done(); 128 | }); 129 | }); 130 | 131 | it ('should return the metric results when the args are an empty object', function(done) { 132 | ctx.backendSrv.datasourceRequest = function(request) { 133 | return ctx.$q.when({ 134 | _request: request, 135 | data: [ 136 | "metric_0", 137 | "metric_1", 138 | "metric_2", 139 | ] 140 | }); 141 | }; 142 | 143 | ctx.templateSrv.replace = function(data) { 144 | return data; 145 | } 146 | 147 | ctx.ds.metricFindQuery().then(function(result) { 148 | expect(result).to.have.length(3); 149 | expect(result[0].text).to.equal('metric_0'); 150 | expect(result[0].value).to.equal('metric_0'); 151 | expect(result[1].text).to.equal('metric_1'); 152 | expect(result[1].value).to.equal('metric_1'); 153 | expect(result[2].text).to.equal('metric_2'); 154 | expect(result[2].value).to.equal('metric_2'); 155 | done(); 156 | }); 157 | }); 158 | 159 | it ('should return the metric target results when the args are a string', function(done) { 160 | ctx.backendSrv.datasourceRequest = function(request) { 161 | var target = request.data.target; 162 | var result = [target + "_0", target + "_1", target + "_2"]; 163 | 164 | return ctx.$q.when({ 165 | _request: request, 166 | data: result 167 | }); 168 | }; 169 | 170 | ctx.templateSrv.replace = function(data) { 171 | return data; 172 | } 173 | 174 | ctx.ds.metricFindQuery('search').then(function(result) { 175 | expect(result).to.have.length(3); 176 | expect(result[0].text).to.equal('search_0'); 177 | expect(result[0].value).to.equal('search_0'); 178 | expect(result[1].text).to.equal('search_1'); 179 | expect(result[1].value).to.equal('search_1'); 180 | expect(result[2].text).to.equal('search_2'); 181 | expect(result[2].value).to.equal('search_2'); 182 | done(); 183 | }); 184 | }); 185 | 186 | it ('should return data as text and as value', function(done) { 187 | var result = ctx.ds.mapToTextValue({data: ["zero", "one", "two"]}); 188 | 189 | expect(result).to.have.length(3); 190 | expect(result[0].text).to.equal('zero'); 191 | expect(result[0].value).to.equal('zero'); 192 | expect(result[1].text).to.equal('one'); 193 | expect(result[1].value).to.equal('one'); 194 | expect(result[2].text).to.equal('two'); 195 | expect(result[2].value).to.equal('two'); 196 | done(); 197 | }); 198 | 199 | it ('should return text as text and value as value', function(done) { 200 | var data = [ 201 | {text: "zero", value: "value_0"}, 202 | {text: "one", value: "value_1"}, 203 | {text: "two", value: "value_2"}, 204 | ]; 205 | 206 | var result = ctx.ds.mapToTextValue({data: data}); 207 | 208 | expect(result).to.have.length(3); 209 | expect(result[0].text).to.equal('zero'); 210 | expect(result[0].value).to.equal('value_0'); 211 | expect(result[1].text).to.equal('one'); 212 | expect(result[1].value).to.equal('value_1'); 213 | expect(result[2].text).to.equal('two'); 214 | expect(result[2].value).to.equal('value_2'); 215 | done(); 216 | }); 217 | 218 | it ('should return data as text and index as value', function(done) { 219 | var data = [ 220 | {a: "zero", b: "value_0"}, 221 | {a: "one", b: "value_1"}, 222 | {a: "two", b: "value_2"}, 223 | ]; 224 | 225 | var result = ctx.ds.mapToTextValue({data: data}); 226 | 227 | expect(result).to.have.length(3); 228 | expect(result[0].text).to.equal(data[0]); 229 | expect(result[0].value).to.equal(0); 230 | expect(result[1].text).to.equal(data[1]); 231 | expect(result[1].value).to.equal(1); 232 | expect(result[2].text).to.equal(data[2]); 233 | expect(result[2].value).to.equal(2); 234 | done(); 235 | }); 236 | 237 | it('should support tag keys', function(done) { 238 | var data = [{'type': 'string', 'text': 'One', 'key': 'one'}, {'type': 'string', 'text': 'two', 'key': 'Two'}]; 239 | 240 | ctx.backendSrv.datasourceRequest = function(request) { 241 | return ctx.$q.when({ 242 | _request: request, 243 | data: data 244 | }); 245 | }; 246 | 247 | ctx.ds.getTagKeys().then(function(result) { 248 | expect(result).to.have.length(2); 249 | expect(result[0].type).to.equal(data[0].type); 250 | expect(result[0].text).to.equal(data[0].text); 251 | expect(result[0].key).to.equal(data[0].key); 252 | expect(result[1].type).to.equal(data[1].type); 253 | expect(result[1].text).to.equal(data[1].text); 254 | expect(result[1].key).to.equal(data[1].key); 255 | done(); 256 | }); 257 | }); 258 | 259 | it('should support tag values', function(done) { 260 | var data = [{'key': 'eins', 'text': 'Eins!'}, {'key': 'zwei', 'text': 'Zwei'}, {'key': 'drei', 'text': 'Drei!'}]; 261 | 262 | ctx.backendSrv.datasourceRequest = function(request) { 263 | return ctx.$q.when({ 264 | _request: request, 265 | data: data 266 | }); 267 | }; 268 | 269 | ctx.ds.getTagValues().then(function(result) { 270 | expect(result).to.have.length(3); 271 | expect(result[0].text).to.equal(data[0].text); 272 | expect(result[0].key).to.equal(data[0].key); 273 | expect(result[1].text).to.equal(data[1].text); 274 | expect(result[1].key).to.equal(data[1].key); 275 | expect(result[2].text).to.equal(data[2].text); 276 | expect(result[2].key).to.equal(data[2].key); 277 | done(); 278 | }); 279 | }); 280 | 281 | }); 282 | -------------------------------------------------------------------------------- /spec/test-main.js: -------------------------------------------------------------------------------- 1 | import prunk from 'prunk'; 2 | import {jsdom} from 'jsdom'; 3 | import chai from 'chai'; 4 | 5 | // Mock Grafana modules that are not available outside of the core project 6 | // Required for loading module.js 7 | prunk.mock('./css/query-editor.css!', 'no css, dude.'); 8 | prunk.mock('app/plugins/sdk', { 9 | QueryCtrl: null 10 | }); 11 | 12 | // Setup jsdom 13 | // Required for loading angularjs 14 | global.document = jsdom('
'); 15 | global.window = global.document.parentWindow; 16 | 17 | // Setup Chai 18 | chai.should(); 19 | global.assert = chai.assert; 20 | global.expect = chai.expect; 21 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliyun/aliyun-cms-grafana/521031f16d43bd1fb64b5ed87c1edf297a774262/src/.DS_Store -------------------------------------------------------------------------------- /src/css/query-editor.css: -------------------------------------------------------------------------------- 1 | .generic-datasource-query-row .query-keyword { 2 | width: 75px; 3 | } -------------------------------------------------------------------------------- /src/datasource.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { Util } from "./util.js"; 3 | import { CmsSigner } from "./signer.js"; 4 | 5 | export class GenericDatasource { 6 | constructor(instanceSettings, $q, backendSrv, templateSrv) { 7 | this.type = instanceSettings.type; 8 | this.datasourceId = instanceSettings.id; 9 | this.aliyunUserId = instanceSettings.jsonData.userId; 10 | this.basePath = instanceSettings.url; 11 | this.name = instanceSettings.name; 12 | this.jsonData = instanceSettings.jsonData; 13 | this.q = $q; 14 | this.backendSrv = backendSrv; 15 | this.templateSrv = templateSrv; 16 | this.util = new Util(templateSrv); 17 | this.headers = { "Content-Type": "application/json" }; 18 | this.cmsVersion = "2018-03-08"; 19 | this.ecsVersion = "2014-05-26"; 20 | this.ecsBasePath = "https://ecs.aliyuncs.com"; 21 | this.rdsVersion = "2014-08-15"; 22 | this.rdsBasePath = "https://rds.aliyuncs.com"; 23 | this.cacheMeta = new Map(); 24 | } 25 | 26 | query(options) { 27 | var requests = []; 28 | var result = []; 29 | options.targets.forEach((target) => { 30 | //非空参数判空处理 31 | if (!target.project || !target.metric || !target.ycol || !target.xcol) { 32 | return; 33 | } 34 | //默认数据 35 | var ycol = target.ycol; 36 | var xcol = target.xcol; 37 | var describe = !target.describe ? target.describe : target.describe + "."; 38 | //处理模版 39 | var project = this.util.exists(target.project) ? this.util.resolve(target.project, {}) : target.project; 40 | var metric = this.util.exists(target.metric) ? this.util.resolve(target.metric, {}) : target.metric; 41 | var period = this.util.exists(target.period) ? this.util.resolve(target.period, {}) : target.period; 42 | var group = this.util.exists(target.group) ? this.util.resolve(target.group, {}) : target.group; 43 | //处理数组模版 44 | var dimensions = ""; 45 | 46 | var dimensions_variables = []; 47 | 48 | target.dimensions.forEach((dimension) => { 49 | this.util.exists(dimension) ? dimensions_variables.push(this.util.resolve(dimension, {})) : dimensions_variables.push(dimension); 50 | }); 51 | var ycol_variables = []; 52 | ycol.forEach((y_col) => { 53 | y_col.indexOf("$") != -1 ? ycol_variables.push(this.util.resolve(y_col, {})) : ycol_variables.push(y_col); 54 | }); 55 | ycol = ycol_variables.length > 0 ? ycol_variables : ycol; 56 | 57 | //自定义监控(acs_custom)、日志监控(acs_logMonitor)处理,只取下标为0的数据 58 | if (project.indexOf("acs_custom") != -1 || project.indexOf("acs_logMonitor") != -1) { 59 | var dimensionAcsJson = target.dimensions[0]; 60 | var dimensionAcsObj = { 61 | groupId: group.toString(), 62 | dimension: dimensionAcsJson.replace(/\&/gi, "%26").replace(/\{/gi, "%7B").replace(/\}/gi, "%7D"), 63 | }; 64 | dimensions = JSON.stringify(dimensionAcsObj); 65 | } else { 66 | //正常数据 67 | dimensions = ""; 68 | dimensions_variables.forEach((dimension, i) => { 69 | if (typeof dimension == "string") { 70 | dimension = dimension.includes("{") ? dimension : "{" + dimension; 71 | dimension = dimension.includes("}") ? dimension : dimension + "}"; 72 | // dimension = dimension.includes("\\") ? dimension.replace("\\/gi", "\\\\") : dimension; 73 | dimensions += dimension + ","; 74 | } else { 75 | dimension.forEach((dimension_i) => { 76 | dimension_i = dimension_i.includes("{") ? dimension_i : "{" + dimension_i; 77 | dimension_i = dimension_i.includes("}") ? dimension_i : dimension_i + "}"; 78 | // dimension = dimension.includes("\\") ? dimension.replace("\\/gi", "\\\\") : dimension; 79 | 80 | dimensions += dimension_i + ","; 81 | }); 82 | } 83 | }); 84 | dimensions = dimensions.substring(0, dimensions.length - 1); 85 | dimensions = "[" + dimensions + "]"; 86 | dimensions = dimensions.replace(/\&/gi, "%26").replace(/\{/gi, "%7B").replace(/\}/gi, "%7D"); 87 | } 88 | var target_keys = []; 89 | var resource = ""; 90 | if(this.isEmpty(project) || this.isEmpty(metric) || this.isEmpty(xcol) || ycol.length == 0){ 91 | return; 92 | } 93 | this.queryMetricMeta(project, metric).then(response => { 94 | resource = response; 95 | if (resource.length == 0 || this.isEmpty(resource[0].Dimensions)) { 96 | return; 97 | } 98 | var target_key_arr = resource[0].Dimensions.split(","); 99 | target_key_arr.forEach(value =>{ 100 | if(target_key_arr.length == 1){ 101 | target_keys.push(value); 102 | }else{ 103 | if(value != "userId"){ 104 | target_keys.push(value); 105 | } 106 | } 107 | }) 108 | }); 109 | 110 | //拼接url参数 111 | var queryConcat = "/?Action=QueryMetricList&Length=1000&Project=" + project + "&Metric=" + metric + "&Period=" + period 112 | + "&Dimensions=" + dimensions + "&StartTime=" + parseInt(options.range.from._d.getTime()) + "&EndTime=" + parseInt(options.range.to._d.getTime()); 113 | 114 | //定义Promise元数据、根据URL发起请求 115 | var request = this.doNextToken(queryConcat, "", 0, "list").then((response) => { 116 | var resResult = []; 117 | this.dataGroupByKeys(response, target_keys).then(dataMap => { 118 | dataMap.forEach((value, key) => { 119 | 120 | ycol.map((ycolTarget) => { 121 | var dataPoints = []; 122 | 123 | value.forEach(valueObj =>{ 124 | var dataPoint = []; 125 | dataPoint.push(valueObj[ycolTarget], valueObj[xcol]); 126 | // 封装返回目标的第二层数组值 127 | dataPoints.push(dataPoint); 128 | // 封装返回目标的第三层数组值 129 | }) 130 | 131 | resResult.push({ 132 | target: describe + key + "." + ycolTarget, 133 | datapoints: dataPoints, 134 | }); 135 | }); 136 | }); 137 | 138 | result = result.concat(typeof resResult == "string" ? JSON.parse(resResult) : resResult); 139 | }); 140 | }); 141 | requests.push(request); 142 | }); 143 | 144 | // 统一单独处理返回值 145 | return Promise.all(requests.map((p) => p.catch((e) => e))).then(() => { 146 | return { data: result }; 147 | }); 148 | } 149 | 150 | async dataGroupByKeys(list, keys) { 151 | let tmpMap = new Map(); 152 | for(let i = 0; i < list.length; i++){ 153 | let dataPoint = list[i]; 154 | 155 | let key_obj = {}; 156 | let key_target = dataPoint[keys[0]]; 157 | for(let j = 0; j < keys.length; j++){ 158 | let key = keys[j]; 159 | let value = dataPoint[key]; 160 | key_obj[key] = value; 161 | 162 | if(j > 0){ 163 | key_target += "_" + value; 164 | } 165 | } 166 | let value_arr = []; 167 | if(tmpMap.has(key_target)){ 168 | value_arr = tmpMap.get(key_target); 169 | } 170 | 171 | value_arr.push(dataPoint); 172 | tmpMap.set(key_target, value_arr); 173 | } 174 | return tmpMap; 175 | } 176 | 177 | wait(ms) { 178 | return new Promise(resolve => setTimeout(resolve, ms)) 179 | } 180 | 181 | async doNextToken(queryConcat, cursor, count, type) { 182 | var path = ""; 183 | if (this.isEmpty(cursor)) { 184 | path = queryConcat; 185 | } else { 186 | path = queryConcat + "&Cursor=" + cursor; 187 | } 188 | var param = { 189 | path: path, 190 | method: "GET", 191 | }; 192 | // 签名已拼接的待查询URL 193 | var query = this.buildRealUrl(param); 194 | if (_.isEmpty(query)) { 195 | var d = this.q.defer(); 196 | d.resolve({ data: [] }); 197 | return d.promise; 198 | } 199 | if("list" == type){ 200 | await this.wait(1000); 201 | }else if("last" == type){ 202 | await this.wait(100); 203 | } 204 | //定义Promise元数据、根据URL发起请求 205 | return this.backendSrv.datasourceRequest({ 206 | url: query, 207 | method: "GET", 208 | headers: this.headers, 209 | }).then((response) => { 210 | var result = []; 211 | if (response.status == "200" && response.data.Code == "200") { 212 | result = angular.fromJson(response.data.Datapoints); 213 | if(count > 20){ 214 | return result; 215 | } 216 | count++; 217 | var nextToken = response.data.Cursor; 218 | if (this.isEmpty(response.data.Cursor)) { 219 | return result; 220 | } else { 221 | return this.doNextToken(queryConcat, nextToken, count, type).then((data) => { 222 | return result.concat(data); 223 | }); 224 | } 225 | } 226 | return result; 227 | }).catch(() => { 228 | return []; 229 | }); 230 | } 231 | 232 | // 测试连接数据源接口 233 | testDatasource() { 234 | var param = { 235 | path: "?Action=QueryMetricMeta&PageNumber=1&PageSize=1&Project=acs_ecs_dashboard", 236 | method: "GET", 237 | }; 238 | return this.backendSrv.datasourceRequest({ 239 | url: this.buildRealUrl(param), 240 | method: "GET", 241 | }).then((response) => { 242 | var data = response.data; 243 | if (data.Code == "200" && data.Success == true) { 244 | return { 245 | status: "success", 246 | message: "Data source is working", 247 | title: "Success", 248 | }; 249 | }else{ 250 | return { 251 | status: "failure", 252 | message: "Data source is not working", 253 | title: "Failure", 254 | }; 255 | } 256 | }); 257 | } 258 | 259 | annotationQuery(options) {} 260 | 261 | metricFindQuery(options) { 262 | var result = []; 263 | //接受一个参数 264 | var namespacesQuery = options.match(/^namespaces\(([^\)]+?)(,\s?([^,]+?))?\)/); 265 | namespacesQuery = 266 | namespacesQuery == null ? options.match(/^namespace\(([^\)]+?)(,\s?([^,]+?))?\)/) : namespacesQuery; 267 | if (namespacesQuery != null) { 268 | var filter = this.util.templateToStr(namespacesQuery[1]); 269 | return this.getProject().then((namespaces) => { 270 | result = namespaces; 271 | if (!this.isEmpty(filter)) { 272 | result = []; 273 | namespaces.map((namespace) => { 274 | if (namespace.text.includes(filter)) { 275 | result.push(namespace); 276 | } 277 | }); 278 | } 279 | return result; 280 | }); 281 | } 282 | 283 | //接受二个参数 284 | var metricsQuery = options.match(/^metrics\(([^,]+?),\s?([^,]+?)\)/); 285 | metricsQuery = 286 | metricsQuery == null ? options.match(/^metric\(([^,]+?),\s?([^,]+?)\)/) : metricsQuery; 287 | if (metricsQuery != null) { 288 | var namespace = this.util.templateToStr(metricsQuery[1]); 289 | var filter = this.util.templateToStr(metricsQuery[2]); 290 | 291 | result = []; 292 | return this.getMetrics(namespace).then((metrics) => { 293 | result = metrics; 294 | if (!this.isEmpty(filter)) { 295 | result = []; 296 | metrics.map((metric) => { 297 | if (metric.text.includes(filter)) { 298 | result.push(metric); 299 | } 300 | }); 301 | } 302 | return result; 303 | }); 304 | } 305 | 306 | //接受四个参数,过滤Tag提供key、value选择 307 | var tagFilterQuery = options.match(/^tagFilter\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?(.+))?\)/); 308 | tagFilterQuery = 309 | tagFilterQuery == null ? options.match(/^tagsFilter\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?(.+))?\)/) : tagFilterQuery; 310 | if (tagFilterQuery != null) { 311 | var type = this.util.templateToStr(tagFilterQuery[1]); 312 | var regionId = this.util.templateToStr(tagFilterQuery[2]); 313 | var tagType = this.isEmpty(tagFilterQuery[3]) ? "" : tagFilterQuery[3]; 314 | var tagKey = this.isEmpty(tagFilterQuery[4]) ? "" : tagFilterQuery[4]; 315 | var path = "/?Action=DescribeTags&PageNumber=1&PageSize=100&RegionId=" + regionId; 316 | var tagKeyFilter = []; 317 | if(tagKey){ 318 | path = "/?Action=DescribeTags&PageNumber=1&PageSize=100&RegionId=" + regionId + "&Tag.1.Key=" + tagKey; 319 | if(tagKey.indexOf("&Tag.2.Key=") != -1){ 320 | var tagKeyArry = tagKey.split("&"); 321 | tagKeyArry.forEach(tagKeyInd => { 322 | tagKeyFilter.push(tagKeyInd.substring(tagKeyInd.indexOf("Key=") == -1 ? 0 : tagKeyInd.indexOf("Key=") + 4)); 323 | }) 324 | }else{ 325 | tagKeyFilter.push(tagKey); 326 | } 327 | 328 | if(tagKey.indexOf("Tag.1.Key=") != -1){ 329 | path = "/?Action=DescribeTags&PageNumber=1&PageSize=100&RegionId=" + regionId + "&" + tagKey; 330 | } 331 | if(tagKey.indexOf("PageNumber=") != -1){ 332 | path = "/?Action=DescribeTags&PageSize=100&RegionId=" + regionId + "&" + tagKey; 333 | } 334 | if(tagKey.indexOf("&") == 0){ 335 | path = "/?Action=DescribeTags&PageSize=100&RegionId=" + regionId + tagKey; 336 | } 337 | } 338 | var nextToken = ""; 339 | result = []; 340 | return this.tagsFilter(type.toUpperCase(), nextToken, path, tagType, tagKeyFilter).then((tagsList) => { 341 | return this.util.arrayToMap(tagsList); 342 | }); 343 | } 344 | 345 | //接受四个参数,暂不支持数组,提供dimensions选择 346 | var dimensionsQuery = options.match(/^dimension\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?(.+))?\)/); 347 | dimensionsQuery = 348 | dimensionsQuery == null ? options.match(/^dimensions\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?(.+))?\)/) : dimensionsQuery; 349 | if (dimensionsQuery != null) { 350 | var namespace = this.util.templateToStr(dimensionsQuery[1]); 351 | var metric = this.util.templateToStr(dimensionsQuery[2]); 352 | 353 | var instanceId = dimensionsQuery[3]; 354 | var instanceId_array = this.util.exists(instanceId) ? this.util.resolve(instanceId, {}) : []; 355 | if (instanceId_array.length == 0) { 356 | if (this.isEmpty(instanceId)) { 357 | instanceId_array = []; 358 | } else { 359 | instanceId_array = this.util.strToArray(instanceId); 360 | } 361 | } 362 | var filter = dimensionsQuery[4]; 363 | var filter_array = this.util.exists(filter) ? this.util.resolve(filter, {}) : []; 364 | if (filter_array.length == 0) { 365 | if (this.isEmpty(filter)) { 366 | filter_array = []; 367 | } else { 368 | filter_array = this.util.strToArray(filter); 369 | } 370 | } 371 | result = []; 372 | return this.getDimensions(namespace, metric, "", "", []).then( 373 | (dimensions) => { 374 | var is_instanceId_bool = this.isEmpty(instanceId); 375 | var is_filter_bool = this.isEmpty(filter); 376 | if (is_instanceId_bool) { 377 | result = dimensions; 378 | } else { 379 | var instanceId_result = []; 380 | dimensions.map((dimension) => { 381 | instanceId_array.forEach((i) => { 382 | if (dimension.text.includes(i)) { 383 | instanceId_result.push(dimension); 384 | } 385 | }); 386 | }); 387 | if (is_filter_bool) { 388 | result = instanceId_result; 389 | } else { 390 | instanceId_result.map((dimension) => { 391 | filter_array.forEach((i) => { 392 | if (dimension.text.includes(i)) { 393 | result.push(dimension); 394 | } 395 | }); 396 | }); 397 | } 398 | } 399 | return result; 400 | } 401 | ); 402 | } 403 | 404 | //接受5个参数,暂不支持数组,提供tag选择 405 | var tagQuery = options.match(/^tag\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?(.+))?\)/); 406 | tagQuery = 407 | tagQuery == null ? options.match(/^tags\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?(.+))?\)/) : tagQuery; 408 | if (tagQuery != null) { 409 | var resourceId = tagQuery[4]; 410 | var resourceId_array = this.util.exists(resourceId) ? this.util.resolve(resourceId, {}) : []; 411 | if (resourceId_array.length == 0) { 412 | if (this.isEmpty(resourceId)) { 413 | resourceId_array = []; 414 | } else { 415 | resourceId_array = []; 416 | resourceId_array = this.util.strToArray(resourceId); 417 | } 418 | } 419 | var tag = tagQuery[5]; 420 | var tag_array = this.util.exists(tag) ? this.util.resolve(tag, {}) : []; 421 | if (tag_array.length == 0) { 422 | if (this.isEmpty(tag)) { 423 | tag_array = []; 424 | } else { 425 | tag_array = []; 426 | tag_array = this.util.strToArray(tag); 427 | } 428 | } 429 | return this.listTagResources( 430 | tagQuery[1].toUpperCase(), 431 | tagQuery[2], 432 | tagQuery[3], 433 | resourceId_array, 434 | tag_array 435 | ); 436 | } 437 | return []; 438 | } 439 | 440 | //返回所有的Project,QueryProjectMeta的API无自定义监控project、日志监控project,已拼接 441 | getProject() { 442 | var param = { 443 | path: "?Action=QueryProjectMeta&PageNumber=1&PageSize=1000", 444 | method: "GET", 445 | }; 446 | return this.backendSrv 447 | .datasourceRequest({ 448 | url: this.buildRealUrl(param), 449 | method: "GET", 450 | }) 451 | .then((response) => { 452 | var result = []; 453 | var data = response.data; 454 | if (data.Code == "200" && data.Success == true) { 455 | data.Resources.Resource.map((resource) => { 456 | if (!this.isEmpty(resource.Namespace)) { 457 | result.push(resource.Namespace); 458 | } 459 | }); 460 | } 461 | //增加自定义监控、日志监控选项 462 | result.push("acs_logMonitor_" + this.aliyunUserId); 463 | result.push("acs_customMetric_" + this.aliyunUserId); 464 | return this.util.arrayToMap(result); 465 | }) 466 | .catch(function (error) { 467 | console.log(error); 468 | return; 469 | }); 470 | } 471 | 472 | //根据Project返回对应的所有的Metrics,无自定义监控、日志监控project对应的Metrics 473 | getMetrics(project) { 474 | var param = { 475 | path: 476 | "?Action=QueryMetricMeta&PageNumber=1&PageSize=1000&Project=" + 477 | project, 478 | method: "GET", 479 | }; 480 | return this.backendSrv 481 | .datasourceRequest({ 482 | url: this.buildRealUrl(param), 483 | method: "GET", 484 | }) 485 | .then((response) => { 486 | var data = response.data; 487 | if (data.Code == "200" && data.Success == true) { 488 | var result = []; 489 | data.Resources.Resource.map((resource) => { 490 | if (!this.isEmpty(resource.MetricName)) { 491 | result.push(resource.MetricName); 492 | } 493 | }); 494 | return this.util.arrayToMap(result); 495 | } 496 | }) 497 | .catch(function (error) { 498 | console.log(error); 499 | return; 500 | }); 501 | } 502 | 503 | //根据Project及Metrics返回对应的所有的Period,无自定义监控、日志监控project对应的Period 504 | getPeriod(project, metric) { 505 | var param = { 506 | path: "?Action=QueryMetricMeta&PageNumber=1&PageSize=1&Project=" + project + "&Metric=" + metric, 507 | method: "GET", 508 | }; 509 | return this.backendSrv.datasourceRequest({ 510 | url: this.buildRealUrl(param), 511 | method: "GET", 512 | }).then((response) => { 513 | var data = response.data; 514 | if (data.Code == "200" && data.Success == true) { 515 | var period = []; 516 | var resource = data.Resources.Resource; 517 | if (resource.length > 0 && !this.isEmpty(resource[0].Periods)) { 518 | period = resource[0].Periods.split(","); 519 | period.splice(0, 0, "auto"); 520 | } 521 | return this.util.arrayToMap(period); 522 | } 523 | }).catch(function (error) { 524 | console.log(error); 525 | return; 526 | }); 527 | } 528 | 529 | //根据Project及Metrics返回对应的所有的Statistics,未去除已选项,无自定义监控、日志监控project对应的Period 530 | getStatistics(project, metric) { 531 | var param = { 532 | path: "?Action=QueryMetricMeta&PageNumber=1&PageSize=1&Project=" + project + "&Metric=" + metric, 533 | method: "GET", 534 | }; 535 | return this.backendSrv.datasourceRequest({ 536 | url: this.buildRealUrl(param), 537 | method: "GET", 538 | }).then((response) => { 539 | var data = response.data; 540 | if (data.Code == "200" && data.Success == true) { 541 | var statistics = []; 542 | var resource = data.Resources.Resource; 543 | if (resource.length > 0 && !this.isEmpty(resource[0].Statistics)) { 544 | statistics = resource[0].Statistics.split(","); 545 | } 546 | return this.util.arrayToMap(statistics); 547 | } 548 | }).catch(function (error) { 549 | console.log(error); 550 | return; 551 | }); 552 | } 553 | 554 | //返回所有的Groups,自定义监控、日志使用 555 | getGroups() { 556 | var param = { 557 | path: "?Action=ListMyGroups&PageNumber=1&PageSize=9000", 558 | method: "GET", 559 | }; 560 | return this.backendSrv.datasourceRequest({ 561 | url: this.buildRealUrl(param), 562 | method: "GET", 563 | }).then((response) => { 564 | var data = response.data; 565 | if (data.Code == "200" && data.Success == true) { 566 | var result = []; 567 | var resource = data.Resources.Resource; 568 | var i = resource.length; 569 | while (i--) { 570 | var group = resource[i]; 571 | var groupInfo = []; 572 | var groupId = group.GroupId; 573 | var groupName = group.GroupName; 574 | if (this.isEmpty(groupId) || this.isEmpty(groupName)) { 575 | continue; 576 | } 577 | groupInfo.push(groupId, groupName + " / " + groupId); 578 | result.push(groupInfo); 579 | } 580 | return _.map(result, (d, i) => { 581 | return { text: d[1], value: d[0] }; 582 | }); 583 | } 584 | }) 585 | .catch(function (error) { 586 | console.log(error); 587 | return; 588 | }); 589 | } 590 | 591 | getDimensions(project, metric, period, dimensions, resource) { 592 | if (project.indexOf("acs_customMetric") != -1 || project.indexOf("acs_logMonitor") != -1) { 593 | return; 594 | } 595 | var endTime = new Date().getTime(); 596 | var startTime = endTime - 15 * 60 * 1000; 597 | // TODO 598 | var queryConcat = "?Action=QueryMetricLast&Period=" + period + "&Project=" + project + "&Metric=" + metric + "&StartTime=" + startTime + "&EndTime=" + endTime; 599 | return this.doNextToken(queryConcat, "", 0, "last").then((data) => { 600 | if (data.length == 0) { 601 | return[]; 602 | } 603 | // 构建可选参数dimensions 604 | if(!this.isEmpty(resource)){ 605 | return this.dimensionData(data, resource, dimensions); 606 | }else{ 607 | return this.queryMetricMeta(project, metric).then(response => { 608 | resource = response; 609 | if (this.isEmpty(resource) || this.isEmpty(resource[0].Dimensions)) { 610 | return[]; 611 | } 612 | return this.dimensionData(data, resource, dimensions); 613 | }); 614 | } 615 | }).catch(function (error) { 616 | console.log(error); 617 | return[]; 618 | }); 619 | } 620 | 621 | async dimensionData(data, resource, dimensions){ 622 | var result = []; 623 | var dimension = resource[0].Dimensions.split(","); 624 | 625 | // var datapoints = JSON.parse(data.Datapoints); 626 | data.map((datapoint) => { 627 | var datapointInfo = '{"'; 628 | dimension.forEach(function (value, index) { 629 | value = value.replace(/"/g, ""); 630 | if (value != "userId" || (value == "userId" && dimension.length == 1)) { 631 | if (datapoint[value].indexOf(":\\") != -1) { 632 | datapointInfo += value + '":"' + datapoint[value] + '\\"'; 633 | } else { 634 | datapointInfo += value + '":"' + datapoint[value] + '"'; 635 | } 636 | if (index == dimension.length - 1) { 637 | datapointInfo += "}"; 638 | } else { 639 | datapointInfo += ',"'; 640 | } 641 | } 642 | }); 643 | //去重 644 | if (result.length == 0) { 645 | if (dimensions.length == 0) { 646 | result.push(datapointInfo); 647 | } else if (dimensions.length > 0 && !dimensions.includes(datapointInfo)) { 648 | result.push(datapointInfo); 649 | } 650 | } else if (result.length > 0 && !result.includes(datapointInfo)) { 651 | if (dimensions.length == 0) { 652 | result.push(datapointInfo); 653 | } else if (dimensions.length > 0 && !dimensions.includes(datapointInfo)) { 654 | result.push(datapointInfo); 655 | } 656 | } 657 | }); 658 | return this.util.arrayToMap(result); 659 | } 660 | 661 | async queryMetricMeta(project, metric){ 662 | let rand = Math.floor(Math.random() * 4) + 5; 663 | await this.wait(100 * rand); 664 | 665 | let resource = this.cacheMeta.get(project + "_" + metric); 666 | if(this.isEmpty(resource)){ 667 | var param = { 668 | path: "?Action=QueryMetricMeta&PageNumber=1&PageSize=1&Project=" + project + "&Metric=" + metric, 669 | method: "GET", 670 | }; 671 | return this.backendSrv.datasourceRequest({ 672 | url: this.buildRealUrl(param), 673 | method: "GET", 674 | }).then((response_meta) => { 675 | var data_meta = response_meta.data; 676 | if (data_meta.Code == "200" && data_meta.Success == true) { 677 | resource = data_meta.Resources.Resource; 678 | this.cacheMeta.set(project + "_" + metric, resource); 679 | return resource; 680 | } 681 | }); 682 | }else{ 683 | return resource; 684 | } 685 | } 686 | 687 | // 暂时无翻页功能,无nextToken字段返回 688 | tagsFilter(type, nextToken, path, tagType, tagKeyFilter) { 689 | var reqUrl = path; 690 | if (!this.isEmpty(nextToken)) { 691 | reqUrl += "&NextToken=" + nextToken; 692 | } 693 | var param = { 694 | path: reqUrl, 695 | method: "GET", 696 | }; 697 | var realUrl = ""; 698 | if ("ECS" == type) { 699 | realUrl = this.buildECSRealUrl(param); 700 | return this.backendSrv.datasourceRequest({ 701 | url: realUrl, 702 | method: "GET", 703 | }).then((response) => { 704 | var result = []; 705 | var data = response.data; 706 | var tags = data.Tags.Tag; 707 | if (tags.length > 0) { 708 | tags.forEach((tag) => { 709 | if ("key" == tagType) { 710 | if (!result.includes(tag.TagKey)) { 711 | result.push(tag.TagKey); 712 | } 713 | } else if ("value" == tagType) { 714 | if (tagKeyFilter.includes(tag.TagKey) || tagKeyFilter.length == 0) { 715 | var value = tag.TagKey + ":/:" + tag.TagValue; 716 | if (!result.includes(value)) { 717 | result.push(value); 718 | } 719 | } 720 | } 721 | }); 722 | } 723 | return result; 724 | }); 725 | } else if ("RDS" == type) { 726 | realUrl = this.buildRDSRealUrl(param); 727 | return this.backendSrv 728 | .datasourceRequest({ 729 | url: realUrl, 730 | method: "GET", 731 | }) 732 | .then((response) => { 733 | var result = []; 734 | var data = response.data; 735 | var tags = data.Items.TagInfos; 736 | if (tags.length > 0) { 737 | tags.forEach((tag) => { 738 | if ("key" == tagType) { 739 | if (!result.includes(tag.TagKey)) { 740 | result.push(tag.TagKey); 741 | } 742 | } else if ("value" == tagType) { 743 | if (tagKeyFilter.includes(tag.TagKey) || tagKeyFilter.length == 0) { 744 | var value = tag.TagKey + ":/:" + tag.TagValue; 745 | if (!result.includes(value)) { 746 | result.push(value); 747 | } 748 | } 749 | } 750 | }); 751 | } 752 | return result; 753 | }); 754 | } 755 | } 756 | 757 | listTagResources(type, regionId, resourceType, resourceId, tag) { 758 | type = this.isEmpty(type) ? "ECS" : type; 759 | regionId = this.isEmpty(regionId) ? "cn-hangzhou" : regionId; 760 | if ("ECS" == type) { 761 | resourceType = this.isEmpty(resourceType) ? "instance" : resourceType; 762 | } else if ("RDS" == type) { 763 | resourceType = this.isEmpty(resourceType) ? "INSTANCE" : resourceType; 764 | } 765 | var path = "/?Action=ListTagResources&RegionId=" + regionId + "&ResourceType=" + resourceType; 766 | for (var i = 0; i < resourceId.length; i++) { 767 | if (50 > i) { 768 | var v = resourceId[i]; 769 | if (!this.isEmpty(v)) { 770 | path += "&ResourceId." + (parseInt(i) + 1).toString() + "=" + v; 771 | } 772 | } 773 | } 774 | var tag_key_array = []; 775 | var tag_value_array = []; 776 | tag.forEach((t) => { 777 | if (!this.isEmpty(t)) { 778 | if (t.indexOf(":/:") != -1) { 779 | var t_split = t.split(":/:"); 780 | if (!tag_key_array.includes(t_split[0])) { 781 | tag_key_array.push(t_split[0]); 782 | } 783 | if (!tag_value_array.includes(t_split[1])) { 784 | tag_value_array.push(t_split[1]); 785 | } 786 | } 787 | } 788 | }); 789 | if (tag_key_array.length > 0) { 790 | for (var i = 0; i < tag_key_array.length; i++) { 791 | var key = tag_key_array[i]; 792 | path += "&Tag." + (parseInt(i) + 1).toString() + ".Key=" + key; 793 | } 794 | } else if (tag_key_array.length == 0 && tag.length > 0) { 795 | for (var i = 0; i < tag.length; i++) { 796 | var key = tag[i]; 797 | path += "&Tag." + (parseInt(i) + 1).toString() + ".Key=" + key; 798 | } 799 | } 800 | var nextToken = ""; 801 | return this.tagList(type, nextToken, path, tag_value_array).then((rep) => { 802 | var distinct_result = []; 803 | rep.forEach((instanceId) => { 804 | if (!distinct_result.includes(instanceId)) { 805 | distinct_result.push(instanceId); 806 | } 807 | }); 808 | return this.util.arrayToMap(distinct_result); 809 | }); 810 | } 811 | // 处理nextToken问题 812 | tagList(type, nextToken, path, tag_value_array) { 813 | var reqUrl = path; 814 | if (!this.isEmpty(nextToken)) { 815 | reqUrl += "&NextToken=" + nextToken; 816 | } 817 | var param = { 818 | path: reqUrl, 819 | method: "GET", 820 | }; 821 | var realUrl = ""; 822 | if ("ECS" == type) { 823 | realUrl = this.buildECSRealUrl(param); 824 | } else if ("RDS" == type) { 825 | realUrl = this.buildRDSRealUrl(param); 826 | } 827 | return this.backendSrv.datasourceRequest({ 828 | url: realUrl, 829 | method: "GET", 830 | }).then((response) => { 831 | var result = []; 832 | var data = response.data; 833 | var tagResource = data.TagResources.TagResource; 834 | if (tagResource.length > 0) { 835 | tagResource.forEach((resource) => { 836 | if (tag_value_array.length > 0) { 837 | if (tag_value_array.includes(resource.TagValue)) { 838 | result.push(resource.ResourceId); 839 | } 840 | } else { 841 | result.push(resource.ResourceId); 842 | } 843 | }); 844 | } 845 | if (this.isEmpty(data.NextToken)) { 846 | return result; 847 | } else { 848 | return this.tagList(type, data.NextToken, path, tag_value_array).then( 849 | (nextList) => { 850 | return result.concat(nextList); 851 | } 852 | ); 853 | } 854 | }); 855 | } 856 | 857 | // 根据云监控API文档处理URL签名,做封装调用接口用 858 | buildRealUrl(param) { 859 | return "/api/datasources/"+this.datasourceId+"/resources/proxy_aliyun_cms_pop" + param.path; 860 | } 861 | 862 | // 根据ECS API文档处理URL签名,做封装调用接口用 863 | buildECSRealUrl(param) { 864 | return "/api/datasources/"+this.datasourceId+"/resources/proxy_aliyun_ecs_pop" + param.path; 865 | } 866 | 867 | // 根据RDS API文档处理URL签名,做封装调用接口用 868 | buildRDSRealUrl(param) { 869 | return "/api/datasources/"+this.datasourceId+"/resources/proxy_aliyun_rds_pop" + param.path; 870 | } 871 | 872 | // 判断对象是否为空对象 true 空 873 | isEmpty(obj) { 874 | var re = new RegExp("^[ ]+$"); 875 | if (!obj || obj == "null" || obj == null || obj == " " || obj == "" || obj == '""' || re.test(obj) || typeof obj == "undefined") { 876 | return true; 877 | } // 为空 878 | return false; // 不为空 879 | } 880 | } 881 | -------------------------------------------------------------------------------- /src/img/yunjiankong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliyun/aliyun-cms-grafana/521031f16d43bd1fb64b5ed87c1edf297a774262/src/img/yunjiankong.png -------------------------------------------------------------------------------- /src/json/2019-10-30_ecs_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_CMS_GRAFANA SERVICE", 5 | "label": "CMS Grafana Service", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "aliyun_cms_grafana_datasource", 9 | "pluginName": "CMS Grafana Service" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "datasource", 15 | "id": "aliyun_cms_grafana_datasource", 16 | "name": "CMS Grafana Service", 17 | "version": "1.0.1" 18 | }, 19 | { 20 | "type": "grafana", 21 | "id": "grafana", 22 | "name": "Grafana", 23 | "version": "6.4.3" 24 | }, 25 | { 26 | "type": "panel", 27 | "id": "graph", 28 | "name": "Graph", 29 | "version": "" 30 | } 31 | ], 32 | "annotations": { 33 | "list": [ 34 | { 35 | "builtIn": 1, 36 | "datasource": "-- Grafana --", 37 | "enable": true, 38 | "hide": true, 39 | "iconColor": "rgba(0, 211, 255, 1)", 40 | "name": "Annotations & Alerts", 41 | "type": "dashboard" 42 | } 43 | ] 44 | }, 45 | "editable": true, 46 | "gnetId": null, 47 | "graphTooltip": 0, 48 | "id": null, 49 | "iteration": 1573095675036, 50 | "links": [], 51 | "panels": [ 52 | { 53 | "aliasColors": {}, 54 | "bars": false, 55 | "dashLength": 10, 56 | "dashes": false, 57 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 58 | "fill": 1, 59 | "fillGradient": 0, 60 | "gridPos": { 61 | "h": 9, 62 | "w": 12, 63 | "x": 0, 64 | "y": 0 65 | }, 66 | "id": 2, 67 | "legend": { 68 | "avg": false, 69 | "current": false, 70 | "max": false, 71 | "min": false, 72 | "show": true, 73 | "total": false, 74 | "values": false 75 | }, 76 | "lines": true, 77 | "linewidth": 1, 78 | "nullPointMode": "null", 79 | "options": { 80 | "dataLinks": [] 81 | }, 82 | "percentage": false, 83 | "pointradius": 2, 84 | "points": false, 85 | "renderer": "flot", 86 | "seriesOverrides": [], 87 | "spaceLength": 10, 88 | "stack": false, 89 | "steppedLine": false, 90 | "targets": [ 91 | { 92 | "describe": "", 93 | "dimensions": [ 94 | "$dimension" 95 | ], 96 | "group": "", 97 | "metric": "$metric", 98 | "period": "", 99 | "project": "$namespace", 100 | "refId": "A", 101 | "target": [ 102 | "Average" 103 | ], 104 | "type": "timeserie", 105 | "xcol": "timestamp", 106 | "ycol": [ 107 | "Average" 108 | ] 109 | } 110 | ], 111 | "thresholds": [], 112 | "timeFrom": null, 113 | "timeRegions": [], 114 | "timeShift": null, 115 | "title": "Panel Title", 116 | "tooltip": { 117 | "shared": true, 118 | "sort": 0, 119 | "value_type": "individual" 120 | }, 121 | "type": "graph", 122 | "xaxis": { 123 | "buckets": null, 124 | "mode": "time", 125 | "name": null, 126 | "show": true, 127 | "values": [] 128 | }, 129 | "yaxes": [ 130 | { 131 | "format": "short", 132 | "label": null, 133 | "logBase": 1, 134 | "max": null, 135 | "min": null, 136 | "show": true 137 | }, 138 | { 139 | "format": "short", 140 | "label": null, 141 | "logBase": 1, 142 | "max": null, 143 | "min": null, 144 | "show": true 145 | } 146 | ], 147 | "yaxis": { 148 | "align": false, 149 | "alignLevel": null 150 | } 151 | } 152 | ], 153 | "schemaVersion": 20, 154 | "style": "dark", 155 | "tags": [], 156 | "templating": { 157 | "list": [ 158 | { 159 | "allValue": null, 160 | "current": {}, 161 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 162 | "definition": "namespace(ecs)", 163 | "hide": 0, 164 | "includeAll": false, 165 | "label": null, 166 | "multi": false, 167 | "name": "namespace", 168 | "options": [], 169 | "query": "namespace(ecs)", 170 | "refresh": 1, 171 | "regex": "", 172 | "skipUrlSync": false, 173 | "sort": 0, 174 | "tagValuesQuery": "", 175 | "tags": [], 176 | "tagsQuery": "", 177 | "type": "query", 178 | "useTags": false 179 | }, 180 | { 181 | "allValue": null, 182 | "current": {}, 183 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 184 | "definition": "metric($namespace,null)", 185 | "hide": 0, 186 | "includeAll": false, 187 | "label": null, 188 | "multi": false, 189 | "name": "metric", 190 | "options": [], 191 | "query": "metric($namespace,null)", 192 | "refresh": 1, 193 | "regex": "", 194 | "skipUrlSync": false, 195 | "sort": 0, 196 | "tagValuesQuery": "", 197 | "tags": [], 198 | "tagsQuery": "", 199 | "type": "query", 200 | "useTags": false 201 | }, 202 | { 203 | "allValue": null, 204 | "current": {}, 205 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 206 | "definition": "tagFilter(ecs,cn-shanghai,value,null)", 207 | "hide": 0, 208 | "includeAll": false, 209 | "label": null, 210 | "multi": true, 211 | "name": "tagFilter", 212 | "options": [], 213 | "query": "tagFilter(ecs,cn-shanghai,value,null)", 214 | "refresh": 1, 215 | "regex": "", 216 | "skipUrlSync": false, 217 | "sort": 0, 218 | "tagValuesQuery": "tagFilter(ecs,cn-shanghai,value,$tag)", 219 | "tags": [ 220 | "acs:autoscaling:scalingGroupId", 221 | "acs:ros:stackId", 222 | "app", 223 | "cs.cluster.id", 224 | "cs.cluster.name", 225 | "cs.scaling.group.id", 226 | "GotoAliyun", 227 | "k8s.aliyun.com", 228 | "k8s.io/cluster-autoscaler", 229 | "k8s.io/cluster-autoscaler/node-template/label/policy", 230 | "k8s.io/cluster-autoscaler/node-template/label/workload_type", 231 | "ros-aliyun-created", 232 | "SMC" 233 | ], 234 | "tagsQuery": "tagFilter(ecs,cn-shanghai,key,null)", 235 | "type": "query", 236 | "useTags": true 237 | }, 238 | { 239 | "allValue": null, 240 | "current": {}, 241 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 242 | "definition": "tag(ecs,cn-shanghai,instance,null,$tagFilter)", 243 | "hide": 0, 244 | "includeAll": true, 245 | "label": null, 246 | "multi": true, 247 | "name": "tag", 248 | "options": [], 249 | "query": "tag(ecs,cn-shanghai,instance,null,$tagFilter)", 250 | "refresh": 1, 251 | "regex": "", 252 | "skipUrlSync": false, 253 | "sort": 0, 254 | "tagValuesQuery": "", 255 | "tags": [], 256 | "tagsQuery": "", 257 | "type": "query", 258 | "useTags": false 259 | }, 260 | { 261 | "allValue": null, 262 | "current": {}, 263 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 264 | "definition": "dimension($namespace,$metric,$tag,null)", 265 | "hide": 0, 266 | "includeAll": true, 267 | "label": null, 268 | "multi": true, 269 | "name": "dimension", 270 | "options": [], 271 | "query": "dimension($namespace,$metric,$tag,null)", 272 | "refresh": 1, 273 | "regex": "", 274 | "skipUrlSync": false, 275 | "sort": 0, 276 | "tagValuesQuery": "", 277 | "tags": [], 278 | "tagsQuery": "", 279 | "type": "query", 280 | "useTags": false 281 | } 282 | ] 283 | }, 284 | "time": { 285 | "from": "now-6h", 286 | "to": "now" 287 | }, 288 | "timepicker": { 289 | "refresh_intervals": [ 290 | "5s", 291 | "10s", 292 | "30s", 293 | "1m", 294 | "5m", 295 | "15m", 296 | "30m", 297 | "1h", 298 | "2h", 299 | "1d" 300 | ] 301 | }, 302 | "timezone": "", 303 | "title": "2019-10-30_ecs_test", 304 | "uid": "KVzmuAAWz", 305 | "version": 22 306 | } -------------------------------------------------------------------------------- /src/json/2019-10-30_rds_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_CMS_GRAFANA SERVICE", 5 | "label": "CMS Grafana Service", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "aliyun_cms_grafana_datasource", 9 | "pluginName": "CMS Grafana Service" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "datasource", 15 | "id": "aliyun_cms_grafana_datasource", 16 | "name": "CMS Grafana Service", 17 | "version": "1.0.1" 18 | }, 19 | { 20 | "type": "grafana", 21 | "id": "grafana", 22 | "name": "Grafana", 23 | "version": "6.4.3" 24 | }, 25 | { 26 | "type": "panel", 27 | "id": "graph", 28 | "name": "Graph", 29 | "version": "" 30 | } 31 | ], 32 | "annotations": { 33 | "list": [ 34 | { 35 | "builtIn": 1, 36 | "datasource": "-- Grafana --", 37 | "enable": true, 38 | "hide": true, 39 | "iconColor": "rgba(0, 211, 255, 1)", 40 | "name": "Annotations & Alerts", 41 | "type": "dashboard" 42 | } 43 | ] 44 | }, 45 | "editable": true, 46 | "gnetId": null, 47 | "graphTooltip": 0, 48 | "id": null, 49 | "iteration": 1573095655188, 50 | "links": [], 51 | "panels": [ 52 | { 53 | "aliasColors": {}, 54 | "bars": false, 55 | "dashLength": 10, 56 | "dashes": false, 57 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 58 | "fill": 1, 59 | "fillGradient": 0, 60 | "gridPos": { 61 | "h": 9, 62 | "w": 12, 63 | "x": 0, 64 | "y": 0 65 | }, 66 | "id": 2, 67 | "legend": { 68 | "avg": false, 69 | "current": false, 70 | "max": false, 71 | "min": false, 72 | "show": true, 73 | "total": false, 74 | "values": false 75 | }, 76 | "lines": true, 77 | "linewidth": 1, 78 | "links": [], 79 | "nullPointMode": "null", 80 | "options": { 81 | "dataLinks": [] 82 | }, 83 | "percentage": false, 84 | "pointradius": 5, 85 | "points": false, 86 | "renderer": "flot", 87 | "seriesOverrides": [], 88 | "spaceLength": 10, 89 | "stack": false, 90 | "steppedLine": false, 91 | "targets": [ 92 | { 93 | "describe": "", 94 | "dimensions": [ 95 | "$dimension" 96 | ], 97 | "group": "", 98 | "metric": "$metric", 99 | "period": "60", 100 | "project": "$namespace", 101 | "refId": "A", 102 | "target": [ 103 | "Average" 104 | ], 105 | "type": "timeserie", 106 | "xcol": "timestamp", 107 | "ycol": [ 108 | "Average" 109 | ] 110 | } 111 | ], 112 | "thresholds": [], 113 | "timeFrom": null, 114 | "timeRegions": [], 115 | "timeShift": null, 116 | "title": "Rds", 117 | "tooltip": { 118 | "shared": true, 119 | "sort": 0, 120 | "value_type": "individual" 121 | }, 122 | "type": "graph", 123 | "xaxis": { 124 | "buckets": null, 125 | "mode": "time", 126 | "name": null, 127 | "show": true, 128 | "values": [] 129 | }, 130 | "yaxes": [ 131 | { 132 | "format": "short", 133 | "label": null, 134 | "logBase": 1, 135 | "max": null, 136 | "min": null, 137 | "show": true 138 | }, 139 | { 140 | "format": "short", 141 | "label": null, 142 | "logBase": 1, 143 | "max": null, 144 | "min": null, 145 | "show": true 146 | } 147 | ], 148 | "yaxis": { 149 | "align": false, 150 | "alignLevel": null 151 | } 152 | } 153 | ], 154 | "schemaVersion": 20, 155 | "style": "dark", 156 | "tags": [], 157 | "templating": { 158 | "list": [ 159 | { 160 | "allValue": null, 161 | "current": {}, 162 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 163 | "definition": "namespace(_rds_)", 164 | "hide": 0, 165 | "includeAll": false, 166 | "label": null, 167 | "multi": false, 168 | "name": "namespace", 169 | "options": [], 170 | "query": "namespace(_rds_)", 171 | "refresh": 1, 172 | "regex": "", 173 | "skipUrlSync": false, 174 | "sort": 0, 175 | "tagValuesQuery": "", 176 | "tags": [], 177 | "tagsQuery": "", 178 | "type": "query", 179 | "useTags": false 180 | }, 181 | { 182 | "allValue": null, 183 | "current": {}, 184 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 185 | "definition": "", 186 | "hide": 0, 187 | "includeAll": false, 188 | "label": null, 189 | "multi": false, 190 | "name": "metric", 191 | "options": [], 192 | "query": "metric($namespace,null)", 193 | "refresh": 1, 194 | "regex": "", 195 | "skipUrlSync": false, 196 | "sort": 0, 197 | "tagValuesQuery": "", 198 | "tags": [], 199 | "tagsQuery": "", 200 | "type": "query", 201 | "useTags": false 202 | }, 203 | { 204 | "allValue": null, 205 | "current": {}, 206 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 207 | "definition": "", 208 | "hide": 0, 209 | "includeAll": false, 210 | "label": null, 211 | "multi": true, 212 | "name": "tagsFilter", 213 | "options": [], 214 | "query": "tagsFilter(rds,cn-shanghai,value,null)", 215 | "refresh": 1, 216 | "regex": "", 217 | "skipUrlSync": false, 218 | "sort": 0, 219 | "tagValuesQuery": "tagsFilter(rds,cn-shanghai,value,$tag)", 220 | "tags": [ 221 | "name01", 222 | "name", 223 | "test", 224 | "team" 225 | ], 226 | "tagsQuery": "tagsFilter(rds,cn-shanghai,key,null)", 227 | "type": "query", 228 | "useTags": true 229 | }, 230 | { 231 | "allValue": null, 232 | "current": {}, 233 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 234 | "definition": "", 235 | "hide": 0, 236 | "includeAll": true, 237 | "label": null, 238 | "multi": true, 239 | "name": "tags", 240 | "options": [], 241 | "query": "tag(rds,cn-shanghai,INSTANCE,null,$tagsFilter)", 242 | "refresh": 1, 243 | "regex": "", 244 | "skipUrlSync": false, 245 | "sort": 0, 246 | "tagValuesQuery": "", 247 | "tags": [], 248 | "tagsQuery": "", 249 | "type": "query", 250 | "useTags": false 251 | }, 252 | { 253 | "allValue": null, 254 | "current": {}, 255 | "datasource": "${DS_CMS_GRAFANA SERVICE}", 256 | "definition": "", 257 | "hide": 0, 258 | "includeAll": true, 259 | "label": null, 260 | "multi": true, 261 | "name": "dimension", 262 | "options": [], 263 | "query": "dimension($namespace,$metric,$tags,null)", 264 | "refresh": 1, 265 | "regex": "", 266 | "skipUrlSync": false, 267 | "sort": 0, 268 | "tagValuesQuery": "", 269 | "tags": [], 270 | "tagsQuery": "", 271 | "type": "query", 272 | "useTags": false 273 | } 274 | ] 275 | }, 276 | "time": { 277 | "from": "now-6h", 278 | "to": "now" 279 | }, 280 | "timepicker": { 281 | "refresh_intervals": [ 282 | "5s", 283 | "10s", 284 | "30s", 285 | "1m", 286 | "5m", 287 | "15m", 288 | "30m", 289 | "1h", 290 | "2h", 291 | "1d" 292 | ], 293 | "time_options": [ 294 | "5m", 295 | "15m", 296 | "1h", 297 | "6h", 298 | "12h", 299 | "24h", 300 | "2d", 301 | "7d", 302 | "30d" 303 | ] 304 | }, 305 | "timezone": "", 306 | "title": "2019-10-30_rds_test", 307 | "uid": "FYKKT0AZz", 308 | "version": 8 309 | } -------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | import {GenericDatasource} from './datasource'; 2 | import {GenericDatasourceQueryCtrl} from './query_ctrl'; 3 | 4 | class GenericConfigCtrl { 5 | constructor() { 6 | this.current.url = this.current.url || "http://metrics.cn-hangzhou.aliyuncs.com"; 7 | this.current.secureJsonData = {}; 8 | this.current.jsonData = {}; 9 | } 10 | } 11 | GenericConfigCtrl.templateUrl = 'partials/config.html'; 12 | 13 | class GenericQueryOptionsCtrl {} 14 | GenericQueryOptionsCtrl.templateUrl = 'partials/query.options.html'; 15 | 16 | class GenericAnnotationsQueryCtrl {} 17 | GenericAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html' 18 | 19 | export { 20 | GenericDatasource as Datasource, 21 | GenericDatasourceQueryCtrl as QueryCtrl, 22 | GenericConfigCtrl as ConfigCtrl, 23 | GenericQueryOptionsCtrl as QueryOptionsCtrl, 24 | GenericAnnotationsQueryCtrl as AnnotationsQueryCtrl 25 | }; 26 | -------------------------------------------------------------------------------- /src/partials/annotations.editor.html: -------------------------------------------------------------------------------- 1 | 2 |