├── Gopkg.lock ├── Gopkg.toml ├── Gruntfile.js ├── LICENSE ├── Makefile ├── README.md ├── README_CN.md ├── dist ├── README.md ├── aliyun-log-plugin_darwin_amd64 ├── aliyun-log-plugin_linux_amd64 ├── aliyun-log-plugin_windows_amd64.exe ├── css │ └── query-editor.css ├── datasource.js ├── datasource.js.map ├── datasource.ts ├── img │ └── sls_logo.jpg ├── module.js ├── module.js.map ├── module.ts ├── partials │ ├── annotations.editor.html │ ├── config.html │ ├── query.editor.html │ └── query.options.html ├── plugin.json ├── query_ctrl.js ├── query_ctrl.js.map └── query_ctrl.ts ├── img ├── demo1.png ├── demo2.png ├── demo3.png └── demo4.png ├── jest.config.js ├── package-lock.json ├── package.json ├── pkg ├── datasource.go ├── models.go └── plugin.go ├── spec └── datasource.test.ts ├── src ├── css │ └── query-editor.css ├── datasource.js ├── datasource.js.map ├── datasource.ts ├── img │ └── sls_logo.jpg ├── module.js ├── module.js.map ├── module.ts ├── partials │ ├── annotations.editor.html │ ├── config.html │ ├── query.editor.html │ └── query.options.html ├── plugin.json ├── query_ctrl.js ├── query_ctrl.js.map └── query_ctrl.ts ├── tsconfig.json ├── webpack ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js └── yarn.lock /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:33b18e256b423958d757d255db2da1f6f35eb6e236e5e458a762ea91cbf28b81" 6 | name = "github.com/aliyun/aliyun-log-go-sdk" 7 | packages = ["."] 8 | pruneopts = "" 9 | revision = "d3a95d13616a4e6bd230af106d7abfb2f68a0aab" 10 | version = "v0.1.5" 11 | 12 | [[projects]] 13 | digest = "1:8561fc011218ed9821f6d73057154e692cdfd0f37e929ef6c4a574b6299136cb" 14 | name = "github.com/cenkalti/backoff" 15 | packages = ["."] 16 | pruneopts = "" 17 | revision = "4b4cebaf850ec58f1bb1fec5bdebdf8501c2bc3f" 18 | version = "v3.0.0" 19 | 20 | [[projects]] 21 | digest = "1:d69d2ba23955582a64e367ff2b0808cdbd048458c178cea48f11ab8c40bd7aea" 22 | name = "github.com/gogo/protobuf" 23 | packages = [ 24 | "gogoproto", 25 | "proto", 26 | "protoc-gen-gogo/descriptor", 27 | ] 28 | pruneopts = "" 29 | revision = "5628607bb4c51c3157aacc3a50f0ab707582b805" 30 | version = "v1.3.1" 31 | 32 | [[projects]] 33 | branch = "master" 34 | digest = "1:107b233e45174dbab5b1324201d092ea9448e58243ab9f039e4c0f332e121e3a" 35 | name = "github.com/golang/glog" 36 | packages = ["."] 37 | pruneopts = "" 38 | revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" 39 | 40 | [[projects]] 41 | branch = "master" 42 | digest = "1:4d197686fcd627da0b9d76901203272d24182ed4921342d9911e124777b3f5a2" 43 | name = "github.com/golang/protobuf" 44 | packages = [ 45 | "proto", 46 | "ptypes", 47 | "ptypes/any", 48 | "ptypes/duration", 49 | "ptypes/timestamp", 50 | ] 51 | pruneopts = "" 52 | revision = "e91709a02e0e8ff8b86b7aa913fdc9ae9498e825" 53 | 54 | [[projects]] 55 | branch = "master" 56 | digest = "1:73460e6eaa31824af444a895e5858175883c73b88acc8df767a115b78b578ddb" 57 | name = "github.com/grafana/grafana_plugin_model" 58 | packages = ["go/datasource"] 59 | pruneopts = "" 60 | revision = "84176c64269d8060f99e750ee8aba6f062753336" 61 | 62 | [[projects]] 63 | digest = "1:01601b6de1f04283f90b2a4fd923a9393b519a78f878010a90c36c4417f74e35" 64 | name = "github.com/hashicorp/go-hclog" 65 | packages = ["."] 66 | pruneopts = "" 67 | revision = "6907afbebd2eef854f0be9194eb79b0ba75d7b29" 68 | version = "v0.8.0" 69 | 70 | [[projects]] 71 | digest = "1:de20979176f5f326a028fd0d3698f4ec18f6921b46c9d68a35200355c6e8e6b9" 72 | name = "github.com/hashicorp/go-plugin" 73 | packages = ["."] 74 | pruneopts = "" 75 | revision = "e8d22c780116115ae5624720c9af0c97afe4f551" 76 | 77 | [[projects]] 78 | branch = "master" 79 | digest = "1:f9a62feb8295380942889460d0008f9fc34c4ad288821efb2ec15168a91b3404" 80 | name = "github.com/hashicorp/yamux" 81 | packages = ["."] 82 | pruneopts = "" 83 | revision = "2f1d1f20f75d5404f53b9edf6b53ed5505508675" 84 | 85 | [[projects]] 86 | digest = "1:9adf43f9a17af07a6d587e3b493e2111ad8e07283d5cd58e44e70d23bf6dc644" 87 | name = "github.com/mitchellh/go-testing-interface" 88 | packages = ["."] 89 | pruneopts = "" 90 | revision = "6d0b8010fcc857872e42fc6c931227569016843c" 91 | version = "v1.0.0" 92 | 93 | [[projects]] 94 | digest = "1:94e9081cc450d2cdf4e6886fc2c06c07272f86477df2d74ee5931951fa3d2577" 95 | name = "github.com/oklog/run" 96 | packages = ["."] 97 | pruneopts = "" 98 | revision = "4dadeb3030eda0273a12382bb2348ffc7c9d1a39" 99 | version = "v1.0.0" 100 | 101 | [[projects]] 102 | digest = "1:f5c875bba9e42adaba2627ec78961b655f747d885615beb1b01e40318ada65ea" 103 | name = "github.com/pierrec/lz4" 104 | packages = [ 105 | ".", 106 | "internal/xxh32", 107 | ] 108 | pruneopts = "" 109 | revision = "9085dacd1e1eca033047a5514195779360363ced" 110 | version = "2.4.0" 111 | 112 | [[projects]] 113 | digest = "1:1d7e1867c49a6dd9856598ef7c3123604ea3daabf5b83f303ff457bcbc410b1d" 114 | name = "github.com/pkg/errors" 115 | packages = ["."] 116 | pruneopts = "" 117 | revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" 118 | version = "v0.8.1" 119 | 120 | [[projects]] 121 | branch = "master" 122 | digest = "1:09972eaa1645553c1cf5b0d2b471aa3aef8d9ab88ca45528e131cd32e8572fb9" 123 | name = "golang.org/x/net" 124 | packages = [ 125 | "context", 126 | "http/httpguts", 127 | "http2", 128 | "http2/hpack", 129 | "idna", 130 | "internal/timeseries", 131 | "trace", 132 | ] 133 | pruneopts = "" 134 | revision = "eb5bcb51f2a31c7d5141d810b70815c05d9c9146" 135 | 136 | [[projects]] 137 | branch = "master" 138 | digest = "1:55c52474bb389797ed66db92966e2b9ddc98a25d9d05c8aa55787fe03d4d4084" 139 | name = "golang.org/x/sys" 140 | packages = ["unix"] 141 | pruneopts = "" 142 | revision = "4b34438f7a67ee5f45cc6132e2bad873a20324e9" 143 | 144 | [[projects]] 145 | digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4" 146 | name = "golang.org/x/text" 147 | packages = [ 148 | "collate", 149 | "collate/build", 150 | "internal/colltab", 151 | "internal/gen", 152 | "internal/tag", 153 | "internal/triegen", 154 | "internal/ucd", 155 | "language", 156 | "secure/bidirule", 157 | "transform", 158 | "unicode/bidi", 159 | "unicode/cldr", 160 | "unicode/norm", 161 | "unicode/rangetable", 162 | ] 163 | pruneopts = "" 164 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" 165 | version = "v0.3.0" 166 | 167 | [[projects]] 168 | branch = "master" 169 | digest = "1:6cbc03e8d4c5724d6228c88f1402d8cbd0a515561f73f0cecbb72f3d6576ff28" 170 | name = "google.golang.org/genproto" 171 | packages = ["googleapis/rpc/status"] 172 | pruneopts = "" 173 | revision = "64821d5d210748c883cd2b809589555ae4654203" 174 | 175 | [[projects]] 176 | digest = "1:c4e8733914b7b1b535988fb5d5bd3de60845fb4553227b7a1ce1b7180204e462" 177 | name = "google.golang.org/grpc" 178 | packages = [ 179 | ".", 180 | "balancer", 181 | "balancer/base", 182 | "balancer/roundrobin", 183 | "binarylog/grpc_binarylog_v1", 184 | "codes", 185 | "connectivity", 186 | "credentials", 187 | "credentials/internal", 188 | "encoding", 189 | "encoding/proto", 190 | "grpclog", 191 | "health", 192 | "health/grpc_health_v1", 193 | "internal", 194 | "internal/backoff", 195 | "internal/balancerload", 196 | "internal/binarylog", 197 | "internal/channelz", 198 | "internal/envconfig", 199 | "internal/grpcrand", 200 | "internal/grpcsync", 201 | "internal/syscall", 202 | "internal/transport", 203 | "keepalive", 204 | "metadata", 205 | "naming", 206 | "peer", 207 | "resolver", 208 | "resolver/dns", 209 | "resolver/passthrough", 210 | "stats", 211 | "status", 212 | "tap", 213 | ] 214 | pruneopts = "" 215 | revision = "236199dd5f8031d698fb64091194aecd1c3895b2" 216 | version = "v1.20.0" 217 | 218 | [solve-meta] 219 | analyzer-name = "dep" 220 | analyzer-version = 1 221 | input-imports = [ 222 | "github.com/aliyun/aliyun-log-go-sdk", 223 | "github.com/grafana/grafana_plugin_model/go/datasource", 224 | "github.com/hashicorp/go-hclog", 225 | "github.com/hashicorp/go-plugin", 226 | "golang.org/x/net/context", 227 | ] 228 | solver-name = "gps-cdcl" 229 | solver-version = 1 230 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | name = "github.com/bitly/go-simplejson" 26 | version = "0.5.0" 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "github.com/grafana/grafana_plugin_model" 31 | 32 | [[constraint]] 33 | branch = "master" 34 | name = "golang.org/x/net" 35 | -------------------------------------------------------------------------------- /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 | }, 39 | dist: { 40 | options: { 41 | plugins: ['transform-es2015-modules-systemjs', 'transform-es2015-for-of'] 42 | }, 43 | files: [{ 44 | cwd: 'src', 45 | expand: true, 46 | src: ['**/*.js'], 47 | dest: 'dist', 48 | ext:'.js' 49 | }] 50 | }, 51 | distTestNoSystemJs: { 52 | files: [{ 53 | cwd: 'src', 54 | expand: true, 55 | src: ['**/*.js'], 56 | dest: 'dist/test', 57 | ext:'.js' 58 | }] 59 | }, 60 | distTestsSpecsNoSystemJs: { 61 | files: [{ 62 | expand: true, 63 | cwd: 'spec', 64 | src: ['**/*.js'], 65 | dest: 'dist/test/spec', 66 | ext:'.js' 67 | }] 68 | } 69 | }, 70 | 71 | mochaTest: { 72 | test: { 73 | options: { 74 | reporter: 'spec' 75 | }, 76 | src: ['dist/test/spec/test-main.js', 'dist/test/spec/*_spec.js'] 77 | } 78 | } 79 | }); 80 | 81 | //grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:pluginDef', 'babel', 'mochaTest']); 82 | grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:pluginDef', 'babel']); 83 | }; 84 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: grunt build 2 | 3 | grunt: 4 | grunt 5 | 6 | build: 7 | go build -i -o ./dist/aliyun-log-plugin_darwin_amd64 ./pkg 8 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -i -o ./dist/aliyun-log-plugin_linux_amd64 ./pkg 9 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -i -o ./dist/aliyun-log-plugin_windows_amd64.exe ./pkg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Aliyun log service Datasource 2 | 3 | 4 | More documentation about datasource plugins can be found in the [Docs](https://github.com/grafana/grafana/blob/master/docs/sources/plugins/developing/datasources.md). 5 | 6 | 7 | [**中文文档**](README_CN.md) 8 | 9 | 10 | ## Install 11 | 12 | 13 | Clone this project into grafana plugin directory , then restart grafana. 14 | 15 | In mac the plugin directory is /usr/local/var/lib/grafana/plugins. 16 | 17 | After install the plugin ,restart grafana 18 | 19 | ``` 20 | brew services start grafana 21 | ``` 22 | 23 | ## Add datasource 24 | 25 | In datasource management panel, add a datasource with the type "LogService". 26 | 27 | In Http settings, set Url = http://${log\_service\_endpoint} . e.g. Your projectName is accesslog in qingdao region, then the url is http://cn-qingdao.log.aliyuncs.com. 28 | 29 | Access : select `Server(Default)` 30 | 31 | log service details: 32 | 33 | set Project and logstore 34 | 35 | AccessId and AccessKey : it is better to use a sub user accessId and accessKey. 36 | 37 | To ensure data security, AK is saved and cleared without echo 38 | 39 | 40 | ## Add dashboard 41 | 42 | 43 | Add a panel, in the datasource option, choose the log service datasource that is just created. 44 | 45 | In the query : insert your query , e.g. 46 | 47 | ``` 48 | *|select count(1) as c,count(1)/2 as c1, __time__- __time__%60 as t group by t limit 10000 49 | ``` 50 | 51 | The X column ,insert t (**Second timestamp**) 52 | 53 | The Y column , insert c,c1 (**Multiple columns are separated by commas**) 54 | 55 | Save the dashboard 56 | 57 | ## Usage 58 | 59 | ### Variables 60 | 61 | In the top right corner of the dashboard panel, click dashboard Settings and select Variables. 62 | 63 | Reference variables `$VariableName` 64 | 65 | ### Flow graph 66 | 67 | The X-axis is set to the time column 68 | 69 | The Y-axis is set to the format `col1#:#col2`, where col1 is the aggregate column and col2 is the other columns 70 | 71 | The Query sample is set to 72 | ``` 73 | * | select to_unixtime(time) as time,status,count from (select time_series(__time__, '1m', '%Y-%m-%d %H:%i', '0') as time,status,count(*) as count from log group by status,time order by time limit 10000) 74 | ``` 75 | 76 | ![](/img/demo1.png) 77 | 78 | ### Pie 79 | 80 | The X-axis is set to `pie` 81 | 82 | The Y-axis is set to categories and numeric columns (example `method,pv`) 83 | 84 | The Query sample is set to 85 | ``` 86 | $hostname | select count(1) as pv ,method group by method 87 | ``` 88 | 89 | ![](/img/demo2.png) 90 | 91 | ### Table 92 | 93 | The X-axis is set to `table` or null 94 | 95 | The Y-axis is set to columns 96 | 97 | ### World map penel 98 | 99 | The X-axis is set to `map` 100 | 101 | The Y-axis is set to `country,geo,pv` 102 | 103 | The Query sample is set to 104 | ``` 105 | * | select count(1) as pv ,geohash(ip_to_geo(arbitrary(remote_addr))) as geo,ip_to_country(remote_addr) as country from log group by country having geo <>'' limit 1000 106 | ``` 107 | 108 | Location Data : `geohash` 109 | 110 | Location Name Field : `country` 111 | 112 | Geo_point/Geohash Field :" `geo` 113 | 114 | Metric Field : `pv` 115 | 116 | The query: 117 | 118 | ![](http://logdemo.oss-cn-beijing.aliyuncs.com/worldmap1.png) 119 | 120 | Parameter Settings: 121 | 122 | ![](http://logdemo.oss-cn-beijing.aliyuncs.com/worldmap2.png) 123 | 124 | ### Alert 125 | 126 | #### Mode of notification 127 | 128 | In the alert notification panel, select New channel to add 129 | 130 | #### Alert 131 | 132 | **Attention** :Dashboard alert only, not plug-in alert 133 | 134 | A sample of: 135 | 136 | ![](/img/demo3.png) 137 | 138 | Add the alert panel: 139 | 140 | ![](/img/demo4.png) 141 | 142 | - The red line on the chart represents the set threshold. Click on the right side and drag it up and down. 143 | - Evaluate every `1m` for `5m`, Is the result calculated every minute, and the threshold is exceeded for five consecutive minutes. 144 | - After setting for, if the state exceeds the threshold value and changes from Ok to Pending, the alarm will not be triggered. After continuously exceeding the threshold value for a period of time, the alarm will be sent. If the state changes from Pending to Alerting, the alarm will only be notified once. 145 | - WHEN `avg ()` OF `query (B, 5m, now)` IS ABOVE `89`, That means line B has an average of more than 89 alarms in the last five minutes. 146 | - Add notification mode and notification information under Notifications. 147 | 148 | 149 | ## Contributors 150 | 151 | [@WPH95](https://github.com/WPH95) made a great contribution to this project. 152 | 153 | Thanks for the excellent work by [@WPH95](https://github.com/WPH95). 154 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | ## 阿里云日志服务数据源 2 | 3 | 4 | ## 安装 5 | 6 | 7 | 克隆本项目到grafana插件目录下 , 然后重启grafana 8 | 9 | 在 mac 插件目录是 /usr/local/var/lib/grafana/plugins 10 | 11 | 重启命令为 12 | 13 | ``` 14 | brew services restart grafana 15 | ``` 16 | 17 | ## 添加数据源 18 | 19 | 在数据源管理面板, 添加 `LogService` 数据源 20 | 21 | 在 settings 面板, 设置 Url 为您日志服务 project 的 endpoint ( endpoint 在 project 的概览页可以看到). 22 | 23 | 例如你的 project 在 qingdao region, Url 可以填 `http://cn-qingdao.log.aliyuncs.com` 24 | 25 | Access 设置为 `Server(Default)` 26 | 27 | 28 | 设置 Project 和 logstore 29 | 30 | 设置 AccessId 和 AccessKeySecret , 最好配置为子账号的AK 31 | 32 | 为保证数据安全 , AK保存后清空 , 且不会回显 33 | 34 | 35 | ## 添加仪表盘 36 | 37 | 38 | 添加一个面板, 在 datasource 选项, 选择刚创建的日志服务数据源. 39 | 40 | 在 query 输入查询语句, 查询语法与日志服务控制台相同. 41 | 42 | ``` 43 | *|select count(1) as c,count(1)/2 as c1, __time__- __time__%60 as t group by t limit 10000 44 | ``` 45 | 46 | X轴设置为`t` (**秒级时间戳**) 47 | 48 | Y轴设置为`c,c1` (**多列用逗号分隔**) 49 | 50 | 保存仪表盘 51 | 52 | ## 使用 53 | 54 | ### 设置变量 55 | 56 | 在 dashboard 面板右上角点击 Dashboard settings, 选择 Variables 57 | 58 | 引用变量 `$VariableName` 59 | 60 | ### 设置流图 61 | 62 | X轴 设置为时间列 63 | 64 | Y轴 设置为 `col1#:#col2` 这种格式, 其中 col1 为 聚合列, col2 为其他列 65 | 66 | Query 设置样例为 67 | ``` 68 | * | select to_unixtime(time) as time,status,count from (select time_series(__time__, '1m', '%Y-%m-%d %H:%i', '0') as time,status,count(*) as count from log group by status,time order by time limit 10000) 69 | ``` 70 | 71 | ![](/img/demo1.png) 72 | 73 | ### 设置饼图 74 | 75 | X轴 设置为`pie` 76 | 77 | Y轴 设置为类别和数字列 (样例为`method,pv`) 78 | 79 | Query 设置样例为 80 | ``` 81 | $hostname | select count(1) as pv ,method group by method 82 | ``` 83 | 84 | ![](/img/demo2.png) 85 | 86 | ### 设置表格 87 | 88 | X轴 设置为`table` 或空 89 | 90 | Y轴 设置为列 91 | 92 | ### 设置地图 93 | 94 | X轴 设置为`map` 95 | 96 | Y轴 设置为 `country,geo,pv` 97 | 98 | Query 设置样例为 99 | ``` 100 | * | select count(1) as pv ,geohash(ip_to_geo(arbitrary(remote_addr))) as geo,ip_to_country(remote_addr) as country from log group by country having geo <>'' limit 1000 101 | ``` 102 | 103 | Location Data 设置为 `geohash` 104 | 105 | Location Name Field 设置为 `country` 106 | 107 | geo_point/geohash Field 设置为 `geo` 108 | 109 | Metric Field 设置为 `pv` 110 | 111 | 查询语句: 112 | 113 | ![](http://logdemo.oss-cn-beijing.aliyuncs.com/worldmap1.png) 114 | 115 | 参数设置: 116 | 117 | ![](http://logdemo.oss-cn-beijing.aliyuncs.com/worldmap2.png) 118 | 119 | ### 设置告警 120 | 121 | #### 通知方式 122 | 123 | 在告警通知方式面板, 选择 New channel 添加 124 | 125 | **注意** :选择dingding告警, 在钉钉机器人的安全设置里选自定义关键词, 添加 `Alerting` 126 | 127 | #### 添加告警 128 | 129 | **注意** :只支持dashboard告警, 不支持插件告警 130 | 131 | 样例如下: 132 | 133 | ![](/img/demo3.png) 134 | 135 | 添加告警面板: 136 | 137 | ![](/img/demo4.png) 138 | 139 | - 其中图表上红线代表设置的阈值, 点击右侧可以上下拖动 140 | - Evaluate every `1m` for `5m`, 代表计算每分钟的结果, 连续五分钟超过阈值告警 141 | - 设置for后, 如果超过阈值状态由Ok转为Pending, 不会触发告警, 连续超过阈值一段时候后发送告警, 状态由Pending转为Alerting, 告警只会通知一次 142 | - WHEN `avg ()` OF `query (B, 5m, now)` IS ABOVE `89`, 代表线条B最近五分钟的均值超过89告警 143 | - 在Notifications下添加通知方式及通知信息 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | ## Aliyun log service Datasource 2 | 3 | 4 | More documentation about datasource plugins can be found in the [Docs](https://github.com/grafana/grafana/blob/master/docs/sources/plugins/developing/datasources.md). 5 | 6 | 7 | [**中文文档**](README_CN.md) 8 | 9 | 10 | ## Install 11 | 12 | 13 | Clone this project into grafana plugin directory , then restart grafana. 14 | 15 | In mac the plugin directory is /usr/local/var/lib/grafana/plugins. 16 | 17 | After install the plugin ,restart grafana 18 | 19 | ``` 20 | brew services start grafana 21 | ``` 22 | 23 | ## Add datasource 24 | 25 | In datasource management panel, add a datasource with the type "LogService". 26 | 27 | In Http settings, set Url = http://${log\_service\_endpoint} . e.g. Your projectName is accesslog in qingdao region, then the url is http://cn-qingdao.log.aliyuncs.com. 28 | 29 | Access : select `Server(Default)` 30 | 31 | log service details: 32 | 33 | set Project and logstore 34 | 35 | AccessId and AccessKey : it is better to use a sub user accessId and accessKey. 36 | 37 | 38 | ## Add dashboard 39 | 40 | 41 | Add a panel, in the datasource option, choose the log service datasource that is just created. 42 | 43 | In the query : insert your query , e.g. 44 | 45 | ``` 46 | *|select count(1) as c,count(1)/2 as c1, __time__- __time__%60 as t group by t limit 10000 47 | ``` 48 | 49 | The X column ,insert t (**Second timestamp**) 50 | 51 | The Y column , insert c,c1 (**Multiple columns are separated by commas**) 52 | 53 | Save the dashboard 54 | 55 | ## Usage 56 | 57 | ### Variables 58 | 59 | In the top right corner of the dashboard panel, click dashboard Settings and select Variables. 60 | 61 | Reference variables `$VariableName` 62 | 63 | ### Flow graph 64 | 65 | The X-axis is set to the time column 66 | 67 | The Y-axis is set to the format `col1#:#col2`, where col1 is the aggregate column and col2 is the other columns 68 | 69 | The Query sample is set to 70 | ``` 71 | * | select to_unixtime(time) as time,status,count from (select time_series(__time__, '1m', '%Y-%m-%d %H:%i', '0') as time,status,count(*) as count from log group by status,time order by time limit 10000) 72 | ``` 73 | 74 | ![](/img/demo1.png) 75 | 76 | ### Pie 77 | 78 | The X-axis is set to `pie` 79 | 80 | The Y-axis is set to categories and numeric columns (example `method,pv`) 81 | 82 | The Query sample is set to 83 | ``` 84 | $hostname | select count(1) as pv ,method group by method 85 | ``` 86 | 87 | ![](/img/demo2.png) 88 | 89 | ### Table 90 | 91 | The X-axis is set to `table` or null 92 | 93 | The Y-axis is set to columns 94 | 95 | ### World map penel 96 | 97 | The X-axis is set to `map` 98 | 99 | The Y-axis is set to `country,geo,pv` 100 | 101 | The Query sample is set to 102 | ``` 103 | * | select count(1) as pv ,geohash(ip_to_geo(arbitrary(remote_addr))) as geo,ip_to_country(remote_addr) as country from log group by country having geo <>'' limit 1000 104 | ``` 105 | 106 | Location Data : `geohash` 107 | 108 | Location Name Field : `country` 109 | 110 | Geo_point/Geohash Field :" `geo` 111 | 112 | Metric Field : `pv` 113 | 114 | The query: 115 | 116 | ![](http://logdemo.oss-cn-beijing.aliyuncs.com/worldmap1.png) 117 | 118 | Parameter Settings: 119 | 120 | ![](http://logdemo.oss-cn-beijing.aliyuncs.com/worldmap2.png) 121 | 122 | ### Alert 123 | 124 | #### Mode of notification 125 | 126 | In the alert notification panel, select New channel to add 127 | 128 | #### Alert 129 | 130 | **Attention** :Dashboard alert only, not plug-in alert 131 | 132 | A sample of: 133 | 134 | ![](/img/demo3.png) 135 | 136 | Add the alert panel: 137 | 138 | ![](/img/demo4.png) 139 | 140 | - The red line on the chart represents the set threshold. Click on the right side and drag it up and down. 141 | - Evaluate every `1m` for `5m`, Is the result calculated every minute, and the threshold is exceeded for five consecutive minutes. 142 | - After setting for, if the state exceeds the threshold value and changes from Ok to Pending, the alarm will not be triggered. After continuously exceeding the threshold value for a period of time, the alarm will be sent. If the state changes from Pending to Alerting, the alarm will only be notified once. 143 | - WHEN `avg ()` OF `query (B, 5m, now)` IS ABOVE `89`, That means line B has an average of more than 89 alarms in the last five minutes. 144 | - Add notification mode and notification information under Notifications. 145 | 146 | 147 | ## Contributors 148 | 149 | [@WPH95](https://github.com/WPH95) made a great contribution to this project. 150 | 151 | Thanks for the excellent work by [@WPH95](https://github.com/WPH95). 152 | -------------------------------------------------------------------------------- /dist/aliyun-log-plugin_darwin_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/dist/aliyun-log-plugin_darwin_amd64 -------------------------------------------------------------------------------- /dist/aliyun-log-plugin_linux_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/dist/aliyun-log-plugin_linux_amd64 -------------------------------------------------------------------------------- /dist/aliyun-log-plugin_windows_amd64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/dist/aliyun-log-plugin_windows_amd64.exe -------------------------------------------------------------------------------- /dist/css/query-editor.css: -------------------------------------------------------------------------------- 1 | .generic-datasource-query-row .query-keyword { 2 | width: 75px; 3 | } -------------------------------------------------------------------------------- /dist/datasource.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['lodash'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var _, _typeof, _createClass, GenericDatasource; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | function handleTsdbResponse(response) { 15 | var res = []; 16 | _.forEach(response.data.results, function (r) { 17 | _.forEach(r.series, function (s) { 18 | res.push({ target: s.name, datapoints: s.points }); 19 | }); 20 | _.forEach(r.tables, function (t) { 21 | t.type = 'table'; 22 | t.refId = r.refId; 23 | res.push(t); 24 | }); 25 | }); 26 | response.data = res; 27 | console.log(res); 28 | return response; 29 | } 30 | 31 | _export('handleTsdbResponse', handleTsdbResponse); 32 | 33 | function mapToTextValue(result) { 34 | return _.map(result, function (d, i) { 35 | if (d && d.text && d.value) { 36 | return { text: d.text, value: d.value }; 37 | } else if (_.isObject(d)) { 38 | return { text: d, value: i }; 39 | } 40 | return { text: d, value: d }; 41 | }); 42 | } 43 | //# sourceMappingURL=datasource.js.map 44 | 45 | _export('mapToTextValue', mapToTextValue); 46 | 47 | return { 48 | setters: [function (_lodash) { 49 | _ = _lodash.default; 50 | }], 51 | execute: function () { 52 | _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 53 | return typeof obj; 54 | } : function (obj) { 55 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 56 | }; 57 | 58 | _createClass = function () { 59 | function defineProperties(target, props) { 60 | for (var i = 0; i < props.length; i++) { 61 | var descriptor = props[i]; 62 | descriptor.enumerable = descriptor.enumerable || false; 63 | descriptor.configurable = true; 64 | if ("value" in descriptor) descriptor.writable = true; 65 | Object.defineProperty(target, descriptor.key, descriptor); 66 | } 67 | } 68 | 69 | return function (Constructor, protoProps, staticProps) { 70 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 71 | if (staticProps) defineProperties(Constructor, staticProps); 72 | return Constructor; 73 | }; 74 | }(); 75 | 76 | _export('GenericDatasource', GenericDatasource = function () { 77 | /** @ngInject */ 78 | function GenericDatasource(instanceSettings, backendSrv, templateSrv) { 79 | _classCallCheck(this, GenericDatasource); 80 | 81 | this.backendSrv = backendSrv; 82 | this.templateSrv = templateSrv; 83 | this.type = instanceSettings.type; 84 | this.url = instanceSettings.url; 85 | this.name = instanceSettings.name; 86 | this.id = instanceSettings.id; 87 | this.withCredentials = instanceSettings.withCredentials; 88 | this.headers = { 'Content-Type': 'application/json' }; 89 | if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) { 90 | this.headers['Authorization'] = instanceSettings.basicAuth; 91 | } 92 | } 93 | 94 | _createClass(GenericDatasource, [{ 95 | key: 'query', 96 | value: function query(options) { 97 | var query = this.buildQueryParameters(options); 98 | query.targets = query.targets.filter(function (t) { 99 | return !t.hide; 100 | }); 101 | if (query.targets.length <= 0) { 102 | return Promise.resolve({ data: [] }); 103 | } 104 | return this.doTsdbRequest(query).then(handleTsdbResponse); 105 | } 106 | }, { 107 | key: 'testDatasource', 108 | value: function testDatasource() { 109 | var to = new Date().getTime(); 110 | var from = to - 5000; 111 | var str = '{"requestId":"Q100","timezone":"","range":{"from":"' + from + '","to":"' + to + '"},' + '"targets":[{"queryType":"query","target":"count","refId":"A","type":"timeserie","datasourceId":' + this.id + ',' + '"query":"* | select count(*) as count","ycol":"count"}]}'; 112 | var query = JSON.parse(str); 113 | return this.doTsdbRequest(query).then(function (response) { 114 | if (response.status === 200) { 115 | return { status: "success", message: "Data source is working", title: "Success" }; 116 | } else { 117 | return { status: "failed", message: "Data source is not working", title: "Error" }; 118 | } 119 | }).catch(function () { 120 | return { status: "failed", message: "Data source is not working", title: "Error" }; 121 | }); 122 | } 123 | }, { 124 | key: 'annotationQuery', 125 | value: function annotationQuery(options) { 126 | var query = this.templateSrv.replace(options.annotation.query, {}, 'glob'); 127 | var annotationQuery = { 128 | range: options.range, 129 | annotation: { 130 | name: options.annotation.name, 131 | datasource: options.annotation.datasource, 132 | enable: options.annotation.enable, 133 | iconColor: options.annotation.iconColor, 134 | query: query 135 | }, 136 | rangeRaw: options.rangeRaw 137 | }; 138 | return this.doRequest({ 139 | url: this.url + '/annotations', 140 | method: 'POST', 141 | data: annotationQuery 142 | }).then(function (result) { 143 | return result.data; 144 | }); 145 | } 146 | }, { 147 | key: 'metricFindQuery', 148 | value: function metricFindQuery(q) { 149 | q = this.templateSrv.replace(q, {}, 'glob'); 150 | var to = this.templateSrv.timeRange.to.unix() * 1000; 151 | var from = this.templateSrv.timeRange.from.unix() * 1000; 152 | var str = '{"requestId":"Q100","timezone":"","range":{"from":"' + from + '","to":"' + to + '"},' + '"targets":[{"queryType":"query","target":"query","refId":"A","type":"timeserie","datasourceId":' + this.id + ',' + '"query":"' + q + '"}]}'; 153 | var query = JSON.parse(str); 154 | return this.doTsdbRequest(query).then(function (response) { 155 | var res = handleTsdbResponse(response); 156 | if (res && res.data && res.data.length) { 157 | var rows = res.data[0].rows; 158 | rows = rows.map(function (item) { 159 | return item[0]; 160 | }); 161 | return rows; 162 | } else { 163 | return []; 164 | } 165 | }).then(mapToTextValue); 166 | } 167 | }, { 168 | key: 'doRequest', 169 | value: function doRequest(options) { 170 | options.withCredentials = this.withCredentials; 171 | options.headers = this.headers; 172 | return this.backendSrv.datasourceRequest(options); 173 | } 174 | }, { 175 | key: 'doTsdbRequest', 176 | value: function doTsdbRequest(options) { 177 | var tsdbRequestData = { 178 | queries: options.targets 179 | }; 180 | if (options.range) { 181 | tsdbRequestData.from = options.range.from.valueOf().toString(); 182 | tsdbRequestData.to = options.range.to.valueOf().toString(); 183 | } 184 | return this.backendSrv.datasourceRequest({ 185 | url: '/api/tsdb/query', 186 | method: 'POST', 187 | data: tsdbRequestData 188 | }); 189 | } 190 | }, { 191 | key: 'buildQueryParameters', 192 | value: function buildQueryParameters(options) { 193 | var _this = this; 194 | 195 | //remove placeholder targets 196 | options.targets = _.filter(options.targets, function (target) { 197 | return target.target !== 'select metric'; 198 | }); 199 | options.targets = _.map(options.targets, function (target) { 200 | return { 201 | queryType: 'query', 202 | target: _this.templateSrv.replace(target.target, options.scopedVars, 'regex'), 203 | refId: target.refId, 204 | hide: target.hide, 205 | type: target.type || 'timeserie', 206 | datasourceId: _this.id, 207 | query: _this.replaceQueryParameters(target, options), 208 | xcol: target.xcol, 209 | ycol: target.ycol 210 | }; 211 | }); 212 | return options; 213 | } 214 | }, { 215 | key: 'replaceQueryParameters', 216 | value: function replaceQueryParameters(target, options) { 217 | var query = this.templateSrv.replace(target.query, options.scopedVars, function (value, variable) { 218 | if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) == "object" && (variable.multi || variable.includeAll)) { 219 | var a = []; 220 | value.forEach(function (v) { 221 | if (variable.name == variable.label) a.push('"' + variable.name + '":"' + v + '"');else a.push('"' + v + '"'); 222 | }); 223 | return a.join(" OR "); 224 | } 225 | if (_.isArray(value)) { 226 | return value.join(' OR '); 227 | } 228 | return value; 229 | }); 230 | var re = /\$([0-9]+)([dmhs])/g; 231 | var reArray = query.match(re); 232 | _(reArray).forEach(function (col) { 233 | var old = col; 234 | col = col.replace("$", ''); 235 | var sec = 1; 236 | if (col.indexOf("s") != -1) sec = 1;else if (col.indexOf("m") != -1) sec = 60;else if (col.indexOf("h") != -1) sec = 3600;else if (col.indexOf("d") != -1) sec = 3600 * 24; 237 | col = col.replace(/[smhd]/g, ''); 238 | var v = parseInt(col); 239 | v = v * sec; 240 | console.log(old, v, col, sec, query); 241 | query = query.replace(old, v); 242 | }); 243 | if (query.indexOf("#time_end") != -1) { 244 | query = query.replace("#time_end", parseInt(String(options.range.to._d.getTime() / 1000))); 245 | } 246 | if (query.indexOf("#time_begin") != -1) { 247 | query = query.replace("#time_begin", parseInt(String(options.range.from._d.getTime() / 1000))); 248 | } 249 | return query; 250 | } 251 | }, { 252 | key: 'getTagKeys', 253 | value: function getTagKeys(options) { 254 | var _this2 = this; 255 | 256 | return new Promise(function (resolve) { 257 | _this2.doRequest({ 258 | url: _this2.url + '/tag-keys', 259 | method: 'POST', 260 | data: options 261 | }).then(function (result) { 262 | return resolve(result.data); 263 | }); 264 | }); 265 | } 266 | }, { 267 | key: 'getTagValues', 268 | value: function getTagValues(options) { 269 | var _this3 = this; 270 | 271 | return new Promise(function (resolve) { 272 | _this3.doRequest({ 273 | url: _this3.url + '/tag-values', 274 | method: 'POST', 275 | data: options 276 | }).then(function (result) { 277 | return resolve(result.data); 278 | }); 279 | }); 280 | } 281 | }]); 282 | 283 | return GenericDatasource; 284 | }()); 285 | 286 | _export('GenericDatasource', GenericDatasource); 287 | } 288 | }; 289 | }); 290 | //# sourceMappingURL=datasource.js.map 291 | -------------------------------------------------------------------------------- /dist/datasource.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/datasource.js"],"names":["handleTsdbResponse","response","res","_","forEach","data","results","r","series","push","target","s","name","datapoints","points","tables","t","type","refId","console","log","mapToTextValue","result","map","d","i","text","value","isObject","GenericDatasource","instanceSettings","backendSrv","templateSrv","url","id","withCredentials","headers","basicAuth","length","options","query","buildQueryParameters","targets","filter","hide","Promise","resolve","doTsdbRequest","then","to","Date","getTime","from","str","JSON","parse","status","message","title","catch","replace","annotation","annotationQuery","range","datasource","enable","iconColor","rangeRaw","doRequest","method","q","timeRange","unix","rows","item","datasourceRequest","tsdbRequestData","queries","valueOf","toString","queryType","scopedVars","datasourceId","replaceQueryParameters","xcol","ycol","variable","multi","includeAll","a","v","label","join","isArray","re","reArray","match","col","old","sec","indexOf","parseInt","String","_d"],"mappings":"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","file":"datasource.js","sourcesContent":["import _ from \"lodash\";\nexport class GenericDatasource {\n /** @ngInject */\n constructor(instanceSettings, backendSrv, templateSrv) {\n this.backendSrv = backendSrv;\n this.templateSrv = templateSrv;\n this.type = instanceSettings.type;\n this.url = instanceSettings.url;\n this.name = instanceSettings.name;\n this.id = instanceSettings.id;\n this.withCredentials = instanceSettings.withCredentials;\n this.headers = { 'Content-Type': 'application/json' };\n if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) {\n this.headers['Authorization'] = instanceSettings.basicAuth;\n }\n }\n query(options) {\n const query = this.buildQueryParameters(options);\n query.targets = query.targets.filter(t => !t.hide);\n if (query.targets.length <= 0) {\n return Promise.resolve({ data: [] });\n }\n return this.doTsdbRequest(query).then(handleTsdbResponse);\n }\n testDatasource() {\n const to = new Date().getTime();\n const from = to - 5000;\n const str = '{\"requestId\":\"Q100\",\"timezone\":\"\",\"range\":{\"from\":\"' + from + '\",\"to\":\"' + to + '\"},' +\n '\"targets\":[{\"queryType\":\"query\",\"target\":\"count\",\"refId\":\"A\",\"type\":\"timeserie\",\"datasourceId\":' + this.id + ',' +\n '\"query\":\"* | select count(*) as count\",\"ycol\":\"count\"}]}';\n const query = JSON.parse(str);\n return this.doTsdbRequest(query).then(response => {\n if (response.status === 200) {\n return { status: \"success\", message: \"Data source is working\", title: \"Success\" };\n }\n else {\n return { status: \"failed\", message: \"Data source is not working\", title: \"Error\" };\n }\n }).catch(() => {\n return { status: \"failed\", message: \"Data source is not working\", title: \"Error\" };\n });\n }\n annotationQuery(options) {\n const query = this.templateSrv.replace(options.annotation.query, {}, 'glob');\n const annotationQuery = {\n range: options.range,\n annotation: {\n name: options.annotation.name,\n datasource: options.annotation.datasource,\n enable: options.annotation.enable,\n iconColor: options.annotation.iconColor,\n query: query\n },\n rangeRaw: options.rangeRaw\n };\n return this.doRequest({\n url: this.url + '/annotations',\n method: 'POST',\n data: annotationQuery\n }).then(result => {\n return result.data;\n });\n }\n metricFindQuery(q) {\n q = this.templateSrv.replace(q, {}, 'glob');\n const to = this.templateSrv.timeRange.to.unix() * 1000;\n const from = this.templateSrv.timeRange.from.unix() * 1000;\n const str = '{\"requestId\":\"Q100\",\"timezone\":\"\",\"range\":{\"from\":\"' + from + '\",\"to\":\"' + to + '\"},' +\n '\"targets\":[{\"queryType\":\"query\",\"target\":\"query\",\"refId\":\"A\",\"type\":\"timeserie\",\"datasourceId\":' + this.id + ',' +\n '\"query\":\"' + q + '\"}]}';\n const query = JSON.parse(str);\n return this.doTsdbRequest(query).then(response => {\n const res = handleTsdbResponse(response);\n if (res && res.data && res.data.length) {\n let rows = res.data[0].rows;\n rows = rows.map(item => item[0]);\n return rows;\n }\n else {\n return [];\n }\n }).then(mapToTextValue);\n }\n doRequest(options) {\n options.withCredentials = this.withCredentials;\n options.headers = this.headers;\n return this.backendSrv.datasourceRequest(options);\n }\n doTsdbRequest(options) {\n const tsdbRequestData = {\n queries: options.targets,\n };\n if (options.range) {\n tsdbRequestData.from = options.range.from.valueOf().toString();\n tsdbRequestData.to = options.range.to.valueOf().toString();\n }\n return this.backendSrv.datasourceRequest({\n url: '/api/tsdb/query',\n method: 'POST',\n data: tsdbRequestData\n });\n }\n buildQueryParameters(options) {\n //remove placeholder targets\n options.targets = _.filter(options.targets, target => {\n return target.target !== 'select metric';\n });\n options.targets = _.map(options.targets, target => {\n return {\n queryType: 'query',\n target: this.templateSrv.replace(target.target, options.scopedVars, 'regex'),\n refId: target.refId,\n hide: target.hide,\n type: target.type || 'timeserie',\n datasourceId: this.id,\n query: this.replaceQueryParameters(target, options),\n xcol: target.xcol,\n ycol: target.ycol\n };\n });\n return options;\n }\n replaceQueryParameters(target, options) {\n let query = this.templateSrv.replace(target.query, options.scopedVars, function (value, variable) {\n if (typeof value == \"object\" && (variable.multi || variable.includeAll)) {\n const a = [];\n value.forEach(function (v) {\n if (variable.name == variable.label)\n a.push('\"' + variable.name + '\":\"' + v + '\"');\n else\n a.push('\"' + v + '\"');\n });\n return a.join(\" OR \");\n }\n if (_.isArray(value)) {\n return value.join(' OR ');\n }\n return value;\n });\n const re = /\\$([0-9]+)([dmhs])/g;\n const reArray = query.match(re);\n _(reArray).forEach(function (col) {\n const old = col;\n col = col.replace(\"$\", '');\n let sec = 1;\n if (col.indexOf(\"s\") != -1)\n sec = 1;\n else if (col.indexOf(\"m\") != -1)\n sec = 60;\n else if (col.indexOf(\"h\") != -1)\n sec = 3600;\n else if (col.indexOf(\"d\") != -1)\n sec = 3600 * 24;\n col = col.replace(/[smhd]/g, '');\n let v = parseInt(col);\n v = v * sec;\n console.log(old, v, col, sec, query);\n query = query.replace(old, v);\n });\n if (query.indexOf(\"#time_end\") != -1) {\n query = query.replace(\"#time_end\", parseInt(String(options.range.to._d.getTime() / 1000)));\n }\n if (query.indexOf(\"#time_begin\") != -1) {\n query = query.replace(\"#time_begin\", parseInt(String(options.range.from._d.getTime() / 1000)));\n }\n return query;\n }\n getTagKeys(options) {\n return new Promise((resolve) => {\n this.doRequest({\n url: this.url + '/tag-keys',\n method: 'POST',\n data: options\n }).then(result => {\n return resolve(result.data);\n });\n });\n }\n getTagValues(options) {\n return new Promise((resolve) => {\n this.doRequest({\n url: this.url + '/tag-values',\n method: 'POST',\n data: options\n }).then(result => {\n return resolve(result.data);\n });\n });\n }\n}\nexport function handleTsdbResponse(response) {\n const res = [];\n _.forEach(response.data.results, r => {\n _.forEach(r.series, s => {\n res.push({ target: s.name, datapoints: s.points });\n });\n _.forEach(r.tables, t => {\n t.type = 'table';\n t.refId = r.refId;\n res.push(t);\n });\n });\n response.data = res;\n console.log(res);\n return response;\n}\nexport function mapToTextValue(result) {\n return _.map(result, (d, i) => {\n if (d && d.text && d.value) {\n return { text: d.text, value: d.value };\n }\n else if (_.isObject(d)) {\n return { text: d, value: i };\n }\n return { text: d, value: d };\n });\n}\n//# sourceMappingURL=datasource.js.map"]} -------------------------------------------------------------------------------- /dist/datasource.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | interface TSDBRequest { 4 | queries: any[]; 5 | from?: string; 6 | to?: string; 7 | } 8 | 9 | interface TSDBQuery { 10 | datasourceId: string; 11 | target: any; 12 | queryType?: TSDBQueryType; 13 | refId?: string; 14 | hide?: boolean; 15 | type?: 'timeserie' | 'table'; 16 | } 17 | 18 | type TSDBQueryType = 'query' | 'search'; 19 | 20 | interface TSDBRequestOptions { 21 | range?: { 22 | from: any; 23 | to: any; 24 | }; 25 | targets: TSDBQuery[]; 26 | } 27 | 28 | export class GenericDatasource { 29 | name: string; 30 | type: string; 31 | id: string; 32 | url: string; 33 | withCredentials: boolean; 34 | instanceSettings: any; 35 | headers: any; 36 | 37 | /** @ngInject */ 38 | constructor(instanceSettings, private backendSrv, private templateSrv) { 39 | this.type = instanceSettings.type; 40 | this.url = instanceSettings.url; 41 | this.name = instanceSettings.name; 42 | this.id = instanceSettings.id; 43 | this.withCredentials = instanceSettings.withCredentials; 44 | this.headers = {'Content-Type': 'application/json'}; 45 | if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) { 46 | this.headers['Authorization'] = instanceSettings.basicAuth; 47 | } 48 | } 49 | 50 | query(options) { 51 | const query = this.buildQueryParameters(options); 52 | query.targets = query.targets.filter(t => !t.hide); 53 | 54 | if (query.targets.length <= 0) { 55 | return Promise.resolve({data: []}); 56 | } 57 | 58 | return this.doTsdbRequest(query).then(handleTsdbResponse); 59 | } 60 | 61 | testDatasource() { 62 | const to = new Date().getTime(); 63 | const from = to - 5000; 64 | const str = '{"requestId":"Q100","timezone":"","range":{"from":"' + from + '","to":"' + to + '"},' + 65 | '"targets":[{"queryType":"query","target":"count","refId":"A","type":"timeserie","datasourceId":' + this.id + ',' + 66 | '"query":"* | select count(*) as count","ycol":"count"}]}'; 67 | const query = JSON.parse(str); 68 | return this.doTsdbRequest(query).then(response => { 69 | if (response.status === 200) { 70 | return { status: "success", message: "Data source is working", title: "Success" }; 71 | } else { 72 | return { status: "failed", message: "Data source is not working", title: "Error" }; 73 | } 74 | }).catch(() => { 75 | return { status: "failed", message: "Data source is not working", title: "Error" }; 76 | }); 77 | } 78 | 79 | annotationQuery(options) { 80 | const query = this.templateSrv.replace(options.annotation.query, {}, 'glob'); 81 | const annotationQuery = { 82 | range: options.range, 83 | annotation: { 84 | name: options.annotation.name, 85 | datasource: options.annotation.datasource, 86 | enable: options.annotation.enable, 87 | iconColor: options.annotation.iconColor, 88 | query: query 89 | }, 90 | rangeRaw: options.rangeRaw 91 | }; 92 | 93 | return this.doRequest({ 94 | url: this.url + '/annotations', 95 | method: 'POST', 96 | data: annotationQuery 97 | }).then(result => { 98 | return result.data; 99 | }); 100 | } 101 | 102 | metricFindQuery(q) { 103 | q = this.templateSrv.replace(q, {}, 'glob'); 104 | const to = this.templateSrv.timeRange.to.unix()*1000; 105 | const from = this.templateSrv.timeRange.from.unix()*1000; 106 | const str = '{"requestId":"Q100","timezone":"","range":{"from":"' + from + '","to":"' + to + '"},' + 107 | '"targets":[{"queryType":"query","target":"query","refId":"A","type":"timeserie","datasourceId":' + this.id + ',' + 108 | '"query":"'+ q +'"}]}'; 109 | const query = JSON.parse(str); 110 | return this.doTsdbRequest(query).then(response => { 111 | const res = handleTsdbResponse(response); 112 | if (res && res.data && res.data.length) { 113 | let rows = res.data[0].rows; 114 | rows = rows.map(item => item[0]); 115 | return rows; 116 | } else { 117 | return []; 118 | } 119 | }).then(mapToTextValue); 120 | } 121 | 122 | doRequest(options) { 123 | options.withCredentials = this.withCredentials; 124 | options.headers = this.headers; 125 | 126 | return this.backendSrv.datasourceRequest(options); 127 | } 128 | 129 | doTsdbRequest(options: TSDBRequestOptions) { 130 | const tsdbRequestData: TSDBRequest = { 131 | queries: options.targets, 132 | }; 133 | 134 | if (options.range) { 135 | tsdbRequestData.from = options.range.from.valueOf().toString(); 136 | tsdbRequestData.to = options.range.to.valueOf().toString(); 137 | } 138 | 139 | return this.backendSrv.datasourceRequest({ 140 | url: '/api/tsdb/query', 141 | method: 'POST', 142 | data: tsdbRequestData 143 | }); 144 | } 145 | 146 | buildQueryParameters(options: any): TSDBRequestOptions { 147 | //remove placeholder targets 148 | options.targets = _.filter(options.targets, target => { 149 | return target.target !== 'select metric'; 150 | }); 151 | 152 | options.targets = _.map(options.targets, target => { 153 | return { 154 | queryType: 'query', 155 | target: this.templateSrv.replace(target.target, options.scopedVars, 'regex'), 156 | refId: target.refId, 157 | hide: target.hide, 158 | type: target.type || 'timeserie', 159 | datasourceId: this.id, 160 | query: this.replaceQueryParameters(target, options), 161 | xcol: target.xcol, 162 | ycol: target.ycol 163 | }; 164 | }); 165 | 166 | return options; 167 | } 168 | 169 | replaceQueryParameters(target,options): TSDBRequestOptions { 170 | let query = this.templateSrv.replace(target.query, options.scopedVars, function (value, variable) { 171 | if (typeof value == "object" && (variable.multi || variable.includeAll)) { 172 | const a = []; 173 | value.forEach(function (v) { 174 | if (variable.name == variable.label) a.push('"' + variable.name + '":"' + v + '"');else a.push('"' + v + '"'); 175 | }); 176 | return a.join(" OR "); 177 | } 178 | if (_.isArray(value)) { 179 | return value.join(' OR '); 180 | } 181 | return value; 182 | }); 183 | const re = /\$([0-9]+)([dmhs])/g; 184 | const reArray = query.match(re); 185 | _(reArray).forEach(function (col) { 186 | const old = col; 187 | col = col.replace("$", ''); 188 | let sec = 1; 189 | if (col.indexOf("s") != -1) sec = 1;else if (col.indexOf("m") != -1) sec = 60;else if (col.indexOf("h") != -1) sec = 3600;else if (col.indexOf("d") != -1) sec = 3600 * 24; 190 | col = col.replace(/[smhd]/g, ''); 191 | let v = parseInt(col); 192 | v = v * sec; 193 | console.log(old, v, col, sec, query); 194 | query = query.replace(old, v); 195 | }); 196 | if (query.indexOf("#time_end") != -1) { 197 | query = query.replace("#time_end", parseInt(String(options.range.to._d.getTime() / 1000))); 198 | } 199 | if (query.indexOf("#time_begin") != -1) { 200 | query = query.replace("#time_begin", parseInt(String(options.range.from._d.getTime() / 1000))); 201 | } 202 | return query; 203 | } 204 | 205 | getTagKeys(options) { 206 | return new Promise((resolve) => { 207 | this.doRequest({ 208 | url: this.url + '/tag-keys', 209 | method: 'POST', 210 | data: options 211 | }).then(result => { 212 | return resolve(result.data); 213 | }); 214 | }); 215 | } 216 | 217 | getTagValues(options) { 218 | return new Promise((resolve) => { 219 | this.doRequest({ 220 | url: this.url + '/tag-values', 221 | method: 'POST', 222 | data: options 223 | }).then(result => { 224 | return resolve(result.data); 225 | }); 226 | }); 227 | } 228 | } 229 | 230 | export function handleTsdbResponse(response) { 231 | const res= []; 232 | _.forEach(response.data.results, r => { 233 | _.forEach(r.series, s => { 234 | res.push({target: s.name, datapoints: s.points}); 235 | }); 236 | _.forEach(r.tables, t => { 237 | t.type = 'table'; 238 | t.refId = r.refId; 239 | res.push(t); 240 | }); 241 | }); 242 | 243 | response.data = res; 244 | console.log(res); 245 | return response; 246 | } 247 | 248 | export function mapToTextValue(result) { 249 | return _.map(result, (d, i) => { 250 | if (d && d.text && d.value) { 251 | return { text: d.text, value: d.value }; 252 | } else if (_.isObject(d)) { 253 | return { text: d, value: i}; 254 | } 255 | return { text: d, value: d }; 256 | }); 257 | } 258 | -------------------------------------------------------------------------------- /dist/img/sls_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/dist/img/sls_logo.jpg -------------------------------------------------------------------------------- /dist/module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./datasource', './query_ctrl'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var GenericDatasource, GenericDatasourceQueryCtrl, GenericConfigCtrl, GenericQueryOptionsCtrl, GenericAnnotationsQueryCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | return { 15 | setters: [function (_datasource) { 16 | GenericDatasource = _datasource.GenericDatasource; 17 | }, function (_query_ctrl) { 18 | GenericDatasourceQueryCtrl = _query_ctrl.GenericDatasourceQueryCtrl; 19 | }], 20 | execute: function () { 21 | _export('ConfigCtrl', GenericConfigCtrl = function GenericConfigCtrl() { 22 | _classCallCheck(this, GenericConfigCtrl); 23 | }); 24 | 25 | GenericConfigCtrl.templateUrl = 'partials/config.html'; 26 | 27 | _export('QueryOptionsCtrl', GenericQueryOptionsCtrl = function GenericQueryOptionsCtrl() { 28 | _classCallCheck(this, GenericQueryOptionsCtrl); 29 | }); 30 | 31 | GenericQueryOptionsCtrl.templateUrl = 'partials/query.options.html'; 32 | 33 | _export('AnnotationsQueryCtrl', GenericAnnotationsQueryCtrl = function GenericAnnotationsQueryCtrl() { 34 | _classCallCheck(this, GenericAnnotationsQueryCtrl); 35 | }); 36 | 37 | GenericAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html'; 38 | 39 | _export('Datasource', GenericDatasource); 40 | 41 | _export('QueryCtrl', GenericDatasourceQueryCtrl); 42 | 43 | _export('ConfigCtrl', GenericConfigCtrl); 44 | 45 | _export('QueryOptionsCtrl', GenericQueryOptionsCtrl); 46 | 47 | _export('AnnotationsQueryCtrl', GenericAnnotationsQueryCtrl); 48 | } 49 | }; 50 | }); 51 | //# sourceMappingURL=module.js.map 52 | -------------------------------------------------------------------------------- /dist/module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/module.js"],"names":["GenericDatasource","GenericDatasourceQueryCtrl","GenericConfigCtrl","templateUrl","GenericQueryOptionsCtrl","GenericAnnotationsQueryCtrl"],"mappings":";;;;;;;;;;;;;;;AAASA,uB,eAAAA,iB;;AACAC,gC,eAAAA,0B;;;4BACHC,iB;;;;AAENA,wBAAkBC,WAAlB,GAAgC,sBAAhC;;kCACMC,uB;;;;AAENA,8BAAwBD,WAAxB,GAAsC,6BAAtC;;sCACME,2B;;;;AAENA,kCAA4BF,WAA5B,GAA0C,kCAA1C;;4BACSH,iB;;2BAAiCC,0B;;4BAAyCC,iB;;kCAAiCE,uB;;sCAA6CC,2B","file":"module.js","sourcesContent":["import { GenericDatasource } from './datasource';\nimport { GenericDatasourceQueryCtrl } from './query_ctrl';\nclass GenericConfigCtrl {\n}\nGenericConfigCtrl.templateUrl = 'partials/config.html';\nclass GenericQueryOptionsCtrl {\n}\nGenericQueryOptionsCtrl.templateUrl = 'partials/query.options.html';\nclass GenericAnnotationsQueryCtrl {\n}\nGenericAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html';\nexport { GenericDatasource as Datasource, GenericDatasourceQueryCtrl as QueryCtrl, GenericConfigCtrl as ConfigCtrl, GenericQueryOptionsCtrl as QueryOptionsCtrl, GenericAnnotationsQueryCtrl as AnnotationsQueryCtrl };\n//# sourceMappingURL=module.js.map"]} -------------------------------------------------------------------------------- /dist/module.ts: -------------------------------------------------------------------------------- 1 | import {GenericDatasource} from './datasource'; 2 | import {GenericDatasourceQueryCtrl} from './query_ctrl'; 3 | 4 | class GenericConfigCtrl { 5 | static templateUrl = 'partials/config.html'; 6 | } 7 | 8 | class GenericQueryOptionsCtrl { 9 | static templateUrl = 'partials/query.options.html'; 10 | } 11 | 12 | class GenericAnnotationsQueryCtrl { 13 | static templateUrl = 'partials/annotations.editor.html' 14 | } 15 | 16 | export { 17 | GenericDatasource as Datasource, 18 | GenericDatasourceQueryCtrl as QueryCtrl, 19 | GenericConfigCtrl as ConfigCtrl, 20 | GenericQueryOptionsCtrl as QueryOptionsCtrl, 21 | GenericAnnotationsQueryCtrl as AnnotationsQueryCtrl 22 | }; 23 | -------------------------------------------------------------------------------- /dist/partials/annotations.editor.html: -------------------------------------------------------------------------------- 1 | 2 |
Query
3 |
4 |
5 | 6 |
7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /dist/partials/config.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

log service details

4 |
5 |
6 |
Project
8 |
logstore
10 |
11 |
12 |
13 |
14 |
AccessKeyId
16 |
AccessKeySecret
18 |
19 |
20 |

Default query settings

21 |
22 |
Group by time interval
24 |
25 | -------------------------------------------------------------------------------- /dist/partials/query.editor.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
6 | 7 |
8 |
9 |
12 |
15 | 16 |
17 |
-------------------------------------------------------------------------------- /dist/partials/query.options.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /dist/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LogService", 3 | "id": "grafana-log-service-datasource", 4 | "type": "datasource", 5 | 6 | "partials": { 7 | "config": "public/app/plugins/datasource/simplejson/partials/config.html" 8 | }, 9 | 10 | "metrics": true, 11 | "annotations": true, 12 | "backend": true, 13 | "alerting": true, 14 | "executable": "aliyun-log-plugin", 15 | 16 | "info": { 17 | "description": "aliyun log datasource", 18 | "author": { 19 | "name": "aliyun log service dev", 20 | "url": "https://aliyun.com/product/sls" 21 | }, 22 | "logos": { 23 | "small": "img/sls_logo.jpg", 24 | "large": "img/sls_logo.jpg" 25 | }, 26 | "links": [ 27 | { 28 | "name": "GitHub", 29 | "url": "https://github.com/grafana/simple-json-backend-datasource" 30 | }, 31 | { 32 | "name": "MIT License", 33 | "url": "https://github.com/grafana/simple-json-backend-datasource/blob/master/LICENSE" 34 | } 35 | ], 36 | "version": "1.0.0", 37 | "updated": "2019-11-09" 38 | }, 39 | 40 | "dependencies": { 41 | "grafanaVersion": "6.x.x", 42 | "plugins": [] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /dist/query_ctrl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['app/plugins/sdk', './css/query-editor.css!'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var QueryCtrl, _createClass, GenericDatasourceQueryCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | function _possibleConstructorReturn(self, call) { 15 | if (!self) { 16 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 17 | } 18 | 19 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 20 | } 21 | 22 | function _inherits(subClass, superClass) { 23 | if (typeof superClass !== "function" && superClass !== null) { 24 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 25 | } 26 | 27 | subClass.prototype = Object.create(superClass && superClass.prototype, { 28 | constructor: { 29 | value: subClass, 30 | enumerable: false, 31 | writable: true, 32 | configurable: true 33 | } 34 | }); 35 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 36 | } 37 | 38 | return { 39 | setters: [function (_appPluginsSdk) { 40 | QueryCtrl = _appPluginsSdk.QueryCtrl; 41 | }, function (_cssQueryEditorCss) {}], 42 | execute: function () { 43 | _createClass = function () { 44 | function defineProperties(target, props) { 45 | for (var i = 0; i < props.length; i++) { 46 | var descriptor = props[i]; 47 | descriptor.enumerable = descriptor.enumerable || false; 48 | descriptor.configurable = true; 49 | if ("value" in descriptor) descriptor.writable = true; 50 | Object.defineProperty(target, descriptor.key, descriptor); 51 | } 52 | } 53 | 54 | return function (Constructor, protoProps, staticProps) { 55 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 56 | if (staticProps) defineProperties(Constructor, staticProps); 57 | return Constructor; 58 | }; 59 | }(); 60 | 61 | _export('GenericDatasourceQueryCtrl', GenericDatasourceQueryCtrl = function (_QueryCtrl) { 62 | _inherits(GenericDatasourceQueryCtrl, _QueryCtrl); 63 | 64 | /** @ngInject */ 65 | function GenericDatasourceQueryCtrl($scope, $injector) { 66 | _classCallCheck(this, GenericDatasourceQueryCtrl); 67 | 68 | var _this = _possibleConstructorReturn(this, (GenericDatasourceQueryCtrl.__proto__ || Object.getPrototypeOf(GenericDatasourceQueryCtrl)).call(this, $scope, $injector)); 69 | 70 | _this.target.target = _this.target.ycol; 71 | _this.target.type = _this.target.type || 'timeserie'; 72 | return _this; 73 | } 74 | 75 | _createClass(GenericDatasourceQueryCtrl, [{ 76 | key: 'getOptions', 77 | value: function getOptions(query) { 78 | return this.datasource.metricFindQuery(query || ''); 79 | } 80 | }, { 81 | key: 'toggleEditorMode', 82 | value: function toggleEditorMode() { 83 | this.target.rawQuery = !this.target.rawQuery; 84 | } 85 | }, { 86 | key: 'onChangeInternal', 87 | value: function onChangeInternal() { 88 | this.panelCtrl.refresh(); // Asks the panel to refresh data. 89 | } 90 | }]); 91 | 92 | return GenericDatasourceQueryCtrl; 93 | }(QueryCtrl)); 94 | 95 | _export('GenericDatasourceQueryCtrl', GenericDatasourceQueryCtrl); 96 | 97 | GenericDatasourceQueryCtrl.templateUrl = 'partials/query.editor.html'; 98 | //# sourceMappingURL=query_ctrl.js.map 99 | } 100 | }; 101 | }); 102 | //# sourceMappingURL=query_ctrl.js.map 103 | -------------------------------------------------------------------------------- /dist/query_ctrl.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/query_ctrl.js"],"names":["QueryCtrl","GenericDatasourceQueryCtrl","$scope","$injector","target","ycol","type","query","datasource","metricFindQuery","rawQuery","panelCtrl","refresh","templateUrl"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAASA,qB,kBAAAA,S;;;;;;;;;;;;;;;;;;;;;kDAEIC,0B;;;AACT;AACA,oDAAYC,MAAZ,EAAoBC,SAApB,EAA+B;AAAA;;AAAA,wKACrBD,MADqB,EACbC,SADa;;AAE3B,0BAAKC,MAAL,CAAYA,MAAZ,GAAqB,MAAKA,MAAL,CAAYC,IAAjC;AACA,0BAAKD,MAAL,CAAYE,IAAZ,GAAmB,MAAKF,MAAL,CAAYE,IAAZ,IAAoB,WAAvC;AAH2B;AAI9B;;;;+CACUC,K,EAAO;AACd,+BAAO,KAAKC,UAAL,CAAgBC,eAAhB,CAAgCF,SAAS,EAAzC,CAAP;AACH;;;uDACkB;AACf,6BAAKH,MAAL,CAAYM,QAAZ,GAAuB,CAAC,KAAKN,MAAL,CAAYM,QAApC;AACH;;;uDACkB;AACf,6BAAKC,SAAL,CAAeC,OAAf,GADe,CACW;AAC7B;;;;cAf2CZ,S;;;;AAiBhDC,uCAA2BY,WAA3B,GAAyC,4BAAzC;AACA","file":"query_ctrl.js","sourcesContent":["import { QueryCtrl } from 'app/plugins/sdk';\nimport './css/query-editor.css!';\nexport class GenericDatasourceQueryCtrl extends QueryCtrl {\n /** @ngInject */\n constructor($scope, $injector) {\n super($scope, $injector);\n this.target.target = this.target.ycol;\n this.target.type = this.target.type || 'timeserie';\n }\n getOptions(query) {\n return this.datasource.metricFindQuery(query || '');\n }\n toggleEditorMode() {\n this.target.rawQuery = !this.target.rawQuery;\n }\n onChangeInternal() {\n this.panelCtrl.refresh(); // Asks the panel to refresh data.\n }\n}\nGenericDatasourceQueryCtrl.templateUrl = 'partials/query.editor.html';\n//# sourceMappingURL=query_ctrl.js.map"]} -------------------------------------------------------------------------------- /dist/query_ctrl.ts: -------------------------------------------------------------------------------- 1 | import {QueryCtrl} from 'app/plugins/sdk'; 2 | import './css/query-editor.css!'; 3 | 4 | export class GenericDatasourceQueryCtrl extends QueryCtrl { 5 | static templateUrl = 'partials/query.editor.html'; 6 | 7 | scope: any; 8 | 9 | /** @ngInject */ 10 | constructor($scope, $injector) { 11 | super($scope, $injector); 12 | this.target.target = this.target.ycol; 13 | this.target.type = this.target.type || 'timeserie'; 14 | } 15 | 16 | getOptions(query) { 17 | return this.datasource.metricFindQuery(query || ''); 18 | } 19 | 20 | toggleEditorMode() { 21 | this.target.rawQuery = !this.target.rawQuery; 22 | } 23 | 24 | onChangeInternal() { 25 | this.panelCtrl.refresh(); // Asks the panel to refresh data. 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /img/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/img/demo1.png -------------------------------------------------------------------------------- /img/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/img/demo2.png -------------------------------------------------------------------------------- /img/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/img/demo3.png -------------------------------------------------------------------------------- /img/demo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/img/demo4.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | "transform": { 4 | "^.+\\.(ts|tsx)$": "ts-jest" 5 | }, 6 | "testRegex": "(\\.|/)([jt]est)\\.[jt]s$", 7 | "moduleFileExtensions": [ 8 | "ts", 9 | "tsx", 10 | "js", 11 | "jsx", 12 | "json" 13 | ], 14 | "coverageDirectory": "./tmp/coverage/", 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grafana-sls", 3 | "version": "1.0.0", 4 | "description": "Aliyun Log Datasource (backend version)", 5 | "private": true, 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "webpack --config webpack/webpack.prod.conf.js", 9 | "dev": "webpack --config webpack/webpack.dev.conf.js", 10 | "watch": "webpack --config webpack/webpack.dev.conf.js --watch", 11 | "test": "./node_modules/grunt-cli/bin/grunt mochaTest", 12 | "jest": "jest --config jest.config.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/grafana/simple-json-backend-datasource.git" 17 | }, 18 | "author": "Grafana Labs", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/grafana/simple-json-backend-datasource/issues" 22 | }, 23 | "homepage": "https://github.com/grafana/simple-json-backend-datasource", 24 | "engineStrict": true, 25 | "devDependencies": { 26 | "@types/angular": "^1.6.43", 27 | "@types/grafana": "github:CorpGlory/types-grafana", 28 | "@types/jest": "^24.0.11", 29 | "@types/lodash": "^4.14.116", 30 | "babel-plugin-transform-es2015-for-of": "^6.6.0", 31 | "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", 32 | "babel-preset-es2015": "^6.24.1", 33 | "chai": "~3.5.0", 34 | "clean-webpack-plugin": "^2.0.1", 35 | "copy-webpack-plugin": "^5.0.2", 36 | "css-loader": "^2.1.1", 37 | "grunt": "^1.0.4", 38 | "grunt-babel": "~6.0.0", 39 | "grunt-cli": "^1.2.0", 40 | "grunt-contrib-clean": "^1.1.0", 41 | "grunt-contrib-copy": "^1.0.0", 42 | "grunt-contrib-uglify": "^2.3.0", 43 | "grunt-contrib-watch": "^1.0.0", 44 | "grunt-execute": "~0.2.2", 45 | "grunt-mocha-test": "^0.13.2", 46 | "grunt-systemjs-builder": "^1.0.0", 47 | "jest": "^24.7.1", 48 | "jsdom": "~9.12.0", 49 | "load-grunt-tasks": "^3.5.2", 50 | "lodash": "^4.17.4", 51 | "mocha": "^3.2.0", 52 | "ng-annotate-webpack-plugin": "^0.3.0", 53 | "prunk": "^1.3.0", 54 | "q": "^1.5.0", 55 | "style-loader": "^0.23.1", 56 | "ts-jest": "^24.0.2", 57 | "ts-loader": "^5.3.3", 58 | "tslint": "^5.15.0", 59 | "typescript": "^3.4.3", 60 | "webpack": "^4.29.6", 61 | "webpack-cli": "^3.3.0" 62 | }, 63 | "dependencies": {} 64 | } 65 | -------------------------------------------------------------------------------- /pkg/datasource.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | sls "github.com/aliyun/aliyun-log-go-sdk" 7 | "github.com/grafana/grafana_plugin_model/go/datasource" 8 | "github.com/hashicorp/go-hclog" 9 | "github.com/hashicorp/go-plugin" 10 | "golang.org/x/net/context" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | type SlsDatasource struct { 17 | plugin.NetRPCUnsupportedPlugin 18 | logger hclog.Logger 19 | } 20 | 21 | func (ds *SlsDatasource) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) { 22 | 23 | ds.logger.Debug("Query", "datasource", tsdbReq.Datasource.Name, "TimeRange", tsdbReq.TimeRange) 24 | 25 | logSource := &LogSource{} 26 | 27 | err := json.Unmarshal([]byte(tsdbReq.Datasource.JsonData), &logSource) 28 | if err != nil { 29 | ds.logger.Error("Unmarshal logSource", "error ", err) 30 | return nil, err 31 | } 32 | 33 | accessKeyId := tsdbReq.Datasource.DecryptedSecureJsonData["accessKeyId"] 34 | if len(accessKeyId) == 0 { 35 | ds.logger.Error("AccessKeyID cannot be null") 36 | return nil, errors.New("AccessKeyID cannot be null") 37 | } 38 | 39 | accessKeySecret := tsdbReq.Datasource.DecryptedSecureJsonData["accessKeySecret"] 40 | if len(accessKeySecret) == 0 { 41 | ds.logger.Error("AccessKeySecret cannot be null") 42 | return nil, errors.New("AccessKeySecret cannot be null") 43 | } 44 | 45 | client := &sls.Client{ 46 | Endpoint: tsdbReq.Datasource.Url, 47 | AccessKeyID: accessKeyId, 48 | AccessKeySecret: accessKeySecret, 49 | } 50 | 51 | queries := tsdbReq.Queries 52 | 53 | from := tsdbReq.TimeRange.FromEpochMs / 1000 54 | to := tsdbReq.TimeRange.ToEpochMs / 1000 55 | 56 | var results []*datasource.QueryResult 57 | 58 | ch := make(chan *datasource.QueryResult, len(queries)) 59 | 60 | for _, query := range queries { 61 | go ds.QueryLogs(ch, query, client, logSource, from, to) 62 | } 63 | c := 0 64 | for result := range ch { 65 | c = c + 1 66 | if c == len(queries) { 67 | close(ch) 68 | } 69 | results = append(results, result) 70 | } 71 | rt := &datasource.DatasourceResponse{ 72 | Results: results, 73 | } 74 | return rt, nil 75 | } 76 | 77 | func (ds *SlsDatasource) GetValue(v string) *datasource.RowValue { 78 | value := &datasource.RowValue{} 79 | intValue, err := strconv.ParseInt(v, 10, 10) 80 | if err == nil { 81 | value.Int64Value = intValue 82 | value.Kind = datasource.RowValue_TYPE_INT64 83 | } else { 84 | floatValue, err := strconv.ParseFloat(v, 10) 85 | if err == nil { 86 | value.DoubleValue = floatValue 87 | value.Kind = datasource.RowValue_TYPE_DOUBLE 88 | } else { 89 | value.StringValue = v 90 | value.Kind = datasource.RowValue_TYPE_STRING 91 | } 92 | } 93 | return value 94 | } 95 | 96 | func (ds *SlsDatasource) ParseTimestamp(v string) (int64, error) { 97 | floatV, err := strconv.ParseFloat(v, 10) 98 | if err != nil { 99 | return 0, err 100 | } 101 | int64V := int64(floatV) 102 | return int64V * 1000, nil 103 | } 104 | 105 | func (ds *SlsDatasource) SortLogs(logs []map[string]string, xcol string) { 106 | sort.Slice(logs, func(i, j int) bool { 107 | timestamp1, err := ds.ParseTimestamp(logs[i][xcol]) 108 | if err != nil { 109 | ds.logger.Error("SortLogs1", "error ", err) 110 | } 111 | timestamp2, err := ds.ParseTimestamp(logs[j][xcol]) 112 | if err != nil { 113 | ds.logger.Error("SortLogs2", "error ", err) 114 | } 115 | if timestamp1 < timestamp2 { 116 | return true 117 | } 118 | return false 119 | }) 120 | } 121 | 122 | func (ds *SlsDatasource) QueryLogs(ch chan *datasource.QueryResult, query *datasource.Query, client *sls.Client, logSource *LogSource, from int64, to int64) { 123 | modelJson := query.ModelJson 124 | 125 | queryInfo := &QueryInfo{} 126 | err := json.Unmarshal([]byte(modelJson), &queryInfo) 127 | if err != nil { 128 | ds.logger.Error("Unmarshal queryInfo", "error ", err) 129 | ch <- &datasource.QueryResult{ 130 | Error: err.Error(), 131 | } 132 | return 133 | } 134 | getLogsResp, err := client.GetLogs(logSource.Project, logSource.LogStore, "", 135 | from, to, queryInfo.Query, 0, 0, true) 136 | if err != nil { 137 | ds.logger.Error("GetLogs ", "query : ", queryInfo.Query, "error ", err) 138 | ch <- &datasource.QueryResult{ 139 | Error: err.Error(), 140 | } 141 | return 142 | } 143 | logs := getLogsResp.Logs 144 | 145 | var series []*datasource.TimeSeries 146 | var tables []*datasource.Table 147 | xcol := queryInfo.Xcol 148 | var ycols []string 149 | isFlowGraph := strings.Contains(queryInfo.Ycol, "#:#") 150 | if isFlowGraph { 151 | ycols = strings.Split(queryInfo.Ycol, "#:#") 152 | } else { 153 | ycols = strings.Split(queryInfo.Ycol, ",") 154 | } 155 | if isFlowGraph { 156 | ds.BuildFlowGraph(ch, logs, xcol, ycols, query.RefId) 157 | return 158 | } else if xcol == "pie" { 159 | ds.BuildPieGraph(ch, logs, xcol, ycols, query.RefId) 160 | return 161 | } else if xcol != "" && xcol != "map" && xcol != "pie" && xcol != "bar" && xcol != "table" { 162 | ds.BuildTimingGraph(ch, logs, xcol, ycols, &series) 163 | } else { 164 | ds.BuildTable(ch, logs, xcol, ycols, &tables) 165 | } 166 | ch <- &datasource.QueryResult{ 167 | RefId: query.RefId, 168 | Series: series, 169 | Tables: tables, 170 | } 171 | } 172 | 173 | func (ds *SlsDatasource) BuildFlowGraph(ch chan *datasource.QueryResult, logs []map[string]string, xcol string, ycols []string, refId string) { 174 | ds.SortLogs(logs, xcol) 175 | if len(ycols) < 2 { 176 | ch <- &datasource.QueryResult{ 177 | Error: "The len of ycols must greater than 2 ", 178 | } 179 | } 180 | var set map[string]bool 181 | set = make(map[string]bool) 182 | for _, alog := range logs { 183 | set[alog[ycols[0]]] = true 184 | } 185 | var series []*datasource.TimeSeries 186 | for flowId := range set { 187 | var points []*datasource.Point 188 | for _, alog := range logs { 189 | if flowId == alog[ycols[0]] { 190 | if alog[ycols[1]] == "null" { 191 | continue 192 | } 193 | floatV, err := strconv.ParseFloat(alog[ycols[1]], 10) 194 | if err != nil { 195 | ds.logger.Error("ParseFloat ycols[1]", "error ", err) 196 | ch <- &datasource.QueryResult{ 197 | Error: err.Error(), 198 | } 199 | return 200 | } 201 | floatV1, err := strconv.ParseFloat(alog[xcol], 10) 202 | if err != nil { 203 | ds.logger.Error("ParseFloat xcol", "error ", err) 204 | ch <- &datasource.QueryResult{ 205 | Error: err.Error(), 206 | } 207 | return 208 | } 209 | int64V := int64(floatV1) 210 | point := &datasource.Point{ 211 | Timestamp: int64V * 1000, 212 | Value: floatV, 213 | } 214 | points = append(points, point) 215 | } 216 | } 217 | timeSeries := &datasource.TimeSeries{ 218 | Name: flowId, 219 | Points: points, 220 | } 221 | series = append(series, timeSeries) 222 | } 223 | ch <- &datasource.QueryResult{ 224 | RefId: refId, 225 | Series: series, 226 | } 227 | } 228 | 229 | func (ds *SlsDatasource) BuildPieGraph(ch chan *datasource.QueryResult, logs []map[string]string, xcol string, ycols []string, refId string) { 230 | if len(ycols) < 2 { 231 | ch <- &datasource.QueryResult{ 232 | Error: "The len of ycols must greater than 2 ", 233 | } 234 | } 235 | var series []*datasource.TimeSeries 236 | for _, alog := range logs { 237 | if alog[ycols[1]] == "null" { 238 | continue 239 | } 240 | floatV, err := strconv.ParseFloat(alog[ycols[1]], 10) 241 | if err != nil { 242 | ds.logger.Error("ParseFloat ycols[1]", "error ", err) 243 | ch <- &datasource.QueryResult{ 244 | Error: err.Error(), 245 | } 246 | return 247 | } 248 | point := &datasource.Point{ 249 | Timestamp: 0, 250 | Value: floatV, 251 | } 252 | var points []*datasource.Point 253 | points = append(points, point) 254 | timeSeries := &datasource.TimeSeries{ 255 | Name: alog[ycols[0]], 256 | Points: points, 257 | } 258 | series = append(series, timeSeries) 259 | } 260 | 261 | ch <- &datasource.QueryResult{ 262 | RefId: refId, 263 | Series: series, 264 | } 265 | } 266 | 267 | func (ds *SlsDatasource) BuildTimingGraph(ch chan *datasource.QueryResult, logs []map[string]string, xcol string, ycols []string, series *[]*datasource.TimeSeries) { 268 | ds.SortLogs(logs, xcol) 269 | for _, ycol := range ycols { 270 | var points []*datasource.Point 271 | for _, alog := range logs { 272 | var timestamp int64 273 | var value float64 274 | for k, v := range alog { 275 | if k == xcol { 276 | var err error 277 | timestamp, err = ds.ParseTimestamp(v) 278 | if err != nil { 279 | ds.logger.Error("ParseTimestamp v", "error ", err) 280 | ch <- &datasource.QueryResult{ 281 | Error: err.Error(), 282 | } 283 | return 284 | } 285 | } 286 | if k == ycol { 287 | if v == "null" { 288 | continue 289 | } 290 | floatV, err := strconv.ParseFloat(v, 10) 291 | if err != nil { 292 | ds.logger.Error("ParseFloat v", "error ", err) 293 | ch <- &datasource.QueryResult{ 294 | Error: err.Error(), 295 | } 296 | return 297 | } 298 | value = floatV 299 | } 300 | } 301 | point := &datasource.Point{ 302 | Timestamp: timestamp, 303 | Value: value, 304 | } 305 | points = append(points, point) 306 | } 307 | timeSeries := &datasource.TimeSeries{ 308 | Name: ycol, 309 | Points: points, 310 | } 311 | *series = append(*series, timeSeries) 312 | } 313 | } 314 | 315 | func (ds *SlsDatasource) BuildTable(ch chan *datasource.QueryResult, logs []map[string]string, xcol string, ycols []string, tables *[]*datasource.Table) { 316 | var columns []*datasource.TableColumn 317 | 318 | for _, ycol := range ycols { 319 | columns = append(columns, &datasource.TableColumn{ 320 | Name: ycol, 321 | }) 322 | } 323 | var rows []*datasource.TableRow 324 | for _, alog := range logs { 325 | var values []*datasource.RowValue 326 | for _, ycol := range ycols { 327 | for k, v := range alog { 328 | if k == ycol { 329 | values = append(values, ds.GetValue(v)) 330 | } 331 | } 332 | } 333 | if len(ycols) == 1 && ycols[0] == "" { 334 | for k, v := range alog { 335 | if k != "__source__" && k != "__time__" { 336 | values = append(values, ds.GetValue(v)) 337 | } 338 | } 339 | } 340 | rows = append(rows, &datasource.TableRow{Values: values}) 341 | } 342 | table := &datasource.Table{ 343 | Columns: columns, 344 | Rows: rows, 345 | } 346 | *tables = append(*tables, table) 347 | } 348 | -------------------------------------------------------------------------------- /pkg/models.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type LogSource struct { 4 | Project string `json:"project"` 5 | LogStore string `json:"logstore"` 6 | User string `json:"user"` 7 | Password string `json:"password"` 8 | } 9 | 10 | type QueryInfo struct { 11 | Query string `json:"query"` 12 | Xcol string `json:"xcol"` 13 | Ycol string `json:"ycol"` 14 | } 15 | -------------------------------------------------------------------------------- /pkg/plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/grafana/grafana_plugin_model/go/datasource" 5 | hclog "github.com/hashicorp/go-hclog" 6 | plugin "github.com/hashicorp/go-plugin" 7 | ) 8 | 9 | var pluginLogger = hclog.New(&hclog.LoggerOptions{ 10 | Name: "aliyun-log-backend-datasource", 11 | Level: hclog.LevelFromString("WARN"), 12 | }) 13 | 14 | func main() { 15 | pluginLogger.Debug("Running Aliyun log servcie backend datasource") 16 | 17 | plugin.Serve(&plugin.ServeConfig{ 18 | 19 | HandshakeConfig: plugin.HandshakeConfig{ 20 | ProtocolVersion: 1, 21 | MagicCookieKey: "grafana_plugin_type", 22 | MagicCookieValue: "datasource", 23 | }, 24 | Plugins: map[string]plugin.Plugin{ 25 | "aliyun-log-backend-datasource": &datasource.DatasourcePluginImpl{Plugin: &SlsDatasource{ 26 | logger: pluginLogger, 27 | }}, 28 | }, 29 | 30 | // A non-nil value here enables gRPC serving for this plugin... 31 | GRPCServer: plugin.DefaultGRPCServer, 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /spec/datasource.test.ts: -------------------------------------------------------------------------------- 1 | import { GenericDatasource, mapToTextValue } from "../src/datasource"; 2 | 3 | describe('GenericDatasource', function() { 4 | let ctx: any = {}; 5 | 6 | beforeEach(function() { 7 | ctx.backendSrv = {}; 8 | ctx.templateSrv = { 9 | replace: jest.fn().mockImplementation(value => value) 10 | }; 11 | ctx.ds = new GenericDatasource({}, ctx.backendSrv, ctx.templateSrv); 12 | }); 13 | 14 | describe('When invoking a query', () => { 15 | 16 | it('should return an empty array when no targets are set', done => { 17 | ctx.ds.query({targets: []}).then(result => { 18 | expect(result.data).toHaveLength(0); 19 | done(); 20 | }); 21 | }); 22 | 23 | it('should return the server results when a target is set', done => { 24 | ctx.backendSrv.datasourceRequest = jest.fn().mockResolvedValue({ 25 | data: { results: [{ 26 | refId: 'A', 27 | series: [{ 28 | name: 'X', 29 | points: [1, 2, 3] 30 | }] 31 | }]} 32 | }); 33 | 34 | ctx.ds.query({ targets: ['hits'], range: { from: 'now-1h', to: 'now' }}).then(result => { 35 | var series = result.data[0]; 36 | expect(series.target).toBe('X'); 37 | expect(series.datapoints).toHaveLength(3); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('When invoking a metricFindQuery', () => { 44 | 45 | it ('should return the metric results when a target is null', done => { 46 | ctx.backendSrv.datasourceRequest = jest.fn().mockResolvedValue({ 47 | data: [ 48 | "metric_0", 49 | "metric_1", 50 | "metric_2", 51 | ] 52 | }); 53 | 54 | ctx.ds.metricFindQuery({target: null}).then(function(result) { 55 | expect(result).toHaveLength(3); 56 | expect(result[0].text).toBe('metric_0'); 57 | expect(result[0].value).toBe('metric_0'); 58 | expect(result[1].text).toBe('metric_1'); 59 | expect(result[1].value).toBe('metric_1'); 60 | expect(result[2].text).toBe('metric_2'); 61 | expect(result[2].value).toBe('metric_2'); 62 | done(); 63 | }); 64 | }); 65 | 66 | it ('should return the metric target results when a target is set', done => { 67 | ctx.backendSrv.datasourceRequest = jest.fn().mockImplementation(request => { 68 | var target = request.data.target; 69 | var result = [target + "_0", target + "_1", target + "_2"]; 70 | 71 | return Promise.resolve({ 72 | _request: request, 73 | data: result 74 | }); 75 | }); 76 | 77 | ctx.ds.metricFindQuery('search').then(function(result) { 78 | expect(result).toHaveLength(3); 79 | expect(result[0].text).toBe('search_0'); 80 | expect(result[0].value).toBe('search_0'); 81 | expect(result[1].text).toBe('search_1'); 82 | expect(result[1].value).toBe('search_1'); 83 | expect(result[2].text).toBe('search_2'); 84 | expect(result[2].value).toBe('search_2'); 85 | done(); 86 | }); 87 | }); 88 | 89 | it ('should return the metric results when the target is an empty string', done => { 90 | ctx.backendSrv.datasourceRequest = jest.fn().mockResolvedValue({ 91 | data: [ 92 | "metric_0", 93 | "metric_1", 94 | "metric_2", 95 | ] 96 | }); 97 | 98 | ctx.ds.metricFindQuery('').then(function(result) { 99 | expect(result).toHaveLength(3); 100 | expect(result[0].text).toBe('metric_0'); 101 | expect(result[0].value).toBe('metric_0'); 102 | expect(result[1].text).toBe('metric_1'); 103 | expect(result[1].value).toBe('metric_1'); 104 | expect(result[2].text).toBe('metric_2'); 105 | expect(result[2].value).toBe('metric_2'); 106 | done(); 107 | }); 108 | }); 109 | 110 | it ('should return the metric results when the args are an empty object', done => { 111 | ctx.backendSrv.datasourceRequest = jest.fn().mockResolvedValue({ 112 | data: [ 113 | "metric_0", 114 | "metric_1", 115 | "metric_2", 116 | ] 117 | }); 118 | 119 | ctx.ds.metricFindQuery().then(function(result) { 120 | expect(result).toHaveLength(3); 121 | expect(result[0].text).toBe('metric_0'); 122 | expect(result[0].value).toBe('metric_0'); 123 | expect(result[1].text).toBe('metric_1'); 124 | expect(result[1].value).toBe('metric_1'); 125 | expect(result[2].text).toBe('metric_2'); 126 | expect(result[2].value).toBe('metric_2'); 127 | done(); 128 | }); 129 | }); 130 | 131 | it ('should return the metric target results when the args are a string', done => { 132 | ctx.backendSrv.datasourceRequest = jest.fn().mockImplementation(request => { 133 | var target = request.data.target; 134 | var result = [target + "_0", target + "_1", target + "_2"]; 135 | 136 | return Promise.resolve({ 137 | _request: request, 138 | data: result 139 | }); 140 | }); 141 | 142 | ctx.ds.metricFindQuery('search').then(function(result) { 143 | expect(result).toHaveLength(3); 144 | expect(result[0].text).toBe('search_0'); 145 | expect(result[0].value).toBe('search_0'); 146 | expect(result[1].text).toBe('search_1'); 147 | expect(result[1].value).toBe('search_1'); 148 | expect(result[2].text).toBe('search_2'); 149 | expect(result[2].value).toBe('search_2'); 150 | done(); 151 | }); 152 | }); 153 | }); 154 | 155 | describe('When mapping result to text value', () => { 156 | 157 | it ('should return data as text and as value', done => { 158 | var result = mapToTextValue({data: ["zero", "one", "two"]}); 159 | 160 | expect(result).toHaveLength(3); 161 | expect(result[0].text).toBe('zero'); 162 | expect(result[0].value).toBe('zero'); 163 | expect(result[1].text).toBe('one'); 164 | expect(result[1].value).toBe('one'); 165 | expect(result[2].text).toBe('two'); 166 | expect(result[2].value).toBe('two'); 167 | done(); 168 | }); 169 | 170 | it ('should return text as text and value as value', done => { 171 | var data = [ 172 | {text: "zero", value: "value_0"}, 173 | {text: "one", value: "value_1"}, 174 | {text: "two", value: "value_2"}, 175 | ]; 176 | 177 | var result = mapToTextValue({data: data}); 178 | 179 | expect(result).toHaveLength(3); 180 | expect(result[0].text).toBe('zero'); 181 | expect(result[0].value).toBe('value_0'); 182 | expect(result[1].text).toBe('one'); 183 | expect(result[1].value).toBe('value_1'); 184 | expect(result[2].text).toBe('two'); 185 | expect(result[2].value).toBe('value_2'); 186 | done(); 187 | }); 188 | 189 | it ('should return data as text and index as value', done => { 190 | var data = [ 191 | {a: "zero", b: "value_0"}, 192 | {a: "one", b: "value_1"}, 193 | {a: "two", b: "value_2"}, 194 | ]; 195 | 196 | var result = mapToTextValue({data: data}); 197 | 198 | expect(result).toHaveLength(3); 199 | expect(result[0].text).toBe(data[0]); 200 | expect(result[0].value).toBe(0); 201 | expect(result[1].text).toBe(data[1]); 202 | expect(result[1].value).toBe(1); 203 | expect(result[2].text).toBe(data[2]); 204 | expect(result[2].value).toBe(2); 205 | done(); 206 | }); 207 | }); 208 | }); 209 | -------------------------------------------------------------------------------- /src/css/query-editor.css: -------------------------------------------------------------------------------- 1 | .generic-datasource-query-row .query-keyword { 2 | width: 75px; 3 | } -------------------------------------------------------------------------------- /src/datasource.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | export class GenericDatasource { 3 | /** @ngInject */ 4 | constructor(instanceSettings, backendSrv, templateSrv) { 5 | this.backendSrv = backendSrv; 6 | this.templateSrv = templateSrv; 7 | this.type = instanceSettings.type; 8 | this.url = instanceSettings.url; 9 | this.name = instanceSettings.name; 10 | this.id = instanceSettings.id; 11 | this.withCredentials = instanceSettings.withCredentials; 12 | this.headers = { 'Content-Type': 'application/json' }; 13 | if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) { 14 | this.headers['Authorization'] = instanceSettings.basicAuth; 15 | } 16 | } 17 | query(options) { 18 | const query = this.buildQueryParameters(options); 19 | query.targets = query.targets.filter(t => !t.hide); 20 | if (query.targets.length <= 0) { 21 | return Promise.resolve({ data: [] }); 22 | } 23 | return this.doTsdbRequest(query).then(handleTsdbResponse); 24 | } 25 | testDatasource() { 26 | const to = new Date().getTime(); 27 | const from = to - 5000; 28 | const str = '{"requestId":"Q100","timezone":"","range":{"from":"' + from + '","to":"' + to + '"},' + 29 | '"targets":[{"queryType":"query","target":"count","refId":"A","type":"timeserie","datasourceId":' + this.id + ',' + 30 | '"query":"* | select count(*) as count","ycol":"count"}]}'; 31 | const query = JSON.parse(str); 32 | return this.doTsdbRequest(query).then(response => { 33 | if (response.status === 200) { 34 | return { status: "success", message: "Data source is working", title: "Success" }; 35 | } 36 | else { 37 | return { status: "failed", message: "Data source is not working", title: "Error" }; 38 | } 39 | }).catch(() => { 40 | return { status: "failed", message: "Data source is not working", title: "Error" }; 41 | }); 42 | } 43 | annotationQuery(options) { 44 | const query = this.templateSrv.replace(options.annotation.query, {}, 'glob'); 45 | const annotationQuery = { 46 | range: options.range, 47 | annotation: { 48 | name: options.annotation.name, 49 | datasource: options.annotation.datasource, 50 | enable: options.annotation.enable, 51 | iconColor: options.annotation.iconColor, 52 | query: query 53 | }, 54 | rangeRaw: options.rangeRaw 55 | }; 56 | return this.doRequest({ 57 | url: this.url + '/annotations', 58 | method: 'POST', 59 | data: annotationQuery 60 | }).then(result => { 61 | return result.data; 62 | }); 63 | } 64 | metricFindQuery(q) { 65 | q = this.templateSrv.replace(q, {}, 'glob'); 66 | const to = this.templateSrv.timeRange.to.unix() * 1000; 67 | const from = this.templateSrv.timeRange.from.unix() * 1000; 68 | const str = '{"requestId":"Q100","timezone":"","range":{"from":"' + from + '","to":"' + to + '"},' + 69 | '"targets":[{"queryType":"query","target":"query","refId":"A","type":"timeserie","datasourceId":' + this.id + ',' + 70 | '"query":"' + q + '"}]}'; 71 | const query = JSON.parse(str); 72 | return this.doTsdbRequest(query).then(response => { 73 | const res = handleTsdbResponse(response); 74 | if (res && res.data && res.data.length) { 75 | let rows = res.data[0].rows; 76 | rows = rows.map(item => item[0]); 77 | return rows; 78 | } 79 | else { 80 | return []; 81 | } 82 | }).then(mapToTextValue); 83 | } 84 | doRequest(options) { 85 | options.withCredentials = this.withCredentials; 86 | options.headers = this.headers; 87 | return this.backendSrv.datasourceRequest(options); 88 | } 89 | doTsdbRequest(options) { 90 | const tsdbRequestData = { 91 | queries: options.targets, 92 | }; 93 | if (options.range) { 94 | tsdbRequestData.from = options.range.from.valueOf().toString(); 95 | tsdbRequestData.to = options.range.to.valueOf().toString(); 96 | } 97 | return this.backendSrv.datasourceRequest({ 98 | url: '/api/tsdb/query', 99 | method: 'POST', 100 | data: tsdbRequestData 101 | }); 102 | } 103 | buildQueryParameters(options) { 104 | //remove placeholder targets 105 | options.targets = _.filter(options.targets, target => { 106 | return target.target !== 'select metric'; 107 | }); 108 | options.targets = _.map(options.targets, target => { 109 | return { 110 | queryType: 'query', 111 | target: this.templateSrv.replace(target.target, options.scopedVars, 'regex'), 112 | refId: target.refId, 113 | hide: target.hide, 114 | type: target.type || 'timeserie', 115 | datasourceId: this.id, 116 | query: this.replaceQueryParameters(target, options), 117 | xcol: target.xcol, 118 | ycol: target.ycol 119 | }; 120 | }); 121 | return options; 122 | } 123 | replaceQueryParameters(target, options) { 124 | let query = this.templateSrv.replace(target.query, options.scopedVars, function (value, variable) { 125 | if (typeof value == "object" && (variable.multi || variable.includeAll)) { 126 | const a = []; 127 | value.forEach(function (v) { 128 | if (variable.name == variable.label) 129 | a.push('"' + variable.name + '":"' + v + '"'); 130 | else 131 | a.push('"' + v + '"'); 132 | }); 133 | return a.join(" OR "); 134 | } 135 | if (_.isArray(value)) { 136 | return value.join(' OR '); 137 | } 138 | return value; 139 | }); 140 | const re = /\$([0-9]+)([dmhs])/g; 141 | const reArray = query.match(re); 142 | _(reArray).forEach(function (col) { 143 | const old = col; 144 | col = col.replace("$", ''); 145 | let sec = 1; 146 | if (col.indexOf("s") != -1) 147 | sec = 1; 148 | else if (col.indexOf("m") != -1) 149 | sec = 60; 150 | else if (col.indexOf("h") != -1) 151 | sec = 3600; 152 | else if (col.indexOf("d") != -1) 153 | sec = 3600 * 24; 154 | col = col.replace(/[smhd]/g, ''); 155 | let v = parseInt(col); 156 | v = v * sec; 157 | console.log(old, v, col, sec, query); 158 | query = query.replace(old, v); 159 | }); 160 | if (query.indexOf("#time_end") != -1) { 161 | query = query.replace("#time_end", parseInt(String(options.range.to._d.getTime() / 1000))); 162 | } 163 | if (query.indexOf("#time_begin") != -1) { 164 | query = query.replace("#time_begin", parseInt(String(options.range.from._d.getTime() / 1000))); 165 | } 166 | return query; 167 | } 168 | getTagKeys(options) { 169 | return new Promise((resolve) => { 170 | this.doRequest({ 171 | url: this.url + '/tag-keys', 172 | method: 'POST', 173 | data: options 174 | }).then(result => { 175 | return resolve(result.data); 176 | }); 177 | }); 178 | } 179 | getTagValues(options) { 180 | return new Promise((resolve) => { 181 | this.doRequest({ 182 | url: this.url + '/tag-values', 183 | method: 'POST', 184 | data: options 185 | }).then(result => { 186 | return resolve(result.data); 187 | }); 188 | }); 189 | } 190 | } 191 | export function handleTsdbResponse(response) { 192 | const res = []; 193 | _.forEach(response.data.results, r => { 194 | _.forEach(r.series, s => { 195 | res.push({ target: s.name, datapoints: s.points }); 196 | }); 197 | _.forEach(r.tables, t => { 198 | t.type = 'table'; 199 | t.refId = r.refId; 200 | res.push(t); 201 | }); 202 | }); 203 | response.data = res; 204 | console.log(res); 205 | return response; 206 | } 207 | export function mapToTextValue(result) { 208 | return _.map(result, (d, i) => { 209 | if (d && d.text && d.value) { 210 | return { text: d.text, value: d.value }; 211 | } 212 | else if (_.isObject(d)) { 213 | return { text: d, value: i }; 214 | } 215 | return { text: d, value: d }; 216 | }); 217 | } 218 | //# sourceMappingURL=datasource.js.map -------------------------------------------------------------------------------- /src/datasource.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"datasource.js","sourceRoot":"","sources":["datasource.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/datasource.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | interface TSDBRequest { 4 | queries: any[]; 5 | from?: string; 6 | to?: string; 7 | } 8 | 9 | interface TSDBQuery { 10 | datasourceId: string; 11 | target: any; 12 | queryType?: TSDBQueryType; 13 | refId?: string; 14 | hide?: boolean; 15 | type?: 'timeserie' | 'table'; 16 | } 17 | 18 | type TSDBQueryType = 'query' | 'search'; 19 | 20 | interface TSDBRequestOptions { 21 | range?: { 22 | from: any; 23 | to: any; 24 | }; 25 | targets: TSDBQuery[]; 26 | } 27 | 28 | export class GenericDatasource { 29 | name: string; 30 | type: string; 31 | id: string; 32 | url: string; 33 | withCredentials: boolean; 34 | instanceSettings: any; 35 | headers: any; 36 | 37 | /** @ngInject */ 38 | constructor(instanceSettings, private backendSrv, private templateSrv) { 39 | this.type = instanceSettings.type; 40 | this.url = instanceSettings.url; 41 | this.name = instanceSettings.name; 42 | this.id = instanceSettings.id; 43 | this.withCredentials = instanceSettings.withCredentials; 44 | this.headers = {'Content-Type': 'application/json'}; 45 | if (typeof instanceSettings.basicAuth === 'string' && instanceSettings.basicAuth.length > 0) { 46 | this.headers['Authorization'] = instanceSettings.basicAuth; 47 | } 48 | } 49 | 50 | query(options) { 51 | const query = this.buildQueryParameters(options); 52 | query.targets = query.targets.filter(t => !t.hide); 53 | 54 | if (query.targets.length <= 0) { 55 | return Promise.resolve({data: []}); 56 | } 57 | 58 | return this.doTsdbRequest(query).then(handleTsdbResponse); 59 | } 60 | 61 | testDatasource() { 62 | const to = new Date().getTime(); 63 | const from = to - 5000; 64 | const str = '{"requestId":"Q100","timezone":"","range":{"from":"' + from + '","to":"' + to + '"},' + 65 | '"targets":[{"queryType":"query","target":"count","refId":"A","type":"timeserie","datasourceId":' + this.id + ',' + 66 | '"query":"* | select count(*) as count","ycol":"count"}]}'; 67 | const query = JSON.parse(str); 68 | return this.doTsdbRequest(query).then(response => { 69 | if (response.status === 200) { 70 | return { status: "success", message: "Data source is working", title: "Success" }; 71 | } else { 72 | return { status: "failed", message: "Data source is not working", title: "Error" }; 73 | } 74 | }).catch(() => { 75 | return { status: "failed", message: "Data source is not working", title: "Error" }; 76 | }); 77 | } 78 | 79 | annotationQuery(options) { 80 | const query = this.templateSrv.replace(options.annotation.query, {}, 'glob'); 81 | const annotationQuery = { 82 | range: options.range, 83 | annotation: { 84 | name: options.annotation.name, 85 | datasource: options.annotation.datasource, 86 | enable: options.annotation.enable, 87 | iconColor: options.annotation.iconColor, 88 | query: query 89 | }, 90 | rangeRaw: options.rangeRaw 91 | }; 92 | 93 | return this.doRequest({ 94 | url: this.url + '/annotations', 95 | method: 'POST', 96 | data: annotationQuery 97 | }).then(result => { 98 | return result.data; 99 | }); 100 | } 101 | 102 | metricFindQuery(q) { 103 | q = this.templateSrv.replace(q, {}, 'glob'); 104 | const to = this.templateSrv.timeRange.to.unix()*1000; 105 | const from = this.templateSrv.timeRange.from.unix()*1000; 106 | const str = '{"requestId":"Q100","timezone":"","range":{"from":"' + from + '","to":"' + to + '"},' + 107 | '"targets":[{"queryType":"query","target":"query","refId":"A","type":"timeserie","datasourceId":' + this.id + ',' + 108 | '"query":"'+ q +'"}]}'; 109 | const query = JSON.parse(str); 110 | return this.doTsdbRequest(query).then(response => { 111 | const res = handleTsdbResponse(response); 112 | if (res && res.data && res.data.length) { 113 | let rows = res.data[0].rows; 114 | rows = rows.map(item => item[0]); 115 | return rows; 116 | } else { 117 | return []; 118 | } 119 | }).then(mapToTextValue); 120 | } 121 | 122 | doRequest(options) { 123 | options.withCredentials = this.withCredentials; 124 | options.headers = this.headers; 125 | 126 | return this.backendSrv.datasourceRequest(options); 127 | } 128 | 129 | doTsdbRequest(options: TSDBRequestOptions) { 130 | const tsdbRequestData: TSDBRequest = { 131 | queries: options.targets, 132 | }; 133 | 134 | if (options.range) { 135 | tsdbRequestData.from = options.range.from.valueOf().toString(); 136 | tsdbRequestData.to = options.range.to.valueOf().toString(); 137 | } 138 | 139 | return this.backendSrv.datasourceRequest({ 140 | url: '/api/tsdb/query', 141 | method: 'POST', 142 | data: tsdbRequestData 143 | }); 144 | } 145 | 146 | buildQueryParameters(options: any): TSDBRequestOptions { 147 | //remove placeholder targets 148 | options.targets = _.filter(options.targets, target => { 149 | return target.target !== 'select metric'; 150 | }); 151 | 152 | options.targets = _.map(options.targets, target => { 153 | return { 154 | queryType: 'query', 155 | target: this.templateSrv.replace(target.target, options.scopedVars, 'regex'), 156 | refId: target.refId, 157 | hide: target.hide, 158 | type: target.type || 'timeserie', 159 | datasourceId: this.id, 160 | query: this.replaceQueryParameters(target, options), 161 | xcol: target.xcol, 162 | ycol: target.ycol 163 | }; 164 | }); 165 | 166 | return options; 167 | } 168 | 169 | replaceQueryParameters(target,options): TSDBRequestOptions { 170 | let query = this.templateSrv.replace(target.query, options.scopedVars, function (value, variable) { 171 | if (typeof value == "object" && (variable.multi || variable.includeAll)) { 172 | const a = []; 173 | value.forEach(function (v) { 174 | if (variable.name == variable.label) a.push('"' + variable.name + '":"' + v + '"');else a.push('"' + v + '"'); 175 | }); 176 | return a.join(" OR "); 177 | } 178 | if (_.isArray(value)) { 179 | return value.join(' OR '); 180 | } 181 | return value; 182 | }); 183 | const re = /\$([0-9]+)([dmhs])/g; 184 | const reArray = query.match(re); 185 | _(reArray).forEach(function (col) { 186 | const old = col; 187 | col = col.replace("$", ''); 188 | let sec = 1; 189 | if (col.indexOf("s") != -1) sec = 1;else if (col.indexOf("m") != -1) sec = 60;else if (col.indexOf("h") != -1) sec = 3600;else if (col.indexOf("d") != -1) sec = 3600 * 24; 190 | col = col.replace(/[smhd]/g, ''); 191 | let v = parseInt(col); 192 | v = v * sec; 193 | console.log(old, v, col, sec, query); 194 | query = query.replace(old, v); 195 | }); 196 | if (query.indexOf("#time_end") != -1) { 197 | query = query.replace("#time_end", parseInt(String(options.range.to._d.getTime() / 1000))); 198 | } 199 | if (query.indexOf("#time_begin") != -1) { 200 | query = query.replace("#time_begin", parseInt(String(options.range.from._d.getTime() / 1000))); 201 | } 202 | return query; 203 | } 204 | 205 | getTagKeys(options) { 206 | return new Promise((resolve) => { 207 | this.doRequest({ 208 | url: this.url + '/tag-keys', 209 | method: 'POST', 210 | data: options 211 | }).then(result => { 212 | return resolve(result.data); 213 | }); 214 | }); 215 | } 216 | 217 | getTagValues(options) { 218 | return new Promise((resolve) => { 219 | this.doRequest({ 220 | url: this.url + '/tag-values', 221 | method: 'POST', 222 | data: options 223 | }).then(result => { 224 | return resolve(result.data); 225 | }); 226 | }); 227 | } 228 | } 229 | 230 | export function handleTsdbResponse(response) { 231 | const res= []; 232 | _.forEach(response.data.results, r => { 233 | _.forEach(r.series, s => { 234 | res.push({target: s.name, datapoints: s.points}); 235 | }); 236 | _.forEach(r.tables, t => { 237 | t.type = 'table'; 238 | t.refId = r.refId; 239 | res.push(t); 240 | }); 241 | }); 242 | 243 | response.data = res; 244 | console.log(res); 245 | return response; 246 | } 247 | 248 | export function mapToTextValue(result) { 249 | return _.map(result, (d, i) => { 250 | if (d && d.text && d.value) { 251 | return { text: d.text, value: d.value }; 252 | } else if (_.isObject(d)) { 253 | return { text: d, value: i}; 254 | } 255 | return { text: d, value: d }; 256 | }); 257 | } 258 | -------------------------------------------------------------------------------- /src/img/sls_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayunlei/aliyun-log-grafana-datasource-plugin/1907f18d10e9e52a47169cf0e0bafb5f8a11f89d/src/img/sls_logo.jpg -------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | import { GenericDatasource } from './datasource'; 2 | import { GenericDatasourceQueryCtrl } from './query_ctrl'; 3 | class GenericConfigCtrl { 4 | } 5 | GenericConfigCtrl.templateUrl = 'partials/config.html'; 6 | class GenericQueryOptionsCtrl { 7 | } 8 | GenericQueryOptionsCtrl.templateUrl = 'partials/query.options.html'; 9 | class GenericAnnotationsQueryCtrl { 10 | } 11 | GenericAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html'; 12 | export { GenericDatasource as Datasource, GenericDatasourceQueryCtrl as QueryCtrl, GenericConfigCtrl as ConfigCtrl, GenericQueryOptionsCtrl as QueryOptionsCtrl, GenericAnnotationsQueryCtrl as AnnotationsQueryCtrl }; 13 | //# sourceMappingURL=module.js.map -------------------------------------------------------------------------------- /src/module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"module.js","sourceRoot":"","sources":["module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAC,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAC,0BAA0B,EAAC,MAAM,cAAc,CAAC;AAExD,MAAM,iBAAiB;;AACd,6BAAW,GAAG,sBAAsB,CAAC;AAG9C,MAAM,uBAAuB;;AACpB,mCAAW,GAAG,6BAA6B,CAAC;AAGrD,MAAM,2BAA2B;;AACxB,uCAAW,GAAG,kCAAkC,CAAA;AAGzD,OAAO,EACL,iBAAiB,IAAI,UAAU,EAC/B,0BAA0B,IAAI,SAAS,EACvC,iBAAiB,IAAI,UAAU,EAC/B,uBAAuB,IAAI,gBAAgB,EAC3C,2BAA2B,IAAI,oBAAoB,EACpD,CAAC"} -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import {GenericDatasource} from './datasource'; 2 | import {GenericDatasourceQueryCtrl} from './query_ctrl'; 3 | 4 | class GenericConfigCtrl { 5 | static templateUrl = 'partials/config.html'; 6 | } 7 | 8 | class GenericQueryOptionsCtrl { 9 | static templateUrl = 'partials/query.options.html'; 10 | } 11 | 12 | class GenericAnnotationsQueryCtrl { 13 | static templateUrl = 'partials/annotations.editor.html' 14 | } 15 | 16 | export { 17 | GenericDatasource as Datasource, 18 | GenericDatasourceQueryCtrl as QueryCtrl, 19 | GenericConfigCtrl as ConfigCtrl, 20 | GenericQueryOptionsCtrl as QueryOptionsCtrl, 21 | GenericAnnotationsQueryCtrl as AnnotationsQueryCtrl 22 | }; 23 | -------------------------------------------------------------------------------- /src/partials/annotations.editor.html: -------------------------------------------------------------------------------- 1 | 2 |
Query
3 |
4 |
5 | 6 |
7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /src/partials/config.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

log service details

4 |
5 |
6 |
Project
8 |
logstore
10 |
11 |
12 |
13 |
14 |
AccessKeyId
16 |
AccessKeySecret
18 |
19 |
20 |

Default query settings

21 |
22 |
Group by time interval
24 |
25 | -------------------------------------------------------------------------------- /src/partials/query.editor.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
6 | 7 |
8 |
9 |
12 |
15 | 16 |
17 |
-------------------------------------------------------------------------------- /src/partials/query.options.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /src/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LogService", 3 | "id": "grafana-log-service-datasource", 4 | "type": "datasource", 5 | 6 | "partials": { 7 | "config": "public/app/plugins/datasource/simplejson/partials/config.html" 8 | }, 9 | 10 | "metrics": true, 11 | "annotations": true, 12 | "backend": true, 13 | "alerting": true, 14 | "executable": "aliyun-log-plugin", 15 | 16 | "info": { 17 | "description": "aliyun log datasource", 18 | "author": { 19 | "name": "aliyun log service dev", 20 | "url": "https://aliyun.com/product/sls" 21 | }, 22 | "logos": { 23 | "small": "img/sls_logo.jpg", 24 | "large": "img/sls_logo.jpg" 25 | }, 26 | "links": [ 27 | { 28 | "name": "GitHub", 29 | "url": "https://github.com/grafana/simple-json-backend-datasource" 30 | }, 31 | { 32 | "name": "MIT License", 33 | "url": "https://github.com/grafana/simple-json-backend-datasource/blob/master/LICENSE" 34 | } 35 | ], 36 | "version": "1.0.0", 37 | "updated": "2019-11-09" 38 | }, 39 | 40 | "dependencies": { 41 | "grafanaVersion": "6.x.x", 42 | "plugins": [] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/query_ctrl.js: -------------------------------------------------------------------------------- 1 | import { QueryCtrl } from 'app/plugins/sdk'; 2 | import './css/query-editor.css!'; 3 | export class GenericDatasourceQueryCtrl extends QueryCtrl { 4 | /** @ngInject */ 5 | constructor($scope, $injector) { 6 | super($scope, $injector); 7 | this.target.target = this.target.ycol; 8 | this.target.type = this.target.type || 'timeserie'; 9 | } 10 | getOptions(query) { 11 | return this.datasource.metricFindQuery(query || ''); 12 | } 13 | toggleEditorMode() { 14 | this.target.rawQuery = !this.target.rawQuery; 15 | } 16 | onChangeInternal() { 17 | this.panelCtrl.refresh(); // Asks the panel to refresh data. 18 | } 19 | } 20 | GenericDatasourceQueryCtrl.templateUrl = 'partials/query.editor.html'; 21 | //# sourceMappingURL=query_ctrl.js.map -------------------------------------------------------------------------------- /src/query_ctrl.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"query_ctrl.js","sourceRoot":"","sources":["query_ctrl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC1C,OAAO,yBAAyB,CAAC;AAEjC,MAAM,OAAO,0BAA2B,SAAQ,SAAS;IAKvD,gBAAgB;IAChB,YAAY,MAAM,EAAE,SAAS;QAC3B,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACzB,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,WAAW,CAAC;IACrD,CAAC;IAED,UAAU,CAAC,KAAK;QACd,OAAO,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,gBAAgB;QACd,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC/C,CAAC;IAED,gBAAgB;QACd,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,kCAAkC;IAC9D,CAAC;;AArBM,sCAAW,GAAG,4BAA4B,CAAC"} -------------------------------------------------------------------------------- /src/query_ctrl.ts: -------------------------------------------------------------------------------- 1 | import {QueryCtrl} from 'app/plugins/sdk'; 2 | import './css/query-editor.css!'; 3 | 4 | export class GenericDatasourceQueryCtrl extends QueryCtrl { 5 | static templateUrl = 'partials/query.editor.html'; 6 | 7 | scope: any; 8 | 9 | /** @ngInject */ 10 | constructor($scope, $injector) { 11 | super($scope, $injector); 12 | this.target.target = this.target.ycol; 13 | this.target.type = this.target.type || 'timeserie'; 14 | } 15 | 16 | getOptions(query) { 17 | return this.datasource.metricFindQuery(query || ''); 18 | } 19 | 20 | toggleEditorMode() { 21 | this.target.rawQuery = !this.target.rawQuery; 22 | } 23 | 24 | onChangeInternal() { 25 | this.panelCtrl.refresh(); // Asks the panel to refresh data. 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es6", 5 | "lib": ["es6", "dom"], 6 | "rootDir": "./src", 7 | "jsx": "react", 8 | "module": "es6", 9 | "baseUrl": "./src", 10 | "declaration": false, 11 | "allowSyntheticDefaultImports": true, 12 | "esModuleInterop": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "importHelpers": false, 15 | "noEmitHelpers": true, 16 | "removeComments": false, 17 | "inlineSourceMap": false, 18 | "sourceMap": true, 19 | "noEmitOnError": false, 20 | "emitDecoratorMetadata": false, 21 | "experimentalDecorators": true, 22 | "noImplicitReturns": true, 23 | "noImplicitThis": true, 24 | "noImplicitUseStrict": false, 25 | "noImplicitAny": false, 26 | "downlevelIteration": true, 27 | // "noUnusedLocals": true, 28 | "pretty": true, 29 | "typeRoots": ["node_modules/@types"], 30 | "skipLibCheck": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webpack/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 5 | 6 | function resolve(dir) { 7 | return path.join(__dirname, '..', dir); 8 | } 9 | 10 | module.exports = { 11 | target: 'node', 12 | context: resolve('src'), 13 | entry: './module.ts', 14 | output: { 15 | filename: "module.js", 16 | path: resolve('dist'), 17 | libraryTarget: "amd" 18 | }, 19 | externals: [ 20 | 'jquery', 'lodash', 'moment', 'angular', 21 | function(context, request, callback) { 22 | var prefix = 'grafana/'; 23 | if (request.indexOf(prefix) === 0) { 24 | return callback(null, request.substr(prefix.length)); 25 | } 26 | callback(); 27 | } 28 | ], 29 | plugins: [ 30 | new webpack.optimize.OccurrenceOrderPlugin(), 31 | new CopyWebpackPlugin([ 32 | { from: '../LICENSE' }, 33 | { from: '../README.md' }, 34 | { from: 'plugin.json' }, 35 | { from: 'img/*' }, 36 | { from: 'partials/*' } 37 | ]), 38 | new CleanWebpackPlugin({ 39 | cleanStaleWebpackAssets: false, 40 | cleanAfterEveryBuildPatterns: [ 41 | '!README.md', '!LICENSE', '!plugin.json', '!img/*', '!partials/*', 42 | '!simple-json-plugin_linux_amd64', '!simple-json-plugin_windows_amd64.exe', 43 | ], 44 | }) 45 | ], 46 | resolve: { 47 | extensions: ['.ts', '.tsx', '.js'], 48 | }, 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.tsx?$/, 53 | loaders: ['ts-loader'], 54 | exclude: /node_modules/, 55 | }, 56 | { 57 | test: /\.css$/, 58 | use: ['style-loader', 'css-loader'], 59 | }, 60 | ] 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /webpack/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | const baseWebpackConfig = require('./webpack.base.conf'); 2 | 3 | var conf = baseWebpackConfig; 4 | conf.watch = false; 5 | conf.mode = 'development'; 6 | conf.devtool = 'source-map'; 7 | 8 | module.exports = baseWebpackConfig; 9 | -------------------------------------------------------------------------------- /webpack/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | const baseWebpackConfig = require('./webpack.base.conf'); 2 | const ngAnnotatePlugin = require('ng-annotate-webpack-plugin'); 3 | 4 | 5 | var conf = baseWebpackConfig; 6 | conf.mode = 'production'; 7 | 8 | conf.plugins.push(new ngAnnotatePlugin()); 9 | 10 | module.exports = baseWebpackConfig; 11 | --------------------------------------------------------------------------------