diff --git a/.citools/src/air/go.mod b/.citools/src/air/go.mod index 8619eb0641e..e2fa65b61a1 100644 --- a/.citools/src/air/go.mod +++ b/.citools/src/air/go.mod @@ -21,8 +21,8 @@ require ( github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.8.0 // indirect github.com/tdewolff/parse/v2 v2.8.1 // indirect - golang.org/x/net v0.45.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/.citools/src/air/go.sum b/.citools/src/air/go.sum index fbefab6f556..a18e035a8a3 100644 --- a/.citools/src/air/go.sum +++ b/.citools/src/air/go.sum @@ -167,19 +167,19 @@ golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjG golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/.citools/src/bra/go.mod b/.citools/src/bra/go.mod index 43a1908f9c4..f78a5eb40ca 100644 --- a/.citools/src/bra/go.mod +++ b/.citools/src/bra/go.mod @@ -17,6 +17,6 @@ require ( github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect github.com/urfave/cli v1.22.16 // indirect - golang.org/x/sys v0.36.0 // indirect + golang.org/x/sys v0.38.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect ) diff --git a/.citools/src/bra/go.sum b/.citools/src/bra/go.sum index 723e36ed099..7c438626368 100644 --- a/.citools/src/bra/go.sum +++ b/.citools/src/bra/go.sum @@ -56,8 +56,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/.citools/src/cog/go.mod b/.citools/src/cog/go.mod index 0ee1004f974..9846c35b0f4 100644 --- a/.citools/src/cog/go.mod +++ b/.citools/src/cog/go.mod @@ -40,11 +40,11 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/yalue/merged_fs v1.3.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.45.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/.citools/src/cog/go.sum b/.citools/src/cog/go.sum index fc55dd4132c..1bcacb846b3 100644 --- a/.citools/src/cog/go.sum +++ b/.citools/src/cog/go.sum @@ -85,20 +85,20 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/.citools/src/cue/go.mod b/.citools/src/cue/go.mod index d9a143c75c2..54dd569c1c3 100644 --- a/.citools/src/cue/go.mod +++ b/.citools/src/cue/go.mod @@ -25,13 +25,13 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/tetratelabs/wazero v1.6.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.45.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/.citools/src/cue/go.sum b/.citools/src/cue/go.sum index 86c2675545b..9b95514fe0d 100644 --- a/.citools/src/cue/go.sum +++ b/.citools/src/cue/go.sum @@ -53,20 +53,20 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/.citools/src/golangci-lint/go.mod b/.citools/src/golangci-lint/go.mod index d3ac9b697aa..b62f7ee7d3a 100644 --- a/.citools/src/golangci-lint/go.mod +++ b/.citools/src/golangci-lint/go.mod @@ -198,12 +198,12 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 // indirect - golang.org/x/mod v0.28.0 // indirect - golang.org/x/net v0.45.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.6.1 // indirect diff --git a/.citools/src/golangci-lint/go.sum b/.citools/src/golangci-lint/go.sum index 4748f406fb8..12b2e9cdab6 100644 --- a/.citools/src/golangci-lint/go.sum +++ b/.citools/src/golangci-lint/go.sum @@ -481,8 +481,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -496,8 +496,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -507,8 +507,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -529,8 +529,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -545,8 +545,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= @@ -559,8 +559,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= diff --git a/.citools/src/jb/go.mod b/.citools/src/jb/go.mod index 96fe587d14f..18bfb09d022 100644 --- a/.citools/src/jb/go.mod +++ b/.citools/src/jb/go.mod @@ -15,6 +15,6 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.10.0 // indirect - golang.org/x/sys v0.36.0 // indirect + golang.org/x/sys v0.38.0 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect ) diff --git a/.citools/src/jb/go.sum b/.citools/src/jb/go.sum index 82c114a77ad..6a11e4870ff 100644 --- a/.citools/src/jb/go.sum +++ b/.citools/src/jb/go.sum @@ -54,8 +54,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/.citools/src/lefthook/go.mod b/.citools/src/lefthook/go.mod index e37e46a5ea6..ddc74badabc 100644 --- a/.citools/src/lefthook/go.mod +++ b/.citools/src/lefthook/go.mod @@ -43,9 +43,9 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/.citools/src/lefthook/go.sum b/.citools/src/lefthook/go.sum index c0e796f4ae9..ba532505786 100644 --- a/.citools/src/lefthook/go.sum +++ b/.citools/src/lefthook/go.sum @@ -91,14 +91,14 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 h1:8ajkpB4hXVftY5ko905id+dOnmorcS2CHNxxHLLDcFM= gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfMagxm39Ys4ybJrDb7W3Ob8RwxftP0Yy+or/NVz1O8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/.citools/src/swagger/go.mod b/.citools/src/swagger/go.mod index d0ed2ce2bcf..0ac87ea77e3 100644 --- a/.citools/src/swagger/go.mod +++ b/.citools/src/swagger/go.mod @@ -51,12 +51,12 @@ require ( github.com/toqueteos/webbrowser v1.2.0 // indirect go.mongodb.org/mongo-driver v1.16.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/.citools/src/swagger/go.sum b/.citools/src/swagger/go.sum index dff45a5ee81..44c908540b7 100644 --- a/.citools/src/swagger/go.sum +++ b/.citools/src/swagger/go.sum @@ -101,19 +101,19 @@ go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4 go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 77b60baf905..61ab3a10b1f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -721,9 +721,9 @@ i18next.config.ts @grafana/grafana-frontend-platform /packages/grafana-ui/src/components/Gauge/ @grafana/dataviz-squad /packages/grafana-ui/src/components/RadialGauge/ @grafana/dataviz-squad /packages/grafana-ui/src/components/PluginSignatureBadge/ @grafana/plugins-platform-frontend -/packages/grafana-ui/src/components/Sparkline/ @grafana/grafana-frontend-platform @grafana/app-o11y-visualizations +/packages/grafana-ui/src/components/Sparkline/ @grafana/dataviz-squad /packages/grafana-ui/src/components/Table/ @grafana/dataviz-squad -/packages/grafana-ui/src/components/Table/Cells/SparklineCell.tsx @grafana/dataviz-squad @grafana/app-o11y-visualizations +/packages/grafana-ui/src/components/Table/Cells/SparklineCell.tsx @grafana/dataviz-squad /packages/grafana-ui/src/components/uPlot/ @grafana/dataviz-squad /packages/grafana-ui/src/components/ValuePicker/ @grafana/dataviz-squad /packages/grafana-ui/src/components/VizLayout/ @grafana/dataviz-squad @@ -992,7 +992,7 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform /public/app/plugins/panel/state-timeline/ @grafana/dataviz-squad /public/app/plugins/panel/status-history/ @grafana/dataviz-squad /public/app/plugins/panel/table/ @grafana/dataviz-squad -/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx @grafana/dataviz-squad @grafana/app-o11y-visualizations +/public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx @grafana/dataviz-squad /public/app/plugins/panel/timeseries/ @grafana/dataviz-squad /public/app/plugins/panel/trend/ @grafana/dataviz-squad /public/app/plugins/panel/geomap/ @grafana/dataviz-squad diff --git a/.github/actions/setup-enterprise/action.yml b/.github/actions/setup-enterprise/action.yml index 134a7e7f004..2ccd8ef57da 100644 --- a/.github/actions/setup-enterprise/action.yml +++ b/.github/actions/setup-enterprise/action.yml @@ -33,16 +33,48 @@ runs: env: GH_TOKEN: ${{ steps.generate_token.outputs.token }} run: | - git clone https://x-access-token:${GH_TOKEN}@github.com/grafana/grafana-enterprise.git ../grafana-enterprise; + RETRIES="5" + + for i in $(seq 1 $RETRIES); do + echo "Attempt $i to clone..." + + if git clone https://x-access-token:${GH_TOKEN}@github.com/grafana/grafana-enterprise.git ../grafana-enterprise; then + echo "Clone succeeded on attempt $i" + break + fi + + if [ $i -eq $RETRIES ]; then + echo "Clone failed after $RETRIES attempts, failing pipeline." + exit 1 + fi + + sleep "$i" + done cd ../grafana-enterprise - if git checkout ${GITHUB_HEAD_REF}; then - echo "checked out ${GITHUB_HEAD_REF}" - elif git checkout ${GITHUB_BASE_REF}; then - echo "checked out ${GITHUB_BASE_REF}" - else - git checkout main - fi + for i in $(seq 1 $RETRIES); do + echo "Attempt $i to checkout..." + + if git checkout ${GITHUB_HEAD_REF}; then + echo "checked out ${GITHUB_HEAD_REF}" + elif git checkout ${GITHUB_BASE_REF}; then + echo "checked out ${GITHUB_BASE_REF}" + else + git checkout main + fi + + if [ $? -eq 0 ]; then + echo "Checkout succeeded, breaking retry loop" + break + fi + + if [ $i -eq $RETRIES ]; then + echo "Checkout failed after $RETRIES attempts, failing pipeline." + exit 1 + fi + + sleep "$i" + done QUIET=1 ./build.sh diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index 9a3fabfd18c..6611470fb8f 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -275,7 +275,7 @@ require ( go.uber.org/mock v0.6.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index ef7adf779d1..79aea07d576 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -130,9 +130,6 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= -github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= -github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0= @@ -222,48 +219,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= -github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/blevesearch/bleve/v2 v2.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4= -github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk= -github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY= -github.com/blevesearch/bleve_index_api v1.2.7/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= -github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM= -github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w= -github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U= -github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= -github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= -github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= -github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= -github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= -github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= -github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= -github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o= -github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4= -github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= -github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= -github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= -github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= -github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= -github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= -github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= -github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= -github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA= -github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs= -github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ= -github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II= -github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE= -github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8= -github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE= -github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms= -github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g= -github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y= -github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk= -github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= @@ -526,8 +485,6 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= -github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= -github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -936,8 +893,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= -github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -1184,8 +1139,6 @@ github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtC github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= -go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= -go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= @@ -1302,8 +1255,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index c29ea0b00a8..24c17cc900a 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -84,6 +84,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/sync v0.18.0 // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index b67c48676b8..9b84e11f3d6 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -224,8 +224,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 9e98fe233ec..b281b21e4e4 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -106,7 +106,7 @@ require ( go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index b4158332679..c0efb792788 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -280,8 +280,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/apps/example/go.mod b/apps/example/go.mod index acb714107b3..66b3b93f6df 100644 --- a/apps/example/go.mod +++ b/apps/example/go.mod @@ -73,7 +73,7 @@ require ( go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/sync v0.18.0 // indirect diff --git a/apps/example/go.sum b/apps/example/go.sum index 00f1a6a2620..aaf74c34d44 100644 --- a/apps/example/go.sum +++ b/apps/example/go.sum @@ -169,8 +169,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= diff --git a/apps/iam/go.mod b/apps/iam/go.mod index adb3755c58f..73891acd454 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -42,6 +42,10 @@ replace github.com/grafana/grafana/pkg/apiserver => ../../pkg/apiserver replace github.com/grafana/grafana/pkg/aggregator => ../../pkg/aggregator +replace github.com/grafana/grafana/apps/annotation => ../annotation + +replace github.com/grafana/grafana/apps/collections => ../collections + replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 require ( @@ -89,16 +93,14 @@ require ( github.com/Masterminds/squirrel v1.5.4 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect - github.com/RoaringBitmap/roaring v1.9.3 // indirect github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect github.com/Yiling-J/theine-go v0.6.2 // indirect github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/arrow-go/v18 v18.4.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/armon/go-radix v1.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/at-wat/mqtt-go v0.19.4 // indirect + github.com/at-wat/mqtt-go v0.19.6 // indirect github.com/aws/aws-sdk-go v1.55.7 // indirect github.com/aws/aws-sdk-go-v2 v1.39.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect @@ -119,29 +121,17 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect github.com/aws/smithy-go v1.23.1 // indirect - github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/blevesearch/go-porterstemmer v1.0.3 // indirect - github.com/blevesearch/mmap-go v1.0.4 // indirect - github.com/blevesearch/segment v0.9.1 // indirect - github.com/blevesearch/snowballstem v0.9.0 // indirect - github.com/blevesearch/vellum v1.1.0 // indirect github.com/bluele/gcache v0.0.2 // indirect - github.com/blugelabs/bluge v0.2.2 // indirect - github.com/blugelabs/bluge_segment_api v0.2.0 // indirect - github.com/blugelabs/ice v1.0.0 // indirect - github.com/blugelabs/ice/v2 v2.0.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/bufbuild/protocompile v0.14.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect - github.com/caio/go-tdigest v3.1.0+incompatible // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -155,7 +145,6 @@ require ( github.com/dennwc/varint v1.0.0 // indirect github.com/dgraph-io/badger/v4 v4.7.0 // indirect github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect - github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect github.com/dlmiddlecote/sqlstats v1.0.2 // indirect @@ -229,7 +218,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae // indirect + github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect @@ -318,7 +307,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/natefinch/wrap v0.2.0 // indirect @@ -349,7 +337,7 @@ require ( github.com/pressly/goose/v3 v3.25.0 // indirect github.com/prometheus/alertmanager v0.28.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.2 // indirect + github.com/prometheus/common v0.67.3 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.14.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect @@ -425,7 +413,7 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect gocloud.dev v0.43.0 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index b23eb7f0e4d..4cba06c00ec 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -182,10 +182,6 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/RoaringBitmap/gocroaring v0.4.0/go.mod h1:NieMwz7ZqwU2DD73/vvYwv7r4eWBKuPVSXZIpsaMwCI= -github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76/go.mod h1:oM0MHmQ3nDsq609SS36p+oYbRi16+oVvU2Bw4Ipv0SE= -github.com/RoaringBitmap/roaring v0.9.1/go.mod h1:h1B7iIUOmnAeb5ytYMvnHJwxMc6LUrwBnzXWRuqTQUc= -github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM= github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= @@ -221,7 +217,6 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= @@ -231,8 +226,8 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/at-wat/mqtt-go v0.19.4 h1:R2cbCU7O5PHQ38unbe1Y51ncG3KsFEJV6QeipDoqdLQ= -github.com/at-wat/mqtt-go v0.19.4/go.mod h1:AsiWc9kqVOhqq7LzUeWT/AkKUBfx3Sw5cEe8lc06fqA= +github.com/at-wat/mqtt-go v0.19.6 h1:+jo59yGHB3oqBU8nSFYbq6KDgN4ngMpqts5Qtyq61pc= +github.com/at-wat/mqtt-go v0.19.6/go.mod h1:Y/YrYwUlLE4N53//699Zir3WJnxSdJojvLEAbZxJjgQ= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= @@ -287,7 +282,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0= github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= -github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s= github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0= github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= @@ -307,8 +301,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= @@ -327,21 +319,16 @@ github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+ github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= -github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA= -github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs= github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o= github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4= -github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= -github.com/blevesearch/vellum v1.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+eMD5xZ2OyQ= -github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE= github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA= @@ -433,9 +420,6 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= @@ -444,8 +428,6 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -833,12 +815,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae h1:NLPwY3tIP0lg0g9wTRiMcypm6VRXW6W+MOLBsq8JSVA= -github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae/go.mod h1:VGjS5gDwWEADPP6pF/drqLxEImgeuHlEW5u8E5EfIrM= +github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181 h1:nbxKRtrbuhvOYmI2RhOYauHRJCtpR+vTNIgg1lFUCws= +github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181/go.mod h1:VtPNIFlEOJPPEc13Ax6ZTbNV3M/sAzLID72YjgzOPVA= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= -github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 h1:qEwZ+7MbPjzRvTi31iT9w7NBhKIpKwZrFbYmOZLqkwA= -github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA= @@ -853,8 +833,7 @@ github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f h1:5xkjl5Y/j2QefJKO github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f/go.mod h1:+O5QxOwwgP10jedZHapzXY+IPKTnzHBtIs5UUb9G+kI= github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe h1:q+QaVANzNZxvTovycpQvDTfsNZ2rHh4XIIaccMnrIR4= github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= -github.com/grafana/grafana-app-sdk v0.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+fRS+8zkek= -github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c= +github.com/grafana/grafana-app-sdk v0.48.2 h1:CQQDhwo1fWaXQVKvxxOcK6azbuY3E2TgJHNAZlYYn7U= github.com/grafana/grafana-app-sdk v0.48.2/go.mod h1:LDOvQ7OOyHLcXdSa0InATCa5OMoYAd6E1+rGLrMgHuk= github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng= github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= @@ -868,8 +847,7 @@ github.com/grafana/grafana-google-sdk-go v0.4.2 h1:F44hQF1y6UVJhlJPi+Mz+GCJsioVg github.com/grafana/grafana-google-sdk-go v0.4.2/go.mod h1:U73+w9DlbEtUonhQUzERwlXnzWTtfRoyrtKH8d3VY40= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= -github.com/grafana/grafana-plugin-sdk-go v0.281.0 h1:V8dGyatzcOLQeivFhBV2JWMwTSZH/clDnpfKG9p3dTA= -github.com/grafana/grafana-plugin-sdk-go v0.281.0/go.mod h1:3I0g+v6jAwVmrt6BEjDUP4V6pkhGP5QKY5NkXY4Ayr4= +github.com/grafana/grafana-plugin-sdk-go v0.283.0 h1:G7IHshAr30rLWV9FtX3iLlFTTlBhuOkfe7xVAoIP5rE= github.com/grafana/grafana-plugin-sdk-go v0.283.0/go.mod h1:20qhoYxIgbZRmwCEO1KMP8q2yq/Kge5+xE/99/hLEk0= github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b h1:6Bo65etvjQ4tStkaA5+N3A3ENbO4UAWj53TxF6g2Hdk= github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b/go.mod h1:6+wASOCN8LWt6FJ8dc0oODUBIEY5XHaE6ABi8g0mR+k= @@ -883,8 +861,8 @@ github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLm github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78= github.com/grafana/loki/v3 v3.2.1 h1:VB7u+KHfvL5aHAxgoVBvz5wVhsdGuqKC7uuOFOOe7jw= github.com/grafana/loki/v3 v3.2.1/go.mod h1:WvdLl6wOS+yahaeQY+xhD2m2XzkHDfKr5FZaX7D/X2Y= -github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b h1:rFjoqJFb2KxJ29K9ltuWRSsdA46SbN0GCxoQc36h5kg= -github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= +github.com/grafana/nanogit v0.3.0 h1:XNEef+4Vi+465ZITJs/g/xgnDRJbWhhJ7iQrAnWZ0oQ= +github.com/grafana/nanogit v0.3.0/go.mod h1:6s6CCTpyMOHPpcUZaLGI+rgBEKdmxVbhqSGgCK13j7Y= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U= @@ -903,8 +881,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= @@ -995,10 +972,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/influxdb v1.7.6/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM= github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4= github.com/influxdata/influxql v1.4.0 h1:Lf62rbAF8KWQf+4Djqf4hVXgmQuGozUoSD6kNWjye44= @@ -1080,7 +1055,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= -github.com/klauspost/compress v1.15.2/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -1106,8 +1080,6 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4= -github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= @@ -1123,7 +1095,6 @@ github.com/m3db/prometheus_remote_client_golang v0.4.4 h1:DsAIjVKoCp7Ym35tAOFL1O github.com/m3db/prometheus_remote_client_golang v0.4.4/go.mod h1:wHfVbA3eAK6dQvKjCkHhusWYegCk3bDGkA15zymSHdc= github.com/madflojo/testcerts v1.4.0 h1:I09gN0C1ly9IgeVNcAqKk8RAKIJTe3QnFrrPBDyvzN4= github.com/madflojo/testcerts v1.4.0/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLDUsQhpmkuo0= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -1318,7 +1289,6 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -1377,9 +1347,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= -github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= -github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= +github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= +github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= @@ -1424,8 +1393,6 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -1443,8 +1410,6 @@ github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQcc github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/sercand/kuberesolver/v6 v6.0.0 h1:ScvS2Ga9snVkpOahln/BCLySr3/iBAHJf25u66DweZ0= github.com/sercand/kuberesolver/v6 v6.0.0/go.mod h1:Dxkqms3OJadP5zirIBPLi9FV8Qpys3T3w40XPEcVsu0= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3 h1:/4/IJi5iyTdh6mqOUaASW148HQpujYiHl0Wl78dSOSc= @@ -1471,25 +1436,19 @@ github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= @@ -1547,7 +1506,6 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= @@ -1570,15 +1528,10 @@ github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chq github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1721,7 +1674,6 @@ gocloud.dev v0.43.0/go.mod h1:eD8rkg7LhKUHrzkEdLTZ+Ty/vgPHPCd+yMQdfelQVu4= gocloud.dev/secrets/hashivault v0.43.0 h1:A966rEMpCRUE9209/+k+A2HP2v2qDnrxGpQn+nIH5uY= gocloud.dev/secrets/hashivault v0.43.0/go.mod h1:KdWKL+TXDi0cXgEd/MTeaidKlotvyJtnTDi71B3rR9U= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1734,9 +1686,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1778,8 +1729,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1835,8 +1785,7 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1860,8 +1809,7 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1875,8 +1823,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1884,8 +1831,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1898,7 +1843,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1970,16 +1914,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= -golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1990,8 +1931,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2059,8 +1999,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= @@ -2075,7 +2014,6 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6f gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= @@ -2319,31 +2257,24 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= -k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= -k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= -k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= -k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.34.1 h1:iCFOvewDPzWM9fMTfyIPO+4MeuZ0tcZbugxLNSHFG4w= -k8s.io/kms v0.34.1/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kms v0.34.2 h1:91rj4MDZLyIT9KxG8J5/CcMH666Z88CF/xJQeuPfJc8= k8s.io/kms v0.34.2/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= -k8s.io/kube-aggregator v0.34.1 h1:WNLV0dVNoFKmuyvdWLd92iDSyD/TSTjqwaPj0U9XAEU= -k8s.io/kube-aggregator v0.34.1/go.mod h1:RU8j+5ERfp0h+gIvWtxRPfsa5nK7rboDm8RST8BJfYQ= +k8s.io/kube-aggregator v0.34.2 h1:Nn0Vksj67WHBL2x7bJ6vuxL44RbMTK6uRtXX+3vMVJk= +k8s.io/kube-aggregator v0.34.2/go.mod h1:/tp4cc/1p2AvICsS4mjjSJakdrbhcGbRmj0mdHTdR2Q= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= diff --git a/apps/iam/kinds/team.cue b/apps/iam/kinds/team.cue index 9acf9d60c01..412d8215999 100644 --- a/apps/iam/kinds/team.cue +++ b/apps/iam/kinds/team.cue @@ -18,4 +18,18 @@ teamv0alpha1: teamKind & { schema: { spec: v0alpha1.TeamSpec } + routes: { + "/groups": { + "GET": { + response: { + #ExternalGroupMapping: { + name: string + externalGroup: string + } + items: [...#ExternalGroupMapping] + } + responseMetadata: objectMeta: false + } + } + } } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/register.go b/apps/iam/pkg/apis/iam/v0alpha1/register.go index 54d60c7bd6f..be70a0e1714 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/register.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/register.go @@ -321,6 +321,7 @@ func AddAuthNKnownTypes(scheme *runtime.Scheme) error { &TeamBindingList{}, &ExternalGroupMapping{}, &ExternalGroupMappingList{}, + &GetGroups{}, // For now these are registered in pkg/apis/iam/v0alpha1/register.go // &UserTeamList{}, // &ServiceAccountTokenList{}, diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_client_gen.go index 807c68dc0f6..2a6ce6caedb 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/team_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_client_gen.go @@ -2,6 +2,9 @@ package v0alpha1 import ( "context" + "encoding/json" + "fmt" + "net/http" "github.com/grafana/grafana-app-sdk/resource" ) @@ -78,3 +81,24 @@ func (c *TeamClient) Patch(ctx context.Context, identifier resource.Identifier, func (c *TeamClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } + +type GetGroupsRequest struct { + Headers http.Header +} + +func (c *TeamClient) GetGroups(ctx context.Context, identifier resource.Identifier, request GetGroupsRequest) (*GetGroups, error) { + resp, err := c.client.SubresourceRequest(ctx, identifier, resource.CustomRouteRequestOptions{ + Path: "/groups", + Verb: "GET", + Headers: request.Headers, + }) + if err != nil { + return nil, err + } + cast := GetGroups{} + err = json.Unmarshal(resp, &cast) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal response bytes into GetGroups: %w", err) + } + return &cast, nil +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_getgroups_response_body_types_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_getgroups_response_body_types_gen.go new file mode 100644 index 00000000000..553d7260c94 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_getgroups_response_body_types_gen.go @@ -0,0 +1,26 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping struct { + Name string `json:"name"` + ExternalGroup string `json:"externalGroup"` +} + +// NewVersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping creates a new VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping object. +func NewVersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping() *VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping { + return &VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping{} +} + +// +k8s:openapi-gen=true +type GetGroupsBody struct { + Items []VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping `json:"items"` +} + +// NewGetGroupsBody creates a new GetGroupsBody object. +func NewGetGroupsBody() *GetGroupsBody { + return &GetGroupsBody{ + Items: []VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping{}, + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_getgroups_response_object_types_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_getgroups_response_object_types_gen.go new file mode 100644 index 00000000000..3c8aa40a239 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_getgroups_response_object_types_gen.go @@ -0,0 +1,37 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// +k8s:openapi-gen=true +type GetGroups struct { + metav1.TypeMeta `json:",inline"` + GetGroupsBody `json:",inline"` +} + +func NewGetGroups() *GetGroups { + return &GetGroups{} +} + +func (t *GetGroupsBody) DeepCopyInto(dst *GetGroupsBody) { + _ = resource.CopyObjectInto(dst, t) +} + +func (o *GetGroups) DeepCopyObject() runtime.Object { + dst := NewGetGroups() + o.DeepCopyInto(dst) + return dst +} + +func (o *GetGroups) DeepCopyInto(dst *GetGroups) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.GetGroupsBody.DeepCopyInto(&dst.GetGroupsBody) +} + +var _ runtime.Object = NewGetGroups() diff --git a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go index f0c517afd38..e3b024799d7 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go @@ -12,71 +12,74 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRole": schema_pkg_apis_iam_v0alpha1_CoreRole(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleList": schema_pkg_apis_iam_v0alpha1_CoreRoleList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleSpec": schema_pkg_apis_iam_v0alpha1_CoreRoleSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleStatus": schema_pkg_apis_iam_v0alpha1_CoreRoleStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolespecPermission": schema_pkg_apis_iam_v0alpha1_CoreRolespecPermission(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_CoreRolestatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMapping": schema_pkg_apis_iam_v0alpha1_ExternalGroupMapping(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingList": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingSpec": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingTeamRef": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingTeamRef(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": schema_pkg_apis_iam_v0alpha1_GlobalRole(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBinding": schema_pkg_apis_iam_v0alpha1_GlobalRoleBinding(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingList": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingSpec": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingStatus": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecRoleRef": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingspecRoleRef(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecSubject": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingspecSubject(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingstatusOperatorState": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingstatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleList": schema_pkg_apis_iam_v0alpha1_GlobalRoleList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleSpec": schema_pkg_apis_iam_v0alpha1_GlobalRoleSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleStatus": schema_pkg_apis_iam_v0alpha1_GlobalRoleStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRolespecPermission": schema_pkg_apis_iam_v0alpha1_GlobalRolespecPermission(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_GlobalRolestatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermission": schema_pkg_apis_iam_v0alpha1_ResourcePermission(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionList": schema_pkg_apis_iam_v0alpha1_ResourcePermissionList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionSpec": schema_pkg_apis_iam_v0alpha1_ResourcePermissionSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionStatus": schema_pkg_apis_iam_v0alpha1_ResourcePermissionStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionspecPermission": schema_pkg_apis_iam_v0alpha1_ResourcePermissionspecPermission(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionspecResource": schema_pkg_apis_iam_v0alpha1_ResourcePermissionspecResource(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionstatusOperatorState": schema_pkg_apis_iam_v0alpha1_ResourcePermissionstatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Role": schema_pkg_apis_iam_v0alpha1_Role(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBinding": schema_pkg_apis_iam_v0alpha1_RoleBinding(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingList": schema_pkg_apis_iam_v0alpha1_RoleBindingList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingSpec": schema_pkg_apis_iam_v0alpha1_RoleBindingSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingStatus": schema_pkg_apis_iam_v0alpha1_RoleBindingStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecRoleRef": schema_pkg_apis_iam_v0alpha1_RoleBindingspecRoleRef(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecSubject": schema_pkg_apis_iam_v0alpha1_RoleBindingspecSubject(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingstatusOperatorState": schema_pkg_apis_iam_v0alpha1_RoleBindingstatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleList": schema_pkg_apis_iam_v0alpha1_RoleList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleSpec": schema_pkg_apis_iam_v0alpha1_RoleSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleStatus": schema_pkg_apis_iam_v0alpha1_RoleStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RolespecPermission": schema_pkg_apis_iam_v0alpha1_RolespecPermission(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_RolestatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccount": schema_pkg_apis_iam_v0alpha1_ServiceAccount(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountList": schema_pkg_apis_iam_v0alpha1_ServiceAccountList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec": schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountStatus": schema_pkg_apis_iam_v0alpha1_ServiceAccountStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountstatusOperatorState": schema_pkg_apis_iam_v0alpha1_ServiceAccountstatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Team": schema_pkg_apis_iam_v0alpha1_Team(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBinding": schema_pkg_apis_iam_v0alpha1_TeamBinding(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingList": schema_pkg_apis_iam_v0alpha1_TeamBindingList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec": schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingStatus": schema_pkg_apis_iam_v0alpha1_TeamBindingStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingTeamRef": schema_pkg_apis_iam_v0alpha1_TeamBindingTeamRef(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject": schema_pkg_apis_iam_v0alpha1_TeamBindingspecSubject(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingstatusOperatorState": schema_pkg_apis_iam_v0alpha1_TeamBindingstatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamList": schema_pkg_apis_iam_v0alpha1_TeamList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec": schema_pkg_apis_iam_v0alpha1_TeamSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus": schema_pkg_apis_iam_v0alpha1_TeamStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamstatusOperatorState": schema_pkg_apis_iam_v0alpha1_TeamstatusOperatorState(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.User": schema_pkg_apis_iam_v0alpha1_User(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserList": schema_pkg_apis_iam_v0alpha1_UserList(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec": schema_pkg_apis_iam_v0alpha1_UserSpec(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus": schema_pkg_apis_iam_v0alpha1_UserStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState": schema_pkg_apis_iam_v0alpha1_UserstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRole": schema_pkg_apis_iam_v0alpha1_CoreRole(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleList": schema_pkg_apis_iam_v0alpha1_CoreRoleList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleSpec": schema_pkg_apis_iam_v0alpha1_CoreRoleSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleStatus": schema_pkg_apis_iam_v0alpha1_CoreRoleStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolespecPermission": schema_pkg_apis_iam_v0alpha1_CoreRolespecPermission(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_CoreRolestatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMapping": schema_pkg_apis_iam_v0alpha1_ExternalGroupMapping(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingList": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingSpec": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingTeamRef": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingTeamRef(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetGroups": schema_pkg_apis_iam_v0alpha1_GetGroups(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetGroupsBody": schema_pkg_apis_iam_v0alpha1_GetGroupsBody(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": schema_pkg_apis_iam_v0alpha1_GlobalRole(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBinding": schema_pkg_apis_iam_v0alpha1_GlobalRoleBinding(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingList": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingSpec": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingStatus": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecRoleRef": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingspecRoleRef(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecSubject": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingspecSubject(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingstatusOperatorState": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleList": schema_pkg_apis_iam_v0alpha1_GlobalRoleList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleSpec": schema_pkg_apis_iam_v0alpha1_GlobalRoleSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleStatus": schema_pkg_apis_iam_v0alpha1_GlobalRoleStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRolespecPermission": schema_pkg_apis_iam_v0alpha1_GlobalRolespecPermission(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_GlobalRolestatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermission": schema_pkg_apis_iam_v0alpha1_ResourcePermission(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionList": schema_pkg_apis_iam_v0alpha1_ResourcePermissionList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionSpec": schema_pkg_apis_iam_v0alpha1_ResourcePermissionSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionStatus": schema_pkg_apis_iam_v0alpha1_ResourcePermissionStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionspecPermission": schema_pkg_apis_iam_v0alpha1_ResourcePermissionspecPermission(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionspecResource": schema_pkg_apis_iam_v0alpha1_ResourcePermissionspecResource(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionstatusOperatorState": schema_pkg_apis_iam_v0alpha1_ResourcePermissionstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Role": schema_pkg_apis_iam_v0alpha1_Role(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBinding": schema_pkg_apis_iam_v0alpha1_RoleBinding(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingList": schema_pkg_apis_iam_v0alpha1_RoleBindingList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingSpec": schema_pkg_apis_iam_v0alpha1_RoleBindingSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingStatus": schema_pkg_apis_iam_v0alpha1_RoleBindingStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecRoleRef": schema_pkg_apis_iam_v0alpha1_RoleBindingspecRoleRef(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecSubject": schema_pkg_apis_iam_v0alpha1_RoleBindingspecSubject(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingstatusOperatorState": schema_pkg_apis_iam_v0alpha1_RoleBindingstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleList": schema_pkg_apis_iam_v0alpha1_RoleList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleSpec": schema_pkg_apis_iam_v0alpha1_RoleSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleStatus": schema_pkg_apis_iam_v0alpha1_RoleStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RolespecPermission": schema_pkg_apis_iam_v0alpha1_RolespecPermission(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_RolestatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccount": schema_pkg_apis_iam_v0alpha1_ServiceAccount(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountList": schema_pkg_apis_iam_v0alpha1_ServiceAccountList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec": schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountStatus": schema_pkg_apis_iam_v0alpha1_ServiceAccountStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountstatusOperatorState": schema_pkg_apis_iam_v0alpha1_ServiceAccountstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.Team": schema_pkg_apis_iam_v0alpha1_Team(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBinding": schema_pkg_apis_iam_v0alpha1_TeamBinding(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingList": schema_pkg_apis_iam_v0alpha1_TeamBindingList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec": schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingStatus": schema_pkg_apis_iam_v0alpha1_TeamBindingStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingTeamRef": schema_pkg_apis_iam_v0alpha1_TeamBindingTeamRef(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject": schema_pkg_apis_iam_v0alpha1_TeamBindingspecSubject(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingstatusOperatorState": schema_pkg_apis_iam_v0alpha1_TeamBindingstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamList": schema_pkg_apis_iam_v0alpha1_TeamList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec": schema_pkg_apis_iam_v0alpha1_TeamSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus": schema_pkg_apis_iam_v0alpha1_TeamStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamstatusOperatorState": schema_pkg_apis_iam_v0alpha1_TeamstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.User": schema_pkg_apis_iam_v0alpha1_User(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserList": schema_pkg_apis_iam_v0alpha1_UserList(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec": schema_pkg_apis_iam_v0alpha1_UserSpec(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus": schema_pkg_apis_iam_v0alpha1_UserStatus(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState": schema_pkg_apis_iam_v0alpha1_UserstatusOperatorState(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping": schema_pkg_apis_iam_v0alpha1_VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping(ref), } } @@ -491,6 +494,76 @@ func schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingTeamRef(ref common.Referen } } +func schema_pkg_apis_iam_v0alpha1_GetGroups(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_GetGroupsBody(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping"}, + } +} + func schema_pkg_apis_iam_v0alpha1_GlobalRole(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2856,3 +2929,30 @@ func schema_pkg_apis_iam_v0alpha1_UserstatusOperatorState(ref common.ReferenceCa }, } } + +func schema_pkg_apis_iam_v0alpha1_VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "externalGroup": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "externalGroup"}, + }, + }, + } +} diff --git a/apps/iam/pkg/apis/iam_manifest.go b/apps/iam/pkg/apis/iam_manifest.go index 388c0dca609..9fe171525b4 100644 --- a/apps/iam/pkg/apis/iam_manifest.go +++ b/apps/iam/pkg/apis/iam_manifest.go @@ -81,6 +81,58 @@ var appManifestData = app.ManifestData{ Plural: "Teams", Scope: "Namespaced", Conversion: false, + Routes: map[string]spec3.PathProps{ + "/groups": { + Get: &spec3.Operation{ + OperationProps: spec3.OperationProps{ + + OperationId: "getGroups", + + Responses: &spec3.Responses{ + ResponsesProps: spec3.ResponsesProps{ + Default: &spec3.Response{ + ResponseProps: spec3.ResponseProps{ + Description: "Default OK response", + Content: map[string]*spec3.MediaType{ + "application/json": { + MediaTypeProps: spec3.MediaTypeProps{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + }, + }, + }, + Required: []string{ + "items", + "apiVersion", + "kind", + }, + }}, + }}, + }, + }, + }, + }}, + }, + }, + }, + }, }, { @@ -142,7 +194,9 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist return goType, exists } -var customRouteToGoResponseType = map[string]any{} +var customRouteToGoResponseType = map[string]any{ + "v0alpha1|Team|groups|GET": v0alpha1.GetGroups{}, +} // ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. // kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index b24c9260327..d2b2f2e8045 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -2,10 +2,18 @@ module github.com/grafana/grafana/apps/plugins go 1.25.3 +replace github.com/grafana/grafana => ../.. + +replace github.com/grafana/grafana/pkg/apimachinery => ../../pkg/apimachinery + +replace github.com/grafana/grafana/pkg/apiserver => ../../pkg/apiserver + require ( + github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 + github.com/grafana/grafana v0.0.0-00010101000000-000000000000 github.com/grafana/grafana-app-sdk v0.48.2 github.com/grafana/grafana-app-sdk/logging v0.48.1 - github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde + github.com/grafana/grafana/pkg/apimachinery v0.0.0 github.com/stretchr/testify v1.11.1 k8s.io/apimachinery v0.34.2 k8s.io/apiserver v0.34.2 @@ -14,57 +22,135 @@ require ( ) require ( + github.com/apache/arrow-go/v18 v18.4.1 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.39.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect + github.com/aws/smithy-go v1.23.1 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cheekybits/genny v1.0.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-jose/go-jose/v4 v4.1.2 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-stack/stack v1.8.1 // indirect github.com/go-test/deep v1.1.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect - github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect + github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect + github.com/grafana/grafana-aws-sdk v1.3.0 // indirect + github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // indirect + github.com/grafana/grafana-plugin-sdk-go v0.283.0 // indirect + github.com/grafana/grafana/pkg/apiserver v0.0.0 // indirect + github.com/grafana/otel-profiling-go v0.5.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect + github.com/grafana/sqlds/v4 v4.2.7 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.7.0 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/memberlist v0.5.2 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/jaegertracing/jaeger-idl v0.5.0 // indirect + github.com/jmespath-community/go-jmespath v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect + github.com/mattetti/filebuffer v1.0.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect + github.com/miekg/dns v1.1.63 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mithrandie/csvq v1.18.1 // indirect + github.com/mithrandie/csvq-driver v1.7.0 // indirect + github.com/mithrandie/go-file/v2 v2.1.0 // indirect + github.com/mithrandie/go-text v1.6.0 // indirect + github.com/mithrandie/ternary v1.1.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/onsi/ginkgo/v2 v2.22.2 // indirect - github.com/onsi/gomega v1.36.2 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/alertmanager v0.28.0 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/exporter-toolkit v0.14.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/tjhop/slog-gokit v0.1.5 // indirect github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect @@ -75,24 +161,32 @@ require ( go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect + golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect + golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.39.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect google.golang.org/grpc v1.76.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.2 // indirect k8s.io/apiextensions-apiserver v0.34.2 // indirect k8s.io/client-go v0.34.2 // indirect + k8s.io/component-base v0.34.2 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index b769fbe3916..1c479c59942 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -1,11 +1,59 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/apache/arrow-go/v18 v18.4.1 h1:q/jVkBWCJOB9reDgaIZIdruLQUb1kbkvOnOFezVH1C4= +github.com/apache/arrow-go/v18 v18.4.1/go.mod h1:tLyFubsAl17bvFdUAy24bsSvA/6ww95Iqi67fTpGu3E= +github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc= +github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE= +github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2/credentials v1.18.14 h1:TxkI7QI+sFkTItN/6cJuMZEIVMFXeu2dI1ZffkXngKI= +github.com/aws/aws-sdk-go-v2/credentials v1.18.14/go.mod h1:12x4Uw/vijC11XkctTjy92TNCQ+UnNJkT7fzX0Yd93E= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0= +github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -14,13 +62,29 @@ github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bF github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= +github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -33,16 +97,53 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -50,44 +151,155 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181 h1:nbxKRtrbuhvOYmI2RhOYauHRJCtpR+vTNIgg1lFUCws= +github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181/go.mod h1:VtPNIFlEOJPPEc13Ax6ZTbNV3M/sAzLID72YjgzOPVA= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= +github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s= +github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= github.com/grafana/grafana-app-sdk v0.48.2 h1:CQQDhwo1fWaXQVKvxxOcK6azbuY3E2TgJHNAZlYYn7U= github.com/grafana/grafana-app-sdk v0.48.2/go.mod h1:LDOvQ7OOyHLcXdSa0InATCa5OMoYAd6E1+rGLrMgHuk= github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng= github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde h1:ydSrBIOCxJQ84+JU+cyYsOLL40QeXrB7rYfsY/ezU4w= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde/go.mod h1:3MwgP0ISxGviTy3ZUJZsNz/56NNtHztMlH+gcxDt6Tw= +github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= +github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk= +github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= +github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc= +github.com/grafana/grafana-plugin-sdk-go v0.283.0 h1:G7IHshAr30rLWV9FtX3iLlFTTlBhuOkfe7xVAoIP5rE= +github.com/grafana/grafana-plugin-sdk-go v0.283.0/go.mod h1:20qhoYxIgbZRmwCEO1KMP8q2yq/Kge5+xE/99/hLEk0= +github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= +github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/sqlds/v4 v4.2.7 h1:sFQhsS7DBakNMdxa++yOfJ9BVvkZwFJ0B95o57K0/XA= +github.com/grafana/sqlds/v4 v4.2.7/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/memberlist v0.5.2 h1:rJoNPWZ0juJBgqn48gjy59K5H4rNgvUoM1kUD7bXiuI= +github.com/hashicorp/memberlist v0.5.2/go.mod h1:Ri9p/tRShbjYnpNf4FFPXG7wxEGY4Nrcn6E7jrVa//4= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= +github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/rw+zyQfyg5UF+L4= +github.com/jmespath-community/go-jmespath v1.1.1/go.mod h1:4gOyFJsR/Gk+05RgTKYrifT7tBPWD8Lubtb5jRrfy9I= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= +github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mithrandie/csvq v1.18.1 h1:f7NB2scbb7xx2ffPduJ2VtZ85RpWXfvanYskAkGlCBU= +github.com/mithrandie/csvq v1.18.1/go.mod h1:MRJj7AtcXfk7jhNGxLuJGP3LORmh4lpiPWxQ7VyCRn8= +github.com/mithrandie/csvq-driver v1.7.0 h1:ejiavXNWwTPMyr3fJFnhcqd1L1cYudA0foQy9cZrqhw= +github.com/mithrandie/csvq-driver v1.7.0/go.mod h1:HcN3xL9UCJnBYA/AIQOOB/KlyfXAiYr5yxDmiwrGk5o= +github.com/mithrandie/go-file/v2 v2.1.0 h1:XA5Tl+73GXMDvgwSE3Sg0uC5FkLr3hnXs8SpUas0hyg= +github.com/mithrandie/go-file/v2 v2.1.0/go.mod h1:9YtTF3Xo59GqC1Pxw6KyGVcM/qubAMlxVsqI/u9r++c= +github.com/mithrandie/go-text v1.6.0 h1:8gOXTMPbMY8DJbKMTv8kHhADcJlDWXqS/YQH4SyWO6s= +github.com/mithrandie/go-text v1.6.0/go.mod h1:xCgj1xiNbI/d4xA9sLVvXkjh5B2tNx2ZT2/3rpmh8to= +github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QRdC4= +github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= @@ -95,43 +307,99 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/alertmanager v0.28.0 h1:sLN+6HhZet8hrbmGHLAHWsTXgZSVCvq9Ix3U3wvivqc= +github.com/prometheus/alertmanager v0.28.0/go.mod h1:/okSnb2LlodbMlRoOWQEKtqI/coOo2NKZDm2Hu9QHLQ= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= +github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc= +github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= @@ -140,8 +408,23 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= @@ -150,12 +433,15 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4D go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= @@ -166,34 +452,68 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= +golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= +golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= @@ -209,25 +529,47 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= @@ -240,6 +582,8 @@ k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= +k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= diff --git a/apps/plugins/kinds/manifest.cue b/apps/plugins/kinds/manifest.cue index b3f5251432b..f9a08562cb0 100644 --- a/apps/plugins/kinds/manifest.cue +++ b/apps/plugins/kinds/manifest.cue @@ -4,15 +4,18 @@ manifest: { appName: "plugins" groupOverride: "plugins.grafana.app" versions: { - "v0alpha1": { - served: true - codegen: { - ts: {enabled: false} - go: {enabled: true} - } - kinds: [ - pluginV0Alpha1, - ] - } + "v0alpha1": v0alpha1Version } } + +v0alpha1Version: { + served: true + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + pluginV0Alpha1, + pluginMetaV0Alpha1, + ] +} diff --git a/apps/plugins/kinds/plugin.cue b/apps/plugins/kinds/plugin.cue index 50c475cd430..dc16a3f6de4 100644 --- a/apps/plugins/kinds/plugin.cue +++ b/apps/plugins/kinds/plugin.cue @@ -1,236 +1,15 @@ package plugins -import ( - "time" -) - pluginV0Alpha1: { - kind: "Plugin" + kind: "Plugin" plural: "plugins" - scope: "Namespaced" + scope: "Namespaced" schema: { spec: { - id: string - version: string - url?: string - class: "core" | "external" - } - } - routes: { - "/meta": { - "GET": { - request: {} - response: #JSONData, - responseMetadata: { - typeMeta: false - objectMeta: false - } - } + id: string + version: string + url?: string + class: "core" | "external" } } } - - -// JSON configuration schema for Grafana plugins -// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json -#JSONData: { - // Unique name of the plugin - id: string - - // Plugin type - type: "app" | "datasource" | "panel" | "renderer" - - // Human-readable name of the plugin - name: string - - // Metadata for the plugin - info: #Info - - // Dependency information - dependencies: #Dependencies - - // Optional fields - alerting?: bool - annotations?: bool - autoEnabled?: bool - backend?: bool - buildMode?: string - builtIn?: bool - category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other" - enterpriseFeatures?: #EnterpriseFeatures - executable?: string - hideFromList?: bool - // +listType=atomic - includes?: [...#Include] - logs?: bool - metrics?: bool - multiValueFilterOperators?: bool - pascalName?: string - preload?: bool - queryOptions?: #QueryOptions - // +listType=atomic - routes?: [...#Route] - skipDataQuery?: bool - state?: "alpha" | "beta" - streaming?: bool - tracing?: bool - iam?: #IAM - // +listType=atomic - roles?: [...#Role] - extensions?: #Extensions -} - -#Info: { - // Required fields - // +listType=set - keywords: [...string] - logos: { - small: string - large: string - } - updated: string & time.Time - version: string - // Optional fields - author?: { - name?: string - email?: string - url?: string - } - description?: string - // +listType=atomic - links?: [...{ - name?: string - url?: string - }] - // +listType=atomic - screenshots?: [...{ - name?: string - path?: string - }] -} - -#Dependencies: { - // Required field - grafanaDependency: string - - // Optional fields - grafanaVersion?: string - // +listType=set - // +listMapKey=id - plugins?: [...{ - id: string - type: "app" | "datasource" | "panel" - name: string - }] - extensions?: { - // +listType=set - exposedComponents?: [...string] - } -} - -#EnterpriseFeatures: { - healthDiagnosticsErrors?: bool | *false - // Allow additional properties - [string]: _ -} - -#Include: { - uid?: string - type?: "dashboard" | "page" | "panel" | "datasource" - name?: string - component?: string - role?: "Admin" | "Editor" | "Viewer" - action?: string - path?: string - addToNav?: bool - defaultNav?: bool - icon?: string -} - -#QueryOptions: { - maxDataPoints?: bool - minInterval?: bool - cacheTimeout?: bool -} - -#Route: { - path?: string - method?: string - url?: string - reqSignedIn?: bool - reqRole?: string - reqAction?: string - // +listType=atomic - headers?: [...string] - body?: [string]: _ - tokenAuth?: { - url?: string - // +listType=set - scopes?: [...string] - params?: [string]: _ - } - jwtTokenAuth?: { - url?: string - // +listType=set - scopes?: [...string] - params?: [string]: _ - } - // +listType=atomic - urlParams?: [...{ - name?: string - content?: string - }] -} - -#IAM: { - // +listType=atomic - permissions?: [...{ - action?: string - scope?: string - }] -} - -#Role: { - role?: { - name?: string - description?: string - // +listType=atomic - permissions?: [...{ - action?: string - scope?: string - }] - } - // +listType=set - grants?: [...string] -} - -#Extensions: { - // +listType=atomic - addedComponents?: [...{ - // +listType=set - targets: [...string] - title: string - description?: string - }] - // +listType=atomic - addedLinks?: [...{ - // +listType=set - targets: [...string] - title: string - description?: string - }] - // +listType=set - // +listMapKey=id - exposedComponents?: [...{ - id: string - title?: string - description?: string - }] - // +listType=set - // +listMapKey=id - extensionPoints?: [...{ - id: string - title?: string - description?: string - }] -} diff --git a/apps/plugins/kinds/pluginmeta.cue b/apps/plugins/kinds/pluginmeta.cue new file mode 100644 index 00000000000..0bac48169c6 --- /dev/null +++ b/apps/plugins/kinds/pluginmeta.cue @@ -0,0 +1,216 @@ +package plugins + +pluginMetaV0Alpha1: { + kind: "PluginMeta" + plural: "pluginsmeta" + scope: "Namespaced" + schema: { + spec: { + pluginJSON: #JSONData, + } + } +} + +// JSON configuration schema for Grafana plugins +// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json +#JSONData: { + // Unique name of the plugin + id: string + + // Plugin type + type: "app" | "datasource" | "panel" | "renderer" + + // Human-readable name of the plugin + name: string + + // Metadata for the plugin + info: #Info + + // Dependency information + dependencies: #Dependencies + + // Optional fields + alerting?: bool + annotations?: bool + autoEnabled?: bool + backend?: bool + buildMode?: string + builtIn?: bool + category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other" + enterpriseFeatures?: #EnterpriseFeatures + executable?: string + hideFromList?: bool + // +listType=atomic + includes?: [...#Include] + logs?: bool + metrics?: bool + multiValueFilterOperators?: bool + pascalName?: string + preload?: bool + queryOptions?: #QueryOptions + // +listType=atomic + routes?: [...#Route] + skipDataQuery?: bool + state?: "alpha" | "beta" + streaming?: bool + tracing?: bool + iam?: #IAM + // +listType=atomic + roles?: [...#Role] + extensions?: #Extensions +} + +#Info: { + // Required fields + // +listType=set + keywords: [...string] + logos: { + small: string + large: string + } + updated: string + version: string + // Optional fields + author?: { + name?: string + email?: string + url?: string + } + description?: string + // +listType=atomic + links?: [...{ + name?: string + url?: string + }] + // +listType=atomic + screenshots?: [...{ + name?: string + path?: string + }] +} + +#Dependencies: { + // Required field + grafanaDependency: string + + // Optional fields + grafanaVersion?: string + // +listType=set + // +listMapKey=id + plugins?: [...{ + id: string + type: "app" | "datasource" | "panel" + name: string + }] + extensions?: { + // +listType=set + exposedComponents?: [...string] + } +} + +#EnterpriseFeatures: { + healthDiagnosticsErrors?: bool | *false + // Allow additional properties + [string]: _ +} + +#Include: { + uid?: string + type?: "dashboard" | "page" | "panel" | "datasource" + name?: string + component?: string + role?: "Admin" | "Editor" | "Viewer" + action?: string + path?: string + addToNav?: bool + defaultNav?: bool + icon?: string +} + +#QueryOptions: { + maxDataPoints?: bool + minInterval?: bool + cacheTimeout?: bool +} + +#Route: { + path?: string + method?: string + url?: string + reqSignedIn?: bool + reqRole?: string + reqAction?: string + // +listType=atomic + headers?: [...string] + body?: [string]: _ + tokenAuth?: { + url?: string + // +listType=set + scopes?: [...string] + params?: [string]: _ + } + jwtTokenAuth?: { + url?: string + // +listType=set + scopes?: [...string] + params?: [string]: _ + } + // +listType=atomic + urlParams?: [...{ + name?: string + content?: string + }] +} + +#IAM: { + // +listType=atomic + permissions?: [...{ + action?: string + scope?: string + }] +} + +#Role: { + role?: { + name?: string + description?: string + // +listType=atomic + permissions?: [...{ + action?: string + scope?: string + }] + } + // +listType=set + grants?: [...string] +} + +#Extensions: { + // +listType=atomic + addedComponents?: [...{ + // +listType=set + targets: [...string] + title: string + description?: string + }] + // +listType=atomic + addedLinks?: [...{ + // +listType=set + targets: [...string] + title: string + description?: string + }] + // +listType=set + // +listMapKey=id + exposedComponents?: [...{ + id: string + title?: string + description?: string + }] + // +listType=set + // +listMapKey=id + extensionPoints?: [...{ + id: string + title?: string + description?: string + }] +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go index 3e9bcb3a2bb..42fc1fb6d54 100644 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go @@ -2,9 +2,6 @@ package v0alpha1 import ( "context" - "encoding/json" - "fmt" - "net/http" "github.com/grafana/grafana-app-sdk/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -100,24 +97,3 @@ func (c *PluginClient) UpdateStatus(ctx context.Context, identifier resource.Ide func (c *PluginClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } - -type GetMetaRequest struct { - Headers http.Header -} - -func (c *PluginClient) GetMeta(ctx context.Context, identifier resource.Identifier, request GetMetaRequest) (*GetMeta, error) { - resp, err := c.client.SubresourceRequest(ctx, identifier, resource.CustomRouteRequestOptions{ - Path: "/meta", - Verb: "GET", - Headers: request.Headers, - }) - if err != nil { - return nil, err - } - cast := GetMeta{} - err = json.Unmarshal(resp, &cast) - if err != nil { - return nil, fmt.Errorf("unable to unmarshal response bytes into GetMeta: %w", err) - } - return &cast, nil -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go deleted file mode 100644 index 6b0e6899a5b..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go +++ /dev/null @@ -1,463 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -import ( - time "time" -) - -// +k8s:openapi-gen=true -type Info struct { - // Required fields - // +listType=set - Keywords []string `json:"keywords"` - Logos V0alpha1InfoLogos `json:"logos"` - Updated time.Time `json:"updated"` - Version string `json:"version"` - // Optional fields - Author *V0alpha1InfoAuthor `json:"author,omitempty"` - Description *string `json:"description,omitempty"` - // +listType=atomic - Links []V0alpha1InfoLinks `json:"links,omitempty"` - // +listType=atomic - Screenshots []V0alpha1InfoScreenshots `json:"screenshots,omitempty"` -} - -// NewInfo creates a new Info object. -func NewInfo() *Info { - return &Info{ - Keywords: []string{}, - Logos: *NewV0alpha1InfoLogos(), - } -} - -// +k8s:openapi-gen=true -type Dependencies struct { - // Required field - GrafanaDependency string `json:"grafanaDependency"` - // Optional fields - GrafanaVersion *string `json:"grafanaVersion,omitempty"` - // +listType=set - // +listMapKey=id - Plugins []V0alpha1DependenciesPlugins `json:"plugins,omitempty"` - Extensions *V0alpha1DependenciesExtensions `json:"extensions,omitempty"` -} - -// NewDependencies creates a new Dependencies object. -func NewDependencies() *Dependencies { - return &Dependencies{} -} - -// +k8s:openapi-gen=true -type EnterpriseFeatures struct { - // Allow additional properties - HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` -} - -// NewEnterpriseFeatures creates a new EnterpriseFeatures object. -func NewEnterpriseFeatures() *EnterpriseFeatures { - return &EnterpriseFeatures{ - HealthDiagnosticsErrors: (func(input bool) *bool { return &input })(false), - } -} - -// +k8s:openapi-gen=true -type Include struct { - Uid *string `json:"uid,omitempty"` - Type *IncludeType `json:"type,omitempty"` - Name *string `json:"name,omitempty"` - Component *string `json:"component,omitempty"` - Role *IncludeRole `json:"role,omitempty"` - Action *string `json:"action,omitempty"` - Path *string `json:"path,omitempty"` - AddToNav *bool `json:"addToNav,omitempty"` - DefaultNav *bool `json:"defaultNav,omitempty"` - Icon *string `json:"icon,omitempty"` -} - -// NewInclude creates a new Include object. -func NewInclude() *Include { - return &Include{} -} - -// +k8s:openapi-gen=true -type QueryOptions struct { - MaxDataPoints *bool `json:"maxDataPoints,omitempty"` - MinInterval *bool `json:"minInterval,omitempty"` - CacheTimeout *bool `json:"cacheTimeout,omitempty"` -} - -// NewQueryOptions creates a new QueryOptions object. -func NewQueryOptions() *QueryOptions { - return &QueryOptions{} -} - -// +k8s:openapi-gen=true -type Route struct { - Path *string `json:"path,omitempty"` - Method *string `json:"method,omitempty"` - Url *string `json:"url,omitempty"` - ReqSignedIn *bool `json:"reqSignedIn,omitempty"` - ReqRole *string `json:"reqRole,omitempty"` - ReqAction *string `json:"reqAction,omitempty"` - // +listType=atomic - Headers []string `json:"headers,omitempty"` - Body map[string]interface{} `json:"body,omitempty"` - TokenAuth *V0alpha1RouteTokenAuth `json:"tokenAuth,omitempty"` - JwtTokenAuth *V0alpha1RouteJwtTokenAuth `json:"jwtTokenAuth,omitempty"` - // +listType=atomic - UrlParams []V0alpha1RouteUrlParams `json:"urlParams,omitempty"` -} - -// NewRoute creates a new Route object. -func NewRoute() *Route { - return &Route{} -} - -// +k8s:openapi-gen=true -type IAM struct { - // +listType=atomic - Permissions []V0alpha1IAMPermissions `json:"permissions,omitempty"` -} - -// NewIAM creates a new IAM object. -func NewIAM() *IAM { - return &IAM{} -} - -// +k8s:openapi-gen=true -type Role struct { - Role *V0alpha1RoleRole `json:"role,omitempty"` - // +listType=set - Grants []string `json:"grants,omitempty"` -} - -// NewRole creates a new Role object. -func NewRole() *Role { - return &Role{} -} - -// +k8s:openapi-gen=true -type Extensions struct { - // +listType=atomic - AddedComponents []V0alpha1ExtensionsAddedComponents `json:"addedComponents,omitempty"` - // +listType=atomic - AddedLinks []V0alpha1ExtensionsAddedLinks `json:"addedLinks,omitempty"` - // +listType=set - // +listMapKey=id - ExposedComponents []V0alpha1ExtensionsExposedComponents `json:"exposedComponents,omitempty"` - // +listType=set - // +listMapKey=id - ExtensionPoints []V0alpha1ExtensionsExtensionPoints `json:"extensionPoints,omitempty"` -} - -// NewExtensions creates a new Extensions object. -func NewExtensions() *Extensions { - return &Extensions{} -} - -// +k8s:openapi-gen=true -type GetMeta struct { - // Unique name of the plugin - Id string `json:"id"` - // Plugin type - Type GetMetaType `json:"type"` - // Human-readable name of the plugin - Name string `json:"name"` - // Metadata for the plugin - Info Info `json:"info"` - // Dependency information - Dependencies Dependencies `json:"dependencies"` - // Optional fields - Alerting *bool `json:"alerting,omitempty"` - Annotations *bool `json:"annotations,omitempty"` - AutoEnabled *bool `json:"autoEnabled,omitempty"` - Backend *bool `json:"backend,omitempty"` - BuildMode *string `json:"buildMode,omitempty"` - BuiltIn *bool `json:"builtIn,omitempty"` - Category *GetMetaCategory `json:"category,omitempty"` - EnterpriseFeatures *EnterpriseFeatures `json:"enterpriseFeatures,omitempty"` - Executable *string `json:"executable,omitempty"` - HideFromList *bool `json:"hideFromList,omitempty"` - // +listType=atomic - Includes []Include `json:"includes,omitempty"` - Logs *bool `json:"logs,omitempty"` - Metrics *bool `json:"metrics,omitempty"` - MultiValueFilterOperators *bool `json:"multiValueFilterOperators,omitempty"` - PascalName *string `json:"pascalName,omitempty"` - Preload *bool `json:"preload,omitempty"` - QueryOptions *QueryOptions `json:"queryOptions,omitempty"` - // +listType=atomic - Routes []Route `json:"routes,omitempty"` - SkipDataQuery *bool `json:"skipDataQuery,omitempty"` - State *GetMetaState `json:"state,omitempty"` - Streaming *bool `json:"streaming,omitempty"` - Tracing *bool `json:"tracing,omitempty"` - Iam *IAM `json:"iam,omitempty"` - // +listType=atomic - Roles []Role `json:"roles,omitempty"` - Extensions *Extensions `json:"extensions,omitempty"` -} - -// NewGetMeta creates a new GetMeta object. -func NewGetMeta() *GetMeta { - return &GetMeta{ - Info: *NewInfo(), - Dependencies: *NewDependencies(), - } -} - -// +k8s:openapi-gen=true -type V0alpha1InfoLogos struct { - Small string `json:"small"` - Large string `json:"large"` -} - -// NewV0alpha1InfoLogos creates a new V0alpha1InfoLogos object. -func NewV0alpha1InfoLogos() *V0alpha1InfoLogos { - return &V0alpha1InfoLogos{} -} - -// +k8s:openapi-gen=true -type V0alpha1InfoAuthor struct { - Name *string `json:"name,omitempty"` - Email *string `json:"email,omitempty"` - Url *string `json:"url,omitempty"` -} - -// NewV0alpha1InfoAuthor creates a new V0alpha1InfoAuthor object. -func NewV0alpha1InfoAuthor() *V0alpha1InfoAuthor { - return &V0alpha1InfoAuthor{} -} - -// +k8s:openapi-gen=true -type V0alpha1InfoLinks struct { - Name *string `json:"name,omitempty"` - Url *string `json:"url,omitempty"` -} - -// NewV0alpha1InfoLinks creates a new V0alpha1InfoLinks object. -func NewV0alpha1InfoLinks() *V0alpha1InfoLinks { - return &V0alpha1InfoLinks{} -} - -// +k8s:openapi-gen=true -type V0alpha1InfoScreenshots struct { - Name *string `json:"name,omitempty"` - Path *string `json:"path,omitempty"` -} - -// NewV0alpha1InfoScreenshots creates a new V0alpha1InfoScreenshots object. -func NewV0alpha1InfoScreenshots() *V0alpha1InfoScreenshots { - return &V0alpha1InfoScreenshots{} -} - -// +k8s:openapi-gen=true -type V0alpha1DependenciesPlugins struct { - Id string `json:"id"` - Type V0alpha1DependenciesPluginsType `json:"type"` - Name string `json:"name"` -} - -// NewV0alpha1DependenciesPlugins creates a new V0alpha1DependenciesPlugins object. -func NewV0alpha1DependenciesPlugins() *V0alpha1DependenciesPlugins { - return &V0alpha1DependenciesPlugins{} -} - -// +k8s:openapi-gen=true -type V0alpha1DependenciesExtensions struct { - // +listType=set - ExposedComponents []string `json:"exposedComponents,omitempty"` -} - -// NewV0alpha1DependenciesExtensions creates a new V0alpha1DependenciesExtensions object. -func NewV0alpha1DependenciesExtensions() *V0alpha1DependenciesExtensions { - return &V0alpha1DependenciesExtensions{} -} - -// +k8s:openapi-gen=true -type V0alpha1RouteTokenAuth struct { - Url *string `json:"url,omitempty"` - // +listType=set - Scopes []string `json:"scopes,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` -} - -// NewV0alpha1RouteTokenAuth creates a new V0alpha1RouteTokenAuth object. -func NewV0alpha1RouteTokenAuth() *V0alpha1RouteTokenAuth { - return &V0alpha1RouteTokenAuth{} -} - -// +k8s:openapi-gen=true -type V0alpha1RouteJwtTokenAuth struct { - Url *string `json:"url,omitempty"` - // +listType=set - Scopes []string `json:"scopes,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` -} - -// NewV0alpha1RouteJwtTokenAuth creates a new V0alpha1RouteJwtTokenAuth object. -func NewV0alpha1RouteJwtTokenAuth() *V0alpha1RouteJwtTokenAuth { - return &V0alpha1RouteJwtTokenAuth{} -} - -// +k8s:openapi-gen=true -type V0alpha1RouteUrlParams struct { - Name *string `json:"name,omitempty"` - Content *string `json:"content,omitempty"` -} - -// NewV0alpha1RouteUrlParams creates a new V0alpha1RouteUrlParams object. -func NewV0alpha1RouteUrlParams() *V0alpha1RouteUrlParams { - return &V0alpha1RouteUrlParams{} -} - -// +k8s:openapi-gen=true -type V0alpha1IAMPermissions struct { - Action *string `json:"action,omitempty"` - Scope *string `json:"scope,omitempty"` -} - -// NewV0alpha1IAMPermissions creates a new V0alpha1IAMPermissions object. -func NewV0alpha1IAMPermissions() *V0alpha1IAMPermissions { - return &V0alpha1IAMPermissions{} -} - -// +k8s:openapi-gen=true -type V0alpha1RoleRolePermissions struct { - Action *string `json:"action,omitempty"` - Scope *string `json:"scope,omitempty"` -} - -// NewV0alpha1RoleRolePermissions creates a new V0alpha1RoleRolePermissions object. -func NewV0alpha1RoleRolePermissions() *V0alpha1RoleRolePermissions { - return &V0alpha1RoleRolePermissions{} -} - -// +k8s:openapi-gen=true -type V0alpha1RoleRole struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - // +listType=atomic - Permissions []V0alpha1RoleRolePermissions `json:"permissions,omitempty"` -} - -// NewV0alpha1RoleRole creates a new V0alpha1RoleRole object. -func NewV0alpha1RoleRole() *V0alpha1RoleRole { - return &V0alpha1RoleRole{} -} - -// +k8s:openapi-gen=true -type V0alpha1ExtensionsAddedComponents struct { - // +listType=set - Targets []string `json:"targets"` - Title string `json:"title"` - Description *string `json:"description,omitempty"` -} - -// NewV0alpha1ExtensionsAddedComponents creates a new V0alpha1ExtensionsAddedComponents object. -func NewV0alpha1ExtensionsAddedComponents() *V0alpha1ExtensionsAddedComponents { - return &V0alpha1ExtensionsAddedComponents{ - Targets: []string{}, - } -} - -// +k8s:openapi-gen=true -type V0alpha1ExtensionsAddedLinks struct { - // +listType=set - Targets []string `json:"targets"` - Title string `json:"title"` - Description *string `json:"description,omitempty"` -} - -// NewV0alpha1ExtensionsAddedLinks creates a new V0alpha1ExtensionsAddedLinks object. -func NewV0alpha1ExtensionsAddedLinks() *V0alpha1ExtensionsAddedLinks { - return &V0alpha1ExtensionsAddedLinks{ - Targets: []string{}, - } -} - -// +k8s:openapi-gen=true -type V0alpha1ExtensionsExposedComponents struct { - Id string `json:"id"` - Title *string `json:"title,omitempty"` - Description *string `json:"description,omitempty"` -} - -// NewV0alpha1ExtensionsExposedComponents creates a new V0alpha1ExtensionsExposedComponents object. -func NewV0alpha1ExtensionsExposedComponents() *V0alpha1ExtensionsExposedComponents { - return &V0alpha1ExtensionsExposedComponents{} -} - -// +k8s:openapi-gen=true -type V0alpha1ExtensionsExtensionPoints struct { - Id string `json:"id"` - Title *string `json:"title,omitempty"` - Description *string `json:"description,omitempty"` -} - -// NewV0alpha1ExtensionsExtensionPoints creates a new V0alpha1ExtensionsExtensionPoints object. -func NewV0alpha1ExtensionsExtensionPoints() *V0alpha1ExtensionsExtensionPoints { - return &V0alpha1ExtensionsExtensionPoints{} -} - -// +k8s:openapi-gen=true -type IncludeType string - -const ( - IncludeTypeDashboard IncludeType = "dashboard" - IncludeTypePage IncludeType = "page" - IncludeTypePanel IncludeType = "panel" - IncludeTypeDatasource IncludeType = "datasource" -) - -// +k8s:openapi-gen=true -type IncludeRole string - -const ( - IncludeRoleAdmin IncludeRole = "Admin" - IncludeRoleEditor IncludeRole = "Editor" - IncludeRoleViewer IncludeRole = "Viewer" -) - -// +k8s:openapi-gen=true -type GetMetaType string - -const ( - GetMetaTypeApp GetMetaType = "app" - GetMetaTypeDatasource GetMetaType = "datasource" - GetMetaTypePanel GetMetaType = "panel" - GetMetaTypeRenderer GetMetaType = "renderer" -) - -// +k8s:openapi-gen=true -type GetMetaCategory string - -const ( - GetMetaCategoryTsdb GetMetaCategory = "tsdb" - GetMetaCategoryLogging GetMetaCategory = "logging" - GetMetaCategoryCloud GetMetaCategory = "cloud" - GetMetaCategoryTracing GetMetaCategory = "tracing" - GetMetaCategoryProfiling GetMetaCategory = "profiling" - GetMetaCategorySql GetMetaCategory = "sql" - GetMetaCategoryEnterprise GetMetaCategory = "enterprise" - GetMetaCategoryIot GetMetaCategory = "iot" - GetMetaCategoryOther GetMetaCategory = "other" -) - -// +k8s:openapi-gen=true -type GetMetaState string - -const ( - GetMetaStateAlpha GetMetaState = "alpha" - GetMetaStateBeta GetMetaState = "beta" -) - -// +k8s:openapi-gen=true -type V0alpha1DependenciesPluginsType string - -const ( - V0alpha1DependenciesPluginsTypeApp V0alpha1DependenciesPluginsType = "app" - V0alpha1DependenciesPluginsTypeDatasource V0alpha1DependenciesPluginsType = "datasource" - V0alpha1DependenciesPluginsTypePanel V0alpha1DependenciesPluginsType = "panel" -) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go new file mode 100644 index 00000000000..e7788e27a33 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go @@ -0,0 +1,99 @@ +package v0alpha1 + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type PluginMetaClient struct { + client *resource.TypedClient[*PluginMeta, *PluginMetaList] +} + +func NewPluginMetaClient(client resource.Client) *PluginMetaClient { + return &PluginMetaClient{ + client: resource.NewTypedClient[*PluginMeta, *PluginMetaList](client, PluginMetaKind()), + } +} + +func NewPluginMetaClientFromGenerator(generator resource.ClientGenerator) (*PluginMetaClient, error) { + c, err := generator.ClientFor(PluginMetaKind()) + if err != nil { + return nil, err + } + return NewPluginMetaClient(c), nil +} + +func (c *PluginMetaClient) Get(ctx context.Context, identifier resource.Identifier) (*PluginMeta, error) { + return c.client.Get(ctx, identifier) +} + +func (c *PluginMetaClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginMetaList, error) { + return c.client.List(ctx, namespace, opts) +} + +func (c *PluginMetaClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginMetaList, error) { + resp, err := c.client.List(ctx, namespace, resource.ListOptions{ + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + for resp.GetContinue() != "" { + page, err := c.client.List(ctx, namespace, resource.ListOptions{ + Continue: resp.GetContinue(), + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + resp.SetContinue(page.GetContinue()) + resp.SetResourceVersion(page.GetResourceVersion()) + resp.SetItems(append(resp.GetItems(), page.GetItems()...)) + } + return resp, nil +} + +func (c *PluginMetaClient) Create(ctx context.Context, obj *PluginMeta, opts resource.CreateOptions) (*PluginMeta, error) { + // Make sure apiVersion and kind are set + obj.APIVersion = GroupVersion.Identifier() + obj.Kind = PluginMetaKind().Kind() + return c.client.Create(ctx, obj, opts) +} + +func (c *PluginMetaClient) Update(ctx context.Context, obj *PluginMeta, opts resource.UpdateOptions) (*PluginMeta, error) { + return c.client.Update(ctx, obj, opts) +} + +func (c *PluginMetaClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*PluginMeta, error) { + return c.client.Patch(ctx, identifier, req, opts) +} + +func (c *PluginMetaClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus PluginMetaStatus, opts resource.UpdateOptions) (*PluginMeta, error) { + return c.client.Update(ctx, &PluginMeta{ + TypeMeta: metav1.TypeMeta{ + Kind: PluginMetaKind().Kind(), + APIVersion: GroupVersion.Identifier(), + }, + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, + }, + Status: newStatus, + }, resource.UpdateOptions{ + Subresource: "status", + ResourceVersion: opts.ResourceVersion, + }) +} + +func (c *PluginMetaClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + return c.client.Delete(ctx, identifier, opts) +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go new file mode 100644 index 00000000000..77fb6f918a2 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// PluginMetaJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type PluginMetaJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*PluginMetaJSONCodec) Read(reader io.Reader, into resource.Object) error { + return json.NewDecoder(reader).Decode(into) +} + +// Write writes JSON-encoded bytes into `writer` marshaled from `from` +func (*PluginMetaJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &PluginMetaJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go new file mode 100644 index 00000000000..7d3b3c9c6b8 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go @@ -0,0 +1,31 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// metadata contains embedded CommonMetadata and can be extended with custom string fields +// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +// without external reference as using the CommonMetadata reference breaks thema codegen. +type PluginMetaMetadata struct { + UpdateTimestamp time.Time `json:"updateTimestamp"` + CreatedBy string `json:"createdBy"` + Uid string `json:"uid"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + ResourceVersion string `json:"resourceVersion"` + Generation int64 `json:"generation"` + UpdatedBy string `json:"updatedBy"` + Labels map[string]string `json:"labels"` +} + +// NewPluginMetaMetadata creates a new PluginMetaMetadata object. +func NewPluginMetaMetadata() *PluginMetaMetadata { + return &PluginMetaMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go new file mode 100644 index 00000000000..dac431ebf12 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go @@ -0,0 +1,319 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "fmt" + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "time" +) + +// +k8s:openapi-gen=true +type PluginMeta struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the PluginMeta + Spec PluginMetaSpec `json:"spec" yaml:"spec"` + + Status PluginMetaStatus `json:"status" yaml:"status"` +} + +func (o *PluginMeta) GetSpec() any { + return o.Spec +} + +func (o *PluginMeta) SetSpec(spec any) error { + cast, ok := spec.(PluginMetaSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *PluginMeta) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *PluginMeta) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *PluginMeta) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(PluginMetaStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type PluginMetaStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *PluginMeta) GetStaticMetadata() resource.StaticMetadata { + gvk := o.GroupVersionKind() + return resource.StaticMetadata{ + Name: o.ObjectMeta.Name, + Namespace: o.ObjectMeta.Namespace, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + } +} + +func (o *PluginMeta) SetStaticMetadata(metadata resource.StaticMetadata) { + o.Name = metadata.Name + o.Namespace = metadata.Namespace + o.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.Group, + Version: metadata.Version, + Kind: metadata.Kind, + }) +} + +func (o *PluginMeta) GetCommonMetadata() resource.CommonMetadata { + dt := o.DeletionTimestamp + var deletionTimestamp *time.Time + if dt != nil { + deletionTimestamp = &dt.Time + } + // Legacy ExtraFields support + extraFields := make(map[string]any) + if o.Annotations != nil { + extraFields["annotations"] = o.Annotations + } + if o.ManagedFields != nil { + extraFields["managedFields"] = o.ManagedFields + } + if o.OwnerReferences != nil { + extraFields["ownerReferences"] = o.OwnerReferences + } + return resource.CommonMetadata{ + UID: string(o.UID), + ResourceVersion: o.ResourceVersion, + Generation: o.Generation, + Labels: o.Labels, + CreationTimestamp: o.CreationTimestamp.Time, + DeletionTimestamp: deletionTimestamp, + Finalizers: o.Finalizers, + UpdateTimestamp: o.GetUpdateTimestamp(), + CreatedBy: o.GetCreatedBy(), + UpdatedBy: o.GetUpdatedBy(), + ExtraFields: extraFields, + } +} + +func (o *PluginMeta) SetCommonMetadata(metadata resource.CommonMetadata) { + o.UID = types.UID(metadata.UID) + o.ResourceVersion = metadata.ResourceVersion + o.Generation = metadata.Generation + o.Labels = metadata.Labels + o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) + if metadata.DeletionTimestamp != nil { + dt := metav1.NewTime(*metadata.DeletionTimestamp) + o.DeletionTimestamp = &dt + } else { + o.DeletionTimestamp = nil + } + o.Finalizers = metadata.Finalizers + if o.Annotations == nil { + o.Annotations = make(map[string]string) + } + if !metadata.UpdateTimestamp.IsZero() { + o.SetUpdateTimestamp(metadata.UpdateTimestamp) + } + if metadata.CreatedBy != "" { + o.SetCreatedBy(metadata.CreatedBy) + } + if metadata.UpdatedBy != "" { + o.SetUpdatedBy(metadata.UpdatedBy) + } + // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields + if metadata.ExtraFields != nil { + if annotations, ok := metadata.ExtraFields["annotations"]; ok { + if cast, ok := annotations.(map[string]string); ok { + o.Annotations = cast + } + } + if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { + if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { + o.ManagedFields = cast + } + } + if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { + if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { + o.OwnerReferences = cast + } + } + } +} + +func (o *PluginMeta) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *PluginMeta) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *PluginMeta) GetUpdateTimestamp() time.Time { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) + return parsed +} + +func (o *PluginMeta) SetUpdateTimestamp(updateTimestamp time.Time) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) +} + +func (o *PluginMeta) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *PluginMeta) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *PluginMeta) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *PluginMeta) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginMeta) DeepCopy() *PluginMeta { + cpy := &PluginMeta{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginMeta) DeepCopyInto(dst *PluginMeta) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) + o.Status.DeepCopyInto(&dst.Status) +} + +// Interface compliance compile-time check +var _ resource.Object = &PluginMeta{} + +// +k8s:openapi-gen=true +type PluginMetaList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []PluginMeta `json:"items" yaml:"items"` +} + +func (o *PluginMetaList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginMetaList) Copy() resource.ListObject { + cpy := &PluginMetaList{ + TypeMeta: o.TypeMeta, + Items: make([]PluginMeta, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*PluginMeta); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *PluginMetaList) GetItems() []resource.Object { + items := make([]resource.Object, len(o.Items)) + for i := 0; i < len(o.Items); i++ { + items[i] = &o.Items[i] + } + return items +} + +func (o *PluginMetaList) SetItems(items []resource.Object) { + o.Items = make([]PluginMeta, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*PluginMeta) + } +} + +func (o *PluginMetaList) DeepCopy() *PluginMetaList { + cpy := &PluginMetaList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginMetaList) DeepCopyInto(dst *PluginMetaList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &PluginMetaList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *PluginMetaSpec) DeepCopy() *PluginMetaSpec { + cpy := &PluginMetaSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *PluginMetaSpec) DeepCopyInto(dst *PluginMetaSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of PluginMetaStatus +func (s *PluginMetaStatus) DeepCopy() *PluginMetaStatus { + cpy := &PluginMetaStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies PluginMetaStatus into another PluginMetaStatus object +func (s *PluginMetaStatus) DeepCopyInto(dst *PluginMetaStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go new file mode 100644 index 00000000000..a4022c8de97 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaPluginMeta = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginMeta{}, &PluginMetaList{}, resource.WithKind("PluginMeta"), + resource.WithPlural("pluginmetas"), resource.WithScope(resource.NamespacedScope)) + kindPluginMeta = resource.Kind{ + Schema: schemaPluginMeta, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &PluginMetaJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func PluginMetaKind() resource.Kind { + return kindPluginMeta +} + +// Schema returns a resource.SimpleSchema representation of PluginMeta +func PluginMetaSchema() *resource.SimpleSchema { + return schemaPluginMeta +} + +// Interface compliance checks +var _ resource.Schema = kindPluginMeta diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go new file mode 100644 index 00000000000..f9a58b291ad --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go @@ -0,0 +1,473 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// JSON configuration schema for Grafana plugins +// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json +// +k8s:openapi-gen=true +type PluginMetaJSONData struct { + // Unique name of the plugin + Id string `json:"id"` + // Plugin type + Type PluginMetaJSONDataType `json:"type"` + // Human-readable name of the plugin + Name string `json:"name"` + // Metadata for the plugin + Info PluginMetaInfo `json:"info"` + // Dependency information + Dependencies PluginMetaDependencies `json:"dependencies"` + // Optional fields + Alerting *bool `json:"alerting,omitempty"` + Annotations *bool `json:"annotations,omitempty"` + AutoEnabled *bool `json:"autoEnabled,omitempty"` + Backend *bool `json:"backend,omitempty"` + BuildMode *string `json:"buildMode,omitempty"` + BuiltIn *bool `json:"builtIn,omitempty"` + Category *PluginMetaJSONDataCategory `json:"category,omitempty"` + EnterpriseFeatures *PluginMetaEnterpriseFeatures `json:"enterpriseFeatures,omitempty"` + Executable *string `json:"executable,omitempty"` + HideFromList *bool `json:"hideFromList,omitempty"` + // +listType=atomic + Includes []PluginMetaInclude `json:"includes,omitempty"` + Logs *bool `json:"logs,omitempty"` + Metrics *bool `json:"metrics,omitempty"` + MultiValueFilterOperators *bool `json:"multiValueFilterOperators,omitempty"` + PascalName *string `json:"pascalName,omitempty"` + Preload *bool `json:"preload,omitempty"` + QueryOptions *PluginMetaQueryOptions `json:"queryOptions,omitempty"` + // +listType=atomic + Routes []PluginMetaRoute `json:"routes,omitempty"` + SkipDataQuery *bool `json:"skipDataQuery,omitempty"` + State *PluginMetaJSONDataState `json:"state,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + Tracing *bool `json:"tracing,omitempty"` + Iam *PluginMetaIAM `json:"iam,omitempty"` + // +listType=atomic + Roles []PluginMetaRole `json:"roles,omitempty"` + Extensions *PluginMetaExtensions `json:"extensions,omitempty"` +} + +// NewPluginMetaJSONData creates a new PluginMetaJSONData object. +func NewPluginMetaJSONData() *PluginMetaJSONData { + return &PluginMetaJSONData{ + Info: *NewPluginMetaInfo(), + Dependencies: *NewPluginMetaDependencies(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaInfo struct { + // Required fields + // +listType=set + Keywords []string `json:"keywords"` + Logos PluginMetaV0alpha1InfoLogos `json:"logos"` + Updated string `json:"updated"` + Version string `json:"version"` + // Optional fields + Author *PluginMetaV0alpha1InfoAuthor `json:"author,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Links []PluginMetaV0alpha1InfoLinks `json:"links,omitempty"` + // +listType=atomic + Screenshots []PluginMetaV0alpha1InfoScreenshots `json:"screenshots,omitempty"` +} + +// NewPluginMetaInfo creates a new PluginMetaInfo object. +func NewPluginMetaInfo() *PluginMetaInfo { + return &PluginMetaInfo{ + Keywords: []string{}, + Logos: *NewPluginMetaV0alpha1InfoLogos(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaDependencies struct { + // Required field + GrafanaDependency string `json:"grafanaDependency"` + // Optional fields + GrafanaVersion *string `json:"grafanaVersion,omitempty"` + // +listType=set + // +listMapKey=id + Plugins []PluginMetaV0alpha1DependenciesPlugins `json:"plugins,omitempty"` + Extensions *PluginMetaV0alpha1DependenciesExtensions `json:"extensions,omitempty"` +} + +// NewPluginMetaDependencies creates a new PluginMetaDependencies object. +func NewPluginMetaDependencies() *PluginMetaDependencies { + return &PluginMetaDependencies{} +} + +// +k8s:openapi-gen=true +type PluginMetaEnterpriseFeatures struct { + // Allow additional properties + HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` +} + +// NewPluginMetaEnterpriseFeatures creates a new PluginMetaEnterpriseFeatures object. +func NewPluginMetaEnterpriseFeatures() *PluginMetaEnterpriseFeatures { + return &PluginMetaEnterpriseFeatures{ + HealthDiagnosticsErrors: (func(input bool) *bool { return &input })(false), + } +} + +// +k8s:openapi-gen=true +type PluginMetaInclude struct { + Uid *string `json:"uid,omitempty"` + Type *PluginMetaIncludeType `json:"type,omitempty"` + Name *string `json:"name,omitempty"` + Component *string `json:"component,omitempty"` + Role *PluginMetaIncludeRole `json:"role,omitempty"` + Action *string `json:"action,omitempty"` + Path *string `json:"path,omitempty"` + AddToNav *bool `json:"addToNav,omitempty"` + DefaultNav *bool `json:"defaultNav,omitempty"` + Icon *string `json:"icon,omitempty"` +} + +// NewPluginMetaInclude creates a new PluginMetaInclude object. +func NewPluginMetaInclude() *PluginMetaInclude { + return &PluginMetaInclude{} +} + +// +k8s:openapi-gen=true +type PluginMetaQueryOptions struct { + MaxDataPoints *bool `json:"maxDataPoints,omitempty"` + MinInterval *bool `json:"minInterval,omitempty"` + CacheTimeout *bool `json:"cacheTimeout,omitempty"` +} + +// NewPluginMetaQueryOptions creates a new PluginMetaQueryOptions object. +func NewPluginMetaQueryOptions() *PluginMetaQueryOptions { + return &PluginMetaQueryOptions{} +} + +// +k8s:openapi-gen=true +type PluginMetaRoute struct { + Path *string `json:"path,omitempty"` + Method *string `json:"method,omitempty"` + Url *string `json:"url,omitempty"` + ReqSignedIn *bool `json:"reqSignedIn,omitempty"` + ReqRole *string `json:"reqRole,omitempty"` + ReqAction *string `json:"reqAction,omitempty"` + // +listType=atomic + Headers []string `json:"headers,omitempty"` + Body map[string]interface{} `json:"body,omitempty"` + TokenAuth *PluginMetaV0alpha1RouteTokenAuth `json:"tokenAuth,omitempty"` + JwtTokenAuth *PluginMetaV0alpha1RouteJwtTokenAuth `json:"jwtTokenAuth,omitempty"` + // +listType=atomic + UrlParams []PluginMetaV0alpha1RouteUrlParams `json:"urlParams,omitempty"` +} + +// NewPluginMetaRoute creates a new PluginMetaRoute object. +func NewPluginMetaRoute() *PluginMetaRoute { + return &PluginMetaRoute{} +} + +// +k8s:openapi-gen=true +type PluginMetaIAM struct { + // +listType=atomic + Permissions []PluginMetaV0alpha1IAMPermissions `json:"permissions,omitempty"` +} + +// NewPluginMetaIAM creates a new PluginMetaIAM object. +func NewPluginMetaIAM() *PluginMetaIAM { + return &PluginMetaIAM{} +} + +// +k8s:openapi-gen=true +type PluginMetaRole struct { + Role *PluginMetaV0alpha1RoleRole `json:"role,omitempty"` + // +listType=set + Grants []string `json:"grants,omitempty"` +} + +// NewPluginMetaRole creates a new PluginMetaRole object. +func NewPluginMetaRole() *PluginMetaRole { + return &PluginMetaRole{} +} + +// +k8s:openapi-gen=true +type PluginMetaExtensions struct { + // +listType=atomic + AddedComponents []PluginMetaV0alpha1ExtensionsAddedComponents `json:"addedComponents,omitempty"` + // +listType=atomic + AddedLinks []PluginMetaV0alpha1ExtensionsAddedLinks `json:"addedLinks,omitempty"` + // +listType=set + // +listMapKey=id + ExposedComponents []PluginMetaV0alpha1ExtensionsExposedComponents `json:"exposedComponents,omitempty"` + // +listType=set + // +listMapKey=id + ExtensionPoints []PluginMetaV0alpha1ExtensionsExtensionPoints `json:"extensionPoints,omitempty"` +} + +// NewPluginMetaExtensions creates a new PluginMetaExtensions object. +func NewPluginMetaExtensions() *PluginMetaExtensions { + return &PluginMetaExtensions{} +} + +// +k8s:openapi-gen=true +type PluginMetaSpec struct { + PluginJSON PluginMetaJSONData `json:"pluginJSON"` +} + +// NewPluginMetaSpec creates a new PluginMetaSpec object. +func NewPluginMetaSpec() *PluginMetaSpec { + return &PluginMetaSpec{ + PluginJSON: *NewPluginMetaJSONData(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoLogos struct { + Small string `json:"small"` + Large string `json:"large"` +} + +// NewPluginMetaV0alpha1InfoLogos creates a new PluginMetaV0alpha1InfoLogos object. +func NewPluginMetaV0alpha1InfoLogos() *PluginMetaV0alpha1InfoLogos { + return &PluginMetaV0alpha1InfoLogos{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoAuthor struct { + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewPluginMetaV0alpha1InfoAuthor creates a new PluginMetaV0alpha1InfoAuthor object. +func NewPluginMetaV0alpha1InfoAuthor() *PluginMetaV0alpha1InfoAuthor { + return &PluginMetaV0alpha1InfoAuthor{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoLinks struct { + Name *string `json:"name,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewPluginMetaV0alpha1InfoLinks creates a new PluginMetaV0alpha1InfoLinks object. +func NewPluginMetaV0alpha1InfoLinks() *PluginMetaV0alpha1InfoLinks { + return &PluginMetaV0alpha1InfoLinks{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoScreenshots struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` +} + +// NewPluginMetaV0alpha1InfoScreenshots creates a new PluginMetaV0alpha1InfoScreenshots object. +func NewPluginMetaV0alpha1InfoScreenshots() *PluginMetaV0alpha1InfoScreenshots { + return &PluginMetaV0alpha1InfoScreenshots{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesPlugins struct { + Id string `json:"id"` + Type PluginMetaV0alpha1DependenciesPluginsType `json:"type"` + Name string `json:"name"` +} + +// NewPluginMetaV0alpha1DependenciesPlugins creates a new PluginMetaV0alpha1DependenciesPlugins object. +func NewPluginMetaV0alpha1DependenciesPlugins() *PluginMetaV0alpha1DependenciesPlugins { + return &PluginMetaV0alpha1DependenciesPlugins{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesExtensions struct { + // +listType=set + ExposedComponents []string `json:"exposedComponents,omitempty"` +} + +// NewPluginMetaV0alpha1DependenciesExtensions creates a new PluginMetaV0alpha1DependenciesExtensions object. +func NewPluginMetaV0alpha1DependenciesExtensions() *PluginMetaV0alpha1DependenciesExtensions { + return &PluginMetaV0alpha1DependenciesExtensions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewPluginMetaV0alpha1RouteTokenAuth creates a new PluginMetaV0alpha1RouteTokenAuth object. +func NewPluginMetaV0alpha1RouteTokenAuth() *PluginMetaV0alpha1RouteTokenAuth { + return &PluginMetaV0alpha1RouteTokenAuth{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteJwtTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewPluginMetaV0alpha1RouteJwtTokenAuth creates a new PluginMetaV0alpha1RouteJwtTokenAuth object. +func NewPluginMetaV0alpha1RouteJwtTokenAuth() *PluginMetaV0alpha1RouteJwtTokenAuth { + return &PluginMetaV0alpha1RouteJwtTokenAuth{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteUrlParams struct { + Name *string `json:"name,omitempty"` + Content *string `json:"content,omitempty"` +} + +// NewPluginMetaV0alpha1RouteUrlParams creates a new PluginMetaV0alpha1RouteUrlParams object. +func NewPluginMetaV0alpha1RouteUrlParams() *PluginMetaV0alpha1RouteUrlParams { + return &PluginMetaV0alpha1RouteUrlParams{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1IAMPermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewPluginMetaV0alpha1IAMPermissions creates a new PluginMetaV0alpha1IAMPermissions object. +func NewPluginMetaV0alpha1IAMPermissions() *PluginMetaV0alpha1IAMPermissions { + return &PluginMetaV0alpha1IAMPermissions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RoleRolePermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewPluginMetaV0alpha1RoleRolePermissions creates a new PluginMetaV0alpha1RoleRolePermissions object. +func NewPluginMetaV0alpha1RoleRolePermissions() *PluginMetaV0alpha1RoleRolePermissions { + return &PluginMetaV0alpha1RoleRolePermissions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RoleRole struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Permissions []PluginMetaV0alpha1RoleRolePermissions `json:"permissions,omitempty"` +} + +// NewPluginMetaV0alpha1RoleRole creates a new PluginMetaV0alpha1RoleRole object. +func NewPluginMetaV0alpha1RoleRole() *PluginMetaV0alpha1RoleRole { + return &PluginMetaV0alpha1RoleRole{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsAddedComponents struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsAddedComponents creates a new PluginMetaV0alpha1ExtensionsAddedComponents object. +func NewPluginMetaV0alpha1ExtensionsAddedComponents() *PluginMetaV0alpha1ExtensionsAddedComponents { + return &PluginMetaV0alpha1ExtensionsAddedComponents{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsAddedLinks struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsAddedLinks creates a new PluginMetaV0alpha1ExtensionsAddedLinks object. +func NewPluginMetaV0alpha1ExtensionsAddedLinks() *PluginMetaV0alpha1ExtensionsAddedLinks { + return &PluginMetaV0alpha1ExtensionsAddedLinks{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsExposedComponents struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsExposedComponents creates a new PluginMetaV0alpha1ExtensionsExposedComponents object. +func NewPluginMetaV0alpha1ExtensionsExposedComponents() *PluginMetaV0alpha1ExtensionsExposedComponents { + return &PluginMetaV0alpha1ExtensionsExposedComponents{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsExtensionPoints struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsExtensionPoints creates a new PluginMetaV0alpha1ExtensionsExtensionPoints object. +func NewPluginMetaV0alpha1ExtensionsExtensionPoints() *PluginMetaV0alpha1ExtensionsExtensionPoints { + return &PluginMetaV0alpha1ExtensionsExtensionPoints{} +} + +// +k8s:openapi-gen=true +type PluginMetaJSONDataType string + +const ( + PluginMetaJSONDataTypeApp PluginMetaJSONDataType = "app" + PluginMetaJSONDataTypeDatasource PluginMetaJSONDataType = "datasource" + PluginMetaJSONDataTypePanel PluginMetaJSONDataType = "panel" + PluginMetaJSONDataTypeRenderer PluginMetaJSONDataType = "renderer" +) + +// +k8s:openapi-gen=true +type PluginMetaJSONDataCategory string + +const ( + PluginMetaJSONDataCategoryTsdb PluginMetaJSONDataCategory = "tsdb" + PluginMetaJSONDataCategoryLogging PluginMetaJSONDataCategory = "logging" + PluginMetaJSONDataCategoryCloud PluginMetaJSONDataCategory = "cloud" + PluginMetaJSONDataCategoryTracing PluginMetaJSONDataCategory = "tracing" + PluginMetaJSONDataCategoryProfiling PluginMetaJSONDataCategory = "profiling" + PluginMetaJSONDataCategorySql PluginMetaJSONDataCategory = "sql" + PluginMetaJSONDataCategoryEnterprise PluginMetaJSONDataCategory = "enterprise" + PluginMetaJSONDataCategoryIot PluginMetaJSONDataCategory = "iot" + PluginMetaJSONDataCategoryOther PluginMetaJSONDataCategory = "other" +) + +// +k8s:openapi-gen=true +type PluginMetaJSONDataState string + +const ( + PluginMetaJSONDataStateAlpha PluginMetaJSONDataState = "alpha" + PluginMetaJSONDataStateBeta PluginMetaJSONDataState = "beta" +) + +// +k8s:openapi-gen=true +type PluginMetaIncludeType string + +const ( + PluginMetaIncludeTypeDashboard PluginMetaIncludeType = "dashboard" + PluginMetaIncludeTypePage PluginMetaIncludeType = "page" + PluginMetaIncludeTypePanel PluginMetaIncludeType = "panel" + PluginMetaIncludeTypeDatasource PluginMetaIncludeType = "datasource" +) + +// +k8s:openapi-gen=true +type PluginMetaIncludeRole string + +const ( + PluginMetaIncludeRoleAdmin PluginMetaIncludeRole = "Admin" + PluginMetaIncludeRoleEditor PluginMetaIncludeRole = "Editor" + PluginMetaIncludeRoleViewer PluginMetaIncludeRole = "Viewer" +) + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesPluginsType string + +const ( + PluginMetaV0alpha1DependenciesPluginsTypeApp PluginMetaV0alpha1DependenciesPluginsType = "app" + PluginMetaV0alpha1DependenciesPluginsTypeDatasource PluginMetaV0alpha1DependenciesPluginsType = "datasource" + PluginMetaV0alpha1DependenciesPluginsTypePanel PluginMetaV0alpha1DependenciesPluginsType = "panel" +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go new file mode 100644 index 00000000000..60fa37dbb32 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginMetastatusOperatorState struct { + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State PluginMetaStatusOperatorStateState `json:"state"` + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` +} + +// NewPluginMetastatusOperatorState creates a new PluginMetastatusOperatorState object. +func NewPluginMetastatusOperatorState() *PluginMetastatusOperatorState { + return &PluginMetastatusOperatorState{} +} + +// +k8s:openapi-gen=true +type PluginMetaStatus struct { + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]PluginMetastatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewPluginMetaStatus creates a new PluginMetaStatus object. +func NewPluginMetaStatus() *PluginMetaStatus { + return &PluginMetaStatus{} +} + +// +k8s:openapi-gen=true +type PluginMetaStatusOperatorStateState string + +const ( + PluginMetaStatusOperatorStateStateSuccess PluginMetaStatusOperatorStateState = "success" + PluginMetaStatusOperatorStateStateInProgress PluginMetaStatusOperatorStateState = "in_progress" + PluginMetaStatusOperatorStateStateFailed PluginMetaStatusOperatorStateState = "failed" +) diff --git a/apps/plugins/pkg/apis/plugins_manifest.go b/apps/plugins/pkg/apis/plugins_manifest.go index 09c57eaa186..b5a47a6715b 100644 --- a/apps/plugins/pkg/apis/plugins_manifest.go +++ b/apps/plugins/pkg/apis/plugins_manifest.go @@ -20,9 +20,12 @@ import ( ) var ( - rawSchemaPluginv0alpha1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"Plugin":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"getMetaDependencies":{"type":"object","required":["grafanaDependency"],"properties":{"extensions":{"type":"object","properties":{"exposedComponents":{"description":"+listType=set","type":"array","items":{"type":"string"}}},"additionalProperties":false},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id","type","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["app","datasource","panel"]}},"additionalProperties":false}}},"additionalProperties":false},"getMetaEnterpriseFeatures":{"type":"object","properties":{"healthDiagnosticsErrors":{"description":"Allow additional properties","type":"boolean","default":false}},"additionalProperties":false},"getMetaExtensions":{"type":"object","properties":{"addedComponents":{"description":"+listType=atomic","type":"array","items":{"type":"object","required":["targets","title"],"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","type":"array","items":{"type":"string"}},"title":{"type":"string"}},"additionalProperties":false}},"addedLinks":{"description":"+listType=atomic","type":"array","items":{"type":"object","required":["targets","title"],"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","type":"array","items":{"type":"string"}},"title":{"type":"string"}},"additionalProperties":false}},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id"],"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"additionalProperties":false}},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id"],"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"getMetaIAM":{"type":"object","properties":{"permissions":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"scope":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"getMetaInclude":{"type":"object","properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"type":"string","enum":["Admin","Editor","Viewer"]},"type":{"type":"string","enum":["dashboard","page","panel","datasource"]},"uid":{"type":"string"}},"additionalProperties":false},"getMetaInfo":{"type":"object","required":["keywords","logos","updated","version"],"properties":{"author":{"description":"Optional fields","type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"additionalProperties":false},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","type":"array","items":{"type":"string"}},"links":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string"}},"additionalProperties":false}},"logos":{"type":"object","required":["small","large"],"properties":{"large":{"type":"string"},"small":{"type":"string"}},"additionalProperties":false},"screenshots":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string"}},"additionalProperties":false}},"updated":{"type":"string","format":"date-time"},"version":{"type":"string"}},"additionalProperties":false},"getMetaQueryOptions":{"type":"object","properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"additionalProperties":false},"getMetaRole":{"type":"object","properties":{"grants":{"description":"+listType=set","type":"array","items":{"type":"string"}},"role":{"type":"object","properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"scope":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false}},"additionalProperties":false},"getMetaRoute":{"type":"object","properties":{"body":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"headers":{"description":"+listType=atomic","type":"array","items":{"type":"string"}},"jwtTokenAuth":{"type":"object","properties":{"params":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"scopes":{"description":"+listType=set","type":"array","items":{"type":"string"}},"url":{"type":"string"}},"additionalProperties":false},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"type":"object","properties":{"params":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"scopes":{"description":"+listType=set","type":"array","items":{"type":"string"}},"url":{"type":"string"}},"additionalProperties":false},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"content":{"type":"string"},"name":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"spec":{"additionalProperties":false,"properties":{"class":{"enum":["core","external"],"type":"string"},"id":{"type":"string"},"url":{"type":"string"},"version":{"type":"string"}},"required":["id","version","class"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) - versionSchemaPluginv0alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaPluginv0alpha1, &versionSchemaPluginv0alpha1) + rawSchemaPluginv0alpha1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"Plugin":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"class":{"enum":["core","external"],"type":"string"},"id":{"type":"string"},"url":{"type":"string"},"version":{"type":"string"}},"required":["id","version","class"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPluginv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPluginv0alpha1, &versionSchemaPluginv0alpha1) + rawSchemaPluginMetav0alpha1 = []byte(`{"Dependencies":{"additionalProperties":false,"properties":{"extensions":{"additionalProperties":false,"properties":{"exposedComponents":{"description":"+listType=set","items":{"type":"string"},"type":"array"}},"type":"object"},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"enum":["app","datasource","panel"],"type":"string"}},"required":["id","type","name"],"type":"object"},"type":"array"}},"required":["grafanaDependency"],"type":"object"},"EnterpriseFeatures":{"additionalProperties":false,"properties":{"healthDiagnosticsErrors":{"default":false,"description":"Allow additional properties","type":"boolean"}},"type":"object"},"Extensions":{"additionalProperties":false,"properties":{"addedComponents":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedLinks":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"}},"type":"object"},"IAM":{"additionalProperties":false,"properties":{"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"Include":{"additionalProperties":false,"properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"enum":["Admin","Editor","Viewer"],"type":"string"},"type":{"enum":["dashboard","page","panel","datasource"],"type":"string"},"uid":{"type":"string"}},"type":"object"},"Info":{"additionalProperties":false,"properties":{"author":{"additionalProperties":false,"description":"Optional fields","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","items":{"type":"string"},"type":"array"},"links":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"logos":{"additionalProperties":false,"properties":{"large":{"type":"string"},"small":{"type":"string"}},"required":["small","large"],"type":"object"},"screenshots":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"path":{"type":"string"}},"type":"object"},"type":"array"},"updated":{"type":"string"},"version":{"type":"string"}},"required":["keywords","logos","updated","version"],"type":"object"},"JSONData":{"additionalProperties":false,"description":"JSON configuration schema for Grafana plugins\nConverted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json","properties":{"alerting":{"description":"Optional fields","type":"boolean"},"annotations":{"type":"boolean"},"autoEnabled":{"type":"boolean"},"backend":{"type":"boolean"},"buildMode":{"type":"string"},"builtIn":{"type":"boolean"},"category":{"enum":["tsdb","logging","cloud","tracing","profiling","sql","enterprise","iot","other"],"type":"string"},"dependencies":{"$ref":"#/components/schemas/Dependencies","description":"Dependency information"},"enterpriseFeatures":{"$ref":"#/components/schemas/EnterpriseFeatures"},"executable":{"type":"string"},"extensions":{"$ref":"#/components/schemas/Extensions"},"hideFromList":{"type":"boolean"},"iam":{"$ref":"#/components/schemas/IAM"},"id":{"description":"Unique name of the plugin","type":"string"},"includes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Include"},"type":"array"},"info":{"$ref":"#/components/schemas/Info","description":"Metadata for the plugin"},"logs":{"type":"boolean"},"metrics":{"type":"boolean"},"multiValueFilterOperators":{"type":"boolean"},"name":{"description":"Human-readable name of the plugin","type":"string"},"pascalName":{"type":"string"},"preload":{"type":"boolean"},"queryOptions":{"$ref":"#/components/schemas/QueryOptions"},"roles":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Role"},"type":"array"},"routes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Route"},"type":"array"},"skipDataQuery":{"type":"boolean"},"state":{"enum":["alpha","beta"],"type":"string"},"streaming":{"type":"boolean"},"tracing":{"type":"boolean"},"type":{"description":"Plugin type","enum":["app","datasource","panel","renderer"],"type":"string"}},"required":["id","type","name","info","dependencies"],"type":"object"},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"PluginMeta":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"QueryOptions":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"type":"object"},"Role":{"additionalProperties":false,"properties":{"grants":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"role":{"additionalProperties":false,"properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"},"Route":{"additionalProperties":false,"properties":{"body":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"headers":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"jwtTokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"content":{"type":"string"},"name":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"spec":{"additionalProperties":false,"properties":{"pluginJSON":{"$ref":"#/components/schemas/JSONData"}},"required":["pluginJSON"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPluginMetav0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPluginMetav0alpha1, &versionSchemaPluginMetav0alpha1) ) var appManifestData = app.ManifestData{ @@ -40,229 +43,14 @@ var appManifestData = app.ManifestData{ Scope: "Namespaced", Conversion: false, Schema: &versionSchemaPluginv0alpha1, - Routes: map[string]spec3.PathProps{ - "/meta": { - Get: &spec3.Operation{ - OperationProps: spec3.OperationProps{ + }, - OperationId: "getMeta", - - Responses: &spec3.Responses{ - ResponsesProps: spec3.ResponsesProps{ - Default: &spec3.Response{ - ResponseProps: spec3.ResponseProps{ - Description: "Default OK response", - Content: map[string]*spec3.MediaType{ - "application/json": { - MediaTypeProps: spec3.MediaTypeProps{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "alerting": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - Description: "Optional fields", - }, - }, - "annotations": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "autoEnabled": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "backend": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "buildMode": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - }, - }, - "builtIn": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "category": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Enum: []interface{}{ - "tsdb", - "logging", - "cloud", - "tracing", - "profiling", - "sql", - "enterprise", - "iot", - "other", - }, - }, - }, - "dependencies": { - SchemaProps: spec.SchemaProps{ - - Description: "Dependency information", - Ref: spec.MustCreateRef("#/components/schemas/getMetaDependencies"), - }, - }, - "enterpriseFeatures": { - SchemaProps: spec.SchemaProps{ - - Ref: spec.MustCreateRef("#/components/schemas/getMetaEnterpriseFeatures"), - }, - }, - "executable": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - }, - }, - "extensions": { - SchemaProps: spec.SchemaProps{ - - Ref: spec.MustCreateRef("#/components/schemas/getMetaExtensions"), - }, - }, - "hideFromList": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "iam": { - SchemaProps: spec.SchemaProps{ - - Ref: spec.MustCreateRef("#/components/schemas/getMetaIAM"), - }, - }, - "id": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Description: "Unique name of the plugin", - }, - }, - "includes": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Description: "+listType=atomic", - }, - }, - "info": { - SchemaProps: spec.SchemaProps{ - - Description: "Metadata for the plugin", - Ref: spec.MustCreateRef("#/components/schemas/getMetaInfo"), - }, - }, - "logs": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "metrics": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "multiValueFilterOperators": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "name": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Description: "Human-readable name of the plugin", - }, - }, - "pascalName": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - }, - }, - "preload": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "queryOptions": { - SchemaProps: spec.SchemaProps{ - - Ref: spec.MustCreateRef("#/components/schemas/getMetaQueryOptions"), - }, - }, - "roles": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Description: "+listType=atomic", - }, - }, - "routes": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Description: "+listType=atomic", - }, - }, - "skipDataQuery": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Enum: []interface{}{ - "alpha", - "beta", - }, - }, - }, - "streaming": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "tracing": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - }, - "type": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Description: "Plugin type", - Enum: []interface{}{ - "app", - "datasource", - "panel", - "renderer", - }, - }, - }, - }, - Required: []string{ - "id", - "type", - "name", - "info", - "dependencies", - }, - }}, - }}, - }, - }, - }, - }}, - }, - }, - }, - }, + { + Kind: "PluginMeta", + Plural: "PluginMetas", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaPluginMetav0alpha1, }, }, Routes: app.ManifestVersionRoutes{ @@ -283,7 +71,8 @@ func RemoteManifest() app.Manifest { } var kindVersionToGoType = map[string]resource.Kind{ - "Plugin/v0alpha1": v0alpha1.PluginKind(), + "Plugin/v0alpha1": v0alpha1.PluginKind(), + "PluginMeta/v0alpha1": v0alpha1.PluginMetaKind(), } // ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. @@ -293,9 +82,7 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist return goType, exists } -var customRouteToGoResponseType = map[string]any{ - "v0alpha1|Plugin|meta|GET": v0alpha1.GetMeta{}, -} +var customRouteToGoResponseType = map[string]any{} // ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. // kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. diff --git a/apps/plugins/pkg/app/app.go b/apps/plugins/pkg/app/app.go index 903e2580ab0..21c132ad081 100644 --- a/apps/plugins/pkg/app/app.go +++ b/apps/plugins/pkg/app/app.go @@ -2,27 +2,23 @@ package app import ( "context" - "net/http" + "fmt" "github.com/grafana/grafana-app-sdk/app" - "github.com/grafana/grafana-app-sdk/k8s" - "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/operator" - "github.com/grafana/grafana-app-sdk/resource" "github.com/grafana/grafana-app-sdk/simple" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog/v2" - pluginsapi "github.com/grafana/grafana/apps/plugins/pkg/apis" pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" + "github.com/grafana/grafana/apps/plugins/pkg/app/meta" ) func New(cfg app.Config) (app.App, error) { cfg.KubeConfig.APIPath = "apis" - clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()) - client, err := pluginsv0alpha1.NewPluginClientFromGenerator(clientGenerator) - if err != nil { - return nil, err + + specificConfig, ok := cfg.SpecificConfig.(*PluginAppConfig) + if !ok { + return nil, fmt.Errorf("invalid config type") } simpleConfig := simple.AppConfig{ @@ -38,27 +34,9 @@ func New(cfg app.Config) (app.App, error) { ManagedKinds: []simple.AppManagedKind{ { Kind: pluginsv0alpha1.PluginKind(), - CustomRoutes: simple.AppCustomRouteHandlers{ - simple.AppCustomRoute{ - Method: http.MethodGet, - Path: "meta", - }: func(ctx context.Context, w app.CustomRouteResponseWriter, req *app.CustomRouteRequest) error { - plugin, err := client.Get(ctx, resource.Identifier{ - Namespace: req.ResourceIdentifier.Namespace, - Name: req.ResourceIdentifier.Name, - }) - if err != nil { - return err - } - logging.DefaultLogger.Debug("fetched plugin", "plugin", plugin) - // TODO: Implement this in future PR - w.WriteHeader(http.StatusNotImplemented) - if _, err := w.Write([]byte("Not implemented")); err != nil { - return err - } - return nil - }, - }, + }, + { + Kind: pluginsv0alpha1.PluginMetaKind(), }, }, } @@ -73,23 +51,12 @@ func New(cfg app.Config) (app.App, error) { return nil, err } + // Register MetaProviderManager as a runnable so its cleanup goroutine is managed by the app lifecycle + a.AddRunnable(specificConfig.MetaProviderManager) + return a, nil } -func GetKinds() map[schema.GroupVersion][]resource.Kind { - kinds := make(map[schema.GroupVersion][]resource.Kind) - manifest := pluginsapi.LocalManifest() - for _, v := range manifest.ManifestData.Versions { - gv := schema.GroupVersion{ - Group: manifest.ManifestData.Group, - Version: v.Name, - } - for _, k := range v.Kinds { - kind, ok := pluginsapi.ManifestGoTypeAssociator(k.Kind, v.Name) - if ok { - kinds[gv] = append(kinds[gv], kind) - } - } - } - return kinds +type PluginAppConfig struct { + MetaProviderManager *meta.ProviderManager } diff --git a/apps/plugins/pkg/app/meta/cloud.go b/apps/plugins/pkg/app/meta/cloud.go new file mode 100644 index 00000000000..f49445d093a --- /dev/null +++ b/apps/plugins/pkg/app/meta/cloud.go @@ -0,0 +1,131 @@ +package meta + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "time" + + "github.com/grafana/grafana-app-sdk/logging" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +const ( + defaultCloudTTL = 1 * time.Hour +) + +// CloudProvider retrieves plugin metadata from the grafana.com API. +type CloudProvider struct { + httpClient *http.Client + grafanaComAPIURL string + log logging.Logger + ttl time.Duration +} + +// NewCloudProvider creates a new CloudProvider that fetches metadata from grafana.com. +func NewCloudProvider(grafanaComAPIURL string) *CloudProvider { + return NewCloudProviderWithTTL(grafanaComAPIURL, defaultCloudTTL) +} + +// NewCloudProviderWithTTL creates a new CloudProvider with a custom TTL. +func NewCloudProviderWithTTL(grafanaComAPIURL string, ttl time.Duration) *CloudProvider { + if grafanaComAPIURL == "" { + grafanaComAPIURL = "https://grafana.com/api/plugins" + } + + return &CloudProvider{ + httpClient: &http.Client{ + Timeout: 10 * time.Second, + }, + grafanaComAPIURL: grafanaComAPIURL, + log: logging.DefaultLogger, + ttl: ttl, + } +} + +// GetMeta fetches plugin metadata from grafana.com API endpoint: +// GET /api/plugins/{pluginId}/versions/{version} +func (p *CloudProvider) GetMeta(ctx context.Context, pluginID, version string) (*Result, error) { + u, err := url.Parse(p.grafanaComAPIURL) + if err != nil { + return nil, fmt.Errorf("invalid grafana.com API URL: %w", err) + } + + u.Path = path.Join(u.Path, pluginID, "versions", version) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "grafana-plugins-app") + + resp, err := p.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to fetch plugin metadata: %w", err) + } + defer func() { + if err = resp.Body.Close(); err != nil { + p.log.Warn("Failed to close response body", "error", err) + } + }() + + if resp.StatusCode == http.StatusNotFound { + return nil, ErrMetaNotFound + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d from grafana.com API", resp.StatusCode) + } + + var gcomMeta grafanaComPluginVersionMeta + if err = json.NewDecoder(resp.Body).Decode(&gcomMeta); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &Result{ + Meta: gcomMeta.JSON, + TTL: p.ttl, + }, nil +} + +// grafanaComPluginVersionMeta represents the response from grafana.com API +// GET /api/plugins/{pluginId}/versions/{version} +type grafanaComPluginVersionMeta struct { + PluginID string `json:"pluginSlug"` + Version string `json:"version"` + URL string `json:"url"` + Commit string `json:"commit"` + Description string `json:"description"` + Keywords []string `json:"keywords"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + JSON pluginsv0alpha1.PluginMetaJSONData `json:"json"` + Readme string `json:"readme"` + Downloads int `json:"downloads"` + Verified bool `json:"verified"` + Status string `json:"status"` + StatusContext string `json:"statusContext"` + DownloadSlug string `json:"downloadSlug"` + SignatureType string `json:"signatureType"` + SignedByOrg string `json:"signedByOrg"` + SignedByOrgName string `json:"signedByOrgName"` + Packages struct { + Any struct { + Md5 string `json:"md5"` + Sha256 string `json:"sha256"` + PackageName string `json:"packageName"` + DownloadURL string `json:"downloadUrl"` + } `json:"any"` + } `json:"packages"` + Links []struct { + Rel string `json:"rel"` + Href string `json:"href"` + } `json:"links"` + AngularDetected bool `json:"angularDetected"` + Scopes []string `json:"scopes"` +} diff --git a/apps/plugins/pkg/app/meta/cloud_test.go b/apps/plugins/pkg/app/meta/cloud_test.go new file mode 100644 index 00000000000..bef2d1a5041 --- /dev/null +++ b/apps/plugins/pkg/app/meta/cloud_test.go @@ -0,0 +1,186 @@ +package meta + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +func TestCloudProvider_GetMeta(t *testing.T) { + ctx := context.Background() + + t.Run("successfully fetches plugin metadata", func(t *testing.T) { + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, "/api/plugins/test-plugin/versions/1.0.0", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Accept")) + assert.Equal(t, "grafana-plugins-app", r.Header.Get("User-Agent")) + + response := grafanaComPluginVersionMeta{ + PluginID: "test-plugin", + Version: "1.0.0", + JSON: expectedMeta, + Description: "Test description", + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(response)) + })) + defer server.Close() + + provider := NewCloudProvider(server.URL + "/api/plugins") + result, err := provider.GetMeta(ctx, "test-plugin", "1.0.0") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, expectedMeta, result.Meta) + assert.Equal(t, defaultCloudTTL, result.TTL) + }) + + t.Run("returns ErrMetaNotFound for 404 status", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer server.Close() + + provider := NewCloudProvider(server.URL + "/api/plugins") + result, err := provider.GetMeta(ctx, "nonexistent-plugin", "1.0.0") + + assert.Error(t, err) + assert.True(t, errors.Is(err, ErrMetaNotFound)) + assert.Nil(t, result) + }) + + t.Run("returns error for non-200 status codes", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + + provider := NewCloudProvider(server.URL + "/api/plugins") + result, err := provider.GetMeta(ctx, "test-plugin", "1.0.0") + + assert.Error(t, err) + assert.Contains(t, err.Error(), "unexpected status code 500") + assert.Nil(t, result) + }) + + t.Run("returns error for invalid JSON response", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("invalid json")) + })) + defer server.Close() + + provider := NewCloudProvider(server.URL + "/api/plugins") + result, err := provider.GetMeta(ctx, "test-plugin", "1.0.0") + + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to decode response") + assert.Nil(t, result) + }) + + t.Run("returns error for invalid API URL", func(t *testing.T) { + provider := NewCloudProvider("://invalid-url") + result, err := provider.GetMeta(ctx, "test-plugin", "1.0.0") + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid grafana.com API URL") + assert.Nil(t, result) + }) + + t.Run("uses custom TTL when provided", func(t *testing.T) { + customTTL := 2 * time.Hour + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response := grafanaComPluginVersionMeta{ + PluginID: "test-plugin", + Version: "1.0.0", + JSON: expectedMeta, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + require.NoError(t, json.NewEncoder(w).Encode(response)) + })) + defer server.Close() + + provider := NewCloudProviderWithTTL(server.URL+"/api/plugins", customTTL) + result, err := provider.GetMeta(ctx, "test-plugin", "1.0.0") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, customTTL, result.TTL) + }) + + t.Run("handles context cancellation", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(100 * time.Millisecond) + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + provider := NewCloudProvider(server.URL + "/api/plugins") + result, err := provider.GetMeta(ctx, "test-plugin", "1.0.0") + + assert.Error(t, err) + assert.Nil(t, result) + }) +} + +func TestNewCloudProvider(t *testing.T) { + t.Run("creates provider with default TTL", func(t *testing.T) { + provider := NewCloudProvider("https://grafana.com/api/plugins") + assert.Equal(t, defaultCloudTTL, provider.ttl) + assert.NotNil(t, provider.httpClient) + assert.Equal(t, "https://grafana.com/api/plugins", provider.grafanaComAPIURL) + }) + + t.Run("uses default URL when empty", func(t *testing.T) { + provider := NewCloudProvider("") + assert.Equal(t, "https://grafana.com/api/plugins", provider.grafanaComAPIURL) + }) +} + +func TestNewCloudProviderWithTTL(t *testing.T) { + t.Run("creates provider with custom TTL", func(t *testing.T) { + customTTL := 2 * time.Hour + provider := NewCloudProviderWithTTL("https://grafana.com/api/plugins", customTTL) + assert.Equal(t, customTTL, provider.ttl) + }) + + t.Run("accepts zero TTL", func(t *testing.T) { + provider := NewCloudProviderWithTTL("https://grafana.com/api/plugins", 0) + assert.Equal(t, time.Duration(0), provider.ttl) + }) + + t.Run("uses default URL when empty", func(t *testing.T) { + provider := NewCloudProviderWithTTL("", defaultCloudTTL) + assert.Equal(t, "https://grafana.com/api/plugins", provider.grafanaComAPIURL) + }) +} diff --git a/apps/plugins/pkg/app/meta/core.go b/apps/plugins/pkg/app/meta/core.go new file mode 100644 index 00000000000..3452f95b24b --- /dev/null +++ b/apps/plugins/pkg/app/meta/core.go @@ -0,0 +1,603 @@ +package meta + +import ( + "context" + "encoding/json" + "errors" + "os" + "path/filepath" + "sync" + "time" + + "github.com/grafana/grafana-app-sdk/logging" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/plugins/manager/sources" +) + +const ( + defaultCoreTTL = 24 * time.Hour +) + +// CoreProvider retrieves plugin metadata for core plugins. +type CoreProvider struct { + mu sync.RWMutex + loadedPlugins map[string]pluginsv0alpha1.PluginMetaJSONData + initialized bool + ttl time.Duration +} + +// NewCoreProvider creates a new CoreProvider for core plugins. +func NewCoreProvider() *CoreProvider { + return NewCoreProviderWithTTL(defaultCoreTTL) +} + +// NewCoreProviderWithTTL creates a new CoreProvider with a custom TTL. +func NewCoreProviderWithTTL(ttl time.Duration) *CoreProvider { + return &CoreProvider{ + loadedPlugins: make(map[string]pluginsv0alpha1.PluginMetaJSONData), + ttl: ttl, + } +} + +// GetMeta retrieves plugin metadata for core plugins. +func (p *CoreProvider) GetMeta(ctx context.Context, pluginID, _ string) (*Result, error) { + // Check cache first + p.mu.RLock() + if meta, found := p.loadedPlugins[pluginID]; found { + p.mu.RUnlock() + return &Result{ + Meta: meta, + TTL: p.ttl, + }, nil + } + p.mu.RUnlock() + + // Initialize cache if not already done + p.mu.Lock() + defer p.mu.Unlock() + + // Double-check after acquiring write lock + if meta, found := p.loadedPlugins[pluginID]; found { + return &Result{ + Meta: meta, + TTL: p.ttl, + }, nil + } + + if !p.initialized { + if err := p.loadPlugins(ctx); err != nil { + logging.DefaultLogger.Warn("CoreProvider: could not load core plugins, will return ErrMetaNotFound for all lookups", "error", err) + // Mark as initialized even on failure so we don't keep trying + p.initialized = true + return nil, ErrMetaNotFound + } + p.initialized = true + } + + if meta, found := p.loadedPlugins[pluginID]; found { + return &Result{ + Meta: meta, + TTL: p.ttl, + }, nil + } + + return nil, ErrMetaNotFound +} + +// loadPlugins discovers and caches all core plugins. +// Returns an error if the static root path cannot be found or if plugin discovery fails. +// This error will be handled gracefully by GetMeta, which will return ErrMetaNotFound +// to allow other providers to handle the request. +func (p *CoreProvider) loadPlugins(ctx context.Context) error { + var staticRootPath string + if wd, err := os.Getwd(); err == nil { + // Check if we're in the Grafana root + publicPath := filepath.Join(wd, "public", "app", "plugins") + if _, err = os.Stat(publicPath); err == nil { + staticRootPath = filepath.Join(wd, "public") + } + } + + if staticRootPath == "" { + return errors.New("could not find Grafana static root path") + } + + datasourcePath := filepath.Join(staticRootPath, "app", "plugins", "datasource") + panelPath := filepath.Join(staticRootPath, "app", "plugins", "panel") + + src := sources.NewLocalSource(plugins.ClassCore, []string{datasourcePath, panelPath}) + ps, err := src.Discover(ctx) + if err != nil { + return err + } + + if len(ps) == 0 { + logging.DefaultLogger.Warn("CoreProvider: no core plugins found during discovery") + return nil + } + + for _, bundle := range ps { + meta := jsonDataToPluginMetaJSONData(bundle.Primary.JSONData) + p.loadedPlugins[bundle.Primary.JSONData.ID] = meta + } + + return nil +} + +// jsonDataToPluginMetaJSONData converts a plugins.JSONData to a pluginsv0alpha1.PluginMetaJSONData. +// nolint:gocyclo +func jsonDataToPluginMetaJSONData(jsonData plugins.JSONData) pluginsv0alpha1.PluginMetaJSONData { + meta := pluginsv0alpha1.PluginMetaJSONData{ + Id: jsonData.ID, + Name: jsonData.Name, + } + + // Map plugin type + switch jsonData.Type { + case plugins.TypeApp: + meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeApp + case plugins.TypeDataSource: + meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeDatasource + case plugins.TypePanel: + meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypePanel + case plugins.TypeRenderer: + meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeRenderer + } + + // Map Info + meta.Info = pluginsv0alpha1.PluginMetaInfo{ + Keywords: jsonData.Info.Keywords, + Logos: pluginsv0alpha1.PluginMetaV0alpha1InfoLogos{ + Small: jsonData.Info.Logos.Small, + Large: jsonData.Info.Logos.Large, + }, + Updated: jsonData.Info.Updated, + Version: jsonData.Info.Version, + } + + if jsonData.Info.Description != "" { + meta.Info.Description = &jsonData.Info.Description + } + + if jsonData.Info.Author.Name != "" || jsonData.Info.Author.URL != "" { + author := &pluginsv0alpha1.PluginMetaV0alpha1InfoAuthor{} + if jsonData.Info.Author.Name != "" { + author.Name = &jsonData.Info.Author.Name + } + if jsonData.Info.Author.URL != "" { + author.Url = &jsonData.Info.Author.URL + } + meta.Info.Author = author + } + + if len(jsonData.Info.Links) > 0 { + meta.Info.Links = make([]pluginsv0alpha1.PluginMetaV0alpha1InfoLinks, 0, len(jsonData.Info.Links)) + for _, link := range jsonData.Info.Links { + v0Link := pluginsv0alpha1.PluginMetaV0alpha1InfoLinks{} + if link.Name != "" { + v0Link.Name = &link.Name + } + if link.URL != "" { + v0Link.Url = &link.URL + } + meta.Info.Links = append(meta.Info.Links, v0Link) + } + } + + if len(jsonData.Info.Screenshots) > 0 { + meta.Info.Screenshots = make([]pluginsv0alpha1.PluginMetaV0alpha1InfoScreenshots, 0, len(jsonData.Info.Screenshots)) + for _, screenshot := range jsonData.Info.Screenshots { + v0Screenshot := pluginsv0alpha1.PluginMetaV0alpha1InfoScreenshots{} + if screenshot.Name != "" { + v0Screenshot.Name = &screenshot.Name + } + if screenshot.Path != "" { + v0Screenshot.Path = &screenshot.Path + } + meta.Info.Screenshots = append(meta.Info.Screenshots, v0Screenshot) + } + } + + // Map Dependencies + meta.Dependencies = pluginsv0alpha1.PluginMetaDependencies{ + GrafanaDependency: jsonData.Dependencies.GrafanaDependency, + } + + if jsonData.Dependencies.GrafanaVersion != "" { + meta.Dependencies.GrafanaVersion = &jsonData.Dependencies.GrafanaVersion + } + + if len(jsonData.Dependencies.Plugins) > 0 { + meta.Dependencies.Plugins = make([]pluginsv0alpha1.PluginMetaV0alpha1DependenciesPlugins, 0, len(jsonData.Dependencies.Plugins)) + for _, dep := range jsonData.Dependencies.Plugins { + var depType pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsType + switch dep.Type { + case "app": + depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypeApp + case "datasource": + depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypeDatasource + case "panel": + depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypePanel + } + meta.Dependencies.Plugins = append(meta.Dependencies.Plugins, pluginsv0alpha1.PluginMetaV0alpha1DependenciesPlugins{ + Id: dep.ID, + Type: depType, + Name: dep.Name, + }) + } + } + + if len(jsonData.Dependencies.Extensions.ExposedComponents) > 0 { + meta.Dependencies.Extensions = &pluginsv0alpha1.PluginMetaV0alpha1DependenciesExtensions{ + ExposedComponents: jsonData.Dependencies.Extensions.ExposedComponents, + } + } + + // Map optional boolean fields + if jsonData.Alerting { + meta.Alerting = &jsonData.Alerting + } + if jsonData.Annotations { + meta.Annotations = &jsonData.Annotations + } + if jsonData.AutoEnabled { + meta.AutoEnabled = &jsonData.AutoEnabled + } + if jsonData.Backend { + meta.Backend = &jsonData.Backend + } + if jsonData.BuiltIn { + meta.BuiltIn = &jsonData.BuiltIn + } + if jsonData.HideFromList { + meta.HideFromList = &jsonData.HideFromList + } + if jsonData.Logs { + meta.Logs = &jsonData.Logs + } + if jsonData.Metrics { + meta.Metrics = &jsonData.Metrics + } + if jsonData.MultiValueFilterOperators { + meta.MultiValueFilterOperators = &jsonData.MultiValueFilterOperators + } + if jsonData.Preload { + meta.Preload = &jsonData.Preload + } + if jsonData.SkipDataQuery { + meta.SkipDataQuery = &jsonData.SkipDataQuery + } + if jsonData.Streaming { + meta.Streaming = &jsonData.Streaming + } + if jsonData.Tracing { + meta.Tracing = &jsonData.Tracing + } + + // Map category + if jsonData.Category != "" { + var category pluginsv0alpha1.PluginMetaJSONDataCategory + switch jsonData.Category { + case "tsdb": + category = pluginsv0alpha1.PluginMetaJSONDataCategoryTsdb + case "logging": + category = pluginsv0alpha1.PluginMetaJSONDataCategoryLogging + case "cloud": + category = pluginsv0alpha1.PluginMetaJSONDataCategoryCloud + case "tracing": + category = pluginsv0alpha1.PluginMetaJSONDataCategoryTracing + case "profiling": + category = pluginsv0alpha1.PluginMetaJSONDataCategoryProfiling + case "sql": + category = pluginsv0alpha1.PluginMetaJSONDataCategorySql + case "enterprise": + category = pluginsv0alpha1.PluginMetaJSONDataCategoryEnterprise + case "iot": + category = pluginsv0alpha1.PluginMetaJSONDataCategoryIot + case "other": + category = pluginsv0alpha1.PluginMetaJSONDataCategoryOther + default: + category = pluginsv0alpha1.PluginMetaJSONDataCategoryOther + } + meta.Category = &category + } + + // Map state + if jsonData.State != "" { + var state pluginsv0alpha1.PluginMetaJSONDataState + switch jsonData.State { + case plugins.ReleaseStateAlpha: + state = pluginsv0alpha1.PluginMetaJSONDataStateAlpha + case plugins.ReleaseStateBeta: + state = pluginsv0alpha1.PluginMetaJSONDataStateBeta + default: + } + if state != "" { + meta.State = &state + } + } + + // Map executable + if jsonData.Executable != "" { + meta.Executable = &jsonData.Executable + } + + // Map QueryOptions + if len(jsonData.QueryOptions) > 0 { + queryOptions := &pluginsv0alpha1.PluginMetaQueryOptions{} + if val, ok := jsonData.QueryOptions["maxDataPoints"]; ok { + queryOptions.MaxDataPoints = &val + } + if val, ok := jsonData.QueryOptions["minInterval"]; ok { + queryOptions.MinInterval = &val + } + if val, ok := jsonData.QueryOptions["cacheTimeout"]; ok { + queryOptions.CacheTimeout = &val + } + meta.QueryOptions = queryOptions + } + + // Map Includes + if len(jsonData.Includes) > 0 { + meta.Includes = make([]pluginsv0alpha1.PluginMetaInclude, 0, len(jsonData.Includes)) + for _, include := range jsonData.Includes { + v0Include := pluginsv0alpha1.PluginMetaInclude{} + if include.UID != "" { + v0Include.Uid = &include.UID + } + if include.Type != "" { + var includeType pluginsv0alpha1.PluginMetaIncludeType + switch include.Type { + case "dashboard": + includeType = pluginsv0alpha1.PluginMetaIncludeTypeDashboard + case "page": + includeType = pluginsv0alpha1.PluginMetaIncludeTypePage + case "panel": + includeType = pluginsv0alpha1.PluginMetaIncludeTypePanel + case "datasource": + includeType = pluginsv0alpha1.PluginMetaIncludeTypeDatasource + } + v0Include.Type = &includeType + } + if include.Name != "" { + v0Include.Name = &include.Name + } + if include.Component != "" { + v0Include.Component = &include.Component + } + if include.Role != "" { + var role pluginsv0alpha1.PluginMetaIncludeRole + switch include.Role { + case "Admin": + role = pluginsv0alpha1.PluginMetaIncludeRoleAdmin + case "Editor": + role = pluginsv0alpha1.PluginMetaIncludeRoleEditor + case "Viewer": + role = pluginsv0alpha1.PluginMetaIncludeRoleViewer + } + v0Include.Role = &role + } + if include.Action != "" { + v0Include.Action = &include.Action + } + if include.Path != "" { + v0Include.Path = &include.Path + } + if include.AddToNav { + v0Include.AddToNav = &include.AddToNav + } + if include.DefaultNav { + v0Include.DefaultNav = &include.DefaultNav + } + if include.Icon != "" { + v0Include.Icon = &include.Icon + } + meta.Includes = append(meta.Includes, v0Include) + } + } + + // Map Routes + if len(jsonData.Routes) > 0 { + meta.Routes = make([]pluginsv0alpha1.PluginMetaRoute, 0, len(jsonData.Routes)) + for _, route := range jsonData.Routes { + v0Route := pluginsv0alpha1.PluginMetaRoute{} + if route.Path != "" { + v0Route.Path = &route.Path + } + if route.Method != "" { + v0Route.Method = &route.Method + } + if route.URL != "" { + v0Route.Url = &route.URL + } + if route.ReqRole != "" { + reqRole := string(route.ReqRole) + v0Route.ReqRole = &reqRole + } + if route.ReqAction != "" { + v0Route.ReqAction = &route.ReqAction + } + if len(route.Headers) > 0 { + headers := make([]string, 0, len(route.Headers)) + for _, header := range route.Headers { + headers = append(headers, header.Name+": "+header.Content) + } + v0Route.Headers = headers + } + if len(route.URLParams) > 0 { + v0Route.UrlParams = make([]pluginsv0alpha1.PluginMetaV0alpha1RouteUrlParams, 0, len(route.URLParams)) + for _, param := range route.URLParams { + v0Param := pluginsv0alpha1.PluginMetaV0alpha1RouteUrlParams{} + if param.Name != "" { + v0Param.Name = ¶m.Name + } + if param.Content != "" { + v0Param.Content = ¶m.Content + } + v0Route.UrlParams = append(v0Route.UrlParams, v0Param) + } + } + if route.TokenAuth != nil { + v0Route.TokenAuth = &pluginsv0alpha1.PluginMetaV0alpha1RouteTokenAuth{} + if route.TokenAuth.Url != "" { + v0Route.TokenAuth.Url = &route.TokenAuth.Url + } + if len(route.TokenAuth.Scopes) > 0 { + v0Route.TokenAuth.Scopes = route.TokenAuth.Scopes + } + if len(route.TokenAuth.Params) > 0 { + v0Route.TokenAuth.Params = make(map[string]interface{}) + for k, v := range route.TokenAuth.Params { + v0Route.TokenAuth.Params[k] = v + } + } + } + if route.JwtTokenAuth != nil { + v0Route.JwtTokenAuth = &pluginsv0alpha1.PluginMetaV0alpha1RouteJwtTokenAuth{} + if route.JwtTokenAuth.Url != "" { + v0Route.JwtTokenAuth.Url = &route.JwtTokenAuth.Url + } + if len(route.JwtTokenAuth.Scopes) > 0 { + v0Route.JwtTokenAuth.Scopes = route.JwtTokenAuth.Scopes + } + if len(route.JwtTokenAuth.Params) > 0 { + v0Route.JwtTokenAuth.Params = make(map[string]interface{}) + for k, v := range route.JwtTokenAuth.Params { + v0Route.JwtTokenAuth.Params[k] = v + } + } + } + if len(route.Body) > 0 { + var bodyMap map[string]interface{} + if err := json.Unmarshal(route.Body, &bodyMap); err == nil { + v0Route.Body = bodyMap + } + } + meta.Routes = append(meta.Routes, v0Route) + } + } + + // Map Extensions + if len(jsonData.Extensions.AddedLinks) > 0 || len(jsonData.Extensions.AddedComponents) > 0 || + len(jsonData.Extensions.ExposedComponents) > 0 || len(jsonData.Extensions.ExtensionPoints) > 0 { + extensions := &pluginsv0alpha1.PluginMetaExtensions{} + + if len(jsonData.Extensions.AddedLinks) > 0 { + extensions.AddedLinks = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedLinks, 0, len(jsonData.Extensions.AddedLinks)) + for _, link := range jsonData.Extensions.AddedLinks { + v0Link := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedLinks{ + Targets: link.Targets, + Title: link.Title, + } + if link.Description != "" { + v0Link.Description = &link.Description + } + extensions.AddedLinks = append(extensions.AddedLinks, v0Link) + } + } + + if len(jsonData.Extensions.AddedComponents) > 0 { + extensions.AddedComponents = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedComponents, 0, len(jsonData.Extensions.AddedComponents)) + for _, comp := range jsonData.Extensions.AddedComponents { + v0Comp := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedComponents{ + Targets: comp.Targets, + Title: comp.Title, + } + if comp.Description != "" { + v0Comp.Description = &comp.Description + } + extensions.AddedComponents = append(extensions.AddedComponents, v0Comp) + } + } + + if len(jsonData.Extensions.ExposedComponents) > 0 { + extensions.ExposedComponents = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExposedComponents, 0, len(jsonData.Extensions.ExposedComponents)) + for _, comp := range jsonData.Extensions.ExposedComponents { + v0Comp := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExposedComponents{ + Id: comp.Id, + } + if comp.Title != "" { + v0Comp.Title = &comp.Title + } + if comp.Description != "" { + v0Comp.Description = &comp.Description + } + extensions.ExposedComponents = append(extensions.ExposedComponents, v0Comp) + } + } + + if len(jsonData.Extensions.ExtensionPoints) > 0 { + extensions.ExtensionPoints = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExtensionPoints, 0, len(jsonData.Extensions.ExtensionPoints)) + for _, point := range jsonData.Extensions.ExtensionPoints { + v0Point := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExtensionPoints{ + Id: point.Id, + } + if point.Title != "" { + v0Point.Title = &point.Title + } + if point.Description != "" { + v0Point.Description = &point.Description + } + extensions.ExtensionPoints = append(extensions.ExtensionPoints, v0Point) + } + } + + meta.Extensions = extensions + } + + // Map Roles + if len(jsonData.Roles) > 0 { + meta.Roles = make([]pluginsv0alpha1.PluginMetaRole, 0, len(jsonData.Roles)) + for _, role := range jsonData.Roles { + v0Role := pluginsv0alpha1.PluginMetaRole{ + Grants: role.Grants, + } + if role.Role.Name != "" || role.Role.Description != "" || len(role.Role.Permissions) > 0 { + v0RoleRole := &pluginsv0alpha1.PluginMetaV0alpha1RoleRole{} + if role.Role.Name != "" { + v0RoleRole.Name = &role.Role.Name + } + if role.Role.Description != "" { + v0RoleRole.Description = &role.Role.Description + } + if len(role.Role.Permissions) > 0 { + v0RoleRole.Permissions = make([]pluginsv0alpha1.PluginMetaV0alpha1RoleRolePermissions, 0, len(role.Role.Permissions)) + for _, perm := range role.Role.Permissions { + v0Perm := pluginsv0alpha1.PluginMetaV0alpha1RoleRolePermissions{} + if perm.Action != "" { + v0Perm.Action = &perm.Action + } + if perm.Scope != "" { + v0Perm.Scope = &perm.Scope + } + v0RoleRole.Permissions = append(v0RoleRole.Permissions, v0Perm) + } + } + v0Role.Role = v0RoleRole + } + meta.Roles = append(meta.Roles, v0Role) + } + } + + // Map IAM + if jsonData.IAM != nil && len(jsonData.IAM.Permissions) > 0 { + iam := &pluginsv0alpha1.PluginMetaIAM{ + Permissions: make([]pluginsv0alpha1.PluginMetaV0alpha1IAMPermissions, 0, len(jsonData.IAM.Permissions)), + } + for _, perm := range jsonData.IAM.Permissions { + v0Perm := pluginsv0alpha1.PluginMetaV0alpha1IAMPermissions{} + if perm.Action != "" { + v0Perm.Action = &perm.Action + } + if perm.Scope != "" { + v0Perm.Scope = &perm.Scope + } + iam.Permissions = append(iam.Permissions, v0Perm) + } + meta.Iam = iam + } + + return meta +} diff --git a/apps/plugins/pkg/app/meta/core_test.go b/apps/plugins/pkg/app/meta/core_test.go new file mode 100644 index 00000000000..a9d7103e4a2 --- /dev/null +++ b/apps/plugins/pkg/app/meta/core_test.go @@ -0,0 +1,302 @@ +package meta + +import ( + "context" + "errors" + "os" + "path/filepath" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" + "github.com/grafana/grafana/pkg/plugins" +) + +func TestCoreProvider_GetMeta(t *testing.T) { + ctx := context.Background() + + t.Run("returns cached plugin when available", func(t *testing.T) { + provider := NewCoreProvider() + + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + provider.mu.Lock() + provider.loadedPlugins["test-plugin"] = expectedMeta + provider.initialized = true + provider.mu.Unlock() + + result, err := provider.GetMeta(ctx, "test-plugin", "1.0.0") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, expectedMeta, result.Meta) + assert.Equal(t, defaultCoreTTL, result.TTL) + }) + + t.Run("returns ErrMetaNotFound for non-existent plugin", func(t *testing.T) { + provider := NewCoreProvider() + + provider.mu.Lock() + provider.initialized = true + provider.mu.Unlock() + + result, err := provider.GetMeta(ctx, "nonexistent-plugin", "1.0.0") + + assert.Error(t, err) + assert.True(t, errors.Is(err, ErrMetaNotFound)) + assert.Nil(t, result) + }) + + t.Run("ignores version parameter", func(t *testing.T) { + provider := NewCoreProvider() + + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + provider.mu.Lock() + provider.loadedPlugins["test-plugin"] = expectedMeta + provider.initialized = true + provider.mu.Unlock() + + result1, err1 := provider.GetMeta(ctx, "test-plugin", "1.0.0") + result2, err2 := provider.GetMeta(ctx, "test-plugin", "2.0.0") + + require.NoError(t, err1) + require.NoError(t, err2) + assert.Equal(t, result1.Meta, result2.Meta) + }) + + t.Run("uses custom TTL when provided", func(t *testing.T) { + customTTL := 2 * time.Hour + provider := NewCoreProviderWithTTL(customTTL) + + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + provider.mu.Lock() + provider.loadedPlugins["test-plugin"] = expectedMeta + provider.initialized = true + provider.mu.Unlock() + + result, err := provider.GetMeta(ctx, "test-plugin", "1.0.0") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, customTTL, result.TTL) + }) + + t.Run("gracefully handles initialization failure and returns ErrMetaNotFound", func(t *testing.T) { + tempDir := t.TempDir() + + oldWd, err := os.Getwd() + require.NoError(t, err) + defer func() { + _ = os.Chdir(oldWd) + }() + + require.NoError(t, os.Chdir(tempDir)) + + provider := NewCoreProvider() + result, err := provider.GetMeta(ctx, "any-plugin", "1.0.0") + assert.Error(t, err) + assert.True(t, errors.Is(err, ErrMetaNotFound)) + assert.Nil(t, result) + + initialized := provider.initialized + assert.True(t, initialized, "provider should be marked as initialized even after failure") + }) +} + +func TestCoreProvider_loadPlugins(t *testing.T) { + ctx := context.Background() + + t.Run("loads all core plugins", func(t *testing.T) { + _, filename, _, _ := runtime.Caller(0) + testDir := filepath.Dir(filename) + grafanaRoot := filepath.Join(testDir, "..", "..", "..", "..", "..") + grafanaRoot, err := filepath.Abs(grafanaRoot) + require.NoError(t, err) + + publicPath := filepath.Join(grafanaRoot, "public", "app", "plugins") + if _, err = os.Stat(publicPath); err != nil { + t.Skipf("Grafana root not found at %s, skipping integration test: %v", publicPath, err) + } + + require.NoError(t, os.Chdir(grafanaRoot)) + + provider := NewCoreProvider() + err = provider.loadPlugins(ctx) + require.NoError(t, err) + assert.Len(t, provider.loadedPlugins, 53) + }) + + t.Run("returns error when static root path not found", func(t *testing.T) { + tempDir := t.TempDir() + + oldWd, err := os.Getwd() + require.NoError(t, err) + defer func() { + _ = os.Chdir(oldWd) + }() + + require.NoError(t, os.Chdir(tempDir)) + + provider := NewCoreProvider() + err = provider.loadPlugins(ctx) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "could not find Grafana static root path") + }) + + t.Run("returns no error when no plugins found", func(t *testing.T) { + tempDir := t.TempDir() + publicPath := filepath.Join(tempDir, "public", "app", "plugins") + require.NoError(t, os.MkdirAll(publicPath, 0750)) + + oldWd, err := os.Getwd() + require.NoError(t, err) + defer func() { + _ = os.Chdir(oldWd) + }() + + require.NoError(t, os.Chdir(tempDir)) + + provider := NewCoreProvider() + err = provider.loadPlugins(ctx) + assert.NoError(t, err) + }) + + t.Run("successfully loads plugins when structure exists", func(t *testing.T) { + tempDir := t.TempDir() + publicPath := filepath.Join(tempDir, "public", "app", "plugins") + datasourcePath := filepath.Join(publicPath, "datasource") + panelPath := filepath.Join(publicPath, "panel") + + require.NoError(t, os.MkdirAll(datasourcePath, 0750)) + require.NoError(t, os.MkdirAll(panelPath, 0750)) + + pluginDir := filepath.Join(datasourcePath, "test-datasource") + require.NoError(t, os.MkdirAll(pluginDir, 0750)) + + pluginJSON := `{ + "id": "test-datasource", + "name": "Test Datasource", + "type": "datasource", + "info": { + "version": "1.0.0", + "description": "Test description" + } + }` + + require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.json"), []byte(pluginJSON), 0644)) + + oldWd, err := os.Getwd() + require.NoError(t, err) + defer func() { + _ = os.Chdir(oldWd) + }() + + require.NoError(t, os.Chdir(tempDir)) + + provider := NewCoreProvider() + err = provider.loadPlugins(ctx) + + if err != nil { + require.NoError(t, err) + } + + provider.mu.RLock() + loaded := len(provider.loadedPlugins) > 0 + provider.mu.RUnlock() + + if loaded { + result, err := provider.GetMeta(ctx, "test-datasource", "1.0.0") + require.NoError(t, err) + assert.Equal(t, "test-datasource", result.Meta.Id) + assert.Equal(t, "Test Datasource", result.Meta.Name) + } + }) +} + +func TestNewCoreProvider(t *testing.T) { + t.Run("creates provider with default TTL", func(t *testing.T) { + provider := NewCoreProvider() + assert.Equal(t, defaultCoreTTL, provider.ttl) + assert.NotNil(t, provider.loadedPlugins) + assert.False(t, provider.initialized) + }) +} + +func TestNewCoreProviderWithTTL(t *testing.T) { + t.Run("creates provider with custom TTL", func(t *testing.T) { + customTTL := 2 * time.Hour + provider := NewCoreProviderWithTTL(customTTL) + assert.Equal(t, customTTL, provider.ttl) + }) + + t.Run("accepts zero TTL", func(t *testing.T) { + provider := NewCoreProviderWithTTL(0) + assert.Equal(t, time.Duration(0), provider.ttl) + }) +} + +func TestJsonDataToMeta(t *testing.T) { + t.Run("converts basic plugin JSON data", func(t *testing.T) { + jsonData := plugins.JSONData{ + ID: "test-plugin", + Name: "Test Plugin", + Type: plugins.TypeDataSource, + Info: plugins.Info{ + Version: "1.0.0", + Description: "Test description", + Keywords: []string{"test", "plugin"}, + Logos: plugins.Logos{ + Small: "small.png", + Large: "large.png", + }, + }, + } + + meta := jsonDataToPluginMetaJSONData(jsonData) + + assert.Equal(t, "test-plugin", meta.Id) + assert.Equal(t, "Test Plugin", meta.Name) + assert.Equal(t, pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, meta.Type) + assert.Equal(t, "1.0.0", meta.Info.Version) + assert.Equal(t, "Test description", *meta.Info.Description) + assert.Equal(t, []string{"test", "plugin"}, meta.Info.Keywords) + assert.Equal(t, "small.png", meta.Info.Logos.Small) + assert.Equal(t, "large.png", meta.Info.Logos.Large) + }) + + t.Run("handles optional fields", func(t *testing.T) { + jsonData := plugins.JSONData{ + ID: "test-plugin", + Name: "Test Plugin", + Type: plugins.TypePanel, + Info: plugins.Info{ + Version: "1.0.0", + }, + } + + meta := jsonDataToPluginMetaJSONData(jsonData) + + assert.Nil(t, meta.Info.Description) + assert.Nil(t, meta.Info.Author) + assert.Empty(t, meta.Info.Keywords) + }) +} diff --git a/apps/plugins/pkg/app/meta/manager.go b/apps/plugins/pkg/app/meta/manager.go new file mode 100644 index 00000000000..e694e88bba7 --- /dev/null +++ b/apps/plugins/pkg/app/meta/manager.go @@ -0,0 +1,131 @@ +package meta + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +const ( + defaultCleanupInterval = 10 * time.Minute +) + +// cachedMeta represents a cached metadata entry with expiration time +type cachedMeta struct { + meta pluginsv0alpha1.PluginMetaJSONData + ttl time.Duration + expiresAt time.Time +} + +// ProviderManager searches multiple providers for Plugin Meta in order until one succeeds, and caches +// results with per-provider TTLs. +// It implements app.Runnable to manage the cleanup goroutine lifecycle. +type ProviderManager struct { + providers []Provider + cache map[string]*cachedMeta + cacheMu sync.RWMutex +} + +// NewProviderManager creates a new ProviderManager that chains the given providers +// and caches results with per-provider TTLs. +func NewProviderManager(providers ...Provider) *ProviderManager { + if len(providers) == 0 { + panic("ProviderManager requires at least one provider") + } + + return &ProviderManager{ + providers: providers, + cache: make(map[string]*cachedMeta), + } +} + +// Run implements app.Runnable. It runs the cleanup loop until the context is cancelled. +// This method blocks until the context is cancelled (when the app shuts down). +func (pm *ProviderManager) Run(ctx context.Context) error { + ticker := time.NewTicker(defaultCleanupInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return nil + case <-ticker.C: + pm.cleanupExpired() + } + } +} + +// GetMeta tries each provider in order until one succeeds, using cache when available. +// Returns ErrMetaNotFound only if all providers return ErrMetaNotFound. +// Otherwise, returns the last non-ErrMetaNotFound error if all providers fail. +func (pm *ProviderManager) GetMeta(ctx context.Context, pluginID, version string) (*Result, error) { + cacheKey := pm.cacheKey(pluginID, version) + + // Check cache first + pm.cacheMu.RLock() + cached, exists := pm.cache[cacheKey] + pm.cacheMu.RUnlock() + + if exists && time.Now().Before(cached.expiresAt) { + return &Result{ + Meta: cached.meta, + TTL: cached.ttl, + }, nil + } + + // Try each provider in order until one succeeds + var lastErr error + for _, provider := range pm.providers { + result, err := provider.GetMeta(ctx, pluginID, version) + if err == nil { + // Don't cache results with a zero TTL + if result.TTL == 0 { + continue + } + + pm.cacheMu.Lock() + pm.cache[cacheKey] = &cachedMeta{ + meta: result.Meta, + ttl: result.TTL, + expiresAt: time.Now().Add(result.TTL), + } + pm.cacheMu.Unlock() + + return result, nil + } + + // If not found, try next provider + if errors.Is(err, ErrMetaNotFound) { + continue + } + + lastErr = err + } + + if lastErr != nil { + return nil, fmt.Errorf("failed to fetch plugin metadata from any provider: %w", lastErr) + } + + return nil, ErrMetaNotFound +} + +// cleanupExpired removes expired entries from the cache. +func (pm *ProviderManager) cleanupExpired() { + now := time.Now() + + pm.cacheMu.Lock() + defer pm.cacheMu.Unlock() + for key, entry := range pm.cache { + if now.After(entry.expiresAt) { + delete(pm.cache, key) + } + } +} + +func (pm *ProviderManager) cacheKey(pluginID, version string) string { + return fmt.Sprintf("%s:%s", pluginID, version) +} diff --git a/apps/plugins/pkg/app/meta/manager_test.go b/apps/plugins/pkg/app/meta/manager_test.go new file mode 100644 index 00000000000..3d3fe3c936c --- /dev/null +++ b/apps/plugins/pkg/app/meta/manager_test.go @@ -0,0 +1,434 @@ +package meta + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +func TestNewProviderManager(t *testing.T) { + t.Run("panics with no providers", func(t *testing.T) { + assert.Panics(t, func() { + NewProviderManager() + }) + }) + + t.Run("creates manager with providers", func(t *testing.T) { + provider1 := &mockProvider{} + provider2 := &mockProvider{} + + pm := NewProviderManager(provider1, provider2) + + require.NotNil(t, pm) + assert.Len(t, pm.providers, 2) + assert.NotNil(t, pm.cache) + }) +} + +func TestProviderManager_GetMeta(t *testing.T) { + ctx := context.Background() + + t.Run("returns cached result when available and not expired", func(t *testing.T) { + cachedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + provider := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return &Result{ + Meta: cachedMeta, + TTL: time.Hour, + }, nil + }, + } + + pm := NewProviderManager(provider) + + result1, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + require.NoError(t, err) + require.NotNil(t, result1) + assert.Equal(t, cachedMeta, result1.Meta) + assert.Equal(t, time.Hour, result1.TTL) + + provider.getMetaFunc = func(ctx context.Context, pluginID, version string) (*Result, error) { + return &Result{ + Meta: pluginsv0alpha1.PluginMetaJSONData{Id: "different"}, + TTL: time.Hour, + }, nil + } + + result2, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + require.NoError(t, err) + require.NotNil(t, result2) + assert.Equal(t, cachedMeta, result2.Meta) + assert.Equal(t, time.Hour, result2.TTL) + }) + + t.Run("fetches from provider when not cached", func(t *testing.T) { + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + expectedTTL := 2 * time.Hour + + provider := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return &Result{ + Meta: expectedMeta, + TTL: expectedTTL, + }, nil + }, + } + + pm := NewProviderManager(provider) + + result, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, expectedMeta, result.Meta) + assert.Equal(t, expectedTTL, result.TTL) + + pm.cacheMu.RLock() + cached, exists := pm.cache["test-plugin:1.0.0"] + pm.cacheMu.RUnlock() + + assert.True(t, exists) + assert.Equal(t, expectedMeta, cached.meta) + assert.Equal(t, expectedTTL, cached.ttl) + }) + + t.Run("does not cache result with zero TTL and tries next provider", func(t *testing.T) { + zeroTTLMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Zero TTL Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + provider1 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return &Result{ + Meta: zeroTTLMeta, + TTL: 0, + }, nil + }, + } + provider2 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return &Result{ + Meta: expectedMeta, + TTL: time.Hour, + }, nil + }, + } + + pm := NewProviderManager(provider1, provider2) + + result, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, expectedMeta, result.Meta) + + pm.cacheMu.RLock() + cached, exists := pm.cache["test-plugin:1.0.0"] + pm.cacheMu.RUnlock() + + assert.True(t, exists) + assert.Equal(t, expectedMeta, cached.meta) + assert.Equal(t, time.Hour, cached.ttl) + }) + + t.Run("tries next provider when first returns ErrMetaNotFound", func(t *testing.T) { + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + provider1 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return nil, ErrMetaNotFound + }, + } + provider2 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return &Result{ + Meta: expectedMeta, + TTL: time.Hour, + }, nil + }, + } + + pm := NewProviderManager(provider1, provider2) + + result, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, expectedMeta, result.Meta) + }) + + t.Run("returns ErrMetaNotFound when all providers return ErrMetaNotFound", func(t *testing.T) { + provider1 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return nil, ErrMetaNotFound + }, + } + provider2 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return nil, ErrMetaNotFound + }, + } + + pm := NewProviderManager(provider1, provider2) + + result, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + + assert.Error(t, err) + assert.True(t, errors.Is(err, ErrMetaNotFound)) + assert.Nil(t, result) + }) + + t.Run("returns error when provider returns non-ErrMetaNotFound error", func(t *testing.T) { + expectedErr := errors.New("network error") + + provider1 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return nil, expectedErr + }, + } + provider2 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return nil, ErrMetaNotFound + }, + } + + pm := NewProviderManager(provider1, provider2) + + result, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to fetch plugin metadata from any provider") + assert.ErrorIs(t, err, expectedErr) + assert.Nil(t, result) + }) + + t.Run("skips expired cache entries", func(t *testing.T) { + expiredMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Expired Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + expectedMeta := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Test Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + callCount := 0 + provider := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + callCount++ + if callCount == 1 { + return &Result{ + Meta: expiredMeta, + TTL: time.Nanosecond, + }, nil + } + return &Result{ + Meta: expectedMeta, + TTL: time.Hour, + }, nil + }, + } + + pm := NewProviderManager(provider) + + result1, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + require.NoError(t, err) + assert.Equal(t, expiredMeta, result1.Meta) + + time.Sleep(2 * time.Nanosecond) + + result2, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + require.NoError(t, err) + assert.Equal(t, expectedMeta, result2.Meta) + assert.Equal(t, 2, callCount) + }) + + t.Run("uses first successful provider", func(t *testing.T) { + expectedMeta1 := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Provider 1 Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + expectedMeta2 := pluginsv0alpha1.PluginMetaJSONData{ + Id: "test-plugin", + Name: "Provider 2 Plugin", + Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource, + } + + provider1 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return &Result{ + Meta: expectedMeta1, + TTL: time.Hour, + }, nil + }, + } + provider2 := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + return &Result{ + Meta: expectedMeta2, + TTL: time.Hour, + }, nil + }, + } + + pm := NewProviderManager(provider1, provider2) + + result, err := pm.GetMeta(ctx, "test-plugin", "1.0.0") + + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, expectedMeta1, result.Meta) + }) +} + +func TestProviderManager_Run(t *testing.T) { + t.Run("runs cleanup loop until context cancelled", func(t *testing.T) { + pm := NewProviderManager(&mockProvider{}) + + ctx, cancel := context.WithCancel(context.Background()) + + done := make(chan error, 1) + go func() { + done <- pm.Run(ctx) + }() + + time.Sleep(10 * time.Millisecond) + cancel() + + err := <-done + require.NoError(t, err) + }) +} + +func TestProviderManager_cleanupExpired(t *testing.T) { + t.Run("removes expired entries", func(t *testing.T) { + validMeta := pluginsv0alpha1.PluginMetaJSONData{Id: "valid"} + expiredMeta1 := pluginsv0alpha1.PluginMetaJSONData{Id: "expired1"} + expiredMeta2 := pluginsv0alpha1.PluginMetaJSONData{Id: "expired2"} + + provider := &mockProvider{ + getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) { + switch pluginID { + case "valid": + return &Result{Meta: validMeta, TTL: time.Hour}, nil + case "expired1": + return &Result{Meta: expiredMeta1, TTL: time.Nanosecond}, nil + case "expired2": + return &Result{Meta: expiredMeta2, TTL: time.Nanosecond}, nil + } + return nil, ErrMetaNotFound + }, + } + + pm := NewProviderManager(provider) + ctx := context.Background() + + _, err := pm.GetMeta(ctx, "expired1", "1.0.0") + require.NoError(t, err) + _, err = pm.GetMeta(ctx, "expired2", "1.0.0") + require.NoError(t, err) + _, err = pm.GetMeta(ctx, "valid", "1.0.0") + require.NoError(t, err) + + time.Sleep(2 * time.Nanosecond) + + pm.cleanupExpired() + + provider.getMetaFunc = func(ctx context.Context, pluginID, version string) (*Result, error) { + if pluginID == "valid" { + return &Result{Meta: validMeta, TTL: time.Hour}, nil + } + return nil, ErrMetaNotFound + } + + result, err := pm.GetMeta(ctx, "expired1", "1.0.0") + assert.Error(t, err) + assert.Nil(t, result) + + result, err = pm.GetMeta(ctx, "valid", "1.0.0") + require.NoError(t, err) + assert.Equal(t, validMeta, result.Meta) + }) + + t.Run("handles empty cache", func(t *testing.T) { + pm := NewProviderManager(&mockProvider{}) + pm.cleanupExpired() + }) +} + +func TestProviderManager_cacheKey(t *testing.T) { + pm := NewProviderManager(&mockProvider{}) + + tests := []struct { + name string + pluginID string + version string + expected string + }{ + { + name: "basic key", + pluginID: "test-plugin", + version: "1.0.0", + expected: "test-plugin:1.0.0", + }, + { + name: "empty version", + pluginID: "test-plugin", + version: "", + expected: "test-plugin:", + }, + { + name: "empty plugin ID", + pluginID: "", + version: "1.0.0", + expected: ":1.0.0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + key := pm.cacheKey(tt.pluginID, tt.version) + assert.Equal(t, tt.expected, key) + }) + } +} + +type mockProvider struct { + getMetaFunc func(ctx context.Context, pluginID, version string) (*Result, error) +} + +func (m *mockProvider) GetMeta(ctx context.Context, pluginID, version string) (*Result, error) { + if m.getMetaFunc != nil { + return m.getMetaFunc(ctx, pluginID, version) + } + return nil, ErrMetaNotFound +} diff --git a/apps/plugins/pkg/app/meta/provider.go b/apps/plugins/pkg/app/meta/provider.go new file mode 100644 index 00000000000..8b5d8b0fcbd --- /dev/null +++ b/apps/plugins/pkg/app/meta/provider.go @@ -0,0 +1,27 @@ +package meta + +import ( + "context" + "errors" + "time" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +var ( + ErrMetaNotFound = errors.New("not found") +) + +// Result contains plugin metadata along with its recommended TTL. +type Result struct { + Meta pluginsv0alpha1.PluginMetaJSONData + TTL time.Duration +} + +// Provider is used for retrieving plugin metadata. +type Provider interface { + // GetMeta retrieves plugin metadata for the given plugin ID and version. + // Returns the Result containing the PluginMetaJSONData and its recommended TTL. + // If the plugin is not found, returns ErrMetaNotFound. + GetMeta(ctx context.Context, pluginID, version string) (*Result, error) +} diff --git a/apps/plugins/pkg/app/storage.go b/apps/plugins/pkg/app/storage.go new file mode 100644 index 00000000000..bd277978a91 --- /dev/null +++ b/apps/plugins/pkg/app/storage.go @@ -0,0 +1,200 @@ +package app + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana-app-sdk/resource" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" + + claims "github.com/grafana/authlib/types" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" + "github.com/grafana/grafana/apps/plugins/pkg/app/meta" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" +) + +var ( + _ rest.Scoper = (*PluginMetaStorage)(nil) + _ rest.SingularNameProvider = (*PluginMetaStorage)(nil) + _ rest.Getter = (*PluginMetaStorage)(nil) + _ rest.Lister = (*PluginMetaStorage)(nil) + _ rest.Storage = (*PluginMetaStorage)(nil) + _ rest.TableConvertor = (*PluginMetaStorage)(nil) +) + +type PluginMetaStorage struct { + metaManager *meta.ProviderManager + client *pluginsv0alpha1.PluginClient + clientFactory func(context.Context) (*pluginsv0alpha1.PluginClient, error) + clientErr error + clientOnce sync.Once + + gr schema.GroupResource + namespacer claims.NamespaceFormatter + tableConverter rest.TableConvertor +} + +func NewPluginMetaStorage( + metaManager *meta.ProviderManager, + clientFactory func(context.Context) (*pluginsv0alpha1.PluginClient, error), + namespacer claims.NamespaceFormatter, +) *PluginMetaStorage { + gr := schema.GroupResource{ + Group: pluginsv0alpha1.APIGroup, + Resource: strings.ToLower(pluginsv0alpha1.PluginMetaKind().Plural()), + } + + return &PluginMetaStorage{ + metaManager: metaManager, + clientFactory: clientFactory, + gr: gr, + namespacer: namespacer, + tableConverter: rest.NewDefaultTableConvertor(gr), + } +} + +func (s *PluginMetaStorage) getClient(ctx context.Context) (*pluginsv0alpha1.PluginClient, error) { + s.clientOnce.Do(func() { + client, err := s.clientFactory(ctx) + if err != nil { + s.clientErr = err + s.client = nil + return + } + s.client = client + }) + + return s.client, s.clientErr +} + +func (s *PluginMetaStorage) New() runtime.Object { + return pluginsv0alpha1.PluginMetaKind().ZeroValue() +} + +func (s *PluginMetaStorage) Destroy() {} + +func (s *PluginMetaStorage) NamespaceScoped() bool { + return true +} + +func (s *PluginMetaStorage) GetSingularName() string { + return strings.ToLower(pluginsv0alpha1.PluginMetaKind().Kind()) +} + +func (s *PluginMetaStorage) NewList() runtime.Object { + return pluginsv0alpha1.PluginMetaKind().ZeroListValue() +} + +func (s *PluginMetaStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +} + +func (s *PluginMetaStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + + pluginClient, err := s.getClient(ctx) + if err != nil { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to get plugin client: %w", err)) + } + + plugins, err := pluginClient.ListAll(ctx, ns.Value, resource.ListOptions{}) + if err != nil { + logging.DefaultLogger.Error("Failed to list plugins", "namespace", ns.Value, "error", err) + return nil, apierrors.NewInternalError(fmt.Errorf("failed to list plugins: %w", err)) + } + + // Convert each Plugin to PluginMeta + metaItems := make([]pluginsv0alpha1.PluginMeta, 0, len(plugins.Items)) + for _, plugin := range plugins.Items { + result, err := s.metaManager.GetMeta(ctx, plugin.Spec.Id, plugin.Spec.Version) + if err != nil { + // Log error but continue with other plugins + logging.DefaultLogger.Warn("Failed to fetch metadata for plugin", "pluginId", plugin.Spec.Id, "version", plugin.Spec.Version, "error", err) + continue + } + + pluginMeta := createPluginMetaFromPluginMetaJSONData(result.Meta, plugin.Name, plugin.Namespace) + metaItems = append(metaItems, *pluginMeta) + } + + list := &pluginsv0alpha1.PluginMetaList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: pluginsv0alpha1.APIGroup + "/" + pluginsv0alpha1.APIVersion, + Kind: pluginsv0alpha1.PluginMetaKind().Kind() + "List", + }, + Items: metaItems, + } + + return list, nil +} + +func (s *PluginMetaStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + + pluginClient, err := s.getClient(ctx) + if err != nil { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to get plugin client: %w", err)) + } + + plugin, err := pluginClient.Get(ctx, resource.Identifier{ + Namespace: ns.Value, + Name: name, + }) + if err != nil { + return nil, err + } + + result, err := s.metaManager.GetMeta(ctx, plugin.Spec.Id, plugin.Spec.Version) + if err != nil { + if errors.Is(err, meta.ErrMetaNotFound) { + gr := schema.GroupResource{ + Group: pluginsv0alpha1.APIGroup, + Resource: name, + } + return nil, apierrors.NewNotFound(gr, plugin.Spec.Id) + } + + logging.DefaultLogger.Error("Failed to fetch plugin metadata", "pluginId", plugin.Spec.Id, "version", plugin.Spec.Version, "error", err) + return nil, apierrors.NewInternalError(fmt.Errorf("failed to fetch plugin metadata: %w", err)) + } + + return createPluginMetaFromPluginMetaJSONData(result.Meta, name, ns.Value), nil +} + +// createPluginMetaFromPluginMetaJSONData creates a PluginMeta k8s object from PluginMetaJSONData and plugin metadata. +func createPluginMetaFromPluginMetaJSONData(pluginJSON pluginsv0alpha1.PluginMetaJSONData, name, namespace string) *pluginsv0alpha1.PluginMeta { + pluginMeta := &pluginsv0alpha1.PluginMeta{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: pluginsv0alpha1.PluginMetaSpec{ + PluginJSON: pluginJSON, + }, + } + + // Set the GroupVersionKind + pluginMeta.SetGroupVersionKind(schema.GroupVersionKind{ + Group: pluginsv0alpha1.APIGroup, + Version: pluginsv0alpha1.APIVersion, + Kind: pluginsv0alpha1.PluginMetaKind().Kind(), + }) + + return pluginMeta +} diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod index a72aa77fe68..0fb19b230c8 100644 --- a/apps/provisioning/go.mod +++ b/apps/provisioning/go.mod @@ -68,7 +68,7 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum index 92a4d53c282..db6bd3cf414 100644 --- a/apps/provisioning/go.sum +++ b/apps/provisioning/go.sum @@ -156,8 +156,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= diff --git a/apps/shorturl/go.mod b/apps/shorturl/go.mod index 382cf8dd051..b3db27ba205 100644 --- a/apps/shorturl/go.mod +++ b/apps/shorturl/go.mod @@ -73,7 +73,7 @@ require ( go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/sync v0.18.0 // indirect diff --git a/apps/shorturl/go.sum b/apps/shorturl/go.sum index e31288da121..f8b394c0f37 100644 --- a/apps/shorturl/go.sum +++ b/apps/shorturl/go.sum @@ -169,8 +169,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= diff --git a/devenv/docker/blocks/prometheus_high_card/go.mod b/devenv/docker/blocks/prometheus_high_card/go.mod index 1e3709bbe66..a2608e2cc43 100644 --- a/devenv/docker/blocks/prometheus_high_card/go.mod +++ b/devenv/docker/blocks/prometheus_high_card/go.mod @@ -13,6 +13,6 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect - golang.org/x/sys v0.36.0 // indirect + golang.org/x/sys v0.38.0 // indirect google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/devenv/docker/blocks/prometheus_high_card/go.sum b/devenv/docker/blocks/prometheus_high_card/go.sum index d2e136dfd38..46002ecb53d 100644 --- a/devenv/docker/blocks/prometheus_high_card/go.sum +++ b/devenv/docker/blocks/prometheus_high_card/go.sum @@ -24,8 +24,8 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/devenv/docker/blocks/prometheus_utf8/go.mod b/devenv/docker/blocks/prometheus_utf8/go.mod index 2ecc8267f24..b26177d8e9a 100644 --- a/devenv/docker/blocks/prometheus_utf8/go.mod +++ b/devenv/docker/blocks/prometheus_utf8/go.mod @@ -15,6 +15,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect - golang.org/x/sys v0.36.0 // indirect + golang.org/x/sys v0.38.0 // indirect google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/devenv/docker/blocks/prometheus_utf8/go.sum b/devenv/docker/blocks/prometheus_utf8/go.sum index d2e136dfd38..46002ecb53d 100644 --- a/devenv/docker/blocks/prometheus_utf8/go.sum +++ b/devenv/docker/blocks/prometheus_utf8/go.sum @@ -24,8 +24,8 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/docs/sources/administration/data-source-management/teamlbac/configure-teamlbac-for-tempo/index.md b/docs/sources/administration/data-source-management/teamlbac/configure-teamlbac-for-tempo/index.md new file mode 100644 index 00000000000..d2f6840b023 --- /dev/null +++ b/docs/sources/administration/data-source-management/teamlbac/configure-teamlbac-for-tempo/index.md @@ -0,0 +1,166 @@ +--- +title: "Configure Team LBAC for Tempo or Cloud Traces" +description: "Use label-based access control (LBAC) to restrict Cloud Traces data by team and attribute rules." +keywords: + - tempo + - datasource + - team +labels: + products: + - cloud +title: Configure team LBAC for data sources for Tempo +weight: 350 +_build: + list: false +noindex: true +--- + +# Configure team label-based access control for Tempo or Cloud Traces + +{{< docs/private-preview product="Label-based access control for traces" >}} + +Team label-based access control (LBAC) for Cloud Traces or Tempo lets you restrict which spans teams can access by defining rules using trace attributes. LBAC provides fine-grained, team-based access control within a single tenant and mirrors the experience used for logs and metrics LBAC in Grafana Cloud. + +Grafana uses the term LBAC as an umbrella term for label-based access control for all data sources. +Traces use **attributes**, not labels, for access control, but the Grafana UI surfaces this functionality as LBAC for consistency. + +This feature only applies to Grafana Cloud Traces, specifically, the Cloud-provisioned tracing data source. +If you want to use this feature with a Tempo data source, contact Grafana Support to make sure that this is enabled in your organization. + +## How LBAC works + +When a user queries tracing data source, Grafana evaluates the user’s team memberships and the LBAC rules assigned to those teams. These rules are added to the request so the Cloud Traces data source returns only permitted spans or attributes. + +LBAC rules use **attribute selectors**, such as: + +``` +{ resource.service.name="checkout", resource.env="prod" } +``` + +Multiple conditions in the same rule use **AND** (`,`), while multiple rules across teams use **OR**. + +## Before you begin + +- Be sure that you have the permission setup to create a Tempo or Cloud Traces tenant in Grafana Cloud. +- Be sure that you have administrator permissions for Grafana. +- Be sure that you have a team setup in Grafana. + +### Known limitations + +- Autocomplete in search is still under development. +- LBAC is restricted to only contain resource scope attributes. +- There is a slight performance degradation for users with multiple rules. + +## Configure team LBAC for traces + +Follow this workflow when adding a new data source. The data source must be hosted by Grafana and not self-managed. + +1. Start your Grafana Cloud instance. +2. Access Tempo or Cloud Traces data sources details for your stack. +3. Copy Tempo or Cloud Traces details and create a Cloud Access Policy. + - Copy the [details of your Tempo or Cloud Traces setup](https://grafana.com/docs/grafana-cloud/send-data/traces/set-up/locate-url-user-password/). +4. In Grafana Cloud, navigate to **Administration > Users and access > Cloud Access Policies**. + - Create an access policy for the Tempo or Cloud Traces data source. + - Ensure the access policy includes `traces:read` permissions. + - Ensure the access policy doesn't include `labels` rules. +5. In Grafana, select Tempo or Cloud Traces or create new data source. +6. Navigate back to the Tempo or Cloud Traces data source. + - Set up the Tempo or Cloud Traces data source using basic authentication. Use the [userID/tenantID](https://grafana.com/docs/grafana-cloud/send-data/traces/set-up/locate-url-user-password/) as the username. Use the token from your access policy as the password. + - Select **Save and connect**. +7. Go to the **Permissions** tab of the newly created Tempo or Cloud Traces data source. Here, you find the LBAC for data sources rules section. + +8. Choose a team from the **Team** dropdown. +9. Enter attribute selectors such as: + + ``` + { resource.service.name="checkout", resource.env="prod" } + ``` + + Refer to the Examples section below for more examples. + +10. Select **Save**. + +### Examples of LBAC rules + +An LBAC rule is a `logql` query that filters logs or metrics based on labels. +LBAC rules for traces also use some TraceQL syntax for attribute selection. +Each rule operates independently as its own filter, separate from other rules within a team. + +LBAC rules guidelines: + +- Use only resource scope attributes, for example `{ resource.env="prod" }`. +- Only string values are supported. +- Use double quotes for string values, for example: `{ resource.env="prod" }`. +- You can use regular expressions matching with `=~` operator, for example: `{ resource.team =~ "team-a|team-b" }`. +- You can have up to two conditions in the same rule using a comma (`,`) as an `AND` operator, for example: `{ resource.env="prod", resource.team="frontend" }`. + +Refer to [Create LBAC for data sources rules for a supported data source](https://grafana.com/docs/grafana/next/administration/data-source-management/teamlbac/create-teamlbac-rules/) for more information. + +#### Single rule + +Limit users to only see spans from the `payments` API. + +``` +{ resource.service.name="payments-api" } +``` + +This example matches spans with team A **or** team B. This single example is faster than using multiple rules with the same label. + +``` +{ resource.team =~ "team-a|team-b" } +``` + +#### Multiple rules + +Two rules combined to give user access to spans from `prod` environment or the `billing` team. + +``` +{ resource.env="prod" } +{ resource.team="billing" } +``` + +This example gives users access to spans from the `frontend` team in the `prod` environment or the `checkout` team but doesn't enforce additional conditions. The comma (`,`) acts as an `AND` operator within the same rule. + +``` +{ resource.env="prod", resource.team="frontend" } +{ resource.team="checkout" } +``` + +#### User on multiple teams + +Users on multiple teams receive access based on all combined LBAC rules assigned to each team. + +Team A → `{ resource.cluster="us-east-1" }` +Team B → `{ resource.service.name="frontend" }` + +## How LBAC affects returned data + +For Trace-by-ID endpoints, unauthorized spans show only minimal intrinsic fields: + +- `traceId`, `spanId`, `parentId` +- `name`, `kind` +- `timestamps` +- `status` + +For Search, metrics, autocomplete, only spans matching LBAC rules appear. + +## Manage LBAC rules + +To edit an existing LBAC rule, follow these steps: + +1. Open your stack and select your **Tempo** or **Cloud Traces** data source. +2. Select **Permissions**. +3. Scroll to **Data access**. +4. Select the rule you want to edit and click the Pencil (Edit) icon. +5. Modify **Attribute filters**. +6. Select **Save**. + +To delete an existing LBAC rule, follow these steps: + +Open your stack and select your **Tempo** or **Cloud Traces** data source. + +1. Select **Permissions**. +2. Scroll to **Data access**. +3. Select the rule you want to edit. +4. Select the **X** (Delete) icon. +5. Confirm deletion. diff --git a/docs/sources/as-code/observability-as-code/provision-resources/export-resources.md b/docs/sources/as-code/observability-as-code/provision-resources/export-resources.md new file mode 100644 index 00000000000..96bf299df08 --- /dev/null +++ b/docs/sources/as-code/observability-as-code/provision-resources/export-resources.md @@ -0,0 +1,101 @@ +--- +description: Export non-provisioned resources from Grafana. +keywords: + - dashboards + - resources + - git sync + - github + - export +labels: + products: + - enterprise + - oss + - cloud +title: Add non-provisioned resources from Grafana +menuTitle: Add non-provisioned resources +weight: 300 +canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/provision-resources/export-resources/ +--- + +# Export non-provisioned resources from Grafana + +{{< admonition type="caution" >}} + +Git Sync is available in [private preview](https://grafana.com/docs/release-life-cycle/) for Grafana Cloud. Support and documentation is available but might be limited to enablement, configuration, and some troubleshooting. No SLAs are provided. You can sign up to the private preview using the [Git Sync early access form](https://forms.gle/WKkR3EVMcbqsNnkD9). + +Git Sync and local file provisioning are [experimental features](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. + +{{< /admonition >}} + +Git Sync is under development, and traditional operations like `import`, `copy`, `move`, or `save` to a provisioned folder are not yet supported for dashboards already existing in Grafana. To use Git Sync with existing dashboards you must export them to the repository first. + +Currently, Git Sync doesn't offer any built-in functionality to easily export resources from Grafana in bulk. However, the following options are available: + +- [Export the dashboard with Grafana CLI](#add-a-dashboard-with-grafana-cli) (recommended) +- [Copy the dashboard as JSON and commit to the repository](#add-a-dashboard-via-json-export) + +{{< admonition type="caution" >}} + +Refer to [Known limitations](https://grafana.com/docs/grafana//observability-as-code/provision-resources/intro-git-sync#known-limitations/) before using Git Sync. + +{{< /admonition >}} + +## Add a dashboard with Grafana CLI + +You can export an existing dashboard with the [Grafana CLI](https://grafana.com/docs/grafana//as-code/observability-as-code/grafana-cli/) and commit it to your Git repository. + +Use `grafanactl` to download the resources you want to sync from Grafana, and then commit and push those files to the repository. Git Sync will then detect the commit, and synchronize with Grafana. To do so, follow these steps: + +1. Set up the `grafanactl` context to point to your instance as documented in [Defining contexts](https://grafana.github.io/grafanactl/configuration/#defining-contexts). +1. Pull the resources you want to sync from the instance to your local repository: + +``` +grafanactl resources pull dashboards --path +``` + +Next, commit and push the resources to your Git repository: + +``` +git add +git commit -m "Add dashboards from Grafana" +git push +``` + +Where: + +- __: The path to the repository synced with Git Sync +- __: The path where the dashboards you want to export are located. The dashboards path must be under the repository + +See more at [Manage resources with Grafana CLI](https://grafana.github.io/grafanactl/guides/manage-resources/). + +## Add a dashboard via JSON export + +To add an existing dashboard to Git Sync via JSON export, you need to: + +1. Export the dashboard as JSON +1. Convert it to the Custom Resource Definition (CRD) format required by the Grafana App Platform +1. Commit the converted file to your Git repository + +### Required JSON format + +To export a dashboard as a JSON file it must follow this CRD structure: + +```yaml +{ + 'apiVersion': 'dashboard.grafana.app/v1beta1', + 'kind': 'Dashboard', + 'metadata': { 'name': 'dcf2lve9akj8xsd' }, + 'spec': { /* Original dashboard JSON goes here */ }, +} +``` + +The structure includes: + +- `apiVersion`: Specifies the API version (currently `v1beta1`) +- `kind`: Identifies the resource type (Dashboard) +- `metadata`: Contains the dashboard identifier `uid`. You can find the identifier in the dahsboard's URL or in the exported JSON +- `spec`: Wraps your original dashboard JSON + +## Edit Git-managed dashboards + +After you've saved a dashboard in Git, it'll be synchronized automatically, and you'll be able to work with it as any other provisioned resource. Refer to [Work with provisioned dashboards](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/provisioned-dashboards/) for more information. diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md index 0a7bd7be28b..f6113fe428d 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md @@ -9,8 +9,9 @@ labels: products: - enterprise - oss + - cloud title: Set up Git Sync -weight: 100 +weight: 110 canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/provision-resources/git-sync-setup/ aliases: - ../../../observability-as-code/provision-resources/git-sync-setup/ # /docs/grafana/next/observability-as-code/provision-resources/git-sync-setup/ diff --git a/docs/sources/as-code/observability-as-code/provision-resources/provisioned-dashboards.md b/docs/sources/as-code/observability-as-code/provision-resources/provisioned-dashboards.md index cc4964ff9e1..83ec3869927 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/provisioned-dashboards.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/provisioned-dashboards.md @@ -10,7 +10,7 @@ labels: - enterprise - oss title: Work with provisioned dashboards -weight: 300 +weight: 350 canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/provision-resources/provisioned-dashboards/ aliases: - ../../../observability-as-code/provision-resources/provisioned-dashboards/ # /docs/grafana/next/observability-as-code/provision-resources/provisioned-dashboards/ @@ -26,33 +26,30 @@ Git Sync and local file provisioning are [experimental features](https://grafana {{< /admonition >}} -Using Provisioning, you can choose to store your dashboard JSON files in either GitHub repositories using Git Sync or a local file path. +Using provisioning, you can choose to store your dashboard JSON files in either GitHub repositories using Git Sync or a local path, and manage them through the Grafana interface. Dashboards and folders synchronized using Git Sync or a local path are referred to as _provisioned_ resources. For more information, refer to the [Dashboards](https://grafana.com/docs/grafana//dashboards/) documentation. ## Provisioning methods -Dashboards and folders synchronized using Git Sync or a local file path are referred to as "provisioned" resources. - ### Git Sync provisioning -Of the two experimental options, **Git Sync** is the recommended method for provisioning your dashboards. +Of the two options, **Git Sync** is the recommended method for provisioning your dashboards. You can synchronize any new dashboards and changes to existing dashboards to your configured GitHub repository. If you push a change in the repository, those changes are mirrored in your Grafana instance. -For more information on configuring Git Sync, refer to [Introduction to Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/intro-git-sync/). +For more information on configuring Git Sync, refer to [Introduction to Git Sync](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/intro-git-sync/). ### Local path provisioning Local path provisioning makes files from a specified path available within Grafana, and any changes made in the configured local path are updated in Grafana. Note that these provisioned resources can only be modified in the local files and not within Grafana. -Refer to [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup) to learn more about the version of local file provisioning in Grafana 12. +Refer to [Set up file provisioning](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/file-path-setup) to learn more about the version of local file provisioning in Grafana 12. {{< admonition type="note" >}} -The experimental local path provisioning using **Administration** > **Provisioning** will replace the file provisioning methods Grafana uses for referencing local file. +The **Administration** > **Provisioning** local path provisioning will replace the file provisioning methods Grafana uses for referencing local files. -For production systems, use the established methods for provisioning file systems in Grafana. -Refer to [Provision Grafana](https://grafana.com/docs/grafana//administration/provisioning/#provision-folders-structure-from-filesystem-to-grafana) for more information. +For production systems, use the established methods for provisioning file systems in Grafana. Refer to [Provision Grafana](https://grafana.com/docs/grafana//administration/provisioning/#provision-folders-structure-from-filesystem-to-grafana) for more information. {{< /admonition >}} ## Manage dashboards provisioned with Git Sync @@ -61,9 +58,9 @@ Using Git Sync, you can manage your dashboards in the UI and synchronize them wi Git Sync changes the behavior in Grafana for dashboards that are saved in Git Sync: -- Dashboards saved in your repository or local folder configured with Git Sync appear in a provisioned folder in Grafana. -- Any dashboard folders saved with Git Sync have a **Provisioned** label in the UI. -- Any changes to a provisioned resources have to be saved to the repository by opening a pull request or committing directly to the `main` branch. +- Dashboards saved in your repository or local folder configured with Git Sync appear in a provisioned folder in Grafana +- Any dashboard folders saved with Git Sync have a **Provisioned** label in the UI +- Any changes to a provisioned resources have to be saved to the repository by opening a pull request or committing directly to the `main` branch You can set a single folder, or multiple folders to a different repository, with up to 10 connections. @@ -71,18 +68,29 @@ You can set a single folder, or multiple folders to a different repository, with By default, Git version control uses a branch-based workflow for changes. This means that you can: -- Commit changes to an existing branch (such as `main`) or save them to a new branch in your GitHub repository. -- Use pull requests to review changes to dashboards. -- Preview the changes before merging. +- Commit changes to an existing branch (such as `main`) or save them to a new branch in your GitHub repository +- Use pull requests to review changes to dashboards +- Preview the changes before merging To learn more about Git, refer to [Getting Started - About Version Control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control) of the [Pro Git book](https://git-scm.com/book/en/v2) in the official Git documentation. ### Add and save a new dashboard +{{< admonition type="note" >}} + +If you want to add an existing dashboard to your provisioned resources, refer to [Export non-provisioned resources from Grafana](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/export-resources/). + +{{< /admonition >}} + +You have two options for creating **new** dashboards or folders in Git Sync: + +- Create them directly within Git Sync-managed folders in the Grafana UI +- Add them by committing JSON files to your Git repository + When you create a new dashboard in a provisioned folder associated with a GitHub repository, you follow the same process you use for any new dashboard. Refer to [Create a dashboard](http://grafana.com/docs/grafana//dashboards/build-dashboards/create-dashboard/) for more information. -After you create the dashboard, the steps are similar to [Save dashboard changes to GitHub](#save-dashboard-changes-to-github). +After you create the dashboard, follow these steps: 1. Select **Save** to preserve the new dashboard. 1. Enter a title for the dashboard and a description. @@ -94,26 +102,27 @@ After you create the dashboard, the steps are similar to [Save dashboard changes ### Save dashboard changes to GitHub -When you edit a provisioned resource, you are prompted to save or discard those changes. -Saving changes requires opening a pull request in your GitHub repository. +When you edit a provisioned resource, you're prompted to save or discard those changes. Saving changes requires opening a pull request in your GitHub repository. + +To save dashboard changes: 1. Select **Edit** to update a provisioned dashboard. Make your desired changes. 1. Click **Save dashboard**. 1. On the **Provisioned dashboard** panel, choose the options you want to use: - - **Update default refresh value**: Check this box to make the current refresh the new default. - - **Update default variable values**: Check this box to make the current values the new default. - - **Path**: Provide the path for your repository, ending in a JSON or YAML file. - - **Workflow:** Select **Push to main** to make a Git commit directly to the repository or **Push to a new branch** to create a pull request. - - **Branch**: Specify the branch name in GitHub (for example, main). This option only appears if you select **Push to a new branch**. - - **Comment**: Add a comment describing your changes. + - **Update default refresh value**: Check this box to make the current refresh the new default + - **Update default variable values**: Check this box to make the current values the new default + - **Path**: Provide the path for your repository, ending in a JSON or YAML file + - **Workflow:** Select **Push to main** to make a Git commit directly to the repository or **Push to a new branch** to create a pull request + - **Branch**: Specify the branch name in GitHub (for example, main). This option only appears if you select **Push to a new branch** + - **Comment**: Add a comment describing your changes 1. Optional: Select the **Changes** tab to view the differences between the updates you made and the original resource. 1. Select **Save**. -1. If you chose **Push to a new branch**, select **Open a pull request in GitHub** to open a new PR to your repository. GitHub opens with your dashboard’s code as the contents of the PR. +1. If you chose **Push to a new branch**, select **Open a pull request in GitHub** to open a new PR to your repository. GitHub opens with your dashboard's code as the contents of the PR. 1. Follow your usual GitHub workflow to save and merge the PR to your repository. @@ -121,18 +130,19 @@ Saving changes requires opening a pull request in your GitHub repository. You can remove a provisioned dashboard by deleting the dashboard from the repository. The Grafana UI updates when the changes from the GitHub repository sync. -To restore a deleted dashboard, raise a PR directly in your GitHub repository. Restoring resources from the UI is currently not possible. +To restore a deleted dashboard, raise a PR directly in your GitHub repository. Restoring resources from the UI isn't currently possible. -### Tips +### Best practices -- Use GitHub pull requests for changes to maintain review processes. -- Provide clear commit messages describing your changes. -- Regularly sync your repository to keep Grafana up to date. -- Review the **Events** tab to monitor sync status. +Follow these recommendations when working with provisioned dashboards: + +- **Use GitHub pull requests for changes**: Maintain review processes for dashboard modifications +- **Provide clear commit messages**: Describe your changes to help with tracking and collaboration +- **Regularly sync your repository**: Keep Grafana up to date with the latest changes +- **Review the Events tab**: Monitor sync status to ensure changes are applied correctly ## Manage dashboards provisioned with file provisioning -To update any resources in the local path, you need to edit the files directly and then save them locally. -These changes are synchronized to Grafana. However, you can't create, edit, or delete these resources using the Grafana UI. +To update any resources in the local path, you need to edit the files directly and then save them locally. These changes are synchronized to Grafana. However, you can't create, edit, or delete these resources using the Grafana UI. Refer to [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/) for configuration instructions. diff --git a/docs/sources/as-code/observability-as-code/provision-resources/use-git-sync.md b/docs/sources/as-code/observability-as-code/provision-resources/use-git-sync.md index 257e83c41ed..f7d37d4b94e 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/use-git-sync.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/use-git-sync.md @@ -11,9 +11,10 @@ labels: products: - enterprise - oss + - cloud title: Manage provisioned repositories with Git Sync menuTitle: Manage repositories with Git Sync -weight: 400 +weight: 120 canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/provision-resources/use-git-sync/ aliases: - ../../../observability-as-code/provision-resources/use-git-sync/ # /docs/grafana/next/observability-as-code/provision-resources/use-git-sync/ diff --git a/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md b/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md index 7f1cf5e3cb5..94e7a1eec55 100644 --- a/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md +++ b/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md @@ -266,6 +266,14 @@ If true, it establishes a secure connection to Loki. Defaults to true. Set the tenant ID for Loki communication, which is disabled by default. The tenant ID is required to interact with Loki running in [multi-tenant mode](/docs/loki/latest/operations/multi-tenancy/). +### retries + +The amount of times the HTTP or gRPC client will retry a failed request to Loki. The default is `10`. + +### timeout + +The timeout duration of an HTTP request or gRPC call to Loki. The default is `3s`. + ## [auth.saml] ### enabled diff --git a/e2e-playwright/various-suite/visualization-suggestions.spec.ts b/e2e-playwright/various-suite/visualization-suggestions.spec.ts index 57ccfccc475..d01447b7eb7 100644 --- a/e2e-playwright/various-suite/visualization-suggestions.spec.ts +++ b/e2e-playwright/various-suite/visualization-suggestions.spec.ts @@ -17,10 +17,7 @@ test.describe( // Try visualization suggestions await panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleVizPicker).click(); - await panelEditPage - .getByGrafanaSelector(selectors.components.RadioButton.container) - .filter({ hasText: 'Suggestions' }) - .click(); + await panelEditPage.getByGrafanaSelector(selectors.components.Tab.title('Suggestions')).click(); // Verify we see suggestions const lineChartCard = panelEditPage.getByGrafanaSelector( @@ -28,12 +25,7 @@ test.describe( ); await expect(lineChartCard).toBeVisible(); - // Verify search works - const searchInput = page.getByPlaceholder('Search for...'); - await searchInput.fill('Table'); - - // Should no longer see line chart - await expect(lineChartCard).toBeHidden(); + // TODO: in this part of the test, we will change the query and the transforms and observe suggestions being updated. // Select a visualization await panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.card('Table')).click(); diff --git a/eslint-suppressions.json b/eslint-suppressions.json index b3eaa1feb52..2450d3721d7 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1955,11 +1955,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx": { "no-restricted-syntax": { "count": 1 @@ -2076,9 +2071,6 @@ } }, "public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx": { - "no-restricted-syntax": { - "count": 1 - }, "react-hooks/rules-of-hooks": { "count": 4 } diff --git a/go.mod b/go.mod index 576f6c0614f..936cf97655b 100644 --- a/go.mod +++ b/go.mod @@ -200,7 +200,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // @grafana/alerting-backend gocloud.dev v0.43.0 // @grafana/grafana-app-platform-squad gocloud.dev/secrets/hashivault v0.43.0 // @grafana/grafana-operator-experience-squad - golang.org/x/crypto v0.44.0 // @grafana/grafana-backend-group + golang.org/x/crypto v0.45.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // @grafana/alerting-backend golang.org/x/mod v0.30.0 // indirect; @grafana/grafana-backend-group golang.org/x/net v0.47.0 // @grafana/oss-big-tent @grafana/partner-datasources diff --git a/go.sum b/go.sum index 1c294bc1fe2..f7c4c8e5578 100644 --- a/go.sum +++ b/go.sum @@ -2784,8 +2784,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/go.work.sum b/go.work.sum index bbfe799f72c..72f5dd7e97d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -592,6 +592,7 @@ github.com/couchbase/ghistogram v0.1.0 h1:b95QcQTCzjTUocDXp/uMgSNQi8oj1tGwnJ4bOD github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= github.com/couchbase/moss v0.2.0 h1:VCYrMzFwEryyhRSeI+/b3tRBSeTpi/8gn5Kf6dxqn+o= github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= diff --git a/hack/go.mod b/hack/go.mod index f9212c08bb8..b35dd7f5a76 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -7,10 +7,10 @@ require k8s.io/code-generator v0.34.2 require ( github.com/go-logr/logr v1.4.2 // indirect github.com/spf13/pflag v1.0.6 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect k8s.io/klog/v2 v2.130.1 // indirect ) diff --git a/hack/go.sum b/hack/go.sum index 1dd0408cf4e..25c035d777a 100644 --- a/hack/go.sum +++ b/hack/go.sum @@ -4,14 +4,14 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= k8s.io/code-generator v0.34.2 h1:9bG6jTxmsU3HXE5BNYJTC8AZ1D6hVVfkm8yYSkdkGY0= k8s.io/code-generator v0.34.2/go.mod h1:dnDDEd6S/z4uZ+PG1aE58ySCi/lR4+qT3a4DddE4/2I= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= diff --git a/package.json b/package.json index 1ab0dc7a811..2e6fcba94f2 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "html-loader": "5.1.0", "html-webpack-plugin": "5.6.3", "http-server": "14.1.1", - "i18next-cli": "1.11.12", + "i18next-cli": "^1.24.22", "ini": "^5.0.0", "jest": "29.7.0", "jest-canvas-mock": "2.5.2", diff --git a/packages/grafana-alerting/package.json b/packages/grafana-alerting/package.json index da13aa6fc0f..1c3ab2bf9b4 100644 --- a/packages/grafana-alerting/package.json +++ b/packages/grafana-alerting/package.json @@ -72,7 +72,7 @@ "@types/react-dom": "18.3.5", "@types/tinycolor2": "^1", "i18next": "^25.5.2", - "i18next-cli": "1.11.12", + "i18next-cli": "^1.24.22", "react": "18.3.1", "react-dom": "18.3.1", "react-redux": "^9.2.0", diff --git a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts index 851680b4be4..88ce5985631 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts @@ -561,6 +561,10 @@ const injectedRtkApi = api }), invalidatesTags: ['Team'], }), + getTeamGroups: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.name}/groups` }), + providesTags: ['Team'], + }), getTeamMembers: build.query({ query: (queryArg) => ({ url: `/teams/${queryArg.name}/members` }), providesTags: ['Team'], @@ -1461,6 +1465,11 @@ export type UpdateTeamApiArg = { force?: boolean; patch: Patch; }; +export type GetTeamGroupsApiResponse = /** status 200 OK */ GetGroups; +export type GetTeamGroupsApiArg = { + /** name of the GetGroups */ + name: string; +}; export type GetTeamMembersApiResponse = /** status 200 OK */ TeamMemberList; export type GetTeamMembersApiArg = { /** name of the TeamMemberList */ @@ -1978,6 +1987,17 @@ export type TeamList = { kind?: string; metadata: ListMeta; }; +export type VersionsV0Alpha1Kinds7RoutesGroupsGetResponseExternalGroupMapping = { + externalGroup: string; + name: string; +}; +export type GetGroups = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: VersionsV0Alpha1Kinds7RoutesGroupsGetResponseExternalGroupMapping[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; +}; export type TeamMember = { /** AvatarURL is the url where we can get the avatar for identity */ avatarURL?: string; @@ -2100,6 +2120,8 @@ export const { useReplaceTeamMutation, useDeleteTeamMutation, useUpdateTeamMutation, + useGetTeamGroupsQuery, + useLazyGetTeamGroupsQuery, useGetTeamMembersQuery, useLazyGetTeamMembersQuery, useListUserQuery, diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts index 058aa2b057c..45ff0d1e91e 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts @@ -555,7 +555,7 @@ const injectedRtkApi = api 'x-grafana-alerting-alert-rules-paused': queryArg['x-grafana-alerting-alert-rules-paused'], 'x-grafana-alerting-target-datasource-uid': queryArg['x-grafana-alerting-target-datasource-uid'], 'x-grafana-alerting-folder-uid': queryArg['x-grafana-alerting-folder-uid'], - 'x-grafana-alerting-notification-receiver': queryArg['x-grafana-alerting-notification-receiver'], + 'x-grafana-alerting-notification-settings': queryArg['x-grafana-alerting-notification-settings'], }, }), invalidatesTags: ['convert_prometheus'], @@ -622,7 +622,7 @@ const injectedRtkApi = api 'x-grafana-alerting-alert-rules-paused': queryArg['x-grafana-alerting-alert-rules-paused'], 'x-grafana-alerting-target-datasource-uid': queryArg['x-grafana-alerting-target-datasource-uid'], 'x-grafana-alerting-folder-uid': queryArg['x-grafana-alerting-folder-uid'], - 'x-grafana-alerting-notification-receiver': queryArg['x-grafana-alerting-notification-receiver'], + 'x-grafana-alerting-notification-settings': queryArg['x-grafana-alerting-notification-settings'], }, }), invalidatesTags: ['convert_prometheus'], @@ -656,10 +656,6 @@ const injectedRtkApi = api }), providesTags: ['dashboards', 'snapshots'], }), - calculateDashboardDiff: build.mutation({ - query: (queryArg) => ({ url: `/dashboards/calculate-diff`, method: 'POST', body: queryArg.body }), - invalidatesTags: ['dashboards'], - }), postDashboard: build.mutation({ query: (queryArg) => ({ url: `/dashboards/db`, method: 'POST', body: queryArg.saveDashboardCommand }), invalidatesTags: ['dashboards'], @@ -2498,7 +2494,7 @@ export type RouteConvertPrometheusCortexPostRuleGroupApiArg = { 'x-grafana-alerting-alert-rules-paused'?: boolean; 'x-grafana-alerting-target-datasource-uid'?: string; 'x-grafana-alerting-folder-uid'?: string; - 'x-grafana-alerting-notification-receiver'?: string; + 'x-grafana-alerting-notification-settings'?: string; prometheusRuleGroup: PrometheusRuleGroup; }; export type RouteConvertPrometheusCortexDeleteRuleGroupApiResponse = @@ -2535,7 +2531,7 @@ export type RouteConvertPrometheusPostRuleGroupApiArg = { 'x-grafana-alerting-alert-rules-paused'?: boolean; 'x-grafana-alerting-target-datasource-uid'?: string; 'x-grafana-alerting-folder-uid'?: string; - 'x-grafana-alerting-notification-receiver'?: string; + 'x-grafana-alerting-notification-settings'?: string; prometheusRuleGroup: PrometheusRuleGroup; }; export type RouteConvertPrometheusDeleteRuleGroupApiResponse = @@ -2556,18 +2552,6 @@ export type SearchDashboardSnapshotsApiArg = { /** Limit the number of returned results */ limit?: number; }; -export type CalculateDashboardDiffApiResponse = /** status 200 (empty) */ number[]; -export type CalculateDashboardDiffApiArg = { - body: { - base?: CalculateDiffTarget; - /** The type of diff to return - Description: - `basic` - `json` */ - diffType?: 'basic' | 'json'; - new?: CalculateDiffTarget; - }; -}; export type PostDashboardApiResponse = /** status 200 (empty) */ { /** FolderUID The unique identifier (uid) of the folder the dashboard belongs to. */ folderUid?: string; @@ -4400,11 +4384,6 @@ export type DashboardSnapshotDto = { name?: string; updated?: string; }; -export type CalculateDiffTarget = { - dashboardId?: number; - unsavedDashboard?: Json; - version?: number; -}; export type SaveDashboardCommand = { UpdatedAt?: string; dashboard?: Json; @@ -6060,6 +6039,7 @@ export type TeamGroupDto = { groupId?: string; orgId?: number; teamId?: number; + uid?: string; }; export type TeamGroupMapping = { groupId?: string; @@ -6636,7 +6616,6 @@ export const { useLazyRouteConvertPrometheusGetRuleGroupQuery, useSearchDashboardSnapshotsQuery, useLazySearchDashboardSnapshotsQuery, - useCalculateDashboardDiffMutation, usePostDashboardMutation, useGetHomeDashboardQuery, useLazyGetHomeDashboardQuery, diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 509beeb56c9..0d20e44b19e 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -963,6 +963,10 @@ export interface FeatureToggles { */ kubernetesAuthnMutation?: boolean; /** + * Routes external group mapping requests from /api to the /apis endpoint + */ + kubernetesExternalGroupMapping?: boolean; + /** * Enables restore deleted dashboards feature * @default false */ diff --git a/packages/grafana-data/src/types/pluginExtensions.ts b/packages/grafana-data/src/types/pluginExtensions.ts index 05e7b2b144e..40d20b88c38 100644 --- a/packages/grafana-data/src/types/pluginExtensions.ts +++ b/packages/grafana-data/src/types/pluginExtensions.ts @@ -218,6 +218,8 @@ export enum PluginExtensionPoints { LogsViewResourceAttributes = 'grafana/logsview/resource-attributes', AppChrome = 'grafana/app/chrome/v1', ExtensionSidebar = 'grafana/extension-sidebar/v0-alpha', + MegaMenuAction = 'grafana/megamenu/action', + SingleTopBarAction = 'grafana/singletopbar/action', } // Don't use directly in a plugin! diff --git a/packages/grafana-data/src/types/suggestions.test.ts b/packages/grafana-data/src/types/suggestions.test.ts new file mode 100644 index 00000000000..502590952d9 --- /dev/null +++ b/packages/grafana-data/src/types/suggestions.test.ts @@ -0,0 +1,75 @@ +import { getSuggestionHash, PanelPluginVisualizationSuggestion } from './suggestions'; + +interface FakeOptions { + foo?: number; + bar?: string; +} + +interface FakeFieldOptions { + foo?: { + a: number; + b: number; + }; + bar?: string; +} + +describe('getSuggestionHash', () => { + it.each<{ + name: string; + a: Omit, 'hash'>; + b: Omit, 'hash'>; + expectEqual: boolean; + }>([ + { + name: 'should create different hashes for different pluginIds', + a: { pluginId: 'a', name: 'my suggestion' }, + b: { pluginId: 'b', name: 'my suggestion' }, + expectEqual: false, + }, + { + name: 'should create different hashes for different options', + a: { pluginId: 'a', name: 'my suggestion', options: { foo: 1 } }, + b: { pluginId: 'a', name: 'my suggestion', options: { foo: 2 } }, + expectEqual: false, + }, + { + name: 'should create different hashes for different fieldConfig', + a: { + pluginId: 'a', + name: 'my suggestion', + fieldConfig: { defaults: { custom: { bar: 'x', foo: { a: 1, b: 2 } } }, overrides: [] }, + }, + b: { + pluginId: 'a', + name: 'my suggestion', + fieldConfig: { defaults: { custom: { bar: 'y', foo: { a: 1, b: 2 } } }, overrides: [] }, + }, + expectEqual: false, + }, + { + name: 'should create same hashes for same suggestions', + a: { + pluginId: 'a', + name: 'my suggestion', + options: { foo: 1, bar: 'x' }, + fieldConfig: { defaults: { custom: { bar: 'x', foo: { a: 1, b: 2 } } }, overrides: [] }, + }, + b: { + pluginId: 'a', + name: 'my suggestion', + options: { bar: 'x', foo: 1 }, + fieldConfig: { defaults: { custom: { foo: { b: 2, a: 1 }, bar: 'x' } }, overrides: [] }, + }, + expectEqual: true, + }, + ])('$name', ({ a, b, expectEqual }) => { + const hashA = getSuggestionHash(a); + const hashB = getSuggestionHash(b); + + if (expectEqual) { + expect(hashA).toEqual(hashB); + } else { + expect(hashA).not.toEqual(hashB); + } + }); +}); diff --git a/packages/grafana-data/src/types/suggestions.ts b/packages/grafana-data/src/types/suggestions.ts index 1765e5406f2..475495cc24a 100644 --- a/packages/grafana-data/src/types/suggestions.ts +++ b/packages/grafana-data/src/types/suggestions.ts @@ -8,6 +8,47 @@ import { PanelModel } from './dashboard'; import { FieldConfigSource } from './fieldOverrides'; import { PanelData } from './panel'; +/** + * @internal + * generates a hash for a suggestion based for use by the UI. + */ +export function getSuggestionHash(suggestion: Omit): string { + return deterministicObjectHash(suggestion); +} + +function strHash(str: string): string { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash |= 0; // Convert to 32bit integer + } + return hash.toString(36); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function deterministicObjectHash>(obj: T): string { + let result = ''; + for (const key of Object.keys(obj).sort()) { + const value = obj[key]; + if (value === undefined) { + continue; + } + result += key + ':'; + if (typeof value === 'object' && value !== null) { + result += deterministicObjectHash(value); + } else if (Array.isArray(value)) { + result += value + .map((v) => (typeof value === 'object' && value !== null ? deterministicObjectHash(v) : String(v))) + .join(','); + } else { + result += String(value); + } + result += ';'; + } + return strHash(result); +} + /** * @alpha * A suggestion for a visualization given some data. This represents the shape of the panel (including options and field config) @@ -49,6 +90,8 @@ export interface PanelPluginVisualizationSuggestion( - defaults: PanelPluginVisualizationSuggestion + defaults: Omit, 'hash'> ) { return new VisualizationSuggestionsListAppender(this.list, defaults); } @@ -127,10 +170,15 @@ export class VisualizationSuggestionsListAppender) { - this.list.push(defaultsDeep(suggestion, this.defaults)); + this.appendAll([suggestion]); } appendAll(suggestions: Array>) { - this.list.push(...suggestions.map((o) => defaultsDeep(o, this.defaults))); + this.list.push( + ...suggestions.map((s): PanelPluginVisualizationSuggestion => { + const suggestionWithDefaults = defaultsDeep(s, this.defaults); + return Object.assign(suggestionWithDefaults, { hash: getSuggestionHash(suggestionWithDefaults) }); + }) + ); } } diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index a3a1fc78408..8ed4bd95d5a 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -91,7 +91,7 @@ "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.5", "esbuild": "0.25.8", - "i18next-cli": "1.11.12", + "i18next-cli": "^1.24.22", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "react": "18.3.1", diff --git a/packages/grafana-prometheus/src/locales/cs-CZ/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/cs-CZ/grafana-prometheus.json index 2379436aa18..d266dc1b4d9 100644 --- a/packages/grafana-prometheus/src/locales/cs-CZ/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/cs-CZ/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Interval scrapingu", "label-series-limit": "Limit řady", "label-use-series-endpoint": "Použít koncový bod řady", - "more-info": "", + "more-info": "Další informace o konfiguraci typu a verze Prometheus ve zdrojích dat najdete v <2>dokumentaci k zajišťování.", "placeholder-example-maxsourceresolutionmtimeout": "Příklad: {{example}}", "title-interval-behaviour": "Chování intervalu", "title-other": "Ostatní", diff --git a/packages/grafana-prometheus/src/locales/de-DE/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/de-DE/grafana-prometheus.json index 3b63c9cfc00..bc98703299a 100644 --- a/packages/grafana-prometheus/src/locales/de-DE/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/de-DE/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Scrape-Intervall", "label-series-limit": "Reihengrenze", "label-use-series-endpoint": "Reihenendpunkt verwenden", - "more-info": "", + "more-info": "Weitere Informationen zur Konfiguration des Typs und der Version von Prometheus in Datenquellen finden Sie in der <2>Bereitstellungsdokumentation.", "placeholder-example-maxsourceresolutionmtimeout": "Beispiel: {{example}}", "title-interval-behaviour": "Intervallverhalten", "title-other": "Sonstiges", diff --git a/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json index d21b1c2d693..65289da4d2d 100644 --- a/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Scrape interval", "label-series-limit": "Series limit", "label-use-series-endpoint": "Use series endpoint", - "more-info": "For more information on configuring prometheus type and version in data sources, see the <2>provisioning documentation .", + "more-info": "For more information on configuring prometheus type and version in data sources, see the <2>provisioning documentation.", "placeholder-example-maxsourceresolutionmtimeout": "Example: {{example}}", "title-interval-behaviour": "Interval behaviour", "title-other": "Other", diff --git a/packages/grafana-prometheus/src/locales/es-ES/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/es-ES/grafana-prometheus.json index 80739214809..076b607d879 100644 --- a/packages/grafana-prometheus/src/locales/es-ES/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/es-ES/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Intervalo de recuperación de datos", "label-series-limit": "Límite de series", "label-use-series-endpoint": "Utilizar punto de conexión de la serie", - "more-info": "", + "more-info": "Para obtener más información sobre cómo configurar el tipo y la versión de Prometheus en las fuentes de datos, consulte la <2>documentación de aprovisionamiento.", "placeholder-example-maxsourceresolutionmtimeout": "Ejemplo: {{example}}", "title-interval-behaviour": "Comportamiento del intervalo", "title-other": "Otro", diff --git a/packages/grafana-prometheus/src/locales/fr-FR/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/fr-FR/grafana-prometheus.json index 86169b30725..f9e81ff13f8 100644 --- a/packages/grafana-prometheus/src/locales/fr-FR/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/fr-FR/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Intervalle de scraping", "label-series-limit": "Limite de séries", "label-use-series-endpoint": "Utiliser le point de terminaison de la série", - "more-info": "", + "more-info": "Pour en savoir plus sur la configuration du type et de la version de Prometheus dans les sources de données, consultez la <2>documentation sur le provisionnement.", "placeholder-example-maxsourceresolutionmtimeout": "Exemple : {{example}}", "title-interval-behaviour": "Comportement de l’intervalle", "title-other": "Autre", diff --git a/packages/grafana-prometheus/src/locales/hu-HU/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/hu-HU/grafana-prometheus.json index bceeabefdc1..5a3a67a6bc0 100644 --- a/packages/grafana-prometheus/src/locales/hu-HU/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/hu-HU/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Adatgyűjtési intervallum", "label-series-limit": "Sorozatkorlát", "label-use-series-endpoint": "Sorozat végpontjának használata", - "more-info": "", + "more-info": "A Prometheus-típus és -verzió adatforrásokban történő konfigurálásával kapcsolatos további információkért tekintse meg a <2>kiépítési dokumentációt.", "placeholder-example-maxsourceresolutionmtimeout": "Példa: {{example}}", "title-interval-behaviour": "Intervallum viselkedése", "title-other": "Egyéb", diff --git a/packages/grafana-prometheus/src/locales/id-ID/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/id-ID/grafana-prometheus.json index 74b9ed50d74..de3d66304f9 100644 --- a/packages/grafana-prometheus/src/locales/id-ID/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/id-ID/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Interval scrape", "label-series-limit": "Batas data seri", "label-use-series-endpoint": "Gunakan titik akhir data seri", - "more-info": "", + "more-info": "Untuk informasi lengkap tentang mengonfigurasi jenis dan versi prometheus dalam sumber data, lihat <2>dokumentasi penyediaan.", "placeholder-example-maxsourceresolutionmtimeout": "Contoh: {{example}}", "title-interval-behaviour": "Perilaku interval", "title-other": "Lainnya", diff --git a/packages/grafana-prometheus/src/locales/it-IT/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/it-IT/grafana-prometheus.json index f3cf622ddd1..2e6509fdb30 100644 --- a/packages/grafana-prometheus/src/locales/it-IT/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/it-IT/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Intervallo di raccolta", "label-series-limit": "Limite della serie", "label-use-series-endpoint": "Usa endpoint della serie", - "more-info": "", + "more-info": "Per ulteriori informazioni sulla configurazione del tipo e della versione di Prometheus nelle origini dati, consulta la <2>documentazione sul provisioning.", "placeholder-example-maxsourceresolutionmtimeout": "Esempio: {{example}}", "title-interval-behaviour": "Comportamento intervallo", "title-other": "Altro", diff --git a/packages/grafana-prometheus/src/locales/ja-JP/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/ja-JP/grafana-prometheus.json index 8f6fcef6ab6..6642c9ba08e 100644 --- a/packages/grafana-prometheus/src/locales/ja-JP/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/ja-JP/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "スクレイプ間隔", "label-series-limit": "系列制限", "label-use-series-endpoint": "系列エンドポイントを使用", - "more-info": "", + "more-info": "データソースでのPrometheusタイプとバージョンの設定の詳細については、<2>プロビジョニングドキュメントをご覧ください。", "placeholder-example-maxsourceresolutionmtimeout": "例: {{example}}", "title-interval-behaviour": "間隔の動作", "title-other": "その他", diff --git a/packages/grafana-prometheus/src/locales/ko-KR/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/ko-KR/grafana-prometheus.json index 60576a8c7b6..03050db9014 100644 --- a/packages/grafana-prometheus/src/locales/ko-KR/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/ko-KR/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "스크레이핑 간격", "label-series-limit": "계열 한도", "label-use-series-endpoint": "계열 엔드포인트 사용", - "more-info": "", + "more-info": "데이터 소스에서 prometheus 유형 및 버전을 구성하는 방법에 대한 자세한 내용은 <2>프로비저닝 문서를 참조하시기 바랍니다.", "placeholder-example-maxsourceresolutionmtimeout": "예: {{example}} ", "title-interval-behaviour": "간격 동작", "title-other": "기타", diff --git a/packages/grafana-prometheus/src/locales/nl-NL/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/nl-NL/grafana-prometheus.json index feaca3639d9..721e72812f7 100644 --- a/packages/grafana-prometheus/src/locales/nl-NL/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/nl-NL/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Scrape-interval", "label-series-limit": "Reekslimiet", "label-use-series-endpoint": "Serie-eindpunt gebruiken", - "more-info": "", + "more-info": "Zie de <2>provisioning-documentatie voor meer informatie over het configureren van het Prometheus-type en de versie in gegevensbronnen.", "placeholder-example-maxsourceresolutionmtimeout": "Voorbeeld: {{example}}", "title-interval-behaviour": "Intervalgedrag", "title-other": "Overige", diff --git a/packages/grafana-prometheus/src/locales/pl-PL/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/pl-PL/grafana-prometheus.json index f0b3e694062..d5c06e28cfa 100644 --- a/packages/grafana-prometheus/src/locales/pl-PL/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/pl-PL/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Odstęp czasowy pobierania danych", "label-series-limit": "Limit szeregu", "label-use-series-endpoint": "Użyj punktu końcowego szeregu", - "more-info": "", + "more-info": "Więcej informacji na temat konfigurowania typu i wersji systemu Prometheus w źródłach danych można znaleźć w <2>dokumentach dotyczących aprowizacji.", "placeholder-example-maxsourceresolutionmtimeout": "Przykład: {{example}}", "title-interval-behaviour": "Sposób działania odstępu czasu", "title-other": "Inne", diff --git a/packages/grafana-prometheus/src/locales/pt-BR/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/pt-BR/grafana-prometheus.json index 07598624e45..8e54e6f7aa9 100644 --- a/packages/grafana-prometheus/src/locales/pt-BR/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/pt-BR/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Intervalo de coleta", "label-series-limit": "Limite de série", "label-use-series-endpoint": "Usar endpoint da série", - "more-info": "", + "more-info": "Para mais informações sobre como configurar o tipo e a versão do Prometheus em fontes de dados, consulte a <2>documentação de provisionamento.", "placeholder-example-maxsourceresolutionmtimeout": "Exemplo: {{example}}", "title-interval-behaviour": "Comportamento de intervalo", "title-other": "Outro", diff --git a/packages/grafana-prometheus/src/locales/pt-PT/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/pt-PT/grafana-prometheus.json index eb90445ed3e..6cec7bdbf3c 100644 --- a/packages/grafana-prometheus/src/locales/pt-PT/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/pt-PT/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Intervalo de raspagem", "label-series-limit": "Limite de série", "label-use-series-endpoint": "Utilizar ponto final da série", - "more-info": "", + "more-info": "Para obter mais informações sobre como configurar o tipo e a versão do Prometheus em origens de dados, consulte a <2>documentação de aprovisionamento.", "placeholder-example-maxsourceresolutionmtimeout": "Exemplo: {{example}}", "title-interval-behaviour": "Comportamento de intervalo", "title-other": "Outro", diff --git a/packages/grafana-prometheus/src/locales/ru-RU/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/ru-RU/grafana-prometheus.json index e4ff771a093..ee2ffbaa801 100644 --- a/packages/grafana-prometheus/src/locales/ru-RU/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/ru-RU/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Интервал опроса", "label-series-limit": "Лимит рядов", "label-use-series-endpoint": "Использовать конечную точку ряда", - "more-info": "", + "more-info": "Более подробную информацию о настройке типа и версии Prometheus в источниках данных см. в <2>документации по подготовке.", "placeholder-example-maxsourceresolutionmtimeout": "Пример: {{example}}", "title-interval-behaviour": "Поведение интервалов", "title-other": "Прочее", diff --git a/packages/grafana-prometheus/src/locales/sv-SE/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/sv-SE/grafana-prometheus.json index 8132d631384..ee4865d5b93 100644 --- a/packages/grafana-prometheus/src/locales/sv-SE/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/sv-SE/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Skrapningsintervall", "label-series-limit": "Seriegräns", "label-use-series-endpoint": "Använd serieändpunkt", - "more-info": "", + "more-info": "För mer information om hur du konfigurerar prometheustyp och -version i datakällor, se <2>provisioneringsdokumentationen.", "placeholder-example-maxsourceresolutionmtimeout": "Exempel: {{example}}", "title-interval-behaviour": "Intervallbeteende", "title-other": "Annat", diff --git a/packages/grafana-prometheus/src/locales/tr-TR/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/tr-TR/grafana-prometheus.json index 4b918d3c61d..cd3e0b6505f 100644 --- a/packages/grafana-prometheus/src/locales/tr-TR/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/tr-TR/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "Kazıma aralığı", "label-series-limit": "Seri sınırı", "label-use-series-endpoint": "Seri uç noktasını kullan", - "more-info": "", + "more-info": "Prometheus türü ve sürümünü veri kaynaklarında yapılandırma hakkında daha fazla bilgi için <2>sağlama belgelerine bakın.", "placeholder-example-maxsourceresolutionmtimeout": "Örnek: {{example}}", "title-interval-behaviour": "Aralık davranışı", "title-other": "Diğer", diff --git a/packages/grafana-prometheus/src/locales/zh-Hans/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/zh-Hans/grafana-prometheus.json index b8f110e9226..d93eaac9cad 100644 --- a/packages/grafana-prometheus/src/locales/zh-Hans/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/zh-Hans/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "抓取间隔", "label-series-limit": "序列限制", "label-use-series-endpoint": "使用序列端点", - "more-info": "", + "more-info": "有关在数据源中配置 prometheus 类型和版本的更多信息,请参阅<2>预配文档。", "placeholder-example-maxsourceresolutionmtimeout": "示例:{{example}}", "title-interval-behaviour": "间隔行为", "title-other": "其他", diff --git a/packages/grafana-prometheus/src/locales/zh-Hant/grafana-prometheus.json b/packages/grafana-prometheus/src/locales/zh-Hant/grafana-prometheus.json index c9b5b8bdd99..3598db5f9f3 100644 --- a/packages/grafana-prometheus/src/locales/zh-Hant/grafana-prometheus.json +++ b/packages/grafana-prometheus/src/locales/zh-Hant/grafana-prometheus.json @@ -175,7 +175,7 @@ "label-scrape-interval": "抓取間隔", "label-series-limit": "序列限制", "label-use-series-endpoint": "使用序列端點", - "more-info": "", + "more-info": "如需更多在資料來源中設定 Prometheus 類型與版本的相關資訊,請參閱<2>佈建文件。", "placeholder-example-maxsourceresolutionmtimeout": "範例:{{example}}", "title-interval-behaviour": "間隔行為", "title-other": "其他", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 554eac97254..b184683aff2 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -48,7 +48,7 @@ "@types/react-virtualized-auto-sizer": "1.0.8", "@types/systemjs": "6.15.3", "@types/uuid": "10.0.0", - "i18next-cli": "1.11.12", + "i18next-cli": "^1.24.22", "jest": "^29.6.4", "ts-jest": "29.4.0", "ts-node": "10.9.2", diff --git a/packages/grafana-sql/src/locales/en-US/grafana-sql.json b/packages/grafana-sql/src/locales/en-US/grafana-sql.json index f2a06d8b983..f9572756abd 100644 --- a/packages/grafana-sql/src/locales/en-US/grafana-sql.json +++ b/packages/grafana-sql/src/locales/en-US/grafana-sql.json @@ -54,7 +54,7 @@ } }, "query-header": { - "content-invalid-query": "Your query is invalid. Check below for details.
However, you can still run this query.", + "content-invalid-query": "Your query is invalid. Check below for details.
However, you can still run this query.", "editor-modes": { "label-builder": "Builder", "label-code": "Code" diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index e822e4b6a62..32ba2addf5e 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -117,7 +117,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index 8ab04c12ddf..2f44039e35c 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -314,8 +314,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/pkg/api/api.go b/pkg/api/api.go index 918ec160b69..88f432c206c 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -473,8 +473,6 @@ func (hs *HTTPServer) registerRoutes() { }) }) - dashboardRoute.Post("/calculate-diff", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.CalculateDashboardDiff)) - dashboardRoute.Post("/db", authorize(ac.EvalAny(ac.EvalPermission(dashboards.ActionDashboardsCreate), ac.EvalPermission(dashboards.ActionDashboardsWrite))), routing.Wrap(hs.PostDashboard)) dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard)) dashboardRoute.Get("/tags", hs.GetDashboardTags) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 1383f007c49..9dfa7c95413 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -19,7 +19,6 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/components/dashdiffs" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -829,102 +828,6 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R return response.JSON(http.StatusOK, dashVersionMeta) } -// swagger:route POST /dashboards/calculate-diff dashboards calculateDashboardDiff -// -// Perform diff on two dashboards. -// -// Produces: -// - application/json -// - text/html -// -// Responses: -// 200: calculateDashboardDiffResponse -// 401: unauthorisedError -// 403: forbiddenError -// 500: internalServerError -func (hs *HTTPServer) CalculateDashboardDiff(c *contextmodel.ReqContext) response.Response { - ctx, span := tracer.Start(c.Req.Context(), "api.CalculateDashboardDiff") - defer span.End() - c.Req = c.Req.WithContext(ctx) - - apiOptions := dtos.CalculateDiffOptions{} - if err := web.Bind(c.Req, &apiOptions); err != nil { - return response.Error(http.StatusBadRequest, "bad request data", err) - } - - evaluator := accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScope(strconv.FormatInt(apiOptions.Base.DashboardId, 10))) - if canWrite, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator); err != nil || !canWrite { - return dashboardGuardianResponse(err) - } - - if apiOptions.Base.DashboardId != apiOptions.New.DashboardId { - evaluator = accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScope(strconv.FormatInt(apiOptions.New.DashboardId, 10))) - if canWrite, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator); err != nil || !canWrite { - return dashboardGuardianResponse(err) - } - } - - options := dashdiffs.Options{ - OrgId: c.GetOrgID(), - DiffType: dashdiffs.ParseDiffType(apiOptions.DiffType), - Base: dashdiffs.DiffTarget{ - DashboardId: apiOptions.Base.DashboardId, - Version: apiOptions.Base.Version, - UnsavedDashboard: apiOptions.Base.UnsavedDashboard, - }, - New: dashdiffs.DiffTarget{ - DashboardId: apiOptions.New.DashboardId, - Version: apiOptions.New.Version, - UnsavedDashboard: apiOptions.New.UnsavedDashboard, - }, - } - - baseVersionQuery := dashver.GetDashboardVersionQuery{ - DashboardID: options.Base.DashboardId, - Version: options.Base.Version, - OrgID: options.OrgId, - } - - baseVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &baseVersionQuery) - if err != nil { - if errors.Is(err, dashver.ErrDashboardVersionNotFound) { - return response.Error(http.StatusNotFound, "Dashboard version not found", err) - } - return response.Error(http.StatusInternalServerError, "Unable to compute diff", err) - } - - newVersionQuery := dashver.GetDashboardVersionQuery{ - DashboardID: options.New.DashboardId, - Version: options.New.Version, - OrgID: options.OrgId, - } - - newVersionRes, err := hs.dashboardVersionService.Get(c.Req.Context(), &newVersionQuery) - if err != nil { - if errors.Is(err, dashver.ErrDashboardVersionNotFound) { - return response.Error(http.StatusNotFound, "Dashboard version not found", err) - } - return response.Error(http.StatusInternalServerError, "Unable to compute diff", err) - } - - baseData := baseVersionRes.Data - newData := newVersionRes.Data - - result, err := dashdiffs.CalculateDiff(c.Req.Context(), &options, baseData, newData) - if err != nil { - if errors.Is(err, dashver.ErrDashboardVersionNotFound) { - return response.Error(http.StatusNotFound, "Dashboard version not found", err) - } - return response.Error(http.StatusInternalServerError, "Unable to compute diff", err) - } - - if options.DiffType == dashdiffs.DiffDelta { - return response.Respond(http.StatusOK, result.Delta).SetHeader("Content-Type", "application/json") - } - - return response.Respond(http.StatusOK, result.Delta).SetHeader("Content-Type", "text/html") -} - // swagger:route POST /dashboards/uid/{uid}/restore dashboards versions restoreDashboardVersionByUID // // Restore a dashboard to a given dashboard version using UID. diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 362be520230..eceebaacdd6 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -1,7 +1,6 @@ package api import ( - "bytes" "context" "encoding/json" "fmt" @@ -335,17 +334,6 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) { return server.Send(webtest.RequestWithSignedInUser(server.NewGetRequest("/api/dashboards/uid/1/versions"), userWithPermissions(1, permissions))) } - calculateDiff := func(server *webtest.Server, permissions []accesscontrol.Permission) (*http.Response, error) { - cmd := &dtos.CalculateDiffOptions{ - Base: dtos.CalculateDiffTarget{DashboardId: 1, Version: 1}, - New: dtos.CalculateDiffTarget{DashboardId: 1, Version: 2}, - DiffType: "json", - } - jsonBytes, err := json.Marshal(cmd) - require.NoError(t, err) - return server.SendJSON(webtest.RequestWithSignedInUser(server.NewPostRequest("/api/dashboards/calculate-diff", bytes.NewReader(jsonBytes)), userWithPermissions(1, permissions))) - } - t.Run("Should not be able to list dashboard versions without correct permission", func(t *testing.T) { server := setup() @@ -379,28 +367,6 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) { require.NoError(t, res.Body.Close()) }) - - t.Run("Should be able to diff dashboards with correct permissions", func(t *testing.T) { - server := setup() - - permissions := []accesscontrol.Permission{ - {Action: dashboards.ActionDashboardsWrite, Scope: dashboards.ScopeDashboardsAll}, - } - - res, err := calculateDiff(server, permissions) - require.NoError(t, err) - assert.Equal(t, http.StatusOK, res.StatusCode) - require.NoError(t, res.Body.Close()) - }) - - t.Run("Should not be able to diff dashboards without permissions", func(t *testing.T) { - server := setup() - - res, err := calculateDiff(server, []accesscontrol.Permission{}) - require.NoError(t, err) - assert.Equal(t, http.StatusForbidden, res.StatusCode) - require.NoError(t, res.Body.Close()) - }) } func TestIntegrationDashboardAPIEndpoint(t *testing.T) { diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index df34d602434..fdcf1562f1f 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -43,7 +43,7 @@ require ( go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index f5123c3038a..3b59ab0b8b9 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -96,8 +96,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 6947fb20b67..4a3a9d7a84b 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -83,7 +83,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/sync v0.18.0 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 49b4ab91e93..52c046f42a0 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -207,8 +207,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index e546d23b95b..c1ba032196c 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -238,6 +238,7 @@ type ReleaseState string const ( ReleaseStateAlpha ReleaseState = "alpha" + ReleaseStateBeta ReleaseState = "beta" ) type SignatureType string diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index c60a20cee8d..877b00ae2e4 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -99,6 +99,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index f8c29e54262..066b92e7746 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -321,8 +321,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index dde82737e23..9782ce6326d 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -23,7 +23,7 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - unisearch "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" ) type DashboardSearchClient struct { @@ -37,11 +37,11 @@ func NewDashboardSearchClient(dashboardStore dashboards.Store, sorter sort.Servi } var sortByMapping = map[string]string{ - unisearch.DASHBOARD_VIEWS_LAST_30_DAYS: "viewed-recently", - unisearch.DASHBOARD_VIEWS_TOTAL: "viewed", - unisearch.DASHBOARD_ERRORS_LAST_30_DAYS: "errors-recently", - unisearch.DASHBOARD_ERRORS_TOTAL: "errors", - "title": "alpha", + builders.DASHBOARD_VIEWS_LAST_30_DAYS: "viewed-recently", + builders.DASHBOARD_VIEWS_TOTAL: "viewed", + builders.DASHBOARD_ERRORS_LAST_30_DAYS: "errors-recently", + builders.DASHBOARD_ERRORS_TOTAL: "errors", + "title": "alpha", } func ParseSortName(sortName string) (string, bool, error) { @@ -231,7 +231,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso } query.ManagerIdentity = vals[0] - case unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE: + case builders.DASHBOARD_LIBRARY_PANEL_REFERENCE: if len(vals) != 1 { return nil, apierrors.NewBadRequest("only one library panel uid is supported") } diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go index 8f1974bee68..cb7ead459f6 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go @@ -19,7 +19,7 @@ import ( "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - unisearch "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" ) func TestDashboardSearchClient_Search(t *testing.T) { @@ -130,7 +130,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { }, SortBy: []*resourcepb.ResourceSearchRequest_Sort{ { - Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL, // "fields." prefix should be removed + Field: resource.SEARCH_FIELD_PREFIX + builders.DASHBOARD_VIEWS_TOTAL, // "fields." prefix should be removed Desc: false, }, }, @@ -195,7 +195,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { }, SortBy: []*resourcepb.ResourceSearchRequest_Sort{ { - Field: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS, + Field: builders.DASHBOARD_ERRORS_LAST_30_DAYS, Desc: true, }, }, @@ -560,7 +560,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { }, SortBy: []*resourcepb.ResourceSearchRequest_Sort{ { - Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL, + Field: resource.SEARCH_FIELD_PREFIX + builders.DASHBOARD_VIEWS_TOTAL, }, }, } @@ -591,7 +591,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Key: dashboardKey, Fields: []*resourcepb.Requirement{ { - Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE, + Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE, Operator: "=", Values: []string{"test-library-panel"}, }, @@ -633,7 +633,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Key: dashboardKey, Fields: []*resourcepb.Requirement{ { - Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE, + Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE, Operator: "=", Values: []string{"panel1", "panel2"}, }, @@ -652,7 +652,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Key: dashboardKey, Fields: []*resourcepb.Requirement{ { - Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE, + Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE, Operator: "=", Values: []string{"test-library-panel"}, }, @@ -679,7 +679,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Key: dashboardKey, Fields: []*resourcepb.Requirement{ { - Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE, + Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE, Operator: "=", Values: []string{"unused-library-panel"}, }, @@ -713,28 +713,28 @@ func TestParseSortName(t *testing.T) { { name: "viewed-recently with desc suffix", sortName: "viewed-recently-desc", - wantField: unisearch.DASHBOARD_VIEWS_LAST_30_DAYS, + wantField: builders.DASHBOARD_VIEWS_LAST_30_DAYS, wantDesc: true, wantErr: false, }, { name: "defaults to desc", sortName: "viewed", - wantField: unisearch.DASHBOARD_VIEWS_TOTAL, + wantField: builders.DASHBOARD_VIEWS_TOTAL, wantDesc: true, wantErr: false, }, { name: "errors-recentlyy with asc suffix", sortName: "errors-recently-asc", - wantField: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS, + wantField: builders.DASHBOARD_ERRORS_LAST_30_DAYS, wantDesc: false, wantErr: false, }, { name: "errors - defaults to desc too", sortName: "errors", - wantField: unisearch.DASHBOARD_ERRORS_TOTAL, + wantField: builders.DASHBOARD_ERRORS_TOTAL, wantDesc: true, wantErr: false, }, diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 0f1b83a7e18..6d85a66b344 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -32,7 +32,7 @@ import ( "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" "github.com/grafana/grafana/pkg/util/errhttp" ) @@ -406,7 +406,7 @@ func convertHttpSearchRequestToResourceSearchRequest(queryParams url.Values, use // Add sorting if queryParams.Has("sort") { for _, sort := range queryParams["sort"] { - if slices.Contains(search.DashboardFields(), sort) { + if slices.Contains(builders.DashboardFields(), sort) { sort = resource.SEARCH_FIELD_PREFIX + sort } s := &resourcepb.ResourceSearchRequest_Sort{Field: sort} @@ -441,7 +441,7 @@ func convertHttpSearchRequestToResourceSearchRequest(queryParams url.Values, use // The libraryPanel filter if libraryPanel, ok := queryParams["libraryPanel"]; ok { searchRequest.Options.Fields = append(searchRequest.Options.Fields, &resourcepb.Requirement{ - Key: search.DASHBOARD_LIBRARY_PANEL_REFERENCE, + Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE, Operator: "=", Values: libraryPanel, }) diff --git a/pkg/registry/apis/iam/externalgroupmapping/models.go b/pkg/registry/apis/iam/externalgroupmapping/models.go new file mode 100644 index 00000000000..4a8f2e5df08 --- /dev/null +++ b/pkg/registry/apis/iam/externalgroupmapping/models.go @@ -0,0 +1,10 @@ +package externalgroupmapping + +import "k8s.io/apiserver/pkg/registry/rest" + +type TeamGroupsHandler interface { + rest.Storage + rest.Scoper + rest.StorageMetadata + rest.Connecter +} diff --git a/pkg/registry/apis/iam/externalgroupmapping/team_groups_noop.go b/pkg/registry/apis/iam/externalgroupmapping/team_groups_noop.go new file mode 100644 index 00000000000..9333c50f666 --- /dev/null +++ b/pkg/registry/apis/iam/externalgroupmapping/team_groups_noop.go @@ -0,0 +1,58 @@ +package externalgroupmapping + +import ( + "context" + "net/http" + + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ TeamGroupsHandler = (*NoopTeamGroupsREST)(nil) + +type NoopTeamGroupsREST struct{} + +func ProvideNoopTeamGroupsREST() *NoopTeamGroupsREST { + return &NoopTeamGroupsREST{} +} + +// Connect implements rest.Connecter. +func (n *NoopTeamGroupsREST) Connect(ctx context.Context, id string, options runtime.Object, r rest.Responder) (http.Handler, error) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "functionality not available", http.StatusForbidden) + }), nil +} + +// New implements rest.Storage. +func (s *NoopTeamGroupsREST) New() runtime.Object { + return iamv0alpha1.NewGetGroups() +} + +// Destroy implements rest.Storage. +func (s *NoopTeamGroupsREST) Destroy() {} + +// NamespaceScoped implements rest.Scoper. +func (s *NoopTeamGroupsREST) NamespaceScoped() bool { + return true +} + +// ProducesMIMETypes implements rest.StorageMetadata. +func (s *NoopTeamGroupsREST) ProducesMIMETypes(verb string) []string { + return []string{"application/json"} +} + +// ProducesObject implements rest.StorageMetadata. +func (s *NoopTeamGroupsREST) ProducesObject(verb string) interface{} { + return s.New() +} + +// NewConnectOptions implements rest.Connecter. +func (s *NoopTeamGroupsREST) NewConnectOptions() (runtime.Object, bool, string) { + return nil, false, "" +} + +// ConnectMethods implements rest.Connecter. +func (s *NoopTeamGroupsREST) ConnectMethods() []string { + return []string{http.MethodGet} +} diff --git a/pkg/registry/apis/iam/models.go b/pkg/registry/apis/iam/models.go index 309b266ccb4..35efb2f2155 100644 --- a/pkg/registry/apis/iam/models.go +++ b/pkg/registry/apis/iam/models.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/registry/apis/iam/externalgroupmapping" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount" "github.com/grafana/grafana/pkg/registry/apis/iam/sso" @@ -80,6 +81,8 @@ type IdentityAccessManagementAPIBuilder struct { unified resource.ResourceClient userSearchClient resourcepb.ResourceIndexClient + teamGroupsHandler externalgroupmapping.TeamGroupsHandler + // non-k8s api route display *user.LegacyDisplayREST diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 2f214fd0630..a464568c12f 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -65,6 +65,7 @@ func RegisterAPIService( rolesStorage RoleStorageBackend, roleBindingsStorage RoleBindingStorageBackend, externalGroupMappingStorageBackend ExternalGroupMappingStorageBackend, + teamGroupsHandlerImpl externalgroupmapping.TeamGroupsHandler, dual dualwrite.Service, unified resource.ResourceClient, userService legacyuser.Service, @@ -90,6 +91,7 @@ func RegisterAPIService( resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider), roleBindingsStorage: roleBindingsStorage, externalGroupMappingStorage: externalGroupMappingStorageBackend, + teamGroupsHandler: teamGroupsHandlerImpl, sso: ssoService, authorizer: authorizer, legacyAccessClient: legacyAccessClient, @@ -102,7 +104,8 @@ func RegisterAPIService( features: features, dual: dual, unified: unified, - userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(), unified, user.NewUserLegacySearchClient(userService), features), + userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(), + unified, user.NewUserLegacySearchClient(userService), features), } apiregistration.RegisterAPI(builder) @@ -216,6 +219,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge } storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store) + storage[teamResource.StoragePath("groups")] = b.teamGroupsHandler teamBindingResource := iamv0.TeamBindingResourceInfo teamBindingUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamBindingResource, opts.OptsGetter) diff --git a/pkg/registry/apis/iam/user/legacy_search.go b/pkg/registry/apis/iam/user/legacy_search.go index 759dc7f55a7..534a4f016eb 100644 --- a/pkg/registry/apis/iam/user/legacy_search.go +++ b/pkg/registry/apis/iam/user/legacy_search.go @@ -12,7 +12,7 @@ import ( "github.com/grafana/grafana/pkg/services/user" res "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" ) const ( @@ -20,6 +20,8 @@ const ( UserResourceGroup = "iam.grafana.com" ) +var _ resourcepb.ResourceIndexClient = (*UserLegacySearchClient)(nil) + // UserLegacySearchClient is a client for searching for users in the legacy search engine. type UserLegacySearchClient struct { resourcepb.ResourceIndexClient @@ -129,9 +131,9 @@ func getColumns(fields []string) []*resourcepb.ResourceTableColumnDefinition { for _, field := range fields { switch field { case "email": - columns = append(columns, search.TableColumnDefinitions[search.USER_EMAIL]) + columns = append(columns, builders.UserTableColumnDefinitions[builders.USER_EMAIL]) case "login": - columns = append(columns, search.TableColumnDefinitions[search.USER_LOGIN]) + columns = append(columns, builders.UserTableColumnDefinitions[builders.USER_LOGIN]) } } return columns diff --git a/pkg/registry/apis/ofrep/register.go b/pkg/registry/apis/ofrep/register.go index cd6f8f0eb29..d6a443cfee7 100644 --- a/pkg/registry/apis/ofrep/register.go +++ b/pkg/registry/apis/ofrep/register.go @@ -64,10 +64,6 @@ func NewAPIBuilder(providerType string, url *url.URL, insecure bool, caFile stri } func RegisterAPIService(apiregistration builder.APIRegistrar, cfg *setting.Cfg) (*APIBuilder, error) { - if !cfg.OpenFeature.APIEnabled { - return nil, nil - } - var staticEvaluator featuremgmt.StaticFlagEvaluator // No static evaluator needed for non-static provider var err error if cfg.OpenFeature.ProviderType == setting.StaticProviderType { diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index 5d76f64dfcd..d19b405c6c9 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/datasource" "github.com/grafana/grafana/pkg/registry/apis/folders" "github.com/grafana/grafana/pkg/registry/apis/iam" + "github.com/grafana/grafana/pkg/registry/apis/iam/externalgroupmapping" "github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage" "github.com/grafana/grafana/pkg/registry/apis/ofrep" "github.com/grafana/grafana/pkg/registry/apis/preferences" @@ -30,6 +31,9 @@ var WireSetExts = wire.NewSet( wire.Bind(new(iam.RoleStorageBackend), new(*noopstorage.StorageBackendImpl)), wire.Bind(new(iam.RoleBindingStorageBackend), new(*noopstorage.StorageBackendImpl)), wire.Bind(new(iam.ExternalGroupMappingStorageBackend), new(*noopstorage.StorageBackendImpl)), + + externalgroupmapping.ProvideNoopTeamGroupsREST, + wire.Bind(new(externalgroupmapping.TeamGroupsHandler), new(*externalgroupmapping.NoopTeamGroupsREST)), ) var provisioningExtras = wire.NewSet( diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index 32afa56be46..983605dedbc 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -33,7 +33,7 @@ import ( func ProvideAppInstallers( features featuremgmt.FeatureToggles, playlistAppInstaller *playlist.PlaylistAppInstaller, - pluginsApplInstaller *plugins.PluginsAppInstaller, + pluginsApplInstaller *plugins.AppInstaller, shorturlAppInstaller *shorturl.ShortURLAppInstaller, rulesAppInstaller *rules.AlertingRulesAppInstaller, correlationsAppInstaller *correlations.AppInstaller, diff --git a/pkg/registry/apps/apps_test.go b/pkg/registry/apps/apps_test.go index 5f46fd349be..b7f6c2565e6 100644 --- a/pkg/registry/apps/apps_test.go +++ b/pkg/registry/apps/apps_test.go @@ -18,7 +18,7 @@ import ( func TestProvideAppInstallers_Table(t *testing.T) { playlistInstaller := &playlist.PlaylistAppInstaller{} - pluginsInstaller := &plugins.PluginsAppInstaller{} + pluginsInstaller := &plugins.AppInstaller{} rulesInstaller := &rules.AlertingRulesAppInstaller{} correlationsAppInstaller := &correlations.AppInstaller{} notificationsAppInstaller := ¬ifications.AlertingNotificationsAppInstaller{} diff --git a/pkg/registry/apps/plugins/register.go b/pkg/registry/apps/plugins/register.go index e95b120cd66..bcfb6561ee8 100644 --- a/pkg/registry/apps/plugins/register.go +++ b/pkg/registry/apps/plugins/register.go @@ -1,34 +1,59 @@ package plugins import ( + "context" + "fmt" + "os" + + "github.com/grafana/grafana-app-sdk/k8s" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/simple" + pluginsappapis "github.com/grafana/grafana/apps/plugins/pkg/apis" + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" pluginsapp "github.com/grafana/grafana/apps/plugins/pkg/app" + "github.com/grafana/grafana/apps/plugins/pkg/app/meta" + "github.com/grafana/grafana/pkg/configprovider" + "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" ) var ( - _ appsdkapiserver.AppInstaller = (*PluginsAppInstaller)(nil) - _ appinstaller.AuthorizerProvider = (*PluginsAppInstaller)(nil) + _ appsdkapiserver.AppInstaller = (*AppInstaller)(nil) + _ appinstaller.AuthorizerProvider = (*AppInstaller)(nil) ) -type PluginsAppInstaller struct { +type AppInstaller struct { + metaManager *meta.ProviderManager + cfgProvider configprovider.ConfigProvider + restConfigProvider apiserver.RestConfigProvider + appsdkapiserver.AppInstaller } func RegisterAppInstaller( - cfg *setting.Cfg, - features featuremgmt.FeatureToggles, -) (*PluginsAppInstaller, error) { - installer := &PluginsAppInstaller{} - specificConfig := any(nil) + cfgProvider configprovider.ConfigProvider, + restConfigProvider apiserver.RestConfigProvider, +) (*AppInstaller, error) { + grafanaComAPIURL := os.Getenv("GRAFANA_COM_API_URL") + if grafanaComAPIURL == "" { + grafanaComAPIURL = "https://grafana.com/api/plugins" + } + + coreProvider := meta.NewCoreProvider() + cloudProvider := meta.NewCloudProvider(grafanaComAPIURL) + metaProviderManager := meta.NewProviderManager(coreProvider, cloudProvider) + specificConfig := &pluginsapp.PluginAppConfig{ + MetaProviderManager: metaProviderManager, + } provider := simple.NewAppProvider(pluginsappapis.LocalManifest(), specificConfig, pluginsapp.New) appConfig := app.Config{ KubeConfig: restclient.Config{}, // this will be overridden by the installer's InitializeApp method @@ -39,11 +64,54 @@ func RegisterAppInstaller( if err != nil { return nil, err } - installer.AppInstaller = i - return installer, nil + + return &AppInstaller{ + metaManager: metaProviderManager, + cfgProvider: cfgProvider, + restConfigProvider: restConfigProvider, + AppInstaller: i, + }, nil +} + +func (p *AppInstaller) InstallAPIs( + server appsdkapiserver.GenericAPIServer, + restOptsGetter generic.RESTOptionsGetter, +) error { + ctx := context.Background() + cfg, err := p.cfgProvider.Get(ctx) + if err != nil { + return err + } + + // Create a client factory function that will be called lazily when the client is needed. + // This avoids deadlock issues since the restConfigProvider/API server will not be ready during API installation. + clientFactory := func(ctx context.Context) (*pluginsv0alpha1.PluginClient, error) { + kubeConfig, err := p.restConfigProvider.GetRestConfig(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get rest config: %w", err) + } + + clientGenerator := k8s.NewClientRegistry(*kubeConfig, k8s.DefaultClientConfig()) + client, err := pluginsv0alpha1.NewPluginClientFromGenerator(clientGenerator) + if err != nil { + return nil, fmt.Errorf("failed to create plugin client: %w", err) + } + + return client, nil + } + + pluginMetaGVR := pluginsv0alpha1.PluginMetaKind().GroupVersionResource() + replacedStorage := map[schema.GroupVersionResource]rest.Storage{ + pluginMetaGVR: pluginsapp.NewPluginMetaStorage(p.metaManager, clientFactory, request.GetNamespaceMapper(cfg)), + } + wrappedServer := &customStorageWrapper{ + wrapped: server, + replace: replacedStorage, + } + return p.AppInstaller.InstallAPIs(wrappedServer, restOptsGetter) } // GetAuthorizer returns the authorizer for the plugins app. -func (p *PluginsAppInstaller) GetAuthorizer() authorizer.Authorizer { +func (p *AppInstaller) GetAuthorizer() authorizer.Authorizer { return pluginsapp.GetAuthorizer() } diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index eb448ba41a8..279a67f4134 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -55,6 +55,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/datasource" "github.com/grafana/grafana/pkg/registry/apis/folders" "github.com/grafana/grafana/pkg/registry/apis/iam" + "github.com/grafana/grafana/pkg/registry/apis/iam/externalgroupmapping" "github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage" "github.com/grafana/grafana/pkg/registry/apis/ofrep" "github.com/grafana/grafana/pkg/registry/apis/preferences" @@ -265,6 +266,7 @@ import ( migrations2 "github.com/grafana/grafana/pkg/storage/unified/migrations" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" "github.com/grafana/grafana/pkg/storage/unified/sql" "github.com/grafana/grafana/pkg/tsdb/azuremonitor" "github.com/grafana/grafana/pkg/tsdb/cloud-monitoring" @@ -455,7 +457,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - ossDashboardStats := search.ProvideDashboardStats() + ossDashboardStats := builders.ProvideDashboardStats() documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats) databaseDatabase := database4.ProvideDatabase(sqlStore, tracer) clockClock := clock.ProvideClock() @@ -786,7 +788,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - pluginsAppInstaller, err := plugins.RegisterAppInstaller(cfg, featureToggles) + appInstaller, err := plugins.RegisterAppInstaller(configProvider, eventualRestConfigProvider) if err != nil { return nil, err } @@ -798,7 +800,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - appInstaller, err := correlations2.RegisterAppInstaller(cfg, featureToggles, correlationsService) + correlationsAppInstaller, err := correlations2.RegisterAppInstaller(cfg, featureToggles, correlationsService) if err != nil { return nil, err } @@ -823,7 +825,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, pluginsAppInstaller, shortURLAppInstaller, alertingRulesAppInstaller, appInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller) + v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller) builderMetrics := builder.ProvideBuilderMetrics(registerer) apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) if err != nil { @@ -867,7 +869,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient) storageBackendImpl := noopstorage.ProvideStorageBackend() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl, storageBackendImpl, dualwriteService, resourceClient, userService) + noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST() + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, userService) if err != nil { return nil, err } @@ -1102,7 +1105,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - ossDashboardStats := search.ProvideDashboardStats() + ossDashboardStats := builders.ProvideDashboardStats() documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats) databaseDatabase := database4.ProvideDatabase(sqlStore, tracer) clockClock := clock.ProvideClock() @@ -1435,7 +1438,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - pluginsAppInstaller, err := plugins.RegisterAppInstaller(cfg, featureToggles) + appInstaller, err := plugins.RegisterAppInstaller(configProvider, eventualRestConfigProvider) if err != nil { return nil, err } @@ -1447,7 +1450,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - appInstaller, err := correlations2.RegisterAppInstaller(cfg, featureToggles, correlationsService) + correlationsAppInstaller, err := correlations2.RegisterAppInstaller(cfg, featureToggles, correlationsService) if err != nil { return nil, err } @@ -1472,7 +1475,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, pluginsAppInstaller, shortURLAppInstaller, alertingRulesAppInstaller, appInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller) + v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller) builderMetrics := builder.ProvideBuilderMetrics(registerer) apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) if err != nil { @@ -1516,7 +1519,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient) storageBackendImpl := noopstorage.ProvideStorageBackend() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl, storageBackendImpl, dualwriteService, resourceClient, userService) + noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST() + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, userService) if err != nil { return nil, err } @@ -1757,7 +1761,7 @@ func InitializeDocumentBuilders(cfg *setting.Cfg) (resource.DocumentBuilderSuppl if err != nil { return nil, err } - ossDashboardStats := search.ProvideDashboardStats() + ossDashboardStats := builders.ProvideDashboardStats() documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats) return documentBuilderSupplier, nil } diff --git a/pkg/server/wireexts_oss.go b/pkg/server/wireexts_oss.go index 9f069f807fc..4d70c3f2f4c 100644 --- a/pkg/server/wireexts_oss.go +++ b/pkg/server/wireexts_oss.go @@ -66,6 +66,7 @@ import ( "github.com/grafana/grafana/pkg/storage/unified" "github.com/grafana/grafana/pkg/storage/unified/resource" search2 "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" "github.com/grafana/grafana/pkg/storage/unified/sql" ) @@ -138,8 +139,8 @@ var wireExtsBasicSet = wire.NewSet( wire.Bind(new(auth.IDSigner), new(*idimpl.LocalSigner)), manager.ProvideInstaller, wire.Bind(new(plugins.Installer), new(*manager.PluginInstaller)), - search2.ProvideDashboardStats, - wire.Bind(new(search2.DashboardStats), new(*search2.OssDashboardStats)), + builders.ProvideDashboardStats, + wire.Bind(new(builders.DashboardStats), new(*builders.OssDashboardStats)), search2.ProvideDocumentBuilders, sandbox.ProvideService, wire.Bind(new(sandbox.Sandbox), new(*sandbox.Service)), diff --git a/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go b/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go index 348aa38d477..66d447e8bf9 100644 --- a/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go +++ b/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go @@ -687,7 +687,7 @@ func createAlertRule(t *testing.T, sql *sqlstore.SQLStore, title string, generat rule.PanelID = nil ruleStore := store.SetupStoreForTesting(t, sql) - ids, err := ruleStore.InsertAlertRules(context.Background(), nil, []ngmodels.AlertRule{rule}) + ids, err := ruleStore.InsertAlertRules(context.Background(), nil, []ngmodels.InsertRule{{AlertRule: rule}}) require.NoError(t, err) result, err := ruleStore.GetAlertRuleByUID(context.Background(), &ngmodels.GetAlertRuleByUIDQuery{OrgID: rule.OrgID, UID: ids[0].UID}) require.NoError(t, err) @@ -719,7 +719,7 @@ func createAlertRuleFromDashboard(t *testing.T, sql *sqlstore.SQLStore, title st rule.PanelID = panelID } ruleStore := store.SetupStoreForTesting(t, sql) - ids, err := ruleStore.InsertAlertRules(context.Background(), nil, []ngmodels.AlertRule{rule}) + ids, err := ruleStore.InsertAlertRules(context.Background(), nil, []ngmodels.InsertRule{{AlertRule: rule}}) require.NoError(t, err) result, err := ruleStore.GetAlertRuleByUID(context.Background(), &ngmodels.GetAlertRuleByUIDQuery{OrgID: rule.OrgID, UID: ids[0].UID}) require.NoError(t, err) diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index 603c49f3cb2..ae90295c01d 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -22,6 +22,7 @@ import ( k8srequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" "k8s.io/apiserver/pkg/util/openapi" k8sscheme "k8s.io/client-go/kubernetes/scheme" k8stracing "k8s.io/component-base/tracing" @@ -72,13 +73,36 @@ var PathRewriters = []filters.PathRewriter{ }, } +// GetDefaultBuildHandlerChainFuncForAggregator is a replica of GetDefaultBuildHandlerChainFunc except it skips custom routes handling +func GetDefaultBuildHandlerChainFuncForAggregator() BuildHandlerChainFunc { + return func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler { + // filters.WithRequester needs to be after the K8s chain because it depends on the K8s user in context + handler := filters.WithRequester(delegateHandler) + + // Call DefaultBuildHandlerChain on the main entrypoint http.Handler + // See https://github.com/kubernetes/apiserver/blob/v0.28.0/pkg/server/config.go#L906 + // DefaultBuildHandlerChain provides many things, notably CORS, HSTS, cache-control, authz and latency tracking + handler = genericapiserver.DefaultBuildHandlerChain(handler, c) + + handler = filters.WithAcceptHeader(handler) + handler = filters.WithPathRewriters(handler, PathRewriters) + handler = k8stracing.WithTracing(handler, c.TracerProvider, "KubernetesAPI") + handler = filters.WithExtractJaegerTrace(handler) + // Configure filters.WithPanicRecovery to not crash on panic + utilruntime.ReallyCrash = false + + return handler + } +} func GetDefaultBuildHandlerChainFunc(builders []APIGroupBuilder, reg prometheus.Registerer) BuildHandlerChainFunc { return func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler { requestHandler, err := GetCustomRoutesHandler( delegateHandler, c.LoopbackClientConfig, builders, - reg) + reg, + c.MergedResourceConfig, + ) if err != nil { panic(fmt.Sprintf("could not build the request handler for specified API builders: %s", err.Error())) } @@ -105,6 +129,8 @@ func GetDefaultBuildHandlerChainFunc(builders []APIGroupBuilder, reg prometheus. } } +// SetupConfig sets up the server config for the API server +// specify isAggregator=true, if the chain is being constructed for kube-aggregator func SetupConfig( scheme *runtime.Scheme, serverConfig *genericapiserver.RecommendedConfig, @@ -114,6 +140,7 @@ func SetupConfig( gvs []schema.GroupVersion, additionalOpenAPIDefGetters []common.GetOpenAPIDefinitions, reg prometheus.Registerer, + apiResourceConfig *serverstorage.ResourceConfig, ) error { serverConfig.AdmissionControl = NewAdmissionFromBuilders(builders) defsGetter := GetOpenAPIDefinitions(builders, additionalOpenAPIDefGetters...) @@ -126,7 +153,7 @@ func SetupConfig( openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme)) // Add the custom routes to service discovery - serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders, gvs) + serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders, gvs, apiResourceConfig) serverConfig.OpenAPIV3Config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) { meta := r.Metadata() kind := "" @@ -287,6 +314,7 @@ func InstallAPIs( features featuremgmt.FeatureToggles, dualWriterMetrics *grafanarest.DualWriterMetrics, builderMetrics *BuilderMetrics, + apiResourceConfig *serverstorage.ResourceConfig, ) error { // dual writing is only enabled when the storage type is not legacy. // this is needed to support setting a default RESTOptionsGetter for new APIs that don't @@ -401,34 +429,9 @@ func InstallAPIs( for group, buildersForGroup := range buildersGroupMap { g := genericapiserver.NewDefaultAPIGroupInfo(group, scheme, metav1.ParameterCodec, codecs) for _, b := range buildersForGroup { - if err := b.UpdateAPIGroupInfo(&g, APIGroupOptions{ - Scheme: scheme, - OptsGetter: optsGetter, - DualWriteBuilder: dualWrite, - MetricsRegister: reg, - StorageOptsRegister: optsregister, - StorageOpts: storageOpts, - }); err != nil { + if err := installAPIGroupsForBuilder(&g, group, b, apiResourceConfig, scheme, optsGetter, dualWrite, reg, optsregister, storageOpts, features); err != nil { return err } - if len(g.PrioritizedVersions) < 1 { - continue - } - - // if grafanaAPIServerWithExperimentalAPIs is not enabled, remove v0alpha1 resources unless explicitly allowed - //nolint:staticcheck // not yet migrated to OpenFeature - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { - if resources, ok := g.VersionedResourcesStorageMap["v0alpha1"]; ok { - for name := range resources { - if !allowRegisteringResourceByInfo(b.AllowedV0Alpha1Resources(), name) { - delete(resources, name) - } - } - if len(resources) == 0 { - delete(g.VersionedResourcesStorageMap, "v0alpha1") - } - } - } } // skip installing the group if there are no resources left after filtering @@ -445,6 +448,53 @@ func InstallAPIs( return nil } +func installAPIGroupsForBuilder(g *genericapiserver.APIGroupInfo, group string, b APIGroupBuilder, apiResourceConfig *serverstorage.ResourceConfig, scheme *runtime.Scheme, + optsGetter generic.RESTOptionsGetter, dualWrite grafanarest.DualWriteBuilder, reg prometheus.Registerer, optsregister apistore.StorageOptionsRegister, + storageOpts *options.StorageOptions, features featuremgmt.FeatureToggles) error { + if err := b.UpdateAPIGroupInfo(g, APIGroupOptions{ + Scheme: scheme, + OptsGetter: optsGetter, + DualWriteBuilder: dualWrite, + MetricsRegister: reg, + StorageOptsRegister: optsregister, + StorageOpts: storageOpts, + }); err != nil { + return err + } + if len(g.PrioritizedVersions) < 1 { + return nil + } + + // filter out api groups that are disabled in APIEnablementOptions + for version := range g.VersionedResourcesStorageMap { + gvr := schema.GroupVersionResource{ + Group: group, + Version: version, + } + if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) { + klog.InfoS("Skipping storage for disabled resource", "gvr", gvr.String()) + delete(g.VersionedResourcesStorageMap, version) + } + } + + // if grafanaAPIServerWithExperimentalAPIs is not enabled, remove v0alpha1 resources unless explicitly allowed + //nolint:staticcheck // not yet migrated to OpenFeature + if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { + if resources, ok := g.VersionedResourcesStorageMap["v0alpha1"]; ok { + for name := range resources { + if !allowRegisteringResourceByInfo(b.AllowedV0Alpha1Resources(), name) { + delete(resources, name) + } + } + if len(resources) == 0 { + delete(g.VersionedResourcesStorageMap, "v0alpha1") + } + } + } + + return nil +} + // AddPostStartHooks adds post start hooks to a generic API server config func AddPostStartHooks( config *genericapiserver.RecommendedConfig, diff --git a/pkg/services/apiserver/builder/openapi.go b/pkg/services/apiserver/builder/openapi.go index 9cb7cc17f7c..6d3b33f4baf 100644 --- a/pkg/services/apiserver/builder/openapi.go +++ b/pkg/services/apiserver/builder/openapi.go @@ -9,6 +9,8 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime/schema" + serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/klog/v2" openapi "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/spec3" spec "k8s.io/kube-openapi/pkg/validation/spec" @@ -76,6 +78,7 @@ func addBuilderRoutes( targetGroupVersion schema.GroupVersion, openAPISpec *spec3.OpenAPI, apiGroupBuilders []APIGroupBuilder, + apiResourceConfig *serverstorage.ResourceConfig, ) (*spec3.OpenAPI, error) { for _, apiGroupBuilder := range apiGroupBuilders { // Optionally include raw http handlers for all builders @@ -107,12 +110,24 @@ func addBuilderRoutes( } } } + + // filter out api groups that are disabled in APIEnablementOptions + for path := range openAPISpec.Paths.Paths { + if strings.HasPrefix(path, "/apis/"+targetGroupVersion.String()+"/") { + gv := targetGroupVersion.WithResource("") + if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gv) { + klog.InfoS("removing openapi routes for disabled resource", "gv", gv.String()) + delete(openAPISpec.Paths.Paths, path) + } + } + } + return openAPISpec, nil } // Modify the OpenAPI spec to include the additional routes. // nolint:gocyclo -func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []schema.GroupVersion) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) { +func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []schema.GroupVersion, apiResourceConfig *serverstorage.ResourceConfig) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) { return func(s *spec3.OpenAPI) (*spec3.OpenAPI, error) { if s.Paths == nil { return s, nil @@ -227,7 +242,7 @@ func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []s } } } - return addBuilderRoutes(gv, ©, builders) + return addBuilderRoutes(gv, ©, builders, apiResourceConfig) } } return s, nil diff --git a/pkg/services/apiserver/builder/request_handler.go b/pkg/services/apiserver/builder/request_handler.go index 80fbf6d0eaa..50761f1d42c 100644 --- a/pkg/services/apiserver/builder/request_handler.go +++ b/pkg/services/apiserver/builder/request_handler.go @@ -6,7 +6,9 @@ import ( "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" + serverstorage "k8s.io/apiserver/pkg/server/storage" restclient "k8s.io/client-go/rest" + klog "k8s.io/klog/v2" "k8s.io/kube-openapi/pkg/spec3" ) @@ -14,7 +16,7 @@ type requestHandler struct { router *mux.Router } -func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder, metricsRegistry prometheus.Registerer) (http.Handler, error) { +func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder, metricsRegistry prometheus.Registerer, apiResourceConfig *serverstorage.ResourceConfig) (http.Handler, error) { useful := false // only true if any routes exist anywhere router := mux.NewRouter() @@ -27,6 +29,12 @@ func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient } for _, gv := range GetGroupVersions(builder) { + // filter out api groups that are disabled in APIEnablementOptions + gvr := gv.WithResource("") + if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) { + klog.InfoS("Skipping custom route handler for disabled group version", "gv", gv.String()) + continue + } routes := provider.GetAPIRoutes(gv) if routes == nil { continue diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 28da416536c..605016c112d 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -316,7 +316,11 @@ func (s *service) start(ctx context.Context) error { s.cfg.BuildBranch, ) - if err := o.APIEnablementOptions.ApplyTo(&serverConfig.Config, appinstaller.NewAPIResourceConfig(s.appInstallers), s.scheme); err != nil { + apiResourceConfig := appinstaller.NewAPIResourceConfig(s.appInstallers) + // add the builder group versions to the api resource config + apiResourceConfig.EnableVersions(groupVersions...) + + if err := o.APIEnablementOptions.ApplyTo(&serverConfig.Config, apiResourceConfig, s.scheme); err != nil { return err } @@ -359,6 +363,7 @@ func (s *service) start(ctx context.Context) error { groupVersions, defGetters, s.metrics, + apiResourceConfig, ) if err != nil { return err @@ -400,6 +405,7 @@ func (s *service) start(ctx context.Context) error { s.features, s.dualWriterMetrics, s.builderMetrics, + apiResourceConfig, ) if err != nil { return err diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go index ee2c9889c20..eaa95962a23 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go @@ -457,7 +457,7 @@ func createAlertRuleGroup(t *testing.T, ctx context.Context, service *Service, u Rules: rules, } - err := service.ngAlert.Api.AlertRules.ReplaceRuleGroup(ctx, user, group, "") + err := service.ngAlert.Api.AlertRules.ReplaceRuleGroup(ctx, user, group, "", "") require.NoError(t, err) return group diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index ebf3f24beb6..b63b4a40f96 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -57,7 +57,7 @@ import ( "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/retryer" ) @@ -479,7 +479,7 @@ func (dr *DashboardServiceImpl) GetDashboardsByLibraryPanelUID(ctx context.Conte Options: &resourcepb.ListOptions{ Fields: []*resourcepb.Requirement{ { - Key: search.DASHBOARD_LIBRARY_PANEL_REFERENCE, + Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE, Operator: string(selection.Equals), Values: []string{libraryPanelUID}, }, diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 7aa0e07762c..5725cdf193b 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -46,7 +46,7 @@ import ( "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util/testutil" ) @@ -2654,7 +2654,7 @@ func TestGetDashboardsByLibraryPanelUID(t *testing.T) { k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool { return len(req.Options.Fields) == 1 && - req.Options.Fields[0].Key == search.DASHBOARD_LIBRARY_PANEL_REFERENCE && + req.Options.Fields[0].Key == builders.DASHBOARD_LIBRARY_PANEL_REFERENCE && req.Options.Fields[0].Values[0] == "test-library-panel" })).Return(searchResponse, nil).Once() diff --git a/pkg/services/dashboards/service/search/search_test.go b/pkg/services/dashboards/service/search/search_test.go index 11279799658..0683cbcb55b 100644 --- a/pkg/services/dashboards/service/search/search_test.go +++ b/pkg/services/dashboards/service/search/search_test.go @@ -7,7 +7,7 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" ) // regression test - parsing int32 values from search results was causing a panic @@ -25,11 +25,11 @@ func TestParseResults(t *testing.T) { Type: resourcepb.ResourceTableColumnDefinition_STRING, }, { - Name: search.DASHBOARD_ERRORS_LAST_1_DAYS, + Name: builders.DASHBOARD_ERRORS_LAST_1_DAYS, Type: resourcepb.ResourceTableColumnDefinition_INT64, }, { - Name: search.DASHBOARD_LINK_COUNT, + Name: builders.DASHBOARD_LINK_COUNT, Type: resourcepb.ResourceTableColumnDefinition_INT32, }, { @@ -75,11 +75,11 @@ func TestParseResults(t *testing.T) { Type: resourcepb.ResourceTableColumnDefinition_STRING, }, { - Name: search.DASHBOARD_ERRORS_LAST_1_DAYS, + Name: builders.DASHBOARD_ERRORS_LAST_1_DAYS, Type: resourcepb.ResourceTableColumnDefinition_INT64, }, { - Name: search.DASHBOARD_LINK_COUNT, + Name: builders.DASHBOARD_LINK_COUNT, Type: resourcepb.ResourceTableColumnDefinition_INT32, }, { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 24e057cd882..65f94c24eac 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1589,6 +1589,13 @@ var ( Owner: identityAccessTeam, HideFromDocs: true, }, + { + Name: "kubernetesExternalGroupMapping", + Description: "Routes external group mapping requests from /api to the /apis endpoint", + Stage: FeatureStageExperimental, + Owner: identityAccessTeam, + HideFromDocs: true, + }, { Name: "restoreDashboards", Description: "Enables restore deleted dashboards feature", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index fd1fe6619f9..17fdf27f33d 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -218,6 +218,7 @@ kubernetesAuthZHandlerRedirect,experimental,@grafana/identity-access-team,false, kubernetesAuthzResourcePermissionApis,experimental,@grafana/identity-access-team,false,false,false kubernetesAuthzZanzanaSync,experimental,@grafana/identity-access-team,false,false,false kubernetesAuthnMutation,experimental,@grafana/identity-access-team,false,false,false +kubernetesExternalGroupMapping,experimental,@grafana/identity-access-team,false,false,false restoreDashboards,experimental,@grafana/grafana-search-navigate-organise,false,false,false alertEnrichment,experimental,@grafana/alerting-squad,false,false,false alertEnrichmentMultiStep,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index f68009ab2e3..b88204db708 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -638,6 +638,10 @@ const ( // Enables create, delete, and update mutations for resources owned by IAM identity FlagKubernetesAuthnMutation = "kubernetesAuthnMutation" + // FlagKubernetesExternalGroupMapping + // Routes external group mapping requests from /api to the /apis endpoint + FlagKubernetesExternalGroupMapping = "kubernetesExternalGroupMapping" + // FlagRestoreDashboards // Enables restore deleted dashboards feature FlagRestoreDashboards = "restoreDashboards" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 0e7dba8b5ec..1093b643232 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1910,6 +1910,22 @@ "expression": "true" } }, + { + "metadata": { + "name": "kubernetesExternalGroupMapping", + "resourceVersion": "1764062806326", + "creationTimestamp": "2025-11-25T09:17:49Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-11-25 09:26:46.326986 +0000 UTC" + } + }, + "spec": { + "description": "Routes external group mapping requests from /api to the /apis endpoint", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team", + "hideFromDocs": true + } + }, { "metadata": { "name": "kubernetesFeatureToggles", diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go index 7e999010795..588f6dee213 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus.go +++ b/pkg/services/ngalert/api/api_convert_prometheus.go @@ -63,6 +63,9 @@ const ( // configIdentifierHeader is the header that specifies the identifier for imported Alertmanager config. configIdentifierHeader = "X-Grafana-Alerting-Config-Identifier" defaultConfigIdentifier = "default" + + // versionMessageHeader is the header that specifies an optional message for rule versions. + versionMessageHeader = "X-Grafana-Alerting-Version-Message" ) var ( @@ -407,6 +410,11 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroups(c *context return errorToResponse(err) } + versionMessage, err := parseVersionMessageHeader(c) + if err != nil { + logger.Error("Failed to parse version message header", "error", err) + return errorToResponse(err) + } // 2. Convert Prometheus Rules to GMA grafanaGroups := make([]*models.AlertRuleGroup, 0, len(promNamespaces)) for ns, rgs := range promNamespaces { @@ -444,12 +452,13 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroups(c *context logger.Error("Failed to convert Prometheus rules to Grafana rules", "error", err) return errorToResponse(err) } + grafanaGroups = append(grafanaGroups, grafanaGroup) } } // 3. Update the GMA Rules in the DB - err = srv.alertRuleService.ReplaceRuleGroups(c.Req.Context(), c.SignedInUser, grafanaGroups, provenance) + err = srv.alertRuleService.ReplaceRuleGroups(c.Req.Context(), c.SignedInUser, grafanaGroups, provenance, versionMessage) if err != nil { logger.Error("Failed to replace rule groups", "error", err) return errorToResponse(err) @@ -856,6 +865,16 @@ func parseExtraLabelsHeader(c *contextmodel.ReqContext) (map[string]string, erro return parseKeyValuePairs(labelsStr, extraLabelsHeader) } +// parseVersionMessageHeader obtains and validates the message header value. +func parseVersionMessageHeader(c *contextmodel.ReqContext) (string, error) { + str := strings.TrimSpace(c.Req.Header.Get(versionMessageHeader)) + // Limit message to the same as the dashboards message. + if len(str) > 500 { + return "", errInvalidHeaderValue(versionMessageHeader, errors.New("must be less than 500 characters")) + } + return str, nil +} + func formatMergeMatchers(matchers amconfig.Matchers) string { var pairs []string for _, matcher := range matchers { diff --git a/pkg/services/ngalert/api/api_convert_prometheus_test.go b/pkg/services/ngalert/api/api_convert_prometheus_test.go index 9371ca6a230..08ce147a5c3 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus_test.go +++ b/pkg/services/ngalert/api/api_convert_prometheus_test.go @@ -506,6 +506,68 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) { } }) + t.Run("with version message header should pass message to rule store", func(t *testing.T) { + provenanceStore := fakes.NewFakeProvisioningStore() + folderService := foldertest.NewFakeService() + srv, _, ruleStore := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore), withFolderService(folderService)) + + // Create a folder in the root + fldr := randFolder() + fldr.ParentUID = "" + folderService.ExpectedFolder = fldr + folderService.ExpectedFolders = []*folder.Folder{fldr} + ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr) + + makeGroup := func(alertname string) apimodels.PrometheusRuleGroup { + return apimodels.PrometheusRuleGroup{ + Name: "Test Group", + Interval: prommodel.Duration(1 * time.Minute), + Rules: []apimodels.PrometheusRule{ + { + Alert: alertname, + Expr: "up == 0", + For: util.Pointer(prommodel.Duration(5 * time.Minute)), + Labels: map[string]string{ + "severity": "critical", + }, + }, + }, + } + } + + // Create a rule with the X-Grafana-Alerting-Version-Message to check it's passed to InsertRule. + + rc1 := createRequestCtx() + rc1.Req.Header.Set("X-Grafana-Alerting-Version-Message", "version #1") + response1 := srv.RouteConvertPrometheusPostRuleGroup(rc1, fldr.Title, makeGroup("AlertV1")) + require.Equal(t, http.StatusAccepted, response1.Status()) + + inserts := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { + a, ok := cmd.([]models.InsertRule) + return a, ok + }) + require.Len(t, inserts, 1) + cmd1 := inserts[0].([]models.InsertRule) + require.Len(t, cmd1, 1) + require.Equal(t, "version #1", cmd1[0].Message) + + // Now update the rule to check it gets passed to UpdateRule. + + rc2 := createRequestCtx() + rc2.Req.Header.Set("X-Grafana-Alerting-Version-Message", "version #2") + response2 := srv.RouteConvertPrometheusPostRuleGroup(rc2, fldr.Title, makeGroup("AlertV2")) + require.Equal(t, http.StatusAccepted, response2.Status()) + + updates := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { + a, ok := cmd.([]models.UpdateRule) + return a, ok + }) + require.Len(t, updates, 1) + cmd2 := updates[0].([]models.UpdateRule) + require.Len(t, cmd2, 1) + require.Equal(t, "version #2", cmd2[0].Message) + }) + t.Run("returns error when target datasource does not exist", func(t *testing.T) { srv, _, _ := createConvertPrometheusSrv(t) rc := createRequestCtx() diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index e31e713fb33..25acf56f7d3 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "slices" "testing" "time" @@ -2790,6 +2791,101 @@ func TestRouteGetRuleStatuses(t *testing.T) { require.NotEmpty(t, res.Data.NextToken) }) }) + + t.Run("with search.folder filter", func(t *testing.T) { + fakeStore, fakeAIM, api := setupAPI(t) + + // Create folders with different paths + folder1 := &folder.Folder{UID: "prod-uid", Title: "Production", Fullpath: "Production", OrgID: orgID} + folder2 := &folder.Folder{UID: "prod-alerts-uid", Title: "Alerts", Fullpath: "Production/Alerts", OrgID: orgID} + folder3 := &folder.Folder{UID: "dev-uid", Title: "Monitoring", Fullpath: "Development/Monitoring", OrgID: orgID} + folder4 := &folder.Folder{UID: "prod-crit-uid", Title: "Critical", Fullpath: "Production/Critical", OrgID: orgID} + fakeStore.Folders[orgID] = []*folder.Folder{folder1, folder2, folder3, folder4} + + // Create rules in different folders + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithNamespaceUID("prod-uid"), gen.WithUID("rule1"), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithNamespaceUID("prod-alerts-uid"), gen.WithUID("rule2"), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithNamespaceUID("dev-uid"), gen.WithUID("rule3"), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithNamespaceUID("prod-crit-uid"), gen.WithUID("rule4"), gen.WithNoNotificationSettings()) + + testCases := []struct { + name string + searchFolder string + expectedUIDs []string + }{ + { + name: "search 'production' matches Production folders", + searchFolder: "production", + expectedUIDs: []string{"rule1", "rule2", "rule4"}, + }, + { + name: "search 'prod alerts' matches Production/Alerts", + searchFolder: "prod alerts", + expectedUIDs: []string{"rule2"}, + }, + { + name: "search 'dev' matches Development", + searchFolder: "dev", + expectedUIDs: []string{"rule3"}, + }, + { + name: "search 'critical' matches Critical folder", + searchFolder: "critical", + expectedUIDs: []string{"rule4"}, + }, + { + name: "case insensitive search", + searchFolder: "PRODUCTION", + expectedUIDs: []string{"rule1", "rule2", "rule4"}, + }, + { + name: "empty search returns all rules", + searchFolder: "", + expectedUIDs: []string{"rule1", "rule2", "rule3", "rule4"}, + }, + { + name: "non-matching search returns no rules", + searchFolder: "nonexistent", + expectedUIDs: []string{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + reqURL := "/api/v1/rules" + if tc.searchFolder != "" { + reqURL += "?search.folder=" + url.QueryEscape(tc.searchFolder) + } + req, err := http.NewRequest("GET", reqURL, nil) + require.NoError(t, err) + ctx := &contextmodel.ReqContext{ + Context: &web.Context{Req: req}, + SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions}, + } + + resp := api.RouteGetRuleStatuses(ctx) + require.Equal(t, http.StatusOK, resp.Status()) + + var res apimodels.RuleResponse + require.NoError(t, json.Unmarshal(resp.Body(), &res)) + require.Equal(t, "success", res.Status) + + // Collect rule UIDs from response + actualUIDs := []string{} + for _, group := range res.Data.RuleGroups { + for _, rule := range group.Rules { + actualUIDs = append(actualUIDs, rule.UID) + } + } + + require.ElementsMatch(t, tc.expectedUIDs, actualUIDs) + }) + } + }) } func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, PrometheusSrv) { diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index dc8863e1fb6..8d24fafa351 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -75,7 +75,7 @@ type AlertRuleService interface { UpdateAlertRule(ctx context.Context, user identity.Requester, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error) DeleteAlertRule(ctx context.Context, user identity.Requester, ruleUID string, provenance alerting_models.Provenance) error GetRuleGroup(ctx context.Context, user identity.Requester, folder, group string) (alerting_models.AlertRuleGroup, error) - ReplaceRuleGroup(ctx context.Context, user identity.Requester, group alerting_models.AlertRuleGroup, provenance alerting_models.Provenance) error + ReplaceRuleGroup(ctx context.Context, user identity.Requester, group alerting_models.AlertRuleGroup, provenance alerting_models.Provenance, message string) error DeleteRuleGroup(ctx context.Context, user identity.Requester, folder, group string, provenance alerting_models.Provenance) error DeleteRuleGroups(ctx context.Context, user identity.Requester, provenance alerting_models.Provenance, opts *provisioning.FilterOptions) error GetAlertRuleWithFolderFullpath(ctx context.Context, u identity.Requester, ruleUID string) (provisioning.AlertRuleWithFolderFullpath, error) @@ -492,7 +492,10 @@ func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *contextmodel.ReqContext, a ErrResp(http.StatusBadRequest, err, "") } provenance := determineProvenance(c) - err = srv.alertRules.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser, groupModel, alerting_models.Provenance(provenance)) + // TODO: https://github.com/grafana/grafana/issues/114197 + // Support passing change messages. + changeMessage := "" + err = srv.alertRules.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser, groupModel, alerting_models.Provenance(provenance), changeMessage) if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) { return ErrResp(http.StatusBadRequest, err, "") } diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 268ed7b8c5d..d81f448f600 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -576,9 +576,9 @@ func (srv RulerSrv) performUpdateAlertRules(ctx context.Context, c *contextmodel } if len(finalChanges.New) > 0 { - inserts := make([]ngmodels.AlertRule, 0, len(finalChanges.New)) + inserts := make([]ngmodels.InsertRule, 0, len(finalChanges.New)) for _, rule := range finalChanges.New { - inserts = append(inserts, *rule) + inserts = append(inserts, ngmodels.InsertRule{AlertRule: *rule}) } added, err := srv.store.InsertAlertRules(tranCtx, ngmodels.NewUserUID(c.SignedInUser), inserts) if err != nil { diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go index 9e7df81f48e..0123af54d52 100644 --- a/pkg/services/ngalert/api/persist.go +++ b/pkg/services/ngalert/api/persist.go @@ -28,7 +28,7 @@ type RuleStore interface { // InsertAlertRules will insert all alert rules passed into the function // and return the map of uuid to id. - InsertAlertRules(ctx context.Context, user *ngmodels.UserUID, rules []ngmodels.AlertRule) ([]ngmodels.AlertRuleKeyWithId, error) + InsertAlertRules(ctx context.Context, user *ngmodels.UserUID, rules []ngmodels.InsertRule) ([]ngmodels.AlertRuleKeyWithId, error) UpdateAlertRules(ctx context.Context, user *ngmodels.UserUID, rules []ngmodels.UpdateRule) error DeleteAlertRulesByUID(ctx context.Context, orgID int64, user *ngmodels.UserUID, permanently bool, ruleUID ...string) error DeleteRuleFromTrashByGUID(ctx context.Context, orgID int64, ruleGUID string) (int64, error) diff --git a/pkg/services/ngalert/api/prometheus/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go index 082c1bfd333..d07e07d41fe 100644 --- a/pkg/services/ngalert/api/prometheus/api_prometheus.go +++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go @@ -689,9 +689,20 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt namespaceUIDs := make([]string, 0, len(opts.AllowedNamespaces)) folderUID := opts.Query.Get("folder_uid") + searchFolder := opts.Query.Get("search.folder") + _, exists := opts.AllowedNamespaces[folderUID] if folderUID != "" && exists { + // Exact folder UID match namespaceUIDs = append(namespaceUIDs, folderUID) + } else if searchFolder != "" { + // Search folders by full path + matcher := NewTextMatcher(searchFolder) + for uid, fullpath := range opts.AllowedNamespaces { + if matcher.Match(fullpath) { + namespaceUIDs = append(namespaceUIDs, uid) + } + } } else { for k := range opts.AllowedNamespaces { namespaceUIDs = append(namespaceUIDs, k) @@ -700,9 +711,15 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt span.SetAttributes( attribute.Bool("folder_uid_set", folderUID != ""), + attribute.Bool("search_folder_set", searchFolder != ""), attribute.Int("namespace_count", len(namespaceUIDs)), ) + if searchFolder != "" && len(namespaceUIDs) == 0 { + log.Debug("No folders matched search.folder, returning empty response") + return ruleResponse + } + ruleGroups := opts.Query["rule_group"] ruleUIDs := opts.Query["rule_uid"] diff --git a/pkg/services/ngalert/api/prometheus/util.go b/pkg/services/ngalert/api/prometheus/util.go new file mode 100644 index 00000000000..9c5ecb50d82 --- /dev/null +++ b/pkg/services/ngalert/api/prometheus/util.go @@ -0,0 +1,41 @@ +package api + +import ( + "strings" +) + +// TextMatcher performs case-insensitive sequential substring matching. +// It checks if text contains all search words in the order provided, +// but not necessarily consecutively. +// +// Example: NewTextMatcher("api time").Match("API Response Time") returns true +type TextMatcher struct { + words []string +} + +func NewTextMatcher(search string) *TextMatcher { + words := strings.Fields(search) + for i := range words { + words[i] = strings.ToLower(words[i]) + } + return &TextMatcher{words: words} +} + +func (m *TextMatcher) Match(text string) bool { + if len(m.words) == 0 { + return true + } + lowerText := strings.ToLower(text) + start := 0 + for _, word := range m.words { + if start > len(lowerText) { + return false + } + idx := strings.Index(lowerText[start:], word) + if idx == -1 { + return false + } + start += idx + len(word) + } + return true +} diff --git a/pkg/services/ngalert/api/prometheus/util_test.go b/pkg/services/ngalert/api/prometheus/util_test.go new file mode 100644 index 00000000000..21940a5a8b8 --- /dev/null +++ b/pkg/services/ngalert/api/prometheus/util_test.go @@ -0,0 +1,132 @@ +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewTextMatcher(t *testing.T) { + tests := []struct { + name string + input string + expectedWords []string + }{ + { + name: "empty string", + input: "", + expectedWords: []string{}, + }, + { + name: "whitespace only", + input: " ", + expectedWords: []string{}, + }, + { + name: "single word", + input: "alerts", + expectedWords: []string{"alerts"}, + }, + { + name: "multiple words", + input: "parent alerts", + expectedWords: []string{"parent", "alerts"}, + }, + { + name: "mixed case normalized to lowercase", + input: "Parent Alerts", + expectedWords: []string{"parent", "alerts"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matcher := NewTextMatcher(tt.input) + assert.Equal(t, tt.expectedWords, matcher.words) + }) + } +} + +func TestTextMatcher_Match(t *testing.T) { + tests := []struct { + name string + search string + text string + expected bool + }{ + { + name: "empty search matches everything", + search: "", + text: "any text", + expected: true, + }, + { + name: "whitespace search matches everything", + search: " ", + text: "any text", + expected: true, + }, + { + name: "exact match", + search: "alerts", + text: "alerts", + expected: true, + }, + { + name: "case insensitive match", + search: "parent alerts", + text: "Parent Folder/Alerts", + expected: true, + }, + { + name: "partial match", + search: "folder", + text: "Parent Folder/Alerts", + expected: true, + }, + { + name: "sequential words match", + search: "api time", + text: "API Response Time", + expected: true, + }, + { + name: "non-consecutive but sequential words match", + search: "parent rules", + text: "Parent Folder/Alert Rules", + expected: true, + }, + { + name: "word not found", + search: "missing", + text: "Parent Folder/Alerts", + expected: false, + }, + { + name: "words out of order", + search: "alerts parent", + text: "Parent Folder/Alerts", + expected: false, + }, + { + name: "folder path search", + search: "prod alerts", + text: "Monitoring/Production/Alerts", + expected: true, + }, + { + name: "multiple spaces in search", + search: "api response time", + text: "API Response Time", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matcher := NewTextMatcher(tt.search) + result := matcher.Match(tt.text) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index 1a003d00365..af37efc58d7 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -1707,6 +1707,10 @@ "is_paused": { "type": "boolean" }, + "message": { + "description": "Field is only populated when listing alert rule versions.", + "type": "string" + }, "metadata": { "$ref": "#/definitions/AlertRuleMetadata" }, @@ -2544,6 +2548,28 @@ "TLSConfig": { "$ref": "#/definitions/TLSConfig" }, + "audience": { + "description": "Audience optionally specifies the intended audience of the\nrequest. If empty, the value of TokenURL is used as the\nintended audience. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, + "claims": { + "additionalProperties": {}, + "description": "Claims is a map of claims to be added to the JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "object" + }, + "client_certificate_key": { + "$ref": "#/definitions/Secret" + }, + "client_certificate_key_file": { + "type": "string" + }, + "client_certificate_key_id": { + "type": "string" + }, + "client_certificate_key_ref": { + "description": "ClientCertificateKeyRef is the name of the secret within the secret manager to use as the client\nsecret.", + "type": "string" + }, "client_id": { "type": "string" }, @@ -2563,6 +2589,14 @@ }, "type": "object" }, + "grant_type": { + "description": "GrantType is the OAuth2 grant type to use. It can be one of\n\"client_credentials\" or \"urn:ietf:params:oauth:grant-type:jwt-bearer\" (RFC 7523).\nDefault value is \"client_credentials\"", + "type": "string" + }, + "iss": { + "description": "Iss is the OAuth client identifier used when communicating with\nthe configured OAuth provider. Default value is client_id. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, "no_proxy": { "description": "NoProxy contains addresses that should not use a proxy.", "type": "string" @@ -2583,6 +2617,10 @@ }, "type": "array" }, + "signature_algorithm": { + "description": "SignatureAlgorithm is the RSA algorithm used to sign JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".\nDefault value is RS256 and valid values RS256, RS384, RS512", + "type": "string" + }, "token_url": { "type": "string" } @@ -5781,7 +5819,7 @@ }, { "in": "header", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "type": "string" }, { @@ -6071,7 +6109,7 @@ }, { "in": "header", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "type": "string" }, { diff --git a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go index b3c2db6fe7a..3b672ad6881 100644 --- a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go +++ b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go @@ -259,7 +259,7 @@ type RouteConvertPrometheusPostRuleGroupParams struct { // in: header FolderUID string `json:"x-grafana-alerting-folder-uid"` // in: header - NotificationReceiver string `json:"x-grafana-alerting-notification-receiver"` + NotificationSettings string `json:"x-grafana-alerting-notification-settings"` // in:body Body PrometheusRuleGroup } diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index a73d76c2151..d9d38ce03b0 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -1707,6 +1707,10 @@ "is_paused": { "type": "boolean" }, + "message": { + "description": "Field is only populated when listing alert rule versions.", + "type": "string" + }, "metadata": { "$ref": "#/definitions/AlertRuleMetadata" }, @@ -2544,6 +2548,28 @@ "TLSConfig": { "$ref": "#/definitions/TLSConfig" }, + "audience": { + "description": "Audience optionally specifies the intended audience of the\nrequest. If empty, the value of TokenURL is used as the\nintended audience. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, + "claims": { + "additionalProperties": {}, + "description": "Claims is a map of claims to be added to the JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "object" + }, + "client_certificate_key": { + "$ref": "#/definitions/Secret" + }, + "client_certificate_key_file": { + "type": "string" + }, + "client_certificate_key_id": { + "type": "string" + }, + "client_certificate_key_ref": { + "description": "ClientCertificateKeyRef is the name of the secret within the secret manager to use as the client\nsecret.", + "type": "string" + }, "client_id": { "type": "string" }, @@ -2563,6 +2589,14 @@ }, "type": "object" }, + "grant_type": { + "description": "GrantType is the OAuth2 grant type to use. It can be one of\n\"client_credentials\" or \"urn:ietf:params:oauth:grant-type:jwt-bearer\" (RFC 7523).\nDefault value is \"client_credentials\"", + "type": "string" + }, + "iss": { + "description": "Iss is the OAuth client identifier used when communicating with\nthe configured OAuth provider. Default value is client_id. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, "no_proxy": { "description": "NoProxy contains addresses that should not use a proxy.", "type": "string" @@ -2583,6 +2617,10 @@ }, "type": "array" }, + "signature_algorithm": { + "description": "SignatureAlgorithm is the RSA algorithm used to sign JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".\nDefault value is RS256 and valid values RS256, RS384, RS512", + "type": "string" + }, "token_url": { "type": "string" } @@ -6834,7 +6872,7 @@ }, { "in": "header", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "type": "string" }, { @@ -7241,7 +7279,7 @@ }, { "in": "header", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "type": "string" }, { diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index b590bb3edd7..adb65e92ad9 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -1220,7 +1220,7 @@ }, { "type": "string", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "in": "header" }, { @@ -1634,7 +1634,7 @@ }, { "type": "string", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "in": "header" }, { @@ -6124,6 +6124,10 @@ "is_paused": { "type": "boolean" }, + "message": { + "description": "Field is only populated when listing alert rule versions.", + "type": "string" + }, "metadata": { "$ref": "#/definitions/AlertRuleMetadata" }, @@ -6963,6 +6967,28 @@ "TLSConfig": { "$ref": "#/definitions/TLSConfig" }, + "audience": { + "description": "Audience optionally specifies the intended audience of the\nrequest. If empty, the value of TokenURL is used as the\nintended audience. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, + "claims": { + "description": "Claims is a map of claims to be added to the JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "object", + "additionalProperties": {} + }, + "client_certificate_key": { + "$ref": "#/definitions/Secret" + }, + "client_certificate_key_file": { + "type": "string" + }, + "client_certificate_key_id": { + "type": "string" + }, + "client_certificate_key_ref": { + "description": "ClientCertificateKeyRef is the name of the secret within the secret manager to use as the client\nsecret.", + "type": "string" + }, "client_id": { "type": "string" }, @@ -6982,6 +7008,14 @@ "type": "string" } }, + "grant_type": { + "description": "GrantType is the OAuth2 grant type to use. It can be one of\n\"client_credentials\" or \"urn:ietf:params:oauth:grant-type:jwt-bearer\" (RFC 7523).\nDefault value is \"client_credentials\"", + "type": "string" + }, + "iss": { + "description": "Iss is the OAuth client identifier used when communicating with\nthe configured OAuth provider. Default value is client_id. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, "no_proxy": { "description": "NoProxy contains addresses that should not use a proxy.", "type": "string" @@ -7002,6 +7036,10 @@ "type": "string" } }, + "signature_algorithm": { + "description": "SignatureAlgorithm is the RSA algorithm used to sign JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".\nDefault value is RS256 and valid values RS256, RS384, RS512", + "type": "string" + }, "token_url": { "type": "string" } diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index a4e1e8fcc04..e6177843e8a 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -1036,9 +1036,15 @@ type ListNamespaceAlertRulesQuery struct { NamespaceUID string } +type InsertRule struct { + AlertRule + Message string +} + type UpdateRule struct { Existing *AlertRule New AlertRule + Message string } // Condition contains backend expressions and queries and the RefID diff --git a/pkg/services/ngalert/provisioning/alert_rules.go b/pkg/services/ngalert/provisioning/alert_rules.go index d8cb5c9783d..aa6c4c55b34 100644 --- a/pkg/services/ngalert/provisioning/alert_rules.go +++ b/pkg/services/ngalert/provisioning/alert_rules.go @@ -304,8 +304,10 @@ func (service *AlertRuleService) CreateAlertRule(ctx context.Context, user ident } } err = service.xact.InTransaction(ctx, func(ctx context.Context) error { - ids, err := service.ruleStore.InsertAlertRules(ctx, userUidOrFallback(user), []models.AlertRule{ - rule, + ids, err := service.ruleStore.InsertAlertRules(ctx, userUidOrFallback(user), []models.InsertRule{ + { + AlertRule: rule, + }, }) if err != nil { return err @@ -464,7 +466,7 @@ func (service *AlertRuleService) UpdateRuleGroup(ctx context.Context, user ident }) } -func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, user identity.Requester, group models.AlertRuleGroup, provenance models.Provenance) error { +func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, user identity.Requester, group models.AlertRuleGroup, provenance models.Provenance, versionMessage string) error { if err := models.ValidateRuleGroupInterval(group.Interval, service.baseIntervalSeconds); err != nil { return err } @@ -518,13 +520,13 @@ func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, user iden } } - return service.persistDelta(ctx, user, delta, provenance) + return service.persistDelta(ctx, user, delta, provenance, versionMessage) } -func (service *AlertRuleService) ReplaceRuleGroups(ctx context.Context, user identity.Requester, groups []*models.AlertRuleGroup, provenance models.Provenance) error { +func (service *AlertRuleService) ReplaceRuleGroups(ctx context.Context, user identity.Requester, groups []*models.AlertRuleGroup, provenance models.Provenance, versionMessage string) error { err := service.xact.InTransaction(ctx, func(ctx context.Context) error { for _, group := range groups { - err := service.ReplaceRuleGroup(ctx, user, *group, provenance) + err := service.ReplaceRuleGroup(ctx, user, *group, provenance, versionMessage) if err != nil { return err } @@ -565,7 +567,7 @@ func (service *AlertRuleService) DeleteRuleGroups(ctx context.Context, user iden return err } } - err = service.persistDelta(ctx, user, delta, provenance) + err = service.persistDelta(ctx, user, delta, provenance, "") // Message not used for deletes. if err != nil { return err } @@ -621,7 +623,7 @@ func (service *AlertRuleService) calcDelta(ctx context.Context, user identity.Re return store.UpdateCalculatedRuleFields(delta), nil } -func (service *AlertRuleService) persistDelta(ctx context.Context, user identity.Requester, delta *store.GroupDelta, provenance models.Provenance) error { +func (service *AlertRuleService) persistDelta(ctx context.Context, user identity.Requester, delta *store.GroupDelta, provenance models.Provenance, versionMessage string) error { return service.xact.InTransaction(ctx, func(ctx context.Context) error { // Delete first as this could prevent future unique constraint violations. if len(delta.Delete) > 0 { @@ -669,6 +671,7 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, user identity updates = append(updates, models.UpdateRule{ Existing: update.Existing, New: *update.New, + Message: versionMessage, }) } if err := service.ruleStore.UpdateAlertRules(ctx, userUidOrFallback(user), updates); err != nil { @@ -682,7 +685,7 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, user identity } if len(delta.New) > 0 { - uids, err := service.ruleStore.InsertAlertRules(ctx, userUidOrFallback(user), withoutNilAlertRules(delta.New)) + uids, err := service.ruleStore.InsertAlertRules(ctx, userUidOrFallback(user), makeInsertRules(delta.New, versionMessage)) if err != nil { return fmt.Errorf("failed to insert alert rules: %w", err) } @@ -994,11 +997,15 @@ func syncGroupRuleFields(group *models.AlertRuleGroup, orgID int64) *models.Aler return group } -func withoutNilAlertRules(ptrs []*models.AlertRule) []models.AlertRule { - result := make([]models.AlertRule, 0, len(ptrs)) +// makeInsertRules converts a slice of AlertRule pointers to InsertRule, removing nils and adding version message. +func makeInsertRules(ptrs []*models.AlertRule, versionMessage string) []models.InsertRule { + result := make([]models.InsertRule, 0, len(ptrs)) for _, ptr := range ptrs { if ptr != nil { - result = append(result, *ptr) + result = append(result, models.InsertRule{ + AlertRule: *ptr, + Message: versionMessage, + }) } } return result diff --git a/pkg/services/ngalert/provisioning/alert_rules_test.go b/pkg/services/ngalert/provisioning/alert_rules_test.go index 145f1df2bb8..c552f797429 100644 --- a/pkg/services/ngalert/provisioning/alert_rules_test.go +++ b/pkg/services/ngalert/provisioning/alert_rules_test.go @@ -49,7 +49,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { t.Run("group creation should set the right provenance", func(t *testing.T) { group := createDummyGroup("group-test-1", orgID) - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-1") @@ -96,7 +96,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { group := createDummyGroup("namespace-test", orgID) group.Rules[0].NamespaceUID = "" - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "namespace-test") @@ -109,7 +109,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { group := createDummyGroup("group-test-3", orgID) group.Rules[0].RuleGroup = "something different" - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-3") @@ -169,13 +169,13 @@ func TestIntegrationAlertRuleService(t *testing.T) { t.Run("updating a group by updating a rule should bump that rule's data and version number", func(t *testing.T) { group := createDummyGroup("group-test-5", orgID) - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-5") require.NoError(t, err) updatedGroup.Rules[0].Title = "some-other-title-asdf" - err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-5") @@ -198,7 +198,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { }, } rule.Metadata = ruleMetadata - r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.AlertRule{rule}) + r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: rule}}) require.NoError(t, err) require.Len(t, r, 1) @@ -215,7 +215,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { Rules: []models.AlertRule{rule}, } - err = ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, namespaceUID, groupTitle) @@ -242,7 +242,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { }, } rule.Metadata = ruleMetadata - r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.AlertRule{rule}) + r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: rule}}) require.NoError(t, err) require.Len(t, r, 1) @@ -263,7 +263,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { Rules: []models.AlertRule{rule}, } - err = ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, namespaceUID, groupTitle) @@ -289,7 +289,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { }, } rule.Metadata = ruleMetadata - r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.AlertRule{rule}) + r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: rule}}) require.NoError(t, err) require.Len(t, r, 1) @@ -306,7 +306,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { Rules: []models.AlertRule{rule}, } - err = ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, namespaceUID, groupTitle) @@ -326,7 +326,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { }, } rule.Metadata = ruleMetadata - r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.AlertRule{rule}) + r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: rule}}) require.NoError(t, err) require.Len(t, r, 1) @@ -356,14 +356,14 @@ func TestIntegrationAlertRuleService(t *testing.T) { dummyRule("overlap-test-rule-2", orgID), }, } - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "overlap-test") require.NoError(t, err) updatedGroup.Rules[0].Title = "overlap-test-rule-2" updatedGroup.Rules[1].Title = "overlap-test-rule-3" - err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "overlap-test") @@ -387,14 +387,14 @@ func TestIntegrationAlertRuleService(t *testing.T) { dummyRule("swap-test-rule-2", orgID), }, } - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "swap-test") require.NoError(t, err) updatedGroup.Rules[0].Title = "swap-test-rule-2" updatedGroup.Rules[1].Title = "swap-test-rule-1" - err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "swap-test") @@ -419,7 +419,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { dummyRule("cycle-test-rule-3", orgID), }, } - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "cycle-test") require.NoError(t, err) @@ -427,7 +427,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { updatedGroup.Rules[0].Title = "cycle-test-rule-2" updatedGroup.Rules[1].Title = "cycle-test-rule-3" updatedGroup.Rules[2].Title = "cycle-test-rule-1" - err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "cycle-test") @@ -457,7 +457,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { dummyRule("multi-cycle-test-rule-5", orgID), }, } - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "multi-cycle-test") require.NoError(t, err) @@ -469,7 +469,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { updatedGroup.Rules[3].Title = "multi-cycle-test-rule-5" updatedGroup.Rules[4].Title = "multi-cycle-test-rule-3" - err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "multi-cycle-test") @@ -498,7 +498,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { dummyRule("recreate-test-rule-1", orgID), }, } - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) updatedGroup := models.AlertRuleGroup{ Title: "recreate-test", @@ -508,7 +508,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { dummyRule("recreate-test-rule-1", orgID), }, } - err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "recreate-test") @@ -529,14 +529,14 @@ func TestIntegrationAlertRuleService(t *testing.T) { dummyRule("create-overlap-test-rule-1", orgID), }, } - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "create-overlap-test") require.NoError(t, err) updatedGroup.Rules[0].Title = "create-overlap-test-rule-2" updatedGroup.Rules = append(updatedGroup.Rules, dummyRule("create-overlap-test-rule-1", orgID)) - err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI) + err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI, "") require.NoError(t, err) readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "create-overlap-test") @@ -558,7 +558,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { models.PanelIDAnnotation: strconv.FormatInt(panelId, 10), } - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-5") require.NoError(t, err) @@ -713,11 +713,11 @@ func TestIntegrationAlertRuleService(t *testing.T) { t.Run(test.name, func(t *testing.T) { var orgID int64 = 1 group := createDummyGroup(t.Name(), orgID) - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, test.from) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, test.from, "") require.NoError(t, err) group.Rules[0].Title = t.Name() - err = ruleService.ReplaceRuleGroup(context.Background(), u, group, test.to) + err = ruleService.ReplaceRuleGroup(context.Background(), u, group, test.to, "") if test.errNil { require.NoError(t, err) } else { @@ -745,7 +745,7 @@ func TestIntegrationAlertRuleService(t *testing.T) { ruleService.quotas = checker group := createDummyGroup("quota-reached", orgID) - err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.ErrorIs(t, err, models.ErrQuotaReached) }) @@ -912,11 +912,11 @@ func TestIntegrationCreateAlertRule(t *testing.T) { t.Run("inserts to database", func(t *testing.T) { inserts := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { - a, ok := cmd.([]models.AlertRule) + a, ok := cmd.([]models.InsertRule) return a, ok }) require.Len(t, inserts, 1) - cmd := inserts[0].([]models.AlertRule) + cmd := inserts[0].([]models.InsertRule) require.Len(t, cmd, 1) }) @@ -946,11 +946,11 @@ func TestIntegrationCreateAlertRule(t *testing.T) { t.Run("inserts to database", func(t *testing.T) { inserts := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { - a, ok := cmd.([]models.AlertRule) + a, ok := cmd.([]models.InsertRule) return a, ok }) require.Len(t, inserts, 1) - cmd := inserts[0].([]models.AlertRule) + cmd := inserts[0].([]models.InsertRule) require.Len(t, cmd, 1) }) @@ -993,11 +993,11 @@ func TestIntegrationCreateAlertRule(t *testing.T) { t.Run("inserts to database", func(t *testing.T) { inserts := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { - a, ok := cmd.([]models.AlertRule) + a, ok := cmd.([]models.InsertRule) return a, ok }) require.Len(t, inserts, 1) - cmd := inserts[0].([]models.AlertRule) + cmd := inserts[0].([]models.InsertRule) require.Len(t, cmd, 1) }) @@ -1040,11 +1040,11 @@ func TestIntegrationCreateAlertRule(t *testing.T) { t.Run("inserts to database", func(t *testing.T) { inserts := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { - a, ok := cmd.([]models.AlertRule) + a, ok := cmd.([]models.InsertRule) return a, ok }) require.Len(t, inserts, 1) - cmd := inserts[0].([]models.AlertRule) + cmd := inserts[0].([]models.InsertRule) require.Len(t, cmd, 1) }) @@ -1075,7 +1075,7 @@ func TestIntegrationCreateAlertRule(t *testing.T) { assert.Equal(t, "AuthorizeRuleGroupWrite", ac.Calls[1].Method) inserts := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { - a, ok := cmd.([]models.AlertRule) + a, ok := cmd.([]models.InsertRule) return a, ok }) require.Empty(t, inserts) @@ -1276,7 +1276,7 @@ func TestUpdateAlertRule(t *testing.T) { rule := models.CopyRule(rules[0]) - _, err := service.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.AlertRule{*rule}) + _, err := service.ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: *rule}}) require.NoError(t, err) ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { @@ -1300,7 +1300,7 @@ func TestUpdateAlertRule(t *testing.T) { ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { return true, nil } rule := createNoGroupRule("nogroup-update", orgID, "my-namespace") - _, err := ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.AlertRule{rule}) + _, err := ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: rule}}) require.NoError(t, err) require.NoError(t, provenanceStore.SetProvenance(context.Background(), &rule, orgID, models.ProvenanceNone)) @@ -1418,7 +1418,7 @@ func TestDeleteAlertRule(t *testing.T) { // create two NoGroup rules in the same namespace (distinct sentinel groups) r1 := createNoGroupRule("nogroup-del-1", orgID, "my-namespace") r2 := createNoGroupRule("nogroup-del-2", orgID, "my-namespace") - _, err := ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.AlertRule{r1, r2}) + _, err := ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: r1}, {AlertRule: r2}}) require.NoError(t, err) require.NoError(t, provenanceStore.SetProvenance(context.Background(), &r1, orgID, models.ProvenanceNone)) require.NoError(t, provenanceStore.SetProvenance(context.Background(), &r2, orgID, models.ProvenanceNone)) @@ -1444,7 +1444,7 @@ func TestDeleteAlertRule(t *testing.T) { ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { return false, nil } r := createNoGroupRule("nogroup-del-auth", orgID, "my-namespace") - _, err := ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.AlertRule{r}) + _, err := ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: r}}) require.NoError(t, err) require.NoError(t, provenanceStore.SetProvenance(context.Background(), &r, orgID, models.ProvenanceNone)) @@ -1917,7 +1917,7 @@ func TestReplaceGroup(t *testing.T) { return true, nil } - err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) require.Len(t, ac.Calls, 1) @@ -1953,7 +1953,7 @@ func TestReplaceGroup(t *testing.T) { return expectedErr } - err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.ErrorIs(t, err, expectedErr) require.Len(t, ac.Calls, 2) @@ -1976,7 +1976,7 @@ func TestReplaceGroup(t *testing.T) { return nil } - err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI) + err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI, "") require.NoError(t, err) require.Len(t, ac.Calls, 2) @@ -2013,11 +2013,11 @@ func TestReplaceGroup(t *testing.T) { Rules: []models.AlertRule{rule}, } - err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceNone) + err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceNone, "") require.NoError(t, err) rule.Metadata.PrometheusStyleRule.OriginalRuleDefinition = "new" - err = service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceNone) + err = service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceNone, "") require.NoError(t, err) rule, _, err = service.GetAlertRule(context.Background(), u, rule.UID) @@ -2036,7 +2036,7 @@ func TestReplaceGroup(t *testing.T) { second.RuleGroup = group.Title group.Rules = append(group.Rules, second) - err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceNone) + err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceNone, "") require.Error(t, err) require.ErrorContains(t, err, "cannot be used for rule groups with multiple rules") }) @@ -2050,7 +2050,7 @@ func TestReplaceGroup(t *testing.T) { // change the group name away from the sentinel value groupSeed.Title = "some-other-group" // not the sentinel group name - err := service.ReplaceRuleGroup(context.Background(), u, groupSeed, models.ProvenanceNone) + err := service.ReplaceRuleGroup(context.Background(), u, groupSeed, models.ProvenanceNone, "") require.Error(t, err) require.ErrorContains(t, err, "cannot move rule out of this group") }) diff --git a/pkg/services/ngalert/provisioning/persist.go b/pkg/services/ngalert/provisioning/persist.go index 753c874cce3..914a3644984 100644 --- a/pkg/services/ngalert/provisioning/persist.go +++ b/pkg/services/ngalert/provisioning/persist.go @@ -34,7 +34,7 @@ type RuleStore interface { ListAlertRules(ctx context.Context, query *models.ListAlertRulesQuery) (models.RulesGroup, error) ListAlertRulesPaginated(ctx context.Context, query *models.ListAlertRulesExtendedQuery) (models.RulesGroup, string, error) GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error) - InsertAlertRules(ctx context.Context, user *models.UserUID, rule []models.AlertRule) ([]models.AlertRuleKeyWithId, error) + InsertAlertRules(ctx context.Context, user *models.UserUID, rule []models.InsertRule) ([]models.AlertRuleKeyWithId, error) UpdateAlertRules(ctx context.Context, user *models.UserUID, rule []models.UpdateRule) error DeleteAlertRulesByUID(ctx context.Context, orgID int64, user *models.UserUID, permanently bool, ruleUID ...string) error GetAlertRulesGroupByRuleUID(ctx context.Context, query *models.GetAlertRulesGroupByRuleUIDQuery) ([]*models.AlertRule, error) diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 076974ea9c9..3feefac6d73 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -336,7 +336,7 @@ func (st DBstore) GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmode // InsertAlertRules is a handler for creating/updating alert rules. // Returns the UID and ID of rules that were created in the same order as the input rules. -func (st DBstore) InsertAlertRules(ctx context.Context, user *ngmodels.UserUID, rules []ngmodels.AlertRule) ([]ngmodels.AlertRuleKeyWithId, error) { +func (st DBstore) InsertAlertRules(ctx context.Context, user *ngmodels.UserUID, rules []ngmodels.InsertRule) ([]ngmodels.AlertRuleKeyWithId, error) { ids := make([]ngmodels.AlertRuleKeyWithId, 0, len(rules)) keys := make([]ngmodels.AlertRuleKey, 0, len(rules)) return ids, st.SQLStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { @@ -352,14 +352,14 @@ func (st DBstore) InsertAlertRules(ctx context.Context, user *ngmodels.UserUID, r.UID = uid } r.Version = 1 - if err := st.validateAlertRule(r); err != nil { + if err := st.validateAlertRule(r.AlertRule); err != nil { return err } if err := (&r).PreSave(TimeNow, user); err != nil { return err } - converted, err := alertRuleFromModelsAlertRule(r) + converted, err := alertRuleFromModelsAlertRule(r.AlertRule) if err != nil { return fmt.Errorf("failed to convert alert rule %q to storage model: %w", r.Title, err) } @@ -369,7 +369,9 @@ func (st DBstore) InsertAlertRules(ctx context.Context, user *ngmodels.UserUID, converted.GUID = uuid.NewString() newRules = append(newRules, converted) - ruleVersions = append(ruleVersions, alertRuleToAlertRuleVersion(converted)) + v := alertRuleToAlertRuleVersion(converted) + v.Message = r.Message + ruleVersions = append(ruleVersions, v) } if len(newRules) > 0 { // we have to insert the rules one by one as otherwise we are @@ -444,6 +446,7 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, user *ngmodels.UserUID, v := alertRuleToAlertRuleVersion(converted) v.Version++ v.ParentVersion = r.Existing.Version + v.Message = r.Message // check if there is diff between existing and new, and if no, skip saving version. existingConverted, err := alertRuleFromModelsAlertRule(*r.Existing) diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index 9a491120c8c..b9a574d33fc 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -38,6 +38,17 @@ import ( tutil "github.com/grafana/grafana/pkg/util/testutil" ) +func toInsertRules(rules []models.AlertRule) []models.InsertRule { + result := make([]models.InsertRule, len(rules)) + for i := range rules { + result[i] = models.InsertRule{ + AlertRule: rules[i], + Message: "", + } + } + return result +} + func TestIntegrationUpdateAlertRules(t *testing.T) { tutil.SkipIntegrationTestInShortMode(t) @@ -567,7 +578,7 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) { store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore) - result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, gen.GenerateMany(3)) + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, toInsertRules(gen.GenerateMany(3))) uids := make([]string, 0, len(result)) for _, rule := range result { uids = append(uids, rule.UID) @@ -634,7 +645,7 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) { store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore) - result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, gen.GenerateMany(3)) + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, toInsertRules(gen.GenerateMany(3))) uids := make([]string, 0, len(result)) for _, rule := range result { uids = append(uids, rule.UID) @@ -687,7 +698,7 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) { store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore) - result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, gen.GenerateMany(3)) + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, toInsertRules(gen.GenerateMany(3))) uids := make([]string, 0, len(result)) for _, rule := range result { uids = append(uids, rule.UID) @@ -754,7 +765,7 @@ func TestIntegrationInsertAlertRules(t *testing.T) { rules := append(gen.GenerateMany(5), recordingRulesGen.GenerateMany(5)...) - ids, err := store.InsertAlertRules(context.Background(), &usr, rules) + ids, err := store.InsertAlertRules(context.Background(), &usr, toInsertRules(rules)) require.NoError(t, err) require.Len(t, ids, len(rules)) @@ -819,7 +830,7 @@ func TestIntegrationInsertAlertRules(t *testing.T) { invalidMetric := "my_metric\x80" invalidRule := recordingRulesGen.Generate() invalidRule.Record.Metric = invalidMetric - _, err := store.InsertAlertRules(context.Background(), &usr, []models.AlertRule{invalidRule}) + _, err := store.InsertAlertRules(context.Background(), &usr, []models.InsertRule{{AlertRule: invalidRule}}) require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation) require.ErrorContains(t, err, "metric name for recording rule must be a valid utf8 string") }) @@ -827,7 +838,7 @@ func TestIntegrationInsertAlertRules(t *testing.T) { t.Run("clears fields that should not exist on recording rules", func(t *testing.T) { rule := recordingRulesGen.Generate() - rules, err := store.InsertAlertRules(context.Background(), &usr, []models.AlertRule{rule}) + rules, err := store.InsertAlertRules(context.Background(), &usr, []models.InsertRule{{AlertRule: rule}}) require.NoError(t, err) require.Len(t, rules, 1) ruleUID := rules[0].UID @@ -844,19 +855,19 @@ func TestIntegrationInsertAlertRules(t *testing.T) { }) t.Run("fail to insert rules with same ID", func(t *testing.T) { - _, err = store.InsertAlertRules(context.Background(), &usr, []models.AlertRule{rules[0]}) + _, err = store.InsertAlertRules(context.Background(), &usr, []models.InsertRule{{AlertRule: rules[0]}}) require.ErrorIs(t, err, models.ErrAlertRuleConflictBase) }) t.Run("should not fail insert rules with the same title in a folder", func(t *testing.T) { cp := models.CopyRule(&rules[0]) cp.UID = cp.UID + "-new" - _, err = store.InsertAlertRules(context.Background(), &usr, []models.AlertRule{*cp}) + _, err = store.InsertAlertRules(context.Background(), &usr, []models.InsertRule{{AlertRule: *cp}}) require.NoError(t, err) }) t.Run("should not let insert rules with the same UID", func(t *testing.T) { cp := models.CopyRule(&rules[0]) cp.Title = "unique-test-title" - _, err = store.InsertAlertRules(context.Background(), &usr, []models.AlertRule{*cp}) + _, err = store.InsertAlertRules(context.Background(), &usr, []models.InsertRule{{AlertRule: *cp}}) require.ErrorIs(t, err, models.ErrAlertRuleConflictBase) require.ErrorContains(t, err, fmt.Sprintf("Failed to save alert rule '%s' in organization %d due to conflict", cp.UID, cp.OrgID)) }) @@ -877,7 +888,7 @@ func TestIntegrationInsertAlertRules(t *testing.T) { return nil } - rules, err := store.InsertAlertRules(context.Background(), &usr, []models.AlertRule{rule}) + rules, err := store.InsertAlertRules(context.Background(), &usr, []models.InsertRule{{AlertRule: rule}}) require.NoError(t, err) require.Len(t, rules, 1) require.True(t, called) @@ -885,7 +896,7 @@ func TestIntegrationInsertAlertRules(t *testing.T) { t.Run("nil identity should be handled correctly", func(t *testing.T) { rule := gen.Generate() - ids, err = store.InsertAlertRules(context.Background(), nil, []models.AlertRule{rule}) + ids, err = store.InsertAlertRules(context.Background(), nil, []models.InsertRule{{AlertRule: rule}}) require.NoError(t, err) insertedRule, err := store.GetRuleByID(context.Background(), models.GetAlertRuleByIDQuery{ ID: ids[0].ID, @@ -942,7 +953,7 @@ func TestIntegrationAlertRulesNotificationSettings(t *testing.T) { require.NoError(t, store.SetProvenance(context.Background(), rule, rule.OrgID, p)) } - _, err := store.InsertAlertRules(context.Background(), &usr, deref) + _, err := store.InsertAlertRules(context.Background(), &usr, toInsertRules(deref)) require.NoError(t, err) t.Run("should find rules by receiver name", func(t *testing.T) { @@ -1220,7 +1231,7 @@ func TestIntegrationListNotificationSettings(t *testing.T) { orgRules := append(rulesWithNotificationsAndReceiver, rulesWithNotificationsAndTimeInterval...) - _, err := store.InsertAlertRules(context.Background(), &usr, deref) + _, err := store.InsertAlertRules(context.Background(), &usr, toInsertRules(deref)) require.NoError(t, err) result, err := store.ListNotificationSettings(context.Background(), models.ListNotificationSettingsQuery{OrgID: 1}) @@ -1319,7 +1330,7 @@ func TestIntegrationGetNamespacesByRuleUID(t *testing.T) { store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) rules := models.RuleGen.With(models.RuleMuts.WithOrgID(1), models.RuleMuts.WithRandomRecordingRules()).GenerateMany(5) - _, err := store.InsertAlertRules(context.Background(), &usr, rules) + _, err := store.InsertAlertRules(context.Background(), &usr, toInsertRules(rules)) require.NoError(t, err) uids := make([]string, 0, len(rules)) @@ -1381,7 +1392,7 @@ func TestIntegrationRuleGroupsCaseSensitive(t *testing.T) { group2 := gen.With(gen.WithGroupKey(groupKey2)).GenerateMany(1, 3) group3 := gen.With(gen.WithGroupKey(groupKey3)).GenerateMany(1, 3) - _, err := store.InsertAlertRules(context.Background(), &usr, append(append(append(misc, group1...), group2...), group3...)) + _, err := store.InsertAlertRules(context.Background(), &usr, toInsertRules(append(append(append(misc, group1...), group2...), group3...))) require.NoError(t, err) t.Run("GetAlertRulesGroupByRuleUID", func(t *testing.T) { @@ -1497,7 +1508,7 @@ func TestIntegrationListAlertRulesByGroupCaseSensitiveOrdering(t *testing.T) { // Insert all rules allRules := append(append(groupUpper, groupMixed...), groupLower...) - _, err := store.InsertAlertRules(context.Background(), &usr, allRules) + _, err := store.InsertAlertRules(context.Background(), &usr, toInsertRules(allRules)) require.NoError(t, err) t.Run("should order groups case-sensitively", func(t *testing.T) { @@ -1516,11 +1527,9 @@ func TestIntegrationListAlertRulesByGroupCaseSensitiveOrdering(t *testing.T) { } // Verify case-sensitive alphabetical ordering - // different databases may sort uppercase before lowercase or vice versa depending on character set, the important part is that the order is consistent and case-sensitive - expectedOrder := []string{"test", "Test", "TEST"} - alternateExpectedOrder := []string{"TEST", "Test", "test"} - if !slices.Equal(groupOrder, expectedOrder) && !slices.Equal(groupOrder, alternateExpectedOrder) { - t.Fatalf("groups are not ordered case-sensitively as expected. got: %v, want: %v or %v", groupOrder, expectedOrder, alternateExpectedOrder) + expectedOrder := []string{"TEST", "Test", "test"} + if !slices.Equal(groupOrder, expectedOrder) { + t.Fatalf("groups are not ordered case-sensitively as expected. got: %v, want: %v", groupOrder, expectedOrder) } // Verify each group contains the correct rules @@ -1663,7 +1672,7 @@ func TestIntegrationGetRuleVersions(t *testing.T) { gen := models.RuleGen gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval), gen.WithOrgID(orgID), gen.WithVersion(1)) - inserted, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, []models.AlertRule{gen.Generate()}) + inserted, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, []models.InsertRule{{AlertRule: gen.Generate()}}) require.NoError(t, err) ruleV1, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: inserted[0].UID}) require.NoError(t, err) @@ -2379,7 +2388,7 @@ func TestIntegration_ListDeletedRules(t *testing.T) { gen := models.RuleGen gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval), gen.WithOrgID(orgID)) - result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, []models.AlertRule{gen.Generate(), gen.Generate()}) + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, []models.InsertRule{{AlertRule: gen.Generate()}, {AlertRule: gen.Generate()}}) require.NoError(t, err) rule1, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: result[0].UID}) require.NoError(t, err) @@ -2467,7 +2476,7 @@ func TestIntegration_CleanUpDeletedAlertRules(t *testing.T) { gen = gen.With(gen.WithOrgID(orgID)) - result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, gen.GenerateMany(3)) + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, toInsertRules(gen.GenerateMany(3))) uids := make([]string, 0, len(result)) for _, rule := range result { uids = append(uids, rule.UID) diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 09abeaffba3..cfe790b9853 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -464,7 +464,7 @@ func (f *RuleStore) UpdateAlertRules(_ context.Context, _ *models.UserUID, q []m return nil } -func (f *RuleStore) InsertAlertRules(_ context.Context, _ *models.UserUID, q []models.AlertRule) ([]models.AlertRuleKeyWithId, error) { +func (f *RuleStore) InsertAlertRules(_ context.Context, _ *models.UserUID, q []models.InsertRule) ([]models.AlertRuleKeyWithId, error) { f.mtx.Lock() defer f.mtx.Unlock() f.RecordedOps = append(f.RecordedOps, q) @@ -475,7 +475,7 @@ func (f *RuleStore) InsertAlertRules(_ context.Context, _ *models.UserUID, q []m AlertRuleKey: rule.GetKey(), ID: rand.Int63(), }) - rulesPerOrg[rule.OrgID] = append(rulesPerOrg[rule.OrgID], rule) + rulesPerOrg[rule.OrgID] = append(rulesPerOrg[rule.OrgID], rule.AlertRule) } for orgID, rules := range rulesPerOrg { diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index 60674e8153c..4a8434d5616 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -134,8 +134,8 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s } require.NoError(t, err) - _, err = dbstore.InsertAlertRules(ctx, models.NewUserUID(user), []models.AlertRule{ - { + _, err = dbstore.InsertAlertRules(ctx, models.NewUserUID(user), []models.InsertRule{{ + AlertRule: models.AlertRule{ ID: 0, OrgID: orgID, @@ -162,7 +162,7 @@ func CreateTestAlertRuleWithLabels(t testing.TB, ctx context.Context, dbstore *s RuleGroup: ruleGroup, NoDataState: models.NoData, ExecErrState: models.AlertingErrState, - }, + }}, }) require.NoError(t, err) diff --git a/pkg/services/pluginsintegration/plugininstaller/service_test.go b/pkg/services/pluginsintegration/plugininstaller/service_test.go index f0d34cfc660..d2d525a3c82 100644 --- a/pkg/services/pluginsintegration/plugininstaller/service_test.go +++ b/pkg/services/pluginsintegration/plugininstaller/service_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "testing" + "time" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/manager/pluginfakes" @@ -208,7 +209,9 @@ func TestService_Run(t *testing.T) { t.Cleanup(func() { s.StopAsync() - err := s.AwaitTerminated(context.Background()) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err := s.AwaitTerminated(ctx) if tt.shouldThrowError { require.ErrorContains(t, err, "Failed to install plugin") return @@ -225,6 +228,8 @@ func TestService_Run(t *testing.T) { } require.NoError(t, err) + <-s.installComplete + if tt.shouldInstall { expectedInstalled := 0 expectedInstalledFromURL := 0 @@ -246,7 +251,6 @@ func TestService_Run(t *testing.T) { expectedInstalled++ } } - <-s.installComplete require.Equal(t, expectedInstalled, installed) require.Equal(t, expectedInstalledFromURL, installedFromURL) } diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 279839a4ae4..4fc4e1df131 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -162,4 +162,6 @@ func (oss *OSSMigrations) AddMigration(mg *Migrator) { ualert.ExpandAlertRuleUpdatedByMigration(mg) ualert.AddAlertRuleGroupIndexMigration(mg) + + ualert.CollateBinAlertRuleGroup(mg) } diff --git a/pkg/services/sqlstore/migrations/ualert/alert_rule_group_collation_bin.go b/pkg/services/sqlstore/migrations/ualert/alert_rule_group_collation_bin.go new file mode 100644 index 00000000000..4464d6669d3 --- /dev/null +++ b/pkg/services/sqlstore/migrations/ualert/alert_rule_group_collation_bin.go @@ -0,0 +1,10 @@ +package ualert + +import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + +// CollateBinAlertRuleGroup ensures that rule_group column collates in the same way go sorts strings. +func CollateBinAlertRuleGroup(mg *migrator.Migrator) { + mg.AddMigration("ensure rule_group column sorts the same way as golang", migrator.NewRawSQLMigration(""). + Mysql("ALTER TABLE alert_rule MODIFY rule_group VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL;"). + Postgres(`ALTER TABLE alert_rule ALTER COLUMN rule_group SET DATA TYPE varchar(190) COLLATE "C";`)) +} diff --git a/pkg/storage/secret/metadata/data/secure_value_get_latest_version_and_created_at.sql b/pkg/storage/secret/metadata/data/secure_value_get_latest_version_and_created_at.sql index 1c804c2d031..c7349f27a03 100644 --- a/pkg/storage/secret/metadata/data/secure_value_get_latest_version_and_created_at.sql +++ b/pkg/storage/secret/metadata/data/secure_value_get_latest_version_and_created_at.sql @@ -1,7 +1,9 @@ SELECT {{ .Ident "created" }}, {{ .Ident "version" }}, - {{ .Ident "active" }} + {{ .Ident "active" }}, + {{ .Ident "namespace" }}, + {{ .Ident "name" }} FROM {{ .Ident "secret_secure_value" }} WHERE diff --git a/pkg/storage/secret/metadata/data/secure_value_list_by_lease_token.sql b/pkg/storage/secret/metadata/data/secure_value_list_by_lease_token.sql index 275143a9672..fc62c271df4 100644 --- a/pkg/storage/secret/metadata/data/secure_value_list_by_lease_token.sql +++ b/pkg/storage/secret/metadata/data/secure_value_list_by_lease_token.sql @@ -18,7 +18,8 @@ SELECT {{ .Ident "owner_reference_api_group" }}, {{ .Ident "owner_reference_api_version" }}, {{ .Ident "owner_reference_kind" }}, - {{ .Ident "owner_reference_name" }} + {{ .Ident "owner_reference_name" }}, + {{ .Ident "lease_token" }} FROM {{ .Ident "secret_secure_value" }} WHERE diff --git a/pkg/storage/secret/metadata/keeper_store.go b/pkg/storage/secret/metadata/keeper_store.go index 6036bfe2502..d4516158c81 100644 --- a/pkg/storage/secret/metadata/keeper_store.go +++ b/pkg/storage/secret/metadata/keeper_store.go @@ -201,6 +201,10 @@ func (s *keeperMetadataStorage) read(ctx context.Context, namespace, name string if err := res.Err(); err != nil { return nil, fmt.Errorf("read rows error: %w", err) } + if keeper.Namespace != namespace || keeper.Name != name { + return nil, fmt.Errorf("bug: expected to find keeper namespace=%+v name=%+v but got keeper namespace=%+v name%+v", + namespace, name, keeper.Namespace, keeper.Name) + } return &keeper, nil } @@ -405,6 +409,10 @@ func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namesp return nil, fmt.Errorf("error reading keeper row: %w", err) } + if row.Namespace != namespace.String() { + return nil, fmt.Errorf("bug: expected to list keepers for namespace %+v but got one from namespace %+v", namespace, row.Namespace) + } + keeper, err := row.toKubernetes() if err != nil { return nil, fmt.Errorf("failed to convert to kubernetes object: %w", err) @@ -706,6 +714,10 @@ func (s *keeperMetadataStorage) GetActiveKeeper(ctx context.Context, namespace s return keeper, fmt.Errorf("converting from keeperDB to kubernetes struct: %w", err) } + if keeperDB.Namespace != namespace { + return nil, fmt.Errorf("bug: expected to find keeper to namespace %+v but got one for namespace %+v", namespace, keeperDB.Namespace) + } + return keeper, nil } diff --git a/pkg/storage/secret/metadata/secure_value_store.go b/pkg/storage/secret/metadata/secure_value_store.go index d3a137ea611..1fc3a788cf2 100644 --- a/pkg/storage/secret/metadata/secure_value_store.go +++ b/pkg/storage/secret/metadata/secure_value_store.go @@ -199,14 +199,21 @@ func (s *secureValueMetadataStorage) getLatestVersionAndCreatedAt(ctx context.Co } var ( - createdAt int64 - version int64 - active bool + createdAt int64 + version int64 + active bool + namespaceFromDB string + nameFromDB string ) - if err := rows.Scan(&createdAt, &version, &active); err != nil { + if err := rows.Scan(&createdAt, &version, &active, &namespaceFromDB, &nameFromDB); err != nil { return versionAndCreatedAt{}, fmt.Errorf("scanning version from returned rows: %w", err) } + if namespaceFromDB != namespace.String() || nameFromDB != name { + return versionAndCreatedAt{}, fmt.Errorf("bug: expected to find latest version for namespace=%+v name=%+v but got version for namespace=%+v name=%+v", + namespace, name, namespaceFromDB, nameFromDB) + } + if !active { createdAt = 0 } @@ -255,6 +262,11 @@ func (s *secureValueMetadataStorage) readActiveVersion(ctx context.Context, name if err := res.Err(); err != nil { return secureValueDB{}, fmt.Errorf("read rows error: %w", err) } + + if secureValue.Namespace != namespace.String() || secureValue.Name != name { + return secureValueDB{}, fmt.Errorf("bug: expected to read secure value %+v from namespace %+v, but got a different row", name, namespace) + } + return secureValue, nil } @@ -364,6 +376,10 @@ func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.N return nil, fmt.Errorf("bug: read an inactive version: row=%+v", row) } + if row.Namespace != namespace.String() { + return nil, fmt.Errorf("bug: expected to list secure values from namespace %+v but got one from namespace %+v", namespace.String(), row.Namespace) + } + secureValue, err := row.toKubernetes() if err != nil { return nil, fmt.Errorf("convert to kubernetes object: %w", err) @@ -644,6 +660,7 @@ func (s *secureValueMetadataStorage) listByLeaseToken(ctx context.Context, lease secureValues := make([]secretv1beta1.SecureValue, 0) for rows.Next() { row := secureValueDB{} + var leaseTokenDB string err = rows.Scan(&row.GUID, &row.Name, &row.Namespace, &row.Annotations, @@ -653,12 +670,17 @@ func (s *secureValueMetadataStorage) listByLeaseToken(ctx context.Context, lease &row.Description, &row.Keeper, &row.Decrypters, &row.Ref, &row.ExternalID, &row.Version, &row.Active, &row.OwnerReferenceAPIGroup, &row.OwnerReferenceAPIVersion, &row.OwnerReferenceKind, &row.OwnerReferenceName, + &leaseTokenDB, ) if err != nil { return nil, fmt.Errorf("error reading secure value row: %w", err) } + if leaseTokenDB != leaseToken { + return nil, fmt.Errorf("bug: expected to list secure values with lease token %+v but got a secure value with another lease token %+v", leaseToken, leaseToken) + } + secureValue, err := row.toKubernetes() if err != nil { return nil, fmt.Errorf("convert to kubernetes object: %w", err) diff --git a/pkg/storage/secret/metadata/testdata/mysql--secure_value_get_latest_version_and_created_at-get latest secure value version.sql b/pkg/storage/secret/metadata/testdata/mysql--secure_value_get_latest_version_and_created_at-get latest secure value version.sql index 661c6474221..2e88ee079c2 100755 --- a/pkg/storage/secret/metadata/testdata/mysql--secure_value_get_latest_version_and_created_at-get latest secure value version.sql +++ b/pkg/storage/secret/metadata/testdata/mysql--secure_value_get_latest_version_and_created_at-get latest secure value version.sql @@ -1,7 +1,9 @@ SELECT `created`, `version`, - `active` + `active`, + `namespace`, + `name` FROM `secret_secure_value` WHERE diff --git a/pkg/storage/secret/metadata/testdata/mysql--secure_value_list_by_lease_token-list by lease token.sql b/pkg/storage/secret/metadata/testdata/mysql--secure_value_list_by_lease_token-list by lease token.sql index 07534407789..0485a5c94c1 100755 --- a/pkg/storage/secret/metadata/testdata/mysql--secure_value_list_by_lease_token-list by lease token.sql +++ b/pkg/storage/secret/metadata/testdata/mysql--secure_value_list_by_lease_token-list by lease token.sql @@ -18,7 +18,8 @@ SELECT `owner_reference_api_group`, `owner_reference_api_version`, `owner_reference_kind`, - `owner_reference_name` + `owner_reference_name`, + `lease_token` FROM `secret_secure_value` WHERE diff --git a/pkg/storage/secret/metadata/testdata/postgres--secure_value_get_latest_version_and_created_at-get latest secure value version.sql b/pkg/storage/secret/metadata/testdata/postgres--secure_value_get_latest_version_and_created_at-get latest secure value version.sql index e1c3834ecbb..a9f21aafc0a 100755 --- a/pkg/storage/secret/metadata/testdata/postgres--secure_value_get_latest_version_and_created_at-get latest secure value version.sql +++ b/pkg/storage/secret/metadata/testdata/postgres--secure_value_get_latest_version_and_created_at-get latest secure value version.sql @@ -1,7 +1,9 @@ SELECT "created", "version", - "active" + "active", + "namespace", + "name" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/metadata/testdata/postgres--secure_value_list_by_lease_token-list by lease token.sql b/pkg/storage/secret/metadata/testdata/postgres--secure_value_list_by_lease_token-list by lease token.sql index 55077bff201..0829c0a7ceb 100755 --- a/pkg/storage/secret/metadata/testdata/postgres--secure_value_list_by_lease_token-list by lease token.sql +++ b/pkg/storage/secret/metadata/testdata/postgres--secure_value_list_by_lease_token-list by lease token.sql @@ -18,7 +18,8 @@ SELECT "owner_reference_api_group", "owner_reference_api_version", "owner_reference_kind", - "owner_reference_name" + "owner_reference_name", + "lease_token" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_get_latest_version_and_created_at-get latest secure value version.sql b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_get_latest_version_and_created_at-get latest secure value version.sql index e1c3834ecbb..a9f21aafc0a 100755 --- a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_get_latest_version_and_created_at-get latest secure value version.sql +++ b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_get_latest_version_and_created_at-get latest secure value version.sql @@ -1,7 +1,9 @@ SELECT "created", "version", - "active" + "active", + "namespace", + "name" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_list_by_lease_token-list by lease token.sql b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_list_by_lease_token-list by lease token.sql index 55077bff201..0829c0a7ceb 100755 --- a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_list_by_lease_token-list by lease token.sql +++ b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_list_by_lease_token-list by lease token.sql @@ -18,7 +18,8 @@ SELECT "owner_reference_api_group", "owner_reference_api_version", "owner_reference_kind", - "owner_reference_name" + "owner_reference_name", + "lease_token" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/unified/federated/stats_test.go b/pkg/storage/unified/federated/stats_test.go index 89d50d1da87..04812084f65 100644 --- a/pkg/storage/unified/federated/stats_test.go +++ b/pkg/storage/unified/federated/stats_test.go @@ -79,8 +79,8 @@ func TestIntegrationDirectSQLStats(t *testing.T) { ruleStore := ngalertstore.SetupStoreForTesting(t, db) dashboardUID := "test" - _, err = ruleStore.InsertAlertRules(context.Background(), ngmodels.NewUserUID(tempUser), []ngmodels.AlertRule{ - { + _, err = ruleStore.InsertAlertRules(context.Background(), ngmodels.NewUserUID(tempUser), []ngmodels.InsertRule{{ + AlertRule: ngmodels.AlertRule{ DashboardUID: &dashboardUID, UID: "test", Title: "test", @@ -102,7 +102,7 @@ func TestIntegrationDirectSQLStats(t *testing.T) { ExecErrState: ngmodels.ExecutionErrorState(ngmodels.Alerting), NoDataState: ngmodels.Alerting, IntervalSeconds: 60, - }}) + }}}) require.NoError(t, err) _, err = dashStore.SaveDashboard(ctx, dashboards.SaveDashboardCommand{ diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index 36c68183203..a8c1efb431c 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -32,6 +32,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" authlib "github.com/grafana/authlib/types" @@ -1178,10 +1179,11 @@ func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resourcepb.R facets[f.Field] = bleve.NewFacetRequest(f.Field, int(f.Limit)) } - // Convert resource-specific fields to bleve fields (just considers dashboard fields for now) + // Convert resource-specific fields to bleve fields. + // TODO: use b.fields.Field(f) instead of builders.DashboardFields() to avoid dashboard-specific code in search server. fields := make([]string, 0, len(req.Fields)) for _, f := range req.Fields { - if slices.Contains(DashboardFields(), f) { + if slices.Contains(builders.DashboardFields(), f) { f = resource.SEARCH_FIELD_PREFIX + f } fields = append(fields, f) @@ -1534,7 +1536,8 @@ func getSortFields(req *resourcepb.ResourceSearchRequest) []string { input = field } - if slices.Contains(DashboardFields(), input) { + // TODO: pass fields parameter and use fields.Field(input) instead of builders.DashboardFields() to avoid dashboard-specific code. + if slices.Contains(builders.DashboardFields(), input) { input = resource.SEARCH_FIELD_PREFIX + input } diff --git a/pkg/storage/unified/search/bleve_search_test.go b/pkg/storage/unified/search/bleve_search_test.go index 600dd0cfe04..7374374fd25 100644 --- a/pkg/storage/unified/search/bleve_search_test.go +++ b/pkg/storage/unified/search/bleve_search_test.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/storage/unified/search" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" ) const threshold = 9999 @@ -265,8 +266,8 @@ func newTestDashboardsIndex(t testing.TB, threshold int64, size int64, writer re ctx := identity.WithRequester(context.Background(), &user.SignedInUser{Namespace: "ns"}) - info, err := search.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { - return &search.DashboardDocumentBuilder{ + info, err := builders.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { + return &builders.DashboardDocumentBuilder{ Namespace: namespace, Blob: blob, Stats: make(map[string]map[string]int64), // empty stats diff --git a/pkg/storage/unified/search/bleve_test.go b/pkg/storage/unified/search/bleve_test.go index 4c66fc4c1b5..2d2e7142eaf 100644 --- a/pkg/storage/unified/search/bleve_test.go +++ b/pkg/storage/unified/search/bleve_test.go @@ -32,6 +32,7 @@ import ( "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" ) // This verifies that we close all indexes properly and shutdown all background goroutines from our tests. @@ -76,8 +77,8 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) { t.Run("build dashboards", func(t *testing.T) { key := dashboardskey - info, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { - return &DashboardDocumentBuilder{ + info, err := builders.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { + return &builders.DashboardDocumentBuilder{ Namespace: namespace, Blob: blob, Stats: make(map[string]map[string]int64), // empty stats @@ -107,9 +108,9 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) { Title: "aaa (dash)", Folder: "xxx", Fields: map[string]any{ - DASHBOARD_PANEL_TYPES: []string{"timeseries", "table"}, - DASHBOARD_ERRORS_TODAY: 25, - DASHBOARD_VIEWS_LAST_1_DAYS: 50, + builders.DASHBOARD_PANEL_TYPES: []string{"timeseries", "table"}, + builders.DASHBOARD_ERRORS_TODAY: 25, + builders.DASHBOARD_VIEWS_LAST_1_DAYS: 50, }, Labels: map[string]string{ utils.LabelKeyDeprecatedInternalID: "10", // nolint:staticcheck @@ -140,9 +141,9 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) { Title: "bbb (dash)", Folder: "xxx", Fields: map[string]any{ - DASHBOARD_PANEL_TYPES: []string{"timeseries"}, - DASHBOARD_ERRORS_TODAY: 40, - DASHBOARD_VIEWS_LAST_1_DAYS: 100, + builders.DASHBOARD_PANEL_TYPES: []string{"timeseries"}, + builders.DASHBOARD_ERRORS_TODAY: 40, + builders.DASHBOARD_VIEWS_LAST_1_DAYS: 100, }, Tags: []string{"aa"}, Labels: map[string]string{ @@ -271,15 +272,15 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) { Key: key, }, Limit: 100000, - Fields: []string{DASHBOARD_ERRORS_TODAY, DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"}, + Fields: []string{builders.DASHBOARD_ERRORS_TODAY, builders.DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"}, SortBy: []*resourcepb.ResourceSearchRequest_Sort{ - {Field: "fields." + DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true}, + {Field: "fields." + builders.DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true}, }, }, nil, nil) require.NoError(t, err) require.Equal(t, 2, len(rsp.Results.Columns)) - require.Equal(t, DASHBOARD_ERRORS_TODAY, rsp.Results.Columns[0].Name) - require.Equal(t, DASHBOARD_VIEWS_LAST_1_DAYS, rsp.Results.Columns[1].Name) + require.Equal(t, builders.DASHBOARD_ERRORS_TODAY, rsp.Results.Columns[0].Name) + require.Equal(t, builders.DASHBOARD_VIEWS_LAST_1_DAYS, rsp.Results.Columns[1].Name) // sorted descending so should start with highest dashboard_views_last_1_days (100) val, err := resource.DecodeCell(rsp.Results.Columns[1], 0, rsp.Results.Rows[0].Cells[1]) require.NoError(t, err) @@ -291,9 +292,9 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) { Key: key, }, Limit: 100000, - Fields: []string{DASHBOARD_ERRORS_TODAY, DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"}, + Fields: []string{builders.DASHBOARD_ERRORS_TODAY, builders.DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"}, SortBy: []*resourcepb.ResourceSearchRequest_Sort{ - {Field: "fields." + DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true}, + {Field: "fields." + builders.DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true}, }, }, nil, nil) require.NoError(t, err) diff --git a/pkg/storage/unified/search/dashboard.go b/pkg/storage/unified/search/builders/dashboard.go similarity index 96% rename from pkg/storage/unified/search/dashboard.go rename to pkg/storage/unified/search/builders/dashboard.go index 44a822a1b4c..4f8d55111a9 100644 --- a/pkg/storage/unified/search/dashboard.go +++ b/pkg/storage/unified/search/builders/dashboard.go @@ -1,4 +1,4 @@ -package search +package builders import ( "bytes" @@ -15,10 +15,6 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resourcepb" ) -//------------------------------------------------------------ -// Standard dashboard fields -//------------------------------------------------------------ - const DASHBOARD_SCHEMA_VERSION = "schema_version" const DASHBOARD_LINK_COUNT = "link_count" const DASHBOARD_PANEL_TYPES = "panel_types" @@ -26,10 +22,6 @@ const DASHBOARD_DS_TYPES = "ds_types" const DASHBOARD_TRANSFORMATIONS = "transformation" const DASHBOARD_LIBRARY_PANEL_REFERENCE = "reference.LibraryPanel" -//------------------------------------------------------------ -// The following fields are added in enterprise -//------------------------------------------------------------ - const DASHBOARD_VIEWS_LAST_1_DAYS = "views_last_1_days" const DASHBOARD_VIEWS_LAST_7_DAYS = "views_last_7_days" const DASHBOARD_VIEWS_LAST_30_DAYS = "views_last_30_days" @@ -268,7 +260,6 @@ func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resou if err != nil { return nil, err } - // metadata name is the dashboard uid summary.UID = obj.GetName() summary.ID = obj.GetDeprecatedInternalID() // nolint:staticcheck @@ -331,7 +322,6 @@ func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resou doc.Fields[DASHBOARD_TRANSFORMATIONS] = transformations } - // Add the stats fields for k, v := range s.Stats[summary.UID] { doc.Fields[k] = v } diff --git a/pkg/storage/unified/search/dashboard_stats.go b/pkg/storage/unified/search/builders/dashboard_stats.go similarity index 94% rename from pkg/storage/unified/search/dashboard_stats.go rename to pkg/storage/unified/search/builders/dashboard_stats.go index c82db38110e..56024275649 100644 --- a/pkg/storage/unified/search/dashboard_stats.go +++ b/pkg/storage/unified/search/builders/dashboard_stats.go @@ -1,4 +1,4 @@ -package search +package builders import ( "context" diff --git a/pkg/storage/unified/search/builders/document.go b/pkg/storage/unified/search/builders/document.go new file mode 100644 index 00000000000..675f95b4b78 --- /dev/null +++ b/pkg/storage/unified/search/builders/document.go @@ -0,0 +1,71 @@ +package builders + +import ( + "context" + + claims "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/store/kind/dashboard" + "github.com/grafana/grafana/pkg/storage/unified/resource" +) + +// All returns all document builders from this package. +// These builders have dependencies on Grafana apps (dashboard and user). +func All(sql db.DB, sprinkles DashboardStats) ([]resource.DocumentBuilderInfo, error) { + dashboards, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { + logger := log.New("dashboard_builder", "namespace", namespace) + dsinfo := []*dashboard.DatasourceQueryResult{{}} + ns, err := claims.ParseNamespace(namespace) + if err != nil && sql != nil { + rows, err := sql.GetSqlxSession().Query(ctx, "SELECT uid,type,name,is_default FROM data_source WHERE org_id=?", ns.OrgID) + if err != nil { + return nil, err + } + + defer func() { + _ = rows.Close() + }() + + for rows.Next() { + info := &dashboard.DatasourceQueryResult{} + err = rows.Scan(&info.UID, &info.Type, &info.Name, &info.IsDefault) + if err != nil { + return nil, err + } + dsinfo = append(dsinfo, info) + } + } + + var stats map[string]map[string]int64 + if sprinkles != nil { + stats, err = sprinkles.GetStats(ctx, namespace) + if err != nil { + logger.Warn("Failed to get sprinkles", "error", err) + } + } + + return &DashboardDocumentBuilder{ + Namespace: namespace, + Blob: blob, + Stats: stats, + DatasourceLookup: dashboard.CreateDatasourceLookup(dsinfo), + }, nil + }) + + if err != nil { + return nil, err + } + + users, err := GetUserBuilder() + if err != nil { + return nil, err + } + + extGroupMappings, err := GetExternalGroupMappingBuilder() + if err != nil { + return nil, err + } + + return []resource.DocumentBuilderInfo{dashboards, users, extGroupMappings}, nil +} diff --git a/pkg/storage/unified/search/document_test.go b/pkg/storage/unified/search/builders/document_test.go similarity index 71% rename from pkg/storage/unified/search/document_test.go rename to pkg/storage/unified/search/builders/document_test.go index 285f3178120..bee719aeefa 100644 --- a/pkg/storage/unified/search/document_test.go +++ b/pkg/storage/unified/search/builders/document_test.go @@ -1,4 +1,4 @@ -package search_test +package builders import ( "context" @@ -14,7 +14,6 @@ import ( "github.com/grafana/grafana/pkg/services/store/kind/dashboard" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - "github.com/grafana/grafana/pkg/storage/unified/search" ) func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string, key *resourcepb.ResourceKey, names []string) { @@ -36,7 +35,6 @@ func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string outpath := filepath.Join("testdata", "doc", prefix+"-out.json") - // test path // nolint:gosec expect, _ := os.ReadFile(outpath) if !assert.JSONEq(t, string(expect), string(out)) { @@ -47,6 +45,31 @@ func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string } } +func TestUserDocumentBuilder(t *testing.T) { + info, err := GetUserBuilder() + require.NoError(t, err) + doSnapshotTests(t, info.Builder, "user", &resourcepb.ResourceKey{ + Namespace: "default", + Group: "iam.grafana.app", + Resource: "users", + }, []string{ + "user-with-login-and-email", + "user-with-login-only", + }) +} + +func TestExternalGroupMappingDocumentBuilder(t *testing.T) { + info, err := GetExternalGroupMappingBuilder() + require.NoError(t, err) + doSnapshotTests(t, info.Builder, "external_group_mapping", &resourcepb.ResourceKey{ + Namespace: "default", + Group: "iam.grafana.app", + Resource: "externalgroupmappings", + }, []string{ + "mapping-with-team-and-group", + }) +} + func TestDashboardDocumentBuilder(t *testing.T) { key := &resourcepb.ResourceKey{ Namespace: "default", @@ -54,18 +77,18 @@ func TestDashboardDocumentBuilder(t *testing.T) { Resource: "dashboards", } - info, err := search.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { - return &search.DashboardDocumentBuilder{ + info, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { + return &DashboardDocumentBuilder{ Namespace: namespace, Blob: blob, Stats: map[string]map[string]int64{ "aaa": { - search.DASHBOARD_ERRORS_LAST_1_DAYS: 1, - search.DASHBOARD_ERRORS_LAST_7_DAYS: 1, + DASHBOARD_ERRORS_LAST_1_DAYS: 1, + DASHBOARD_ERRORS_LAST_7_DAYS: 1, }, }, DatasourceLookup: dashboard.CreateDatasourceLookup([]*dashboard.DatasourceQueryResult{{ - Name: "TheDisplayName", // used to be the unique ID! + Name: "TheDisplayName", Type: "my-custom-plugin", UID: "DSUID", }}), @@ -76,12 +99,10 @@ func TestDashboardDocumentBuilder(t *testing.T) { builder, err := info.Namespaced(context.Background(), key.Namespace, nil) require.NoError(t, err) - // Dashboards (custom) doSnapshotTests(t, builder, "dashboard", key, []string{ "aaa", }) - // Standard builder = resource.StandardDocumentBuilder() doSnapshotTests(t, builder, "folder", &resourcepb.ResourceKey{ Namespace: "default", diff --git a/pkg/storage/unified/search/builders/external_group_mapping.go b/pkg/storage/unified/search/builders/external_group_mapping.go new file mode 100644 index 00000000000..809075c2ec2 --- /dev/null +++ b/pkg/storage/unified/search/builders/external_group_mapping.go @@ -0,0 +1,80 @@ +package builders + +import ( + "bytes" + "context" + "encoding/json" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" +) + +const ( + EXTERNAL_GROUP_MAPPING_TEAM = "team" + EXTERNAL_GROUP_MAPPING_EXTERNAL_GROUP = "external_group" +) + +var ExternalGroupMappingTableColumnDefinitions = map[string]*resourcepb.ResourceTableColumnDefinition{ + EXTERNAL_GROUP_MAPPING_TEAM: { + Name: EXTERNAL_GROUP_MAPPING_TEAM, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + Description: "The team name associated with the external group mapping", + Properties: &resourcepb.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + EXTERNAL_GROUP_MAPPING_EXTERNAL_GROUP: { + Name: EXTERNAL_GROUP_MAPPING_EXTERNAL_GROUP, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + Description: "The external group name/id associated with the external group mapping", + Properties: &resourcepb.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, +} + +func GetExternalGroupMappingBuilder() (resource.DocumentBuilderInfo, error) { + values := make([]*resourcepb.ResourceTableColumnDefinition, 0, len(ExternalGroupMappingTableColumnDefinitions)) + for _, v := range ExternalGroupMappingTableColumnDefinitions { + values = append(values, v) + } + + fields, err := resource.NewSearchableDocumentFields(values) + return resource.DocumentBuilderInfo{ + GroupResource: iamv0.ExternalGroupMappingResourceInfo.GroupResource(), + Fields: fields, + Builder: new(extGroupMappingDocumentBuilder), + }, err +} + +var _ resource.DocumentBuilder = new(extGroupMappingDocumentBuilder) + +type extGroupMappingDocumentBuilder struct{} + +// BuildDocument implements resource.DocumentBuilder. +func (u *extGroupMappingDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*resource.IndexableDocument, error) { + extGroupMapping := &iamv0.ExternalGroupMapping{} + err := json.NewDecoder(bytes.NewReader(value)).Decode(extGroupMapping) + if err != nil { + return nil, err + } + + obj, err := utils.MetaAccessor(extGroupMapping) + if err != nil { + return nil, err + } + + doc := resource.NewIndexableDocument(key, rv, obj) + + doc.Fields = make(map[string]any) + if extGroupMapping.Spec.TeamRef.Name != "" { + doc.Fields[EXTERNAL_GROUP_MAPPING_TEAM] = extGroupMapping.Spec.TeamRef.Name + } + if extGroupMapping.Spec.ExternalGroupId != "" { + doc.Fields[EXTERNAL_GROUP_MAPPING_EXTERNAL_GROUP] = extGroupMapping.Spec.ExternalGroupId + } + + return doc, nil +} diff --git a/pkg/storage/unified/search/testdata/doc/dashboard-aaa-out.json b/pkg/storage/unified/search/builders/testdata/doc/dashboard-aaa-out.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/dashboard-aaa-out.json rename to pkg/storage/unified/search/builders/testdata/doc/dashboard-aaa-out.json diff --git a/pkg/storage/unified/search/testdata/doc/dashboard-aaa.json b/pkg/storage/unified/search/builders/testdata/doc/dashboard-aaa.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/dashboard-aaa.json rename to pkg/storage/unified/search/builders/testdata/doc/dashboard-aaa.json diff --git a/pkg/storage/unified/search/builders/testdata/doc/external_group_mapping-mapping-with-team-and-group-out.json b/pkg/storage/unified/search/builders/testdata/doc/external_group_mapping-mapping-with-team-and-group-out.json new file mode 100644 index 00000000000..935ff7b34b7 --- /dev/null +++ b/pkg/storage/unified/search/builders/testdata/doc/external_group_mapping-mapping-with-team-and-group-out.json @@ -0,0 +1,17 @@ +{ + "key": { + "namespace": "default", + "group": "iam.grafana.app", + "resource": "externalgroupmappings", + "name": "mapping-with-team-and-group" + }, + "name": "mapping-with-team-and-group", + "rv": 1234, + "fields": { + "team": "team1", + "external_group": "group1" + }, + "title": "mapping-with-team-and-group", + "title_ngram": "mapping-with-team-and-group", + "title_phrase": "mapping-with-team-and-group" +} \ No newline at end of file diff --git a/pkg/storage/unified/search/builders/testdata/doc/external_group_mapping-mapping-with-team-and-group.json b/pkg/storage/unified/search/builders/testdata/doc/external_group_mapping-mapping-with-team-and-group.json new file mode 100644 index 00000000000..c34f8ae04ab --- /dev/null +++ b/pkg/storage/unified/search/builders/testdata/doc/external_group_mapping-mapping-with-team-and-group.json @@ -0,0 +1,14 @@ +{ + "apiVersion": "iam.grafana.app/v0alpha1", + "kind": "ExternalGroupMapping", + "metadata": { + "name": "mapping-with-team-and-group", + "namespace": "default" + }, + "spec": { + "teamRef": { + "name": "team1" + }, + "externalGroupId": "group1" + } +} diff --git a/pkg/storage/unified/search/testdata/doc/folder-aaa-out.json b/pkg/storage/unified/search/builders/testdata/doc/folder-aaa-out.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/folder-aaa-out.json rename to pkg/storage/unified/search/builders/testdata/doc/folder-aaa-out.json diff --git a/pkg/storage/unified/search/testdata/doc/folder-aaa.json b/pkg/storage/unified/search/builders/testdata/doc/folder-aaa.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/folder-aaa.json rename to pkg/storage/unified/search/builders/testdata/doc/folder-aaa.json diff --git a/pkg/storage/unified/search/testdata/doc/folder-bbb-out.json b/pkg/storage/unified/search/builders/testdata/doc/folder-bbb-out.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/folder-bbb-out.json rename to pkg/storage/unified/search/builders/testdata/doc/folder-bbb-out.json diff --git a/pkg/storage/unified/search/testdata/doc/folder-bbb.json b/pkg/storage/unified/search/builders/testdata/doc/folder-bbb.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/folder-bbb.json rename to pkg/storage/unified/search/builders/testdata/doc/folder-bbb.json diff --git a/pkg/storage/unified/search/testdata/doc/playlist-aaa-out.json b/pkg/storage/unified/search/builders/testdata/doc/playlist-aaa-out.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/playlist-aaa-out.json rename to pkg/storage/unified/search/builders/testdata/doc/playlist-aaa-out.json diff --git a/pkg/storage/unified/search/testdata/doc/playlist-aaa.json b/pkg/storage/unified/search/builders/testdata/doc/playlist-aaa.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/playlist-aaa.json rename to pkg/storage/unified/search/builders/testdata/doc/playlist-aaa.json diff --git a/pkg/storage/unified/search/testdata/doc/report-aaa-out.json b/pkg/storage/unified/search/builders/testdata/doc/report-aaa-out.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/report-aaa-out.json rename to pkg/storage/unified/search/builders/testdata/doc/report-aaa-out.json diff --git a/pkg/storage/unified/search/testdata/doc/report-aaa.json b/pkg/storage/unified/search/builders/testdata/doc/report-aaa.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/report-aaa.json rename to pkg/storage/unified/search/builders/testdata/doc/report-aaa.json diff --git a/pkg/storage/unified/search/testdata/doc/user-example-out.json b/pkg/storage/unified/search/builders/testdata/doc/user-example-out.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/user-example-out.json rename to pkg/storage/unified/search/builders/testdata/doc/user-example-out.json diff --git a/pkg/storage/unified/search/testdata/doc/user-example.json b/pkg/storage/unified/search/builders/testdata/doc/user-example.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/user-example.json rename to pkg/storage/unified/search/builders/testdata/doc/user-example.json diff --git a/pkg/storage/unified/search/testdata/doc/user-user-with-login-and-email-out.json b/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email-out.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/user-user-with-login-and-email-out.json rename to pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email-out.json diff --git a/pkg/storage/unified/search/testdata/doc/user-user-with-login-and-email.json b/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/user-user-with-login-and-email.json rename to pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email.json diff --git a/pkg/storage/unified/search/testdata/doc/user-user-with-login-only-out.json b/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only-out.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/user-user-with-login-only-out.json rename to pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only-out.json diff --git a/pkg/storage/unified/search/testdata/doc/user-user-with-login-only.json b/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only.json similarity index 100% rename from pkg/storage/unified/search/testdata/doc/user-user-with-login-only.json rename to pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only.json diff --git a/pkg/storage/unified/search/user.go b/pkg/storage/unified/search/builders/user.go similarity index 89% rename from pkg/storage/unified/search/user.go rename to pkg/storage/unified/search/builders/user.go index 6a09dade5f7..b17cdb99b15 100644 --- a/pkg/storage/unified/search/user.go +++ b/pkg/storage/unified/search/builders/user.go @@ -1,4 +1,4 @@ -package search +package builders import ( "bytes" @@ -16,7 +16,7 @@ const ( USER_LOGIN = "login" ) -var TableColumnDefinitions = map[string]*resourcepb.ResourceTableColumnDefinition{ +var UserTableColumnDefinitions = map[string]*resourcepb.ResourceTableColumnDefinition{ USER_EMAIL: { Name: USER_EMAIL, Type: resourcepb.ResourceTableColumnDefinition_STRING, @@ -38,8 +38,8 @@ var TableColumnDefinitions = map[string]*resourcepb.ResourceTableColumnDefinitio } func GetUserBuilder() (resource.DocumentBuilderInfo, error) { - values := make([]*resourcepb.ResourceTableColumnDefinition, 0, len(TableColumnDefinitions)) - for _, v := range TableColumnDefinitions { + values := make([]*resourcepb.ResourceTableColumnDefinition, 0, len(UserTableColumnDefinitions)) + for _, v := range UserTableColumnDefinitions { values = append(values, v) } fields, err := resource.NewSearchableDocumentFields(values) @@ -54,7 +54,6 @@ var _ resource.DocumentBuilder = new(userDocumentBuilder) type userDocumentBuilder struct{} -// BuildDocument implements resource.DocumentBuilder. func (u *userDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*resource.IndexableDocument, error) { user := &iamv0.User{} err := json.NewDecoder(bytes.NewReader(value)).Decode(user) diff --git a/pkg/storage/unified/search/document.go b/pkg/storage/unified/search/document.go index eb0059c010d..fff4c31ead9 100644 --- a/pkg/storage/unified/search/document.go +++ b/pkg/storage/unified/search/document.go @@ -1,87 +1,32 @@ package search import ( - "context" - - claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/store/kind/dashboard" "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" ) -// The default list of open source document builders +// StandardDocumentBuilders provides the default list of document builders for open source Grafana. +// It combines the standard document builder with external builders for dashboards and users. type StandardDocumentBuilders struct { sql db.DB - sprinkles DashboardStats + sprinkles builders.DashboardStats } -// Hooked up so wire can fill in different sprinkles -func ProvideDocumentBuilders(sql db.DB, sprinkles DashboardStats) resource.DocumentBuilderSupplier { +func ProvideDocumentBuilders(sql db.DB, sprinkles builders.DashboardStats) resource.DocumentBuilderSupplier { return &StandardDocumentBuilders{sql, sprinkles} } func (s *StandardDocumentBuilders) GetDocumentBuilders() ([]resource.DocumentBuilderInfo, error) { - dashboards, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) { - logger := log.New("dashboard_builder", "namespace", namespace) - dsinfo := []*dashboard.DatasourceQueryResult{{}} - ns, err := claims.ParseNamespace(namespace) - if err != nil && s.sql != nil { - rows, err := s.sql.GetSqlxSession().Query(ctx, "SELECT uid,type,name,is_default FROM data_source WHERE org_id=?", ns.OrgID) - if err != nil { - return nil, err - } - - defer func() { - _ = rows.Close() - }() - - for rows.Next() { - info := &dashboard.DatasourceQueryResult{} - err = rows.Scan(&info.UID, &info.Type, &info.Name, &info.IsDefault) - if err != nil { - return nil, err - } - dsinfo = append(dsinfo, info) - } - } - - // Fetch dashboard sprinkles for the namespace - // This could take a while if namespace has a lot of dashboards - var stats map[string]map[string]int64 - if s.sprinkles != nil { - stats, err = s.sprinkles.GetStats(ctx, namespace) - if err != nil { - // only log a warning. Don't need to fail the indexer if we can't get sprinkles - logger.Warn("Failed to get sprinkles", "error", err) - } - } - - return &DashboardDocumentBuilder{ - Namespace: namespace, - Blob: blob, - Stats: stats, - DatasourceLookup: dashboard.CreateDatasourceLookup(dsinfo), - }, nil - }) - + all, err := builders.All(s.sql, s.sprinkles) if err != nil { return nil, err } - users, err := GetUserBuilder() - if err != nil { - return nil, err - } - - return []resource.DocumentBuilderInfo{ - // The default builder + result := []resource.DocumentBuilderInfo{ { Builder: resource.StandardDocumentBuilder(), }, - // Dashboard builder - dashboards, - // User builder - users, - }, err + } + return append(result, all...), nil } diff --git a/pkg/storage/unified/search/user_test.go b/pkg/storage/unified/search/user_test.go deleted file mode 100644 index 67b6777f895..00000000000 --- a/pkg/storage/unified/search/user_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package search_test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/selection" - - iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/storage/unified/resource" - "github.com/grafana/grafana/pkg/storage/unified/resourcepb" - "github.com/grafana/grafana/pkg/storage/unified/search" -) - -func TestUserDocumentBuilder(t *testing.T) { - info, err := search.GetUserBuilder() - require.NoError(t, err) - doSnapshotTests(t, info.Builder, "user", &resourcepb.ResourceKey{ - Namespace: "default", - Group: "iam.grafana.app", - Resource: "users", - }, []string{ - "user-with-login-and-email", - "user-with-login-only", - }) -} - -func TestUserSearch(t *testing.T) { - key := resource.NamespacedResource{ - Namespace: "default", - Group: iamv0.UserResourceInfo.GroupResource().Group, - Resource: iamv0.UserResourceInfo.GroupResource().Resource, - } - - index := newTestUsersIndex(t, 100, 2, func(index resource.ResourceIndex) (int64, error) { - return 0, nil - }) - users := []iamv0.User{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "user1", - Namespace: "default", - }, - Spec: iamv0.UserSpec{ - Login: "user.one", - Email: "user.one@test.com", - Role: "Viewer", - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "user2", - Namespace: "default", - }, - Spec: iamv0.UserSpec{ - Login: "user.two", - Email: "user.two@test.com", - Role: "Viewer", - }, - }, - } - indexUserDocuments(t, index, key, users) - - // Sanity check - title search - checkUserSearchQuery(t, index, newTestsUserQueryWithTitle(key, "user2"), []string{"user2"}) - - t.Run("can search users by login", func(t *testing.T) { - // Search by login - checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{ - { - Key: "fields.login", - Operator: string(selection.Equals), - Values: []string{"user.one"}, - }, - }), []string{"user1"}) - checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{ - { - Key: "fields.login", - Operator: string(selection.Equals), - Values: []string{"user.two"}, - }, - }), []string{"user2"}) - }) - - t.Run("can search users by wildcard login", func(t *testing.T) { - checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{ - { - Key: "fields.login", - Operator: string(selection.Equals), - Values: []string{"user.*"}, - }, - }), []string{"user1", "user2"}) - }) - - t.Run("can search users by email", func(t *testing.T) { - // Search by email - checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{ - { - Key: "fields.email", - Operator: string(selection.Equals), - Values: []string{"user.one@test.com"}, - }, - }), []string{"user1"}) - - checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{ - { - Key: "fields.email", - Operator: string(selection.Equals), - Values: []string{"user.two@test.com"}, - }, - }), []string{"user2"}) - }) -} - -func newTestUsersIndex(t testing.TB, threshold int64, size int64, writer resource.BuildFn) resource.ResourceIndex { - t.Helper() - gr := iamv0.UserResourceInfo.GroupResource() - key := &resourcepb.ResourceKey{ - Namespace: "default", - Group: gr.Group, - Resource: gr.Resource, - } - backend, err := search.NewBleveBackend(search.BleveOptions{ - Root: t.TempDir(), - FileThreshold: threshold, // use in-memory for tests - }, tracing.NewNoopTracerService(), nil) - require.NoError(t, err) - - t.Cleanup(backend.Stop) - - ctx := identity.WithRequester(context.Background(), &user.SignedInUser{Namespace: "ns"}) - - info, err := search.GetUserBuilder() - require.NoError(t, err) - - index, err := backend.BuildIndex(ctx, resource.NamespacedResource{ - Namespace: key.Namespace, - Group: key.Group, - Resource: key.Resource, - }, size, info.Fields, "test", writer, nil, false) - require.NoError(t, err) - - return index -} - -func indexUserDocuments(t *testing.T, index resource.ResourceIndex, key resource.NamespacedResource, users []iamv0.User) { - t.Helper() - items := make([]*resource.BulkIndexItem, 0, len(users)) - for _, user := range users { - items = append(items, &resource.BulkIndexItem{ - Action: resource.ActionIndex, - Doc: &resource.IndexableDocument{ - RV: 1, - Name: user.Name, - Key: &resourcepb.ResourceKey{ - Name: user.Name, - Namespace: key.Namespace, - Group: key.Group, - Resource: key.Resource, - }, - Title: user.Name, - Fields: map[string]any{search.USER_LOGIN: user.Spec.Login, search.USER_EMAIL: user.Spec.Email}, - }, - }) - } - req := &resource.BulkIndexRequest{Items: items} - require.NoError(t, index.BulkIndex(req)) -} - -func checkUserSearchQuery(t *testing.T, index resource.ResourceIndex, query *resourcepb.ResourceSearchRequest, orderedExpectedNames []string) { - t.Helper() - res, err := index.Search(context.Background(), nil, query, nil, nil) - require.NoError(t, err) - require.Equal(t, int64(len(orderedExpectedNames)), res.TotalHits) - names := make([]string, len(res.Results.Rows)) - for ix, row := range res.Results.Rows { - names[ix] = row.Key.Name - } - assert.ElementsMatch(t, orderedExpectedNames, names) -} - -func newTestUserQueryWithReqs(key resource.NamespacedResource, filterReqs []*resourcepb.Requirement) *resourcepb.ResourceSearchRequest { - return &resourcepb.ResourceSearchRequest{ - Options: &resourcepb.ListOptions{ - Key: &resourcepb.ResourceKey{ - Namespace: key.Namespace, - Group: key.Group, - Resource: key.Resource, - }, - Fields: filterReqs, - }, - Limit: 100, - } -} - -func newTestsUserQueryWithTitle(key resource.NamespacedResource, title string) *resourcepb.ResourceSearchRequest { - return &resourcepb.ResourceSearchRequest{ - Options: &resourcepb.ListOptions{ - Key: &resourcepb.ResourceKey{ - Namespace: key.Namespace, - Group: key.Group, - Resource: key.Resource, - }, - }, - Query: title, - Limit: 100, - } -} diff --git a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json index d0ac7bbfff3..8449498a061 100644 --- a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json @@ -4001,6 +4001,55 @@ } ] }, + "/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/teams/{name}/groups": { + "get": { + "tags": [ + "Team" + ], + "description": "connect GET requests to groups of Team", + "operationId": "getTeamGroups", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.GetGroups" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "iam.grafana.app", + "version": "v0alpha1", + "kind": "GetGroups" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the GetGroups", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, "/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/teams/{name}/members": { "get": { "tags": [ @@ -5050,6 +5099,40 @@ } } }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.GetGroups": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping" + } + ] + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "iam.grafana.app", + "kind": "GetGroups", + "version": "v0alpha1" + } + ] + }, "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.ServiceAccount": { "type": "object", "required": [ @@ -5549,6 +5632,23 @@ } } }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping": { + "type": "object", + "required": [ + "name", + "externalGroup" + ], + "properties": { + "externalGroup": { + "type": "string", + "default": "" + }, + "name": { + "type": "string", + "default": "" + } + } + }, "com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured": { "type": "object", "additionalProperties": true, @@ -6237,6 +6337,42 @@ } } }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetGroups": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetGroupsBody": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "default": {} + } + } + } + }, "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": { "type": "object", "required": [ @@ -7573,6 +7709,23 @@ } } }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping": { + "type": "object", + "required": [ + "name", + "externalGroup" + ], + "properties": { + "externalGroup": { + "type": "string", + "default": "" + }, + "name": { + "type": "string", + "default": "" + } + } + }, "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { "description": "APIResource specifies the name of a resource and whether it is namespaced.", "type": "object", diff --git a/pkg/tests/apis/plugins/discovery_test.go b/pkg/tests/apis/plugins/discovery_test.go index 168aa259dad..8f0d0cc5ef5 100644 --- a/pkg/tests/apis/plugins/discovery_test.go +++ b/pkg/tests/apis/plugins/discovery_test.go @@ -19,6 +19,35 @@ func TestIntegrationPluginsIntegrationDiscovery(t *testing.T) { "version": "v0alpha1", "freshness": "Current", "resources": [ + { + "resource": "pluginmetas", + "responseKind": { + "group": "", + "kind": "PluginMeta", + "version": "" + }, + "scope": "Namespaced", + "singularResource": "pluginmeta", + "subresources": [ + { + "responseKind": { + "group": "", + "kind": "PluginMeta", + "version": "" + }, + "subresource": "status", + "verbs": [ + "get", + "patch", + "update" + ] + } + ], + "verbs": [ + "get", + "list" + ] + }, { "resource": "plugins", "responseKind": { @@ -29,17 +58,6 @@ func TestIntegrationPluginsIntegrationDiscovery(t *testing.T) { "scope": "Namespaced", "singularResource": "plugins", "subresources": [ - { - "responseKind": { - "group": "", - "kind": "ResourceCallOptions", - "version": "" - }, - "subresource": "meta", - "verbs": [ - "get" - ] - }, { "responseKind": { "group": "", diff --git a/pkg/tests/apis/plugins/pluginmeta_test.go b/pkg/tests/apis/plugins/pluginmeta_test.go new file mode 100644 index 00000000000..af70f02a6f8 --- /dev/null +++ b/pkg/tests/apis/plugins/pluginmeta_test.go @@ -0,0 +1,95 @@ +package plugins + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" + "github.com/grafana/grafana/pkg/tests/apis" + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationPluginMeta(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + t.Run("get plugin meta", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPlugins, + }) + + pluginName := "test-plugin-meta" + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "Plugin", + "metadata": {"name": "%s"}, + "spec": {"id": "grafana-piechart-panel", "version": "1.0.0"} + }`, pluginName)) + _, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) + require.NoError(t, err) + + namespace := helper.Org1.Admin.Identity.GetNamespace() + path := fmt.Sprintf("/apis/plugins.grafana.app/v0alpha1/namespaces/%s/pluginmetas/%s", namespace, pluginName) + response := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, &pluginsv0alpha1.PluginMeta{}) + + require.NotNil(t, response.Result) + require.NotNil(t, response.Result.Spec.PluginJSON) + require.Equal(t, "grafana-piechart-panel", response.Result.Spec.PluginJSON.Id) + require.NotEmpty(t, response.Result.Spec.PluginJSON.Name) + require.NotEmpty(t, response.Result.Spec.PluginJSON.Type) + }) + + t.Run("get plugin meta for non-existent plugin", func(t *testing.T) { + helper := setupHelper(t) + namespace := helper.Org1.Admin.Identity.GetNamespace() + path := fmt.Sprintf("/apis/plugins.grafana.app/v0alpha1/namespaces/%s/pluginmetas/non-existent-plugin", namespace) + response := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, &pluginsv0alpha1.PluginMeta{}) + + require.NotNil(t, response.Status) + require.Equal(t, int32(404), response.Status.Code) + }) + + t.Run("get plugin meta for plugin with non-existent metadata", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPlugins, + }) + + pluginName := "test-plugin-meta-not-found" + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "Plugin", + "metadata": {"name": "%s"}, + "spec": {"id": "non-existent-plugin-id", "version": "1.0.0"} + }`, pluginName)) + _, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) + require.NoError(t, err) + + namespace := helper.Org1.Admin.Identity.GetNamespace() + path := fmt.Sprintf("/apis/plugins.grafana.app/v0alpha1/namespaces/%s/pluginmetas/%s", namespace, pluginName) + response := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, &pluginsv0alpha1.PluginMeta{}) + + require.NotNil(t, response.Status) + require.Equal(t, int32(404), response.Status.Code) + }) +} diff --git a/pkg/tests/apis/plugins/pluginmetas_test.go b/pkg/tests/apis/plugins/pluginmetas_test.go new file mode 100644 index 00000000000..1ffe8d35a50 --- /dev/null +++ b/pkg/tests/apis/plugins/pluginmetas_test.go @@ -0,0 +1,85 @@ +package plugins + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" + "github.com/grafana/grafana/pkg/tests/apis" + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationPluginMetas(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + t.Run("list plugin metas", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPlugins, + }) + + plugin1Name := "test-plugin-metas-1" + plugin1 := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "Plugin", + "metadata": {"name": "%s"}, + "spec": {"id": "grafana-piechart-panel", "version": "1.0.0"} + }`, plugin1Name)) + _, err := client.Resource.Create(ctx, plugin1, metav1.CreateOptions{}) + require.NoError(t, err) + + plugin2Name := "test-plugin-metas-2" + plugin2 := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "Plugin", + "metadata": {"name": "%s"}, + "spec": {"id": "grafana-clock-panel", "version": "1.0.0"} + }`, plugin2Name)) + _, err = client.Resource.Create(ctx, plugin2, metav1.CreateOptions{}) + require.NoError(t, err) + + namespace := helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()) + path := fmt.Sprintf("/apis/plugins.grafana.app/v0alpha1/namespaces/%s/pluginmetas", namespace) + response := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, &pluginsv0alpha1.PluginMetaList{}) + + require.NotNil(t, response.Result) + require.NotNil(t, response.Result.Items) + require.GreaterOrEqual(t, len(response.Result.Items), 2) + + foundIDs := make(map[string]bool) + for _, item := range response.Result.Items { + require.NotNil(t, item.Spec.PluginJSON) + foundIDs[item.Spec.PluginJSON.Id] = true + require.NotEmpty(t, item.Spec.PluginJSON.Id) + require.NotEmpty(t, item.Spec.PluginJSON.Type) + require.NotEmpty(t, item.Spec.PluginJSON.Name) + } + require.True(t, foundIDs["grafana-piechart-panel"]) + require.True(t, foundIDs["grafana-clock-panel"]) + }) + + t.Run("list plugin metas with no plugins", func(t *testing.T) { + helper := setupHelper(t) + namespace := helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()) + path := fmt.Sprintf("/apis/plugins.grafana.app/v0alpha1/namespaces/%s/pluginmetas", namespace) + response := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, &pluginsv0alpha1.PluginMetaList{}) + + require.NotNil(t, response.Result) + require.NotNil(t, response.Result.Items) + require.GreaterOrEqual(t, len(response.Result.Items), 0) + }) +} diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index a7dfb2004ec..c976dcb9e7d 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -7967,6 +7967,9 @@ "teamId": { "type": "integer", "format": "int64" + }, + "uid": { + "type": "string" } } }, diff --git a/public/api-merged.json b/public/api-merged.json index 589d1f2a222..0d9c4129adc 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -2885,7 +2885,7 @@ }, { "type": "string", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "in": "header" }, { @@ -3175,7 +3175,7 @@ }, { "type": "string", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "in": "header" }, { @@ -3353,59 +3353,6 @@ } } }, - "/dashboards/calculate-diff": { - "post": { - "produces": [ - "application/json", - "text/html" - ], - "tags": [ - "dashboards" - ], - "summary": "Perform diff on two dashboards.", - "operationId": "calculateDashboardDiff", - "parameters": [ - { - "name": "Body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "base": { - "$ref": "#/definitions/CalculateDiffTarget" - }, - "diffType": { - "description": "The type of diff to return\nDescription:\n`basic`\n`json`", - "type": "string", - "enum": [ - "basic", - "json" - ] - }, - "new": { - "$ref": "#/definitions/CalculateDiffTarget" - } - } - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/calculateDashboardDiffResponse" - }, - "401": { - "$ref": "#/responses/unauthorisedError" - }, - "403": { - "$ref": "#/responses/forbiddenError" - }, - "500": { - "$ref": "#/responses/internalServerError" - } - } - } - }, "/dashboards/db": { "post": { "description": "Creates a new dashboard or updates an existing dashboard.\nNote: This endpoint is not intended for creating folders, use `POST /api/folders` for that.", @@ -16720,6 +16667,10 @@ "is_paused": { "type": "boolean" }, + "message": { + "description": "Field is only populated when listing alert rule versions.", + "type": "string" + }, "metadata": { "$ref": "#/definitions/AlertRuleMetadata" }, @@ -18273,6 +18224,28 @@ "TLSConfig": { "$ref": "#/definitions/TLSConfig" }, + "audience": { + "description": "Audience optionally specifies the intended audience of the\nrequest. If empty, the value of TokenURL is used as the\nintended audience. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, + "claims": { + "description": "Claims is a map of claims to be added to the JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "object", + "additionalProperties": {} + }, + "client_certificate_key": { + "$ref": "#/definitions/Secret" + }, + "client_certificate_key_file": { + "type": "string" + }, + "client_certificate_key_id": { + "type": "string" + }, + "client_certificate_key_ref": { + "description": "ClientCertificateKeyRef is the name of the secret within the secret manager to use as the client\nsecret.", + "type": "string" + }, "client_id": { "type": "string" }, @@ -18292,6 +18265,14 @@ "type": "string" } }, + "grant_type": { + "description": "GrantType is the OAuth2 grant type to use. It can be one of\n\"client_credentials\" or \"urn:ietf:params:oauth:grant-type:jwt-bearer\" (RFC 7523).\nDefault value is \"client_credentials\"", + "type": "string" + }, + "iss": { + "description": "Iss is the OAuth client identifier used when communicating with\nthe configured OAuth provider. Default value is client_id. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, "no_proxy": { "description": "NoProxy contains addresses that should not use a proxy.", "type": "string" @@ -18312,6 +18293,10 @@ "type": "string" } }, + "signature_algorithm": { + "description": "SignatureAlgorithm is the RSA algorithm used to sign JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".\nDefault value is RS256 and valid values RS256, RS384, RS512", + "type": "string" + }, "token_url": { "type": "string" } @@ -22085,6 +22070,9 @@ "teamId": { "type": "integer", "format": "int64" + }, + "uid": { + "type": "string" } } }, diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx index 16d100b3bb4..dead4bd810e 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx @@ -13,6 +13,7 @@ import { useGrafana } from 'app/core/context/GrafanaContext'; import { setBookmark } from 'app/core/reducers/navBarTree'; import { useDispatch, useSelector } from 'app/types/store'; +import { MegaMenuExtensionPoint } from './MegaMenuExtensionPoint'; import { MegaMenuHeader } from './MegaMenuHeader'; import { MegaMenuItem } from './MegaMenuItem'; import { usePinnedItems } from './hooks'; @@ -110,18 +111,21 @@ export const MegaMenu = memo( diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenuExtensionPoint.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenuExtensionPoint.tsx new file mode 100644 index 00000000000..96e667ac6ec --- /dev/null +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenuExtensionPoint.tsx @@ -0,0 +1,25 @@ +import { PluginExtensionPoints } from '@grafana/data'; +import { renderLimitedComponents } from '@grafana/runtime'; +import { usePluginComponents } from 'app/features/plugins/extensions/usePluginComponents'; + +/** + * Extension point for plugins to add components to the mega menu. + * Currently restricted to grafana-setupguide-app plugin. + */ +export function MegaMenuExtensionPoint() { + const { components } = usePluginComponents({ + extensionPointId: PluginExtensionPoints.MegaMenuAction, + }); + + // Return null if no components are registered + if (components.length === 0) { + return null; + } + + return renderLimitedComponents({ + props: {}, + components: components, + limit: 1, + pluginId: 'grafana-setupguide-app', + }); +} diff --git a/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx b/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx index 94603269de4..0f7c967f9f6 100644 --- a/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx +++ b/public/app/core/components/AppChrome/QuickAdd/QuickAdd.tsx @@ -47,6 +47,14 @@ export const QuickAdd = ({}: Props) => { if (!showQuickAdd) { return null; } + const handleVisibleChange = () => { + if (!isOpen) { + reportInteraction('grafana_create_new_button_menu_opened', { + from: 'quickadd', + }); + } + setIsOpen(!isOpen); + }; const MenuActions = () => { return ( @@ -68,7 +76,7 @@ export const QuickAdd = ({}: Props) => { return showQuickAdd ? ( <> - + + {unifiedHistoryEnabled && !isSmallScreen && } {!isSmallScreen && } diff --git a/public/app/core/components/AppChrome/TopBar/TopBarExtensionPoint.tsx b/public/app/core/components/AppChrome/TopBar/TopBarExtensionPoint.tsx new file mode 100644 index 00000000000..42f81081e30 --- /dev/null +++ b/public/app/core/components/AppChrome/TopBar/TopBarExtensionPoint.tsx @@ -0,0 +1,25 @@ +import { PluginExtensionPoints } from '@grafana/data'; +import { renderLimitedComponents } from '@grafana/runtime'; +import { usePluginComponents } from 'app/features/plugins/extensions/usePluginComponents'; + +/** + * Extension point for plugins to add components to the top bar. + * Currently restricted to grafana-setupguide-app plugin. + */ +export function TopBarExtensionPoint() { + const { components } = usePluginComponents({ + extensionPointId: PluginExtensionPoints.SingleTopBarAction, + }); + + // Return null if no components are registered + if (components.length === 0) { + return null; + } + + return renderLimitedComponents({ + props: {}, + components: components, + limit: 1, + pluginId: 'grafana-setupguide-app', + }); +} diff --git a/public/app/features/alerting/unified/components/expressions/Expression.tsx b/public/app/features/alerting/unified/components/expressions/Expression.tsx index 173bca42a4c..79f805d52ad 100644 --- a/public/app/features/alerting/unified/components/expressions/Expression.tsx +++ b/public/app/features/alerting/unified/components/expressions/Expression.tsx @@ -18,7 +18,7 @@ import { ClassicConditions } from 'app/features/expressions/components/ClassicCo import { Math } from 'app/features/expressions/components/Math'; import { Reduce } from 'app/features/expressions/components/Reduce'; import { Resample } from 'app/features/expressions/components/Resample'; -import { SqlExpr } from 'app/features/expressions/components/SqlExpr'; +import { SqlExpr } from 'app/features/expressions/components/SqlExpressions/SqlExpr'; import { Threshold } from 'app/features/expressions/components/Threshold'; import { ExpressionQuery, diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx index b7ef11375f0..52d8cab8b70 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx @@ -8,7 +8,6 @@ import { GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { Button, Field, Input, Stack, Text, TextArea, useStyles2 } from '@grafana/ui'; -import { DashboardModel } from '../../../../dashboard/state/DashboardModel'; import { AIImproveAnnotationsButtonComponent } from '../../enterprise-components/AI/AIGenImproveAnnotationsButton/addAIImproveAnnotationsButton'; import { RuleFormValues } from '../../types/rule-form'; import { Annotation, annotationLabels } from '../../utils/constants'; @@ -19,7 +18,7 @@ import DashboardAnnotationField from './DashboardAnnotationField'; import { DashboardPicker, PanelDTO, getVisualPanels } from './DashboardPicker'; import { NeedHelpInfo } from './NeedHelpInfo'; import { RuleEditorSection } from './RuleEditorSection'; -import { useDashboardQuery } from './useDashboardQuery'; +import { DashboardResponse, useDashboardQuery } from './useDashboardQuery'; const AnnotationsStep = () => { const styles = useStyles2(getStyles); @@ -40,22 +39,22 @@ const AnnotationsStep = () => { const selectedDashboardUid = annotations.find((annotation) => annotation.key === Annotation.dashboardUID)?.value; const selectedPanelId = Number(annotations.find((annotation) => annotation.key === Annotation.panelID)?.value); - const [selectedDashboard, setSelectedDashboard] = useState(undefined); + const [selectedDashboard, setSelectedDashboard] = useState(undefined); const [selectedPanel, setSelectedPanel] = useState(undefined); - const { dashboardModel, isFetching: isDashboardFetching } = useDashboardQuery(selectedDashboardUid); + const { dashboard, isFetching: isDashboardFetching } = useDashboardQuery(selectedDashboardUid); useEffect(() => { - if (isDashboardFetching || !dashboardModel) { + if (isDashboardFetching || !dashboard) { return; } - setSelectedDashboard(dashboardModel); + setSelectedDashboard(dashboard); - const allPanels = getVisualPanels(dashboardModel); + const allPanels = getVisualPanels(dashboard); const currentPanel = allPanels.find((panel) => panel.id === selectedPanelId); setSelectedPanel(currentPanel); - }, [selectedPanelId, dashboardModel, isDashboardFetching]); + }, [selectedPanelId, dashboard, isDashboardFetching]); const setSelectedDashboardAndPanelId = (dashboardUid: string, panelId: number) => { const updatedAnnotations = produce(annotations, (draft) => { diff --git a/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx b/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx index 26d32af3104..3354a8b102c 100644 --- a/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx @@ -3,11 +3,11 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans } from '@grafana/i18n'; import { Icon, Text, useStyles2 } from '@grafana/ui'; -import { DashboardDataDTO } from 'app/types/dashboard'; import { makeDashboardLink, makePanelLink } from '../../utils/misc'; -import { PanelDTO } from './DashboardPicker'; +import { PanelDTO, getDashboardTitle, getDashboardUid } from './DashboardPicker'; +import { DashboardResponse } from './useDashboardQuery'; const DashboardAnnotationField = ({ dashboard, @@ -17,7 +17,7 @@ const DashboardAnnotationField = ({ onEditClick, onDeleteClick, }: { - dashboard?: DashboardDataDTO; + dashboard?: DashboardResponse; panel?: PanelDTO; dashboardUid: string; //fallback panelId: string; //fallback @@ -26,8 +26,8 @@ const DashboardAnnotationField = ({ }) => { const styles = useStyles2(getStyles); - const dashboardLink = makeDashboardLink(dashboard?.uid || dashboardUid); - const panelLink = makePanelLink(dashboard?.uid || dashboardUid, panel?.id?.toString() || panelId); + const dashboardLink = makeDashboardLink(getDashboardUid(dashboard) || dashboardUid); + const panelLink = makePanelLink(getDashboardUid(dashboard) || dashboardUid, panel?.id?.toString() || panelId); return (
{dashboard && ( @@ -38,7 +38,7 @@ const DashboardAnnotationField = ({ rel="noreferrer" data-testid="dashboard-annotation" > - {dashboard.title} + {getDashboardTitle(dashboard)} )} diff --git a/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx b/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx index afd4a4e80f2..68fa9d2df4d 100644 --- a/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx @@ -7,6 +7,7 @@ import { FixedSizeList } from 'react-window'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; +import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { Alert, Button, @@ -18,11 +19,13 @@ import { clearButtonStyles, useStyles2, } from '@grafana/ui'; +import { AnnoKeyFolderTitle } from 'app/features/apiserver/types'; +import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; +import { isDashboardV2Resource } from 'app/features/dashboard/api/utils'; import { getGrafanaSearcher } from 'app/features/search/service/searcher'; +import { DashboardDTO } from 'app/types/dashboard'; -import { DashboardModel } from '../../../../dashboard/state/DashboardModel'; - -import { useDashboardQuery } from './useDashboardQuery'; +import { DashboardResponse, useDashboardQuery } from './useDashboardQuery'; export interface PanelDTO { id?: number; @@ -74,7 +77,7 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis const [debouncedDashboardFilter, setDebouncedDashboardFilter] = useState(''); const [panelFilter, setPanelFilter] = useState(''); const { value, loading: isDashSearchFetching } = useFilteredDashboards(debouncedDashboardFilter); - const { dashboardModel, isFetching: isDashboardFetching } = useDashboardQuery(selectedDashboardUid); + const { dashboard, isFetching: isDashboardFetching } = useDashboardQuery(selectedDashboardUid); const handleDashboardChange = useCallback((dashboardUid: string) => { setSelectedDashboardUid(dashboardUid); setSelectedPanelId(undefined); @@ -82,7 +85,7 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis const { dashboards: filteredDashboards = [], locationInfo: locationInfo = {} } = value || {}; - const allDashboardPanels = getVisualPanels(dashboardModel); + const allDashboardPanels = getVisualPanels(dashboard); const filteredPanels = allDashboardPanels @@ -197,7 +200,7 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis contentClassName={styles.modalContent} > {/* This alert shows if the selected dashboard is not found in the first page of dashboards */} - {!selectedDashboardIsInPageResult && dashboardUid && dashboardModel && ( + {!selectedDashboardIsInPageResult && dashboardUid && dashboard && ( Dashboard: {'{{dashboardTitle}}'} ({'{{ dashboardUid }}'}) in folder {'{{ folderTitle }}'} @@ -318,13 +321,35 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis ); }; -export function getVisualPanels(dashboardModel: DashboardModel | undefined) { - if (!dashboardModel) { +export function getVisualPanels(dashboardDTO: DashboardResponse | undefined) { + if (!dashboardDTO || !('dashboard' in dashboardDTO)) { return []; } - const panelsWithoutRows = dashboardModel.panels.filter((panel) => panel.type !== 'row'); - const panelsNestedInRows = dashboardModel.panels + // process v2 dashboard + if (isDashboardV2Resource(dashboardDTO)) { + return Object.values(dashboardDTO.spec.elements).map((element) => ({ + id: element.spec.id, + title: element.spec.title, + type: element.kind === 'Panel' ? element.spec.vizConfig.group : 'LibraryPanel', + ...(element.kind === 'LibraryPanel' && { + libraryPanel: { + uid: element.spec.libraryPanel.uid, + name: element.spec.libraryPanel.name, + }, + }), + })); + } + + // process v1 dashboard + const { dashboard } = dashboardDTO; + + if (!dashboard || !dashboard.panels) { + return []; + } + + const panelsWithoutRows = dashboard.panels.filter((panel) => panel.type !== 'row'); + const panelsNestedInRows = dashboard.panels .filter((rowPanel) => rowPanel.collapsed) .flatMap((collapsedRow) => collapsedRow.panels ?? []); @@ -332,6 +357,46 @@ export function getVisualPanels(dashboardModel: DashboardModel | undefined) { return allDashboardPanels; } +export function getDashboardTitle(dashboardDTO: DashboardResponse | undefined) { + if (!dashboardDTO || !('dashboard' in dashboardDTO)) { + return ''; + } + + if (isDashboardV2Resource(dashboardDTO)) { + return dashboardDTO.spec.title; + } + + return dashboardDTO.dashboard.title; +} + +export function getDashboardUid(dashboardDTO: DashboardResponse | undefined) { + if (!dashboardDTO || !('dashboard' in dashboardDTO)) { + return ''; + } + + if (isDashboardV2Resource(dashboardDTO)) { + return dashboardDTO.metadata.name; + } + + return dashboardDTO.dashboard.uid; +} + +export function getDashboardFolderTitle( + dashboardDTO: DashboardDTO | DashboardWithAccessInfo | undefined +) { + if (!dashboardDTO || !('dashboard' in dashboardDTO)) { + return undefined; + } + + if (isDashboardV2Resource(dashboardDTO)) { + return dashboardDTO.metadata.annotations?.[AnnoKeyFolderTitle]; + } + + const { meta } = dashboardDTO; + + return meta.folderTitle; +} + const isValidPanel = (panel: PanelDTO): boolean => { const hasValidID = typeof panel.id === 'number'; const isValidPanelType = typeof panel.type === 'string'; diff --git a/public/app/features/alerting/unified/components/rule-editor/useDashboardQuery.ts b/public/app/features/alerting/unified/components/rule-editor/useDashboardQuery.ts index 3427bd0486b..22c95aa797f 100644 --- a/public/app/features/alerting/unified/components/rule-editor/useDashboardQuery.ts +++ b/public/app/features/alerting/unified/components/rule-editor/useDashboardQuery.ts @@ -1,36 +1,47 @@ import memoizeOne from 'memoize-one'; import { useEffect, useState } from 'react'; +import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api'; +import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; +import { isDashboardV2Resource } from 'app/features/dashboard/api/utils'; import { DashboardDTO } from 'app/types/dashboard'; import { DashboardModel } from '../../../../dashboard/state/DashboardModel'; -const convertToDashboardModel = memoizeOne((dashboardDTO: DashboardDTO) => { +export type DashboardResponse = DashboardDTO | DashboardWithAccessInfo; + +const ensureV1PanelsHaveIds = memoizeOne((dashboardDTO: DashboardDTO): DashboardResponse => { // RTKQuery freezes all returned objects. DashboardModel constructor runs migrations which might change the internal object // Hence we need to add structuredClone to make a deep copy of the API response object - const { dashboard, meta } = structuredClone(dashboardDTO); - return new DashboardModel(dashboard, meta); + const dashboardDTOClone = structuredClone(dashboardDTO); + const model = new DashboardModel(dashboardDTOClone.dashboard, dashboardDTOClone.meta); + + dashboardDTOClone.dashboard.panels = model.panels; + + return dashboardDTOClone; }); export function useDashboardQuery(dashboardUid?: string) { - const [dashboardModel, setDashboardModel] = useState(); + const [dashboard, setDashboard] = useState(); const [isFetching, setIsFetching] = useState(false); useEffect(() => { if (dashboardUid) { setIsFetching(true); getDashboardAPI() .getDashboardDTO(dashboardUid) - .then((dashboard) => { - if (!('dashboard' in dashboard)) { - console.error('Something went wrong, unexpected dashboard format'); + .then((dashboardDTO) => { + if ('dashboard' in dashboardDTO) { + setDashboard(ensureV1PanelsHaveIds(dashboardDTO)); + } else if (isDashboardV2Resource(dashboardDTO)) { + setDashboard(dashboardDTO); } else { - setDashboardModel(convertToDashboardModel(dashboard)); + console.error('Something went wrong, unexpected dashboard format'); } setIsFetching(false); }); } }, [dashboardUid]); - return { dashboardModel, isFetching }; + return { dashboard, isFetching }; } diff --git a/public/app/features/alerting/unified/components/rules/Filter/useRuleFilterAutocomplete.test.ts b/public/app/features/alerting/unified/components/rules/Filter/useRuleFilterAutocomplete.test.ts new file mode 100644 index 00000000000..0f4c2b49741 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/Filter/useRuleFilterAutocomplete.test.ts @@ -0,0 +1,124 @@ +import { renderHook } from 'test/test-utils'; + +import { GrafanaPromRuleGroupDTO } from 'app/types/unified-alerting-dto'; + +import { prometheusApi } from '../../../api/prometheusApi'; +import { setupMswServer } from '../../../mockApi'; + +import { useNamespaceAndGroupOptions } from './useRuleFilterAutocomplete'; + +setupMswServer(); + +jest.mock('../../../api/prometheusApi', () => ({ + prometheusApi: { + useLazyGetGrafanaGroupsQuery: jest.fn(), + useLazyGetGroupsQuery: jest.fn(), + }, +})); + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getDataSourceSrv: () => ({ + getList: jest.fn().mockReturnValue([]), + }), +})); + +describe('useNamespaceAndGroupOptions', () => { + let mockFetchGrafanaGroups: jest.Mock; + let mockFetchExternalGroups: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + + mockFetchGrafanaGroups = jest.fn(); + mockFetchExternalGroups = jest.fn(); + + (prometheusApi.useLazyGetGrafanaGroupsQuery as jest.Mock).mockReturnValue([mockFetchGrafanaGroups]); + (prometheusApi.useLazyGetGroupsQuery as jest.Mock).mockReturnValue([mockFetchExternalGroups]); + }); + + describe('groupOptions', () => { + it('should require minimum 3 characters before searching', async () => { + const { result } = renderHook(() => useNamespaceAndGroupOptions()); + + const options = await result.current.groupOptions('ab'); + + expect(options).toHaveLength(1); + expect(options[0]).toEqual({ + label: 'Type at least 3 characters to search groups', + value: '__GRAFANA_INFO_OPTION__', + infoOption: true, + }); + expect(mockFetchGrafanaGroups).not.toHaveBeenCalled(); + }); + + it('should call API with searchGroupName when 3+ characters entered', async () => { + const mockGroups: GrafanaPromRuleGroupDTO[] = [ + { name: 'cpu-alerts', file: 'folder1', folderUid: 'uid1', interval: 60, rules: [] }, + { name: 'cpu-usage', file: 'folder2', folderUid: 'uid2', interval: 60, rules: [] }, + ]; + + mockFetchGrafanaGroups.mockReturnValue({ + unwrap: () => + Promise.resolve({ + data: { groups: mockGroups }, + }), + }); + + const { result } = renderHook(() => useNamespaceAndGroupOptions()); + + const options = await result.current.groupOptions('cpu'); + + expect(mockFetchGrafanaGroups).toHaveBeenCalledWith({ + limitAlerts: 0, + searchGroupName: 'cpu', + groupLimit: 100, + }); + + expect(options).toHaveLength(2); + expect(options[0]).toEqual({ label: 'cpu-alerts', value: 'cpu-alerts' }); + expect(options[1]).toEqual({ label: 'cpu-usage', value: 'cpu-usage' }); + }); + + it('should show message when no groups match search', async () => { + mockFetchGrafanaGroups.mockReturnValue({ + unwrap: () => + Promise.resolve({ + data: { groups: [] }, + }), + }); + + const { result } = renderHook(() => useNamespaceAndGroupOptions()); + + const options = await result.current.groupOptions('xyz123'); + + expect(options).toHaveLength(1); + expect(options[0]).toEqual({ + label: 'No groups found matching "xyz123"', + value: '__GRAFANA_INFO_OPTION__', + infoOption: true, + }); + }); + + it('should handle API errors gracefully', async () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + + mockFetchGrafanaGroups.mockReturnValue({ + unwrap: () => Promise.reject(new Error('API Error')), + }); + + const { result } = renderHook(() => useNamespaceAndGroupOptions()); + + const options = await result.current.groupOptions('cpu'); + + expect(options).toHaveLength(1); + expect(options[0]).toEqual({ + label: 'Error searching groups', + value: '__GRAFANA_INFO_OPTION__', + infoOption: true, + }); + + consoleErrorSpy.mockRestore(); + }); + }); +}); diff --git a/public/app/features/alerting/unified/components/rules/Filter/useRuleFilterAutocomplete.ts b/public/app/features/alerting/unified/components/rules/Filter/useRuleFilterAutocomplete.ts index df6a317d071..67b3591eae0 100644 --- a/public/app/features/alerting/unified/components/rules/Filter/useRuleFilterAutocomplete.ts +++ b/public/app/features/alerting/unified/components/rules/Filter/useRuleFilterAutocomplete.ts @@ -1,3 +1,4 @@ +import { chain } from 'lodash'; import { useCallback } from 'react'; import { DataSourceInstanceSettings } from '@grafana/data'; @@ -15,7 +16,9 @@ function getExternalRuleDataSources() { return getRulesDataSources().filter((ds: DataSourceInstanceSettings) => !!ds?.url); } -const THRESHOLD_LIMIT = 500; +const NAMESPACE_THRESHOLD_LIMIT = 500; +const MIN_GROUP_SEARCH_CHARACTERS = 3; +const GROUP_SEARCH_LIMIT = 100; function createInfoOption(message: string): ComboboxOption { return { @@ -60,7 +63,10 @@ export function useNamespaceAndGroupOptions(): { const namespaceOptions = useCallback( async (inputValue: string) => { // Grafana namespaces - fetch with limit to check threshold - const grafanaResponse = await fetchGrafanaGroups({ limitAlerts: 0, groupLimit: THRESHOLD_LIMIT + 1 }).unwrap(); + const grafanaResponse = await fetchGrafanaGroups({ + limitAlerts: 0, + groupLimit: NAMESPACE_THRESHOLD_LIMIT + 1, + }).unwrap(); const grafanaFolderNames = Array.from( new Set(grafanaResponse.data.groups.map((g: GrafanaPromRuleGroupDTO) => g.file || 'default')) ); @@ -71,7 +77,7 @@ export function useNamespaceAndGroupOptions(): { fetchExternalGroups({ ruleSource: { uid: ds.uid }, excludeAlerts: true, - groupLimit: THRESHOLD_LIMIT + 1, + groupLimit: NAMESPACE_THRESHOLD_LIMIT + 1, notificationOptions: { showErrorAlert: false }, }).unwrap() ); @@ -84,8 +90,8 @@ export function useNamespaceAndGroupOptions(): { const totalNamespaces = grafanaFolderNames.length + namespaceNameSet.size; - // If we have more than THRESHOLD_LIMIT unique namespaces, show info message - if (totalNamespaces > THRESHOLD_LIMIT) { + // If we have more than NAMESPACE_THRESHOLD_LIMIT unique namespaces, show info message + if (totalNamespaces > NAMESPACE_THRESHOLD_LIMIT) { return [ createInfoOption( t( @@ -117,33 +123,53 @@ export function useNamespaceAndGroupOptions(): { const groupOptions = useCallback( async (inputValue: string) => { - const grafanaResponse = await fetchGrafanaGroups({ limitAlerts: 0, groupLimit: THRESHOLD_LIMIT + 1 }).unwrap(); - const groupNames = Array.from(new Set(grafanaResponse.data.groups.map((g: GrafanaPromRuleGroupDTO) => g.name))); - - // If we have more than THRESHOLD_LIMIT unique groups, show info message - if (groupNames.length > THRESHOLD_LIMIT) { + // Require minimum characters for search + const trimmedInput = inputValue?.trim() || ''; + if (trimmedInput.length < MIN_GROUP_SEARCH_CHARACTERS) { return [ createInfoOption( - t( - 'alerting.rules-filter.group-autocomplete-unavailable', - 'Due to large number of groups, autocomplete is not available' - ) + t('alerting.rules-filter.group-search-prompt', 'Type at least 3 characters to search groups') ), ]; } - const options: Array> = groupNames - .map((name) => ({ label: name, value: name })) - .sort((a, b) => collator.compare(a.label ?? '', b.label ?? '')); + try { + // Use the backend search with lightweight response + const grafanaResponse = await fetchGrafanaGroups({ + limitAlerts: 0, // Lightweight - no alert data + searchGroupName: trimmedInput, // Backend filtering via search.rule_group parameter + groupLimit: GROUP_SEARCH_LIMIT, // Reasonable limit for dropdown results + }).unwrap(); - const filtered = filterBySearch(options, inputValue); - return filtered; + // Deduplicate group names + const groupNames = chain(grafanaResponse.data.groups).map('name').compact().uniq().value(); + + // No results found + if (groupNames.length === 0) { + return [ + createInfoOption( + t('alerting.rules-filter.group-no-results', 'No groups found matching "{{search}}"', { + search: trimmedInput, + }) + ), + ]; + } + + const options: Array> = groupNames + .map((name) => ({ label: name, value: name })) + .sort((a, b) => collator.compare(a.label ?? '', b.label ?? '')); + + return options; + } catch (error) { + console.error('Error fetching groups:', error); + return [createInfoOption(t('alerting.rules-filter.group-search-error', 'Error searching groups'))]; + } }, [fetchGrafanaGroups] ); const namespacePlaceholder = t('alerting.rules-filter.filter-options.placeholder-namespace', 'Select namespace'); - const groupPlaceholder = t('grafana.select-group', 'Select group'); + const groupPlaceholder = t('alerting.rules-filter.placeholder-group-search', 'Search group'); return { namespaceOptions, groupOptions, namespacePlaceholder, groupPlaceholder }; } diff --git a/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.tsx b/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.tsx index 1cc702038ac..a2a1566be29 100644 --- a/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.tsx +++ b/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.tsx @@ -439,13 +439,7 @@ function GroupField({ groupPlaceholder: string; portalContainer?: HTMLElement; }) { - const { control, setValue } = useFormContext(); - - const wrappedOptions = useCallback( - (inputValue: string) => - createThresholdAwareOptions(groupOptions, (value) => setValue('groupName', value), 'groupName')(inputValue), - [groupOptions, setValue] - ); + const { control } = useFormContext(); return ( <> @@ -458,7 +452,7 @@ function GroupField({ render={({ field }) => ( placeholder={groupPlaceholder} - options={wrappedOptions} + options={groupOptions} onChange={(option) => { if (!option?.infoOption) { field.onChange(option?.value || null); diff --git a/public/app/features/browse-dashboards/api/services.ts b/public/app/features/browse-dashboards/api/services.ts index 2bb1ec6d39e..98be615583b 100644 --- a/public/app/features/browse-dashboards/api/services.ts +++ b/public/app/features/browse-dashboards/api/services.ts @@ -1,7 +1,8 @@ +import { getBackendSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import { GENERAL_FOLDER_UID } from 'app/features/search/constants'; import { getGrafanaSearcher } from 'app/features/search/service/searcher'; -import { DashboardQueryResult } from 'app/features/search/service/types'; +import { NestedFolderDTO } from 'app/features/search/service/types'; import { queryResultToViewItem } from 'app/features/search/service/utils'; import { DashboardViewItem } from 'app/features/search/types'; import { AccessControlAction } from 'app/types/accessControl'; @@ -16,24 +17,22 @@ export async function listFolders( page = 1, pageSize = PAGE_SIZE ): Promise { - const searcher = getGrafanaSearcher(); + const backendSrv = getBackendSrv(); - let folders: DashboardQueryResult[] = []; + // TODO: what to do here for unified search? + let folders: NestedFolderDTO[] = []; if (contextSrv.hasPermission(AccessControlAction.FoldersRead)) { - const foldersResults = await searcher.search({ - kind: ['folder'], - location: parentUID || 'general', - from: (page - 1) * pageSize, // our pages are 1-indexed, so we need to -1 to convert that to correct value to skip + folders = await backendSrv.get('/api/folders', { + parentUid: parentUID, + page, limit: pageSize, - offset: (page - 1) * pageSize, }); - folders = foldersResults.view.toArray(); } return folders.map((item) => ({ kind: 'folder', uid: item.uid, - title: item.name, + title: item.title, parentTitle, parentUID, managedBy: item.managedBy, diff --git a/public/app/features/browse-dashboards/components/CreateNewButton.tsx b/public/app/features/browse-dashboards/components/CreateNewButton.tsx index 46bff8848f5..f63b70d5040 100644 --- a/public/app/features/browse-dashboards/components/CreateNewButton.tsx +++ b/public/app/features/browse-dashboards/components/CreateNewButton.tsx @@ -51,7 +51,11 @@ export default function CreateNewButton({ const isProvisionedInstance = useIsProvisionedInstance(); const handleVisibleChange = () => { - !isOpen && reportInteraction('grafana_create_new_button_menu_opened'); + if (!isOpen) { + reportInteraction('grafana_create_new_button_menu_opened', { + from: location.pathname, + }); + } setIsOpen(!isOpen); }; diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index 1ff7eef89ca..75e82ef6093 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -16,13 +16,13 @@ import { sceneGraph, sceneUtils, VizPanel, - isSceneObject, } from '@grafana/scenes'; import { Panel } from '@grafana/schema'; import { OptionFilter } from 'app/features/dashboard/components/PanelEditor/OptionsPaneOptions'; import { getLastUsedDatasourceFromStorage } from 'app/features/dashboard/utils/dashboard'; import { saveLibPanel } from 'app/features/library-panels/state/api'; import { getAllSuggestions } from 'app/features/panel/suggestions/getAllSuggestions'; +import { hasData } from 'app/features/panel/suggestions/utils'; import { DashboardEditActionEvent } from '../edit-pane/shared'; import { DashboardSceneChangeTracker } from '../saving/DashboardSceneChangeTracker'; @@ -103,16 +103,6 @@ export class PanelEditor extends SceneObjectBase { const deactivateParents = activateSceneObjectAndParentTree(panel); - // Ensure headerActions are activated - const headerActions = panel.state.headerActions; - if (headerActions) { - (Array.isArray(headerActions) ? headerActions : [headerActions]).forEach((action) => { - if (isSceneObject(action)) { - action.activate(); - } - }); - } - this.waitForPlugin(); return () => { @@ -130,9 +120,7 @@ export class PanelEditor extends SceneObjectBase { this._subs.add( dataObject.subscribeToState(async () => { const { data } = dataObject.state; - const hasData = data && data.series && data.series.length > 0 && data.series.some((frame) => frame.length > 0); - - if (hasData && panel.state.pluginId === UNCONFIGURED_PANEL_PLUGIN_ID) { + if (hasData(data) && panel.state.pluginId === UNCONFIGURED_PANEL_PLUGIN_ID) { const panelModel = new PanelModelCompatibilityWrapper(panel); const suggestions = await getAllSuggestions(data, panelModel); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx index f71d1238971..4c5b7729745 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx @@ -99,7 +99,10 @@ export class PanelOptionsPane extends SceneObjectBase { panel.onFieldConfigChange(fieldConfigWithOverrides, true); } - this.onToggleVizPicker(); + // Handle preview suggestions + if (!options.withModKey) { + this.onToggleVizPicker(); + } }; onSetSearchQuery = (searchQuery: string) => { diff --git a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx index a57aac9d427..d5b236fdd58 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx @@ -1,14 +1,13 @@ import { css } from '@emotion/css'; import { debounce } from 'lodash'; -import { useEffect, useMemo, useState } from 'react'; -import { useLocalStorage } from 'react-use'; +import { useCallback, useMemo, useState } from 'react'; +import { useSessionStorage } from 'react-use'; -import { GrafanaTheme2, PanelData, SelectableValue } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; +import { GrafanaTheme2, PanelData } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { reportInteraction } from '@grafana/runtime'; +import { config, reportInteraction } from '@grafana/runtime'; import { VizPanel } from '@grafana/scenes'; -import { Button, Field, FilterInput, RadioButtonGroup, ScrollContainer, useStyles2 } from '@grafana/ui'; +import { FilterInput, ScrollContainer, Tab, TabContent, TabsBar, useStyles2 } from '@grafana/ui'; import { LS_VISUALIZATION_SELECT_TAB_KEY } from 'app/core/constants'; import { VisualizationSelectPaneTab } from 'app/features/dashboard/components/PanelEditor/types'; import { VisualizationSuggestions } from 'app/features/panel/components/VizTypePicker/VisualizationSuggestions'; @@ -26,8 +25,25 @@ export interface Props { onClose: () => void; } +const getTabs = (): Array<{ label: string; value: VisualizationSelectPaneTab }> => { + const suggestionsTab = { + label: t('dashboard-scene.panel-viz-type-picker.radio-options.label.suggestions', 'Suggestions'), + value: VisualizationSelectPaneTab.Suggestions, + }; + const allVisualizationsTab = { + label: t('dashboard-scene.panel-viz-type-picker.radio-options.label.all-visualizations', 'All visualizations'), + value: VisualizationSelectPaneTab.Visualizations, + }; + return config.featureToggles.newVizSuggestions + ? [suggestionsTab, allVisualizationsTab] + : [allVisualizationsTab, suggestionsTab]; +}; + export function PanelVizTypePicker({ panel, data, onChange, onClose }: Props) { const styles = useStyles2(getStyles); + const panelModel = useMemo(() => new PanelModelCompatibilityWrapper(panel), [panel]); + + /** SEARCH */ const [searchQuery, setSearchQuery] = useState(''); const trackSearch = useMemo( () => @@ -44,87 +60,74 @@ export function PanelVizTypePicker({ panel, data, onChange, onClose }: Props) { }, 300), [] ); - const handleSearchChange = (value: string) => { setSearchQuery(value); }; - const tabKey = LS_VISUALIZATION_SELECT_TAB_KEY; - const defaultTab = VisualizationSelectPaneTab.Visualizations; - const panelModel = useMemo(() => new PanelModelCompatibilityWrapper(panel), [panel]); + /** TABS */ + const tabs = useMemo(getTabs, []); + const defaultTab = tabs[0].value; + const [listMode, setListMode] = useSessionStorage(LS_VISUALIZATION_SELECT_TAB_KEY, defaultTab); - const supportedListModes = useMemo( - () => new Set([VisualizationSelectPaneTab.Visualizations, VisualizationSelectPaneTab.Suggestions]), - [] + const handleListModeChange = useCallback( + (value: VisualizationSelectPaneTab) => { + reportInteraction(INTERACTION_EVENT_NAME, { + item: INTERACTION_ITEM.CHANGE_TAB, + tab: VisualizationSelectPaneTab[value], + creator_team: 'grafana_plugins_catalog', + schema_version: '1.0.0', + }); + setListMode(value); + }, + [setListMode] ); - const [listMode, setListMode] = useLocalStorage(tabKey, defaultTab); - const handleListModeChange = (value: VisualizationSelectPaneTab) => { - reportInteraction(INTERACTION_EVENT_NAME, { - item: INTERACTION_ITEM.CHANGE_TAB, - tab: VisualizationSelectPaneTab[value], - creator_team: 'grafana_plugins_catalog', - schema_version: '1.0.0', - }); - setListMode(value); - }; - - useEffect(() => { - if (listMode && !supportedListModes.has(listMode)) { - setListMode(defaultTab); - } - }, [defaultTab, listMode, setListMode, supportedListModes]); - - const radioOptions: Array> = [ - { - label: t('dashboard-scene.panel-viz-type-picker.radio-options.label.visualizations', 'Visualizations'), - value: VisualizationSelectPaneTab.Visualizations, - }, - { - label: t('dashboard-scene.panel-viz-type-picker.radio-options.label.suggestions', 'Suggestions'), - value: VisualizationSelectPaneTab.Suggestions, - }, - ]; return (
-
- -
- - - + {/*@TODO: Re-enable/move close button*/} + {/**/} + + {tabs.map((tab) => ( + handleListModeChange(tab.value)} + /> + ))} + - {listMode === VisualizationSelectPaneTab.Visualizations && ( - - )} - {listMode === VisualizationSelectPaneTab.Suggestions && ( - - )} + + {listMode === VisualizationSelectPaneTab.Suggestions && ( + + )} + {listMode === VisualizationSelectPaneTab.Visualizations && ( + <> +
+ +
+ + + )} +
); @@ -141,10 +144,18 @@ const getStyles = (theme: GrafanaTheme2) => ({ }), searchRow: css({ display: 'flex', - marginBottom: theme.spacing(1), + marginBottom: theme.spacing(2), + }), + tabs: css({ + width: '100%', + }), + tab: css({ + flexGrow: 1, + justifyContent: 'center', + textAlign: 'center', }), closeButton: css({ - marginLeft: theme.spacing(1), + marginLeft: 'auto', }), customFieldMargin: css({ marginBottom: theme.spacing(1), diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index 686a7f785e0..5268ea4d865 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -240,6 +240,7 @@ function getStyles(theme: GrafanaTheme2) { display: 'flex', justifyContent: 'flex-end', gap: theme.spacing(1), + marginBottom: theme.spacing(1), }), rightControlsWrap: css({ flexWrap: 'wrap', diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 0534ad76e56..683f22c16fb 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -64,6 +64,7 @@ import { DashboardMeta } from 'app/types/dashboard'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; import { dashboardAnalyticsInitializer } from '../behaviors/DashboardAnalyticsInitializerBehavior'; +import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; @@ -123,6 +124,15 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo + isEditableVariableType(variable.state.type)); } public useEditPaneOptions = useEditPaneOptions.bind(this, this.set); @@ -71,9 +73,12 @@ export function VariableList({ set }: { set: SceneVariableSet }) { DashboardInteractions.addVariableButtonClicked({ source: 'edit_pane' }); }, [set]); + // Filter out system and snapshot variables - they should not appear in the list + const editableVariables = variables.filter((variable) => isEditableVariableType(variable.state.type)); + return ( - {variables.map((variable) => ( + {editableVariables.map((variable) => ( // TODO fix keyboard a11y here // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
onEditVariable(variable)}> diff --git a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx index 5434068f21e..334d7db98da 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx @@ -98,9 +98,9 @@ export function AdHocVariableForm({ > { - if (defaultKeys === undefined) { + if (defaultKeys == null) { onDefaultKeysChange([]); } else { onDefaultKeysChange(undefined); @@ -109,7 +109,7 @@ export function AdHocVariableForm({ /> - {defaultKeys !== undefined && ( + {defaultKeys != null && ( { }); it('should return false for non-editable variable types', () => { - const nonEditableTypes: VariableType[] = ['system']; + const nonEditableTypes: VariableType[] = ['system', 'snapshot']; nonEditableTypes.forEach((type) => { expect(isEditableVariableType(type)).toBe(false); }); diff --git a/public/app/features/dashboard-scene/settings/variables/utils.ts b/public/app/features/dashboard-scene/settings/variables/utils.ts index 23941e3e330..bbb56b0e8c6 100644 --- a/public/app/features/dashboard-scene/settings/variables/utils.ts +++ b/public/app/features/dashboard-scene/settings/variables/utils.ts @@ -47,7 +47,7 @@ interface EditableVariableConfig { export type EditableVariableType = Exclude; export function isEditableVariableType(type: VariableType): type is EditableVariableType { - return type !== 'system'; + return type !== 'system' && type !== 'snapshot'; } export const getEditableVariables: () => Record = () => ({ diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx index ffa32a42254..779dfe3d360 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx @@ -108,7 +108,7 @@ export const VisualizationSelectPane = ({ panel, data }: Props) => { )} {listMode === VisualizationSelectPaneTab.Suggestions && ( - + )} {listMode === VisualizationSelectPaneTab.LibraryPanels && ( diff --git a/public/app/features/expressions/ExpressionQueryEditor.tsx b/public/app/features/expressions/ExpressionQueryEditor.tsx index 538aa2e52f6..e08cdd5d94c 100644 --- a/public/app/features/expressions/ExpressionQueryEditor.tsx +++ b/public/app/features/expressions/ExpressionQueryEditor.tsx @@ -11,7 +11,7 @@ import { ExpressionTypeDropdown } from './components/ExpressionTypeDropdown'; import { Math } from './components/Math'; import { Reduce } from './components/Reduce'; import { Resample } from './components/Resample'; -import { SqlExpr } from './components/SqlExpr'; +import { SqlExpr } from './components/SqlExpressions/SqlExpr'; import { Threshold } from './components/Threshold'; import { ExpressionQuery, ExpressionQueryType, expressionTypes } from './types'; import { getDefaults } from './utils/expressionTypes'; diff --git a/public/app/features/expressions/components/sqlCompletionProvider.ts b/public/app/features/expressions/components/SqlExpressions/CompletionProvider/sqlCompletionProvider.ts similarity index 95% rename from public/app/features/expressions/components/sqlCompletionProvider.ts rename to public/app/features/expressions/components/SqlExpressions/CompletionProvider/sqlCompletionProvider.ts index 013088aa770..f94b08992bd 100644 --- a/public/app/features/expressions/components/sqlCompletionProvider.ts +++ b/public/app/features/expressions/components/SqlExpressions/CompletionProvider/sqlCompletionProvider.ts @@ -2,7 +2,7 @@ import { SelectableValue } from '@grafana/data'; import { ColumnDefinition, LanguageCompletionProvider, TableDefinition, TableIdentifier } from '@grafana/plugin-ui'; import { config } from '@grafana/runtime'; -import { ALLOWED_FUNCTIONS } from '../utils/metaSqlExpr'; +import { ALLOWED_FUNCTIONS } from '../../../utils/metaSqlExpr'; interface CompletionProviderGetterArgs { getFields: (t: TableIdentifier) => Promise; diff --git a/public/app/features/expressions/components/GenAI/GenAIExplanationDrawer.tsx b/public/app/features/expressions/components/SqlExpressions/GenAI/GenAIExplanationDrawer.tsx similarity index 100% rename from public/app/features/expressions/components/GenAI/GenAIExplanationDrawer.tsx rename to public/app/features/expressions/components/SqlExpressions/GenAI/GenAIExplanationDrawer.tsx diff --git a/public/app/features/expressions/components/GenAI/GenAISQLExplainButton.tsx b/public/app/features/expressions/components/SqlExpressions/GenAI/GenAISQLExplainButton.tsx similarity index 92% rename from public/app/features/expressions/components/GenAI/GenAISQLExplainButton.tsx rename to public/app/features/expressions/components/SqlExpressions/GenAI/GenAISQLExplainButton.tsx index 86c5953025a..cd9a1071c5f 100644 --- a/public/app/features/expressions/components/GenAI/GenAISQLExplainButton.tsx +++ b/public/app/features/expressions/components/SqlExpressions/GenAI/GenAISQLExplainButton.tsx @@ -2,9 +2,9 @@ import { useCallback } from 'react'; import { t } from '@grafana/i18n'; -import { GenAIButton } from '../../../dashboard/components/GenAI/GenAIButton'; -import { EventTrackingSrc } from '../../../dashboard/components/GenAI/tracking'; -import { Message, Role } from '../../../dashboard/components/GenAI/utils'; +import { GenAIButton } from '../../../../dashboard/components/GenAI/GenAIButton'; +import { EventTrackingSrc } from '../../../../dashboard/components/GenAI/tracking'; +import { Message, Role } from '../../../../dashboard/components/GenAI/utils'; import { getSQLExplanationSystemPrompt, QueryUsageContext } from './sqlPromptConfig'; diff --git a/public/app/features/expressions/components/GenAI/GenAISQLSuggestionsButton.tsx b/public/app/features/expressions/components/SqlExpressions/GenAI/GenAISQLSuggestionsButton.tsx similarity index 94% rename from public/app/features/expressions/components/GenAI/GenAISQLSuggestionsButton.tsx rename to public/app/features/expressions/components/SqlExpressions/GenAI/GenAISQLSuggestionsButton.tsx index 3b386879354..bdc839739c3 100644 --- a/public/app/features/expressions/components/GenAI/GenAISQLSuggestionsButton.tsx +++ b/public/app/features/expressions/components/SqlExpressions/GenAI/GenAISQLSuggestionsButton.tsx @@ -2,9 +2,9 @@ import { useCallback } from 'react'; import { t } from '@grafana/i18n'; -import { GenAIButton } from '../../../dashboard/components/GenAI/GenAIButton'; -import { EventTrackingSrc } from '../../../dashboard/components/GenAI/tracking'; -import { Message, Role } from '../../../dashboard/components/GenAI/utils'; +import { GenAIButton } from '../../../../dashboard/components/GenAI/GenAIButton'; +import { EventTrackingSrc } from '../../../../dashboard/components/GenAI/tracking'; +import { Message, Role } from '../../../../dashboard/components/GenAI/utils'; import { getSQLSuggestionSystemPrompt, QueryUsageContext } from './sqlPromptConfig'; diff --git a/public/app/features/expressions/components/GenAI/GenAISuggestionsDrawer.tsx b/public/app/features/expressions/components/SqlExpressions/GenAI/GenAISuggestionsDrawer.tsx similarity index 100% rename from public/app/features/expressions/components/GenAI/GenAISuggestionsDrawer.tsx rename to public/app/features/expressions/components/SqlExpressions/GenAI/GenAISuggestionsDrawer.tsx diff --git a/public/app/features/expressions/components/GenAI/SuggestionsDrawerButton.tsx b/public/app/features/expressions/components/SqlExpressions/GenAI/SuggestionsDrawerButton.tsx similarity index 100% rename from public/app/features/expressions/components/GenAI/SuggestionsDrawerButton.tsx rename to public/app/features/expressions/components/SqlExpressions/GenAI/SuggestionsDrawerButton.tsx diff --git a/public/app/features/expressions/components/GenAI/hooks/useSQLExplanations.ts b/public/app/features/expressions/components/SqlExpressions/GenAI/hooks/useSQLExplanations.ts similarity index 100% rename from public/app/features/expressions/components/GenAI/hooks/useSQLExplanations.ts rename to public/app/features/expressions/components/SqlExpressions/GenAI/hooks/useSQLExplanations.ts diff --git a/public/app/features/expressions/components/GenAI/hooks/useSQLSuggestions.ts b/public/app/features/expressions/components/SqlExpressions/GenAI/hooks/useSQLSuggestions.ts similarity index 100% rename from public/app/features/expressions/components/GenAI/hooks/useSQLSuggestions.ts rename to public/app/features/expressions/components/SqlExpressions/GenAI/hooks/useSQLSuggestions.ts diff --git a/public/app/features/expressions/components/GenAI/sqlPromptConfig.ts b/public/app/features/expressions/components/SqlExpressions/GenAI/sqlPromptConfig.ts similarity index 100% rename from public/app/features/expressions/components/GenAI/sqlPromptConfig.ts rename to public/app/features/expressions/components/SqlExpressions/GenAI/sqlPromptConfig.ts diff --git a/public/app/features/expressions/components/GenAI/utils.ts b/public/app/features/expressions/components/SqlExpressions/GenAI/utils.ts similarity index 100% rename from public/app/features/expressions/components/GenAI/utils.ts rename to public/app/features/expressions/components/SqlExpressions/GenAI/utils.ts diff --git a/public/app/features/expressions/components/SqlExpressions/SchemaInspector/SchemaInspectorPanel.tsx b/public/app/features/expressions/components/SqlExpressions/SchemaInspector/SchemaInspectorPanel.tsx new file mode 100644 index 00000000000..c84bf0d4223 --- /dev/null +++ b/public/app/features/expressions/components/SqlExpressions/SchemaInspector/SchemaInspectorPanel.tsx @@ -0,0 +1,264 @@ +import { css } from '@emotion/css'; +import { useState, useMemo } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { t, Trans } from '@grafana/i18n'; +import { + Stack, + Tab, + TabsBar, + TabContent, + Icon, + Badge, + Text, + useStyles2, + InteractiveTable, + ScrollContainer, + Alert, + Spinner, + IconButton, +} from '@grafana/ui'; + +import { SQLSchemas, SQLSchemaField, SQLSchemaData } from '../hooks/useSQLSchemas'; + +import { getFieldTypeIcon } from './utils'; + +type SchemaField = SQLSchemaField; +type SampleValue = string | number | boolean; +type SampleRow = SampleValue[]; +type SampleRows = SampleRow[]; +type SchemaData = SQLSchemaData; + +interface SchemaInspectorPanelProps { + schemas: SQLSchemas | null; + loading: boolean; + error: Error | null; + onClose: () => void; +} + +export const SchemaInspectorPanel = ({ schemas, loading, error, onClose }: SchemaInspectorPanelProps) => { + const styles = useStyles2(getStyles); + + const schemaResponse: SQLSchemas = schemas ?? {}; + const refIds = Object.keys(schemaResponse); + + const [selectedTab, setSelectedTab] = useState(''); + const activeSchemaTab = refIds.includes(selectedTab) ? selectedTab : refIds[0] || ''; + const activeSchemaData = schemaResponse[activeSchemaTab]; + + const columns = useMemo( + () => [ + { + id: 'field', + header: 'Field', + accessorKey: 'name', + cell: ({ row }: { row: { original: SchemaField } }) => ( + + +
{row.original.name}
+
+ ), + }, + { + id: 'type', + header: 'Type', + accessorKey: 'mysqlType', + cell: ({ row }: { row: { original: SchemaField } }) => , + }, + { + id: 'nullable', + header: 'Nullable', + accessorKey: 'nullable', + cell: ({ row }: { row: { original: SchemaField } }) => ( + + ), + }, + { + id: 'sample', + header: 'Sample values', + accessorKey: 'sample', + cell: ({ + row, + }: { + row: { + original: SchemaField & { + fieldIndex: number; + sampleRows: SchemaData['sampleRows']; + }; + }; + }) => { + const { fieldIndex, sampleRows } = row.original; + + // Extract sample values for this field (column index) + const sampleValues = sampleRows?.map((sampleRow) => sampleRow[fieldIndex]) ?? []; + + // Format as a proper array string with quoted strings + const arrayString = JSON.stringify(sampleValues); + + return
{arrayString}
; + }, + }, + ], + [styles] + ); + + const renderSchemaFields = (fields: SchemaField[], sampleRows: SampleRows | null) => { + // Enhance fields with fieldIndex and sampleRows for the Sample column + const enhancedFields = fields.map((field, index) => ({ + ...field, + fieldIndex: index, + sampleRows, + })); + + return ( +
+ name} + pageSize={0} // No pagination + /> +
+ ); + }; + + const renderSchemaTabContent = ({ columns, error, sampleRows }: SchemaData) => { + if (error) { + return ( +
+ + {error} + +
+ ); + } + + if (!columns || columns.length === 0) { + return ( +
+ + This query returned no schema information. + +
+ ); + } + + return renderSchemaFields(columns, sampleRows); + }; + + const renderContent = () => { + if (error) { + return ( +
+ + {error.message} + +
+ ); + } + + if (loading) { + return ( +
+ + + + Loading schema information... + + +
+ ); + } + + if (!activeSchemaData || refIds.length === 0) { + return ( +
+ +
+ ); + } + + return ( + + {renderSchemaTabContent(activeSchemaData)} + + ); + }; + + return ( +
+
+ {refIds.length > 0 && ( + + {refIds.map((refId) => ( + setSelectedTab(refId)} + /> + ))} + + )} + +
+ {renderContent()} +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + schemaInfoContainer: css({ + padding: theme.spacing(1), + }), + schemaInspector: css({ + height: '100%', + display: 'flex', + flexDirection: 'column', + }), + // Unfortunate hack to get the close button to align with the tabs since we need to + // override the default styles of the TabsBar component. + tabsBarWrapper: css({ + flexShrink: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + padding: `0 ${theme.spacing(1)}`, + }), + tableCell: css({ + fontSize: theme.typography.bodySmall.fontSize, + fontWeight: theme.typography.fontWeightMedium, + fontFamily: theme.typography.fontFamilyMonospace, + }), + tableContainer: css({ + margin: theme.spacing(1), + flex: 1, + overflowY: 'auto', + overflowX: 'auto', + minHeight: 0, // Allow flex child to shrink + border: `1px solid ${theme.colors.border.medium}`, + borderRadius: theme.shape.radius.default, + backgroundColor: theme.colors.background.primary, + }), + nullableIcon: css({ + color: theme.colors.success.text, + }), + requiredIcon: css({ + color: theme.colors.warning.text, + }), +}); diff --git a/public/app/features/expressions/components/SqlExpressions/SchemaInspector/utils.ts b/public/app/features/expressions/components/SqlExpressions/SchemaInspector/utils.ts new file mode 100644 index 00000000000..7412274b3e1 --- /dev/null +++ b/public/app/features/expressions/components/SqlExpressions/SchemaInspector/utils.ts @@ -0,0 +1,18 @@ +export const getFieldTypeIcon = (mysqlType: string) => { + switch (mysqlType.toLowerCase()) { + case 'text': + return 'font'; + case 'double': + case 'float': + case 'int': + case 'bigint': + return 'calculator-alt'; + case 'datetime': + case 'timestamp': + return 'clock-nine'; + case 'boolean': + return 'toggle-on'; + default: + return 'question-circle'; + } +}; diff --git a/public/app/features/expressions/components/SqlExpr.test.tsx b/public/app/features/expressions/components/SqlExpressions/SqlExpr.test.tsx similarity index 68% rename from public/app/features/expressions/components/SqlExpr.test.tsx rename to public/app/features/expressions/components/SqlExpressions/SqlExpr.test.tsx index 53931e785db..b7acc586447 100644 --- a/public/app/features/expressions/components/SqlExpr.test.tsx +++ b/public/app/features/expressions/components/SqlExpressions/SqlExpr.test.tsx @@ -1,6 +1,6 @@ -import { render, waitFor, fireEvent, act } from 'test/test-utils'; +import { render, waitFor, fireEvent, act, testWithFeatureToggles } from 'test/test-utils'; -import { ExpressionQuery, ExpressionQueryType } from '../types'; +import { ExpressionQuery, ExpressionQueryType } from '../../types'; import { SqlExpr, SqlExprProps } from './SqlExpr'; @@ -50,6 +50,20 @@ jest.mock('./GenAI/hooks/useSQLExplanations', () => ({ })), })); +// Mock the backend API +const mockBackendSrv = { + post: jest.fn().mockResolvedValue({ + kind: 'SQLSchemaResponse', + apiVersion: 'query.grafana.app/v0alpha1', + sqlSchemas: {}, + }), +}; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getBackendSrv: () => mockBackendSrv, +})); + // Note: Add more mocks if needed for other lazy components describe('SqlExpr', () => { @@ -204,3 +218,90 @@ describe('SqlExpr with GenAI features', () => { expect(await findByTestId('explanation-drawer')).toBeInTheDocument(); }); }); + +describe('Schema Inspector feature toggle', () => { + const defaultProps: SqlExprProps = { + onChange: jest.fn(), + refIds: [{ value: 'A' }], + query: { refId: 'expression_1', type: ExpressionQueryType.sql, expression: `SELECT * FROM A LIMIT 10` }, + queries: [], + }; + + describe('when feature enabled', () => { + testWithFeatureToggles({ enable: ['queryService', 'grafanaAPIServerWithExperimentalAPIs'] }); + + afterEach(() => { + mockBackendSrv.post.mockResolvedValue({ + kind: 'SQLSchemaResponse', + apiVersion: 'query.grafana.app/v0alpha1', + sqlSchemas: {}, + }); + }); + + it('renders panel open by default', () => { + const { getByText } = render(); + + expect(getByText('No schema information available')).toBeInTheDocument(); + }); + + it('closes panel and shows reopen button when close button clicked', async () => { + const { queryByText, getByLabelText, findByText } = render(); + + expect(queryByText('No schema information available')).toBeInTheDocument(); + + const closeButton = getByLabelText('Close schema inspector'); + await act(async () => fireEvent.click(closeButton)); + + expect(queryByText('No schema information available')).not.toBeInTheDocument(); + expect(await findByText('Inspect schema')).toBeInTheDocument(); + }); + + it('reopens panel when inspect schema button clicked after closing', async () => { + const { queryByText, getByLabelText, getByText } = render(); + + const closeButton = getByLabelText('Close schema inspector'); + await act(async () => fireEvent.click(closeButton)); + + expect(queryByText('No schema information available')).not.toBeInTheDocument(); + + const reopenButton = getByText('Inspect schema'); + await act(async () => fireEvent.click(reopenButton)); + + expect(queryByText('No schema information available')).toBeInTheDocument(); + }); + + it('renders tabs for multiple query refIds', async () => { + mockBackendSrv.post.mockResolvedValue({ + kind: 'SQLSchemaResponse', + apiVersion: 'query.grafana.app/v0alpha1', + sqlSchemas: { + A: { columns: [], sampleRows: [] }, + B: { columns: [], sampleRows: [] }, + C: { columns: [], sampleRows: [] }, + }, + }); + + const propsWithQueries = { + ...defaultProps, + queries: [{ refId: 'A' }, { refId: 'B' }, { refId: 'C' }], + }; + + const { findByRole } = render(); + + expect(await findByRole('tab', { name: 'A' })).toBeInTheDocument(); + expect(await findByRole('tab', { name: 'B' })).toBeInTheDocument(); + expect(await findByRole('tab', { name: 'C' })).toBeInTheDocument(); + }); + }); + + describe('when feature disabled', () => { + testWithFeatureToggles({ enable: [] }); + + it('does not render panel or button', () => { + const { queryByText } = render(); + + expect(queryByText('Inspect schema')).not.toBeInTheDocument(); + expect(queryByText('No schema information available')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/expressions/components/SqlExpr.tsx b/public/app/features/expressions/components/SqlExpressions/SqlExpr.tsx similarity index 73% rename from public/app/features/expressions/components/SqlExpr.tsx rename to public/app/features/expressions/components/SqlExpressions/SqlExpr.tsx index 8ae4d704962..361d748b716 100644 --- a/public/app/features/expressions/components/SqlExpr.tsx +++ b/public/app/features/expressions/components/SqlExpressions/SqlExpr.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { useMemo, useRef, useEffect, useState, lazy, Suspense, useCallback } from 'react'; import { useMeasure } from 'react-use'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -11,14 +11,16 @@ import { DataQuery } from '@grafana/schema/dist/esm/index'; import { formatSQL } from '@grafana/sql'; import { useStyles2, Stack, Button, Modal } from '@grafana/ui'; -import { ExpressionQueryEditorProps } from '../ExpressionQueryEditor'; -import { SqlExpressionQuery } from '../types'; -import { fetchSQLFields } from '../utils/metaSqlExpr'; +import { ExpressionQueryEditorProps } from '../../ExpressionQueryEditor'; +import { SqlExpressionQuery } from '../../types'; +import { fetchSQLFields } from '../../utils/metaSqlExpr'; +import { QueryToolbox } from '../QueryToolbox'; +import { getSqlCompletionProvider } from './CompletionProvider/sqlCompletionProvider'; import { useSQLExplanations } from './GenAI/hooks/useSQLExplanations'; import { useSQLSuggestions } from './GenAI/hooks/useSQLSuggestions'; -import { QueryToolbox } from './QueryToolbox'; -import { getSqlCompletionProvider } from './sqlCompletionProvider'; +import { SchemaInspectorPanel } from './SchemaInspector/SchemaInspectorPanel'; +import { useSQLSchemas } from './hooks/useSQLSchemas'; // Lazy load the GenAI components to avoid circular dependencies const GenAISQLSuggestionsButton = lazy(() => @@ -53,6 +55,7 @@ const GenAIExplanationDrawer = lazy(() => // Account for Monaco editor's border to prevent clipping const EDITOR_BORDER_ADJUSTMENT = 2; // 1px border on top and bottom +const EDITOR_HEIGHT = 300; export interface SqlExprProps { refIds: Array>; @@ -90,11 +93,12 @@ FROM LIMIT 10`; - const styles = useStyles2(getStyles); - const containerRef = useRef(null); const [dimensions, setDimensions] = useState({ height: 0 }); + const styles = useStyles2((theme) => getStyles(theme, dimensions.height || EDITOR_HEIGHT)); + const containerRef = useRef(null); const [toolboxRef, toolboxMeasure] = useMeasure(); const [isExpanded, setIsExpanded] = useState(false); + const [isSchemaInspectorOpen, setIsSchemaInspectorOpen] = useState(true); const { handleApplySuggestion, handleHistoryUpdate, handleCloseDrawer, handleOpenDrawer, isDrawerOpen, suggestions } = useSQLSuggestions(); @@ -109,6 +113,18 @@ LIMIT updatePrevExpression, } = useSQLExplanations(query.expression || ''); + const { + schemas, + loading: schemasLoading, + error: schemasError, + isFeatureEnabled: isSchemasFeatureEnabled, + refetch: refetchSchemas, + } = useSQLSchemas({ + queries, + enabled: isSchemaInspectorOpen, + timeRange: metadata?.range, + }); + const queryContext = useMemo( () => ({ alerting, @@ -172,7 +188,12 @@ LIMIT onRunQuery(); } - }, [onRunQuery]); + + // Refetch schemas when query is run (only if inspector is open) + if (isSchemaInspectorOpen) { + refetchSchemas(); + } + }, [onRunQuery, refetchSchemas, isSchemaInspectorOpen]); // Set up resize observer to handle container resizing useEffect(() => { @@ -224,6 +245,17 @@ LIMIT const renderSQLButtons = () => (
+ {isSchemasFeatureEnabled && !isSchemaInspectorOpen && ( + + )} @@ -273,16 +305,32 @@ LIMIT <>
{renderSQLButtons()} -
- - {({ formatQuery }) => renderToolbox(formatQuery)} - +
+
+ + {({ formatQuery }) => renderToolbox(formatQuery)} + +
+ {isSchemaInspectorOpen && isSchemasFeatureEnabled && ( +
+ setIsSchemaInspectorOpen(false)} + /> +
+ )}
@@ -335,19 +383,35 @@ LIMIT ); }; -const getStyles = (theme: GrafanaTheme2) => ({ +const getStyles = (theme: GrafanaTheme2, editorHeight: number) => ({ sqlContainer: css({ display: 'grid', + gap: theme.spacing(1), gridTemplateRows: 'auto 1fr', gridTemplateAreas: ` "buttons" - "editor" + "content" `, - gap: theme.spacing(0.5), + }), + + contentContainer: css({ + gridArea: 'content', + display: 'grid', + gap: theme.spacing(1), + gridTemplateColumns: '1fr 0fr', + gridTemplateAreas: '"editor schema"', + [theme.transitions.handleMotion('no-preference')]: { + transition: theme.transitions.create(['grid-template-columns'], { + duration: theme.transitions.duration.standard, + }), + }, + }), + contentContainerWithSchema: css({ + gridTemplateColumns: '1fr 1fr', }), editorContainer: css({ gridArea: 'editor', - height: '240px', + height: editorHeight, // Use dynamic height from ResizeObserver resize: 'vertical', overflow: 'auto', minHeight: '100px', @@ -374,6 +438,37 @@ const getStyles = (theme: GrafanaTheme2) => ({ alignItems: 'center', gap: theme.spacing(1), }), + schemaInspector: css({ + gridArea: 'schema', + height: editorHeight, + overflow: 'hidden', + minWidth: 0, + }), + schemaInspectorOpen: css({ + border: `1px solid ${theme.colors.border.weak}`, + borderRadius: theme.shape.radius.default, + }), + schemaFields: css({ + display: 'flex', + flexWrap: 'wrap', + gap: theme.spacing(1), + padding: theme.spacing(1), + maxHeight: '120px', + overflowY: 'auto', + }), + fieldItem: css({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(0.5), + padding: theme.spacing(1), + backgroundColor: theme.colors.background.secondary, + borderRadius: theme.shape.radius.default, + border: `1px solid ${theme.colors.border.weak}`, + fontSize: theme.typography.bodySmall.fontSize, + }), + responseContainer: css({ + padding: theme.spacing(2), + }), }); async function fetchFields(identifier: TableIdentifier, queries: DataQuery[]) { diff --git a/public/app/features/expressions/components/SqlExpressions/hooks/useSQLSchemas.ts b/public/app/features/expressions/components/SqlExpressions/hooks/useSQLSchemas.ts new file mode 100644 index 00000000000..fd9f5f41a1e --- /dev/null +++ b/public/app/features/expressions/components/SqlExpressions/hooks/useSQLSchemas.ts @@ -0,0 +1,95 @@ +import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; + +import { getAPINamespace } from '@grafana/api-clients'; +import { getDefaultTimeRange, TimeRange } from '@grafana/data'; +import { config, getBackendSrv } from '@grafana/runtime'; +import { DataQuery } from '@grafana/schema'; + +export interface SQLSchemaField { + name: string; + mysqlType: string; + dataFrameFieldType: string; + nullable: boolean; +} + +export interface SQLSchemaData { + columns: SQLSchemaField[] | null; + sampleRows: Array> | null; + error?: string; +} + +export type SQLSchemas = Record; + +export interface SQLSchemasResponse { + kind: string; + apiVersion: string; + sqlSchemas: SQLSchemas; +} + +interface UseSQLSchemasOptions { + queries?: DataQuery[]; + enabled: boolean; + timeRange?: TimeRange; +} + +export function useSQLSchemas({ queries, enabled, timeRange }: UseSQLSchemasOptions) { + const isFeatureEnabled = useMemo( + () => config.featureToggles.queryService || config.featureToggles.grafanaAPIServerWithExperimentalAPIs || false, + [] + ); + + // Start with loading=true if we're going to fetch on mount + const [schemas, setSchemas] = useState(null); + const [loading, setLoading] = useState(enabled && isFeatureEnabled && Boolean(queries)); + const [error, setError] = useState(null); + + // Store queries in ref so we can access current value without triggering effect + const queriesRef = useRef(queries); + queriesRef.current = queries; + + const fetchSchemas = useCallback(async () => { + if (!enabled || !isFeatureEnabled) { + return; + } + + const currentQueries = queriesRef.current; + if (!currentQueries) { + return; + } + + setLoading(true); + setError(null); + + try { + if (currentQueries.length === 0) { + setSchemas({ kind: 'SQLSchemaResponse', apiVersion: 'query.grafana.app/v0alpha1', sqlSchemas: {} }); + setLoading(false); + return; + } + + const namespace = getAPINamespace(); + const currentTimeRange = timeRange || getDefaultTimeRange(); + + const response = await getBackendSrv().post( + `/apis/query.grafana.app/v0alpha1/namespaces/${namespace}/sqlschemas/name`, + { + queries: currentQueries, + from: currentTimeRange.from.toISOString(), + to: currentTimeRange.to.toISOString(), + } + ); + + setSchemas(response); + } catch (err) { + setError(err instanceof Error ? err : new Error('Failed to fetch SQL schemas')); + } finally { + setLoading(false); + } + }, [enabled, isFeatureEnabled, timeRange]); + + useEffect(() => { + fetchSchemas(); + }, [fetchSchemas]); + + return { schemas, loading, error, isFeatureEnabled, refetch: fetchSchemas }; +} diff --git a/public/app/features/logs/components/panel/processing.test.ts b/public/app/features/logs/components/panel/processing.test.ts index 2b58efeee15..5998ff36d21 100644 --- a/public/app/features/logs/components/panel/processing.test.ts +++ b/public/app/features/logs/components/panel/processing.test.ts @@ -190,6 +190,45 @@ describe('preProcessLogs', () => { expect(logListModel.body).not.toBe(entry); }); + test('Prettifies and escapes wrapped JSON', () => { + const entry = '{"key": "value", "otherKey": "other\\nValue"}'; + const logListModel = createLogLine( + { entry, hasUnescapedContent: true }, + { + escape: true, // escape + order: LogsSortOrder.Descending, + timeZone: 'browser', + wrapLogMessage: true, // wrapped + prettifyJSON: true, + } + ); + expect(logListModel.entry).toBe(entry); + expect(logListModel.body).toBe(`{ + "key": "value", + "otherKey": "other +Value" +}`); + }); + + test('Prettifies JSON without escaping', () => { + const entry = '{"key": "value", "otherKey": "other\\nValue"}'; + const logListModel = createLogLine( + { entry, hasUnescapedContent: true }, + { + escape: false, // escape = false + order: LogsSortOrder.Descending, + timeZone: 'browser', + wrapLogMessage: true, // wrapped + prettifyJSON: true, + } + ); + expect(logListModel.entry).toBe(entry); + expect(logListModel.body).toBe(`{ + "key": "value", + "otherKey": "other\\nValue" +}`); + }); + test('Uses lossless parsing', () => { const entry = '{"number": 90071992547409911}'; const logListModel = createLogLine( diff --git a/public/app/features/logs/components/panel/processing.ts b/public/app/features/logs/components/panel/processing.ts index 21324f942ef..0f3ea0ac6b1 100644 --- a/public/app/features/logs/components/panel/processing.ts +++ b/public/app/features/logs/components/panel/processing.ts @@ -62,6 +62,7 @@ export class LogListModel implements LogRowModel { private _highlightedBody: string | undefined = undefined; private _highlightedLogAttributesTokens: Array | undefined = undefined; private _highlightTokens: Array | undefined = undefined; + private _escapeUnescapedString = false; private _fields: FieldDef[] | undefined = undefined; private _getFieldLinks: GetFieldLinksFn | undefined = undefined; private _prettifyJSON: boolean; @@ -111,11 +112,10 @@ export class LogListModel implements LogRowModel { this._virtualization = virtualization; this._wrapLogMessage = wrapLogMessage; - let raw = log.raw; if (escape && log.hasUnescapedContent) { - raw = escapeUnescapedString(raw); + this._escapeUnescapedString = true; } - this.raw = raw; + this.raw = log.raw; if (config.featureToggles.otelLogsFormatting && this.otelLanguage) { this.labels[OTEL_LOG_LINE_ATTRIBUTES_FIELD_NAME] = getOtelAttributesField(this, wrapLogMessage); @@ -143,6 +143,9 @@ export class LogListModel implements LogRowModel { if (reStringified) { this.raw = reStringified; } + if (this._escapeUnescapedString) { + this.raw = escapeUnescapedString(this.raw); + } } catch (error) {} const raw = this.raw; this._body = this.collapsed diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx index 32eae1afc87..7f04c8a9dea 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx @@ -1,45 +1,42 @@ import { css, cx } from '@emotion/css'; import { cloneDeep } from 'lodash'; -import { CSSProperties } from 'react'; +import { CSSProperties, HTMLAttributes } from 'react'; import { GrafanaTheme2, PanelData, PanelPluginVisualizationSuggestion } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { config } from '@grafana/runtime'; import { Tooltip, useStyles2 } from '@grafana/ui'; import { PanelRenderer } from '../PanelRenderer'; -import { VizTypeChangeDetails } from './types'; - -export interface Props { +export interface Props extends HTMLAttributes { data: PanelData; width: number; suggestion: PanelPluginVisualizationSuggestion; - onChange: (details: VizTypeChangeDetails) => void; + isSelected?: boolean; } -export function VisualizationSuggestionCard({ data, suggestion, onChange, width }: Props) { +export function VisualizationSuggestionCard({ data, suggestion, width, isSelected = false, onClick }: Props) { const styles = useStyles2(getStyles); const { innerStyles, outerStyles, renderWidth, renderHeight } = getPreviewDimensionsAndStyles(width); const cardOptions = suggestion.cardOptions ?? {}; + const isNewVizSuggestionsEnabled = config.featureToggles.newVizSuggestions; const commonButtonProps = { 'aria-label': suggestion.name, - className: styles.vizBox, + className: cx(styles.vizBox, isNewVizSuggestionsEnabled && isSelected && styles.selectedBox), 'data-testid': selectors.components.VisualizationPreview.card(suggestion.name), style: outerStyles, - onClick: () => { - onChange({ - pluginId: suggestion.pluginId, - options: suggestion.options, - fieldConfig: suggestion.fieldConfig, - }); - }, + onClick, }; if (cardOptions.imgSrc) { return ( - @@ -100,6 +97,10 @@ const getStyles = (theme: GrafanaTheme2) => { background: theme.colors.background.secondary, }, }), + selectedBox: css({ + border: `2px solid ${theme.colors.primary.main}`, + boxShadow: `0 0 0 1px ${theme.colors.primary.main}`, + }), imgBox: css({ display: 'flex', flexDirection: 'column', diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx index 46232beff41..ad4583fc55f 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @@ -1,118 +1,137 @@ import { css } from '@emotion/css'; -import { useMemo } from 'react'; -import { useAsync } from 'react-use'; +import { useState, useEffect, useCallback } from 'react'; +import { useAsync, useMeasure } from 'react-use'; import AutoSizer from 'react-virtualized-auto-sizer'; import { GrafanaTheme2, PanelData, PanelModel, PanelPluginVisualizationSuggestion } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; +import { Trans, t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; -import { Icon, Text, useStyles2 } from '@grafana/ui'; +import { Button, Icon, Text, useStyles2 } from '@grafana/ui'; +import { UNCONFIGURED_PANEL_PLUGIN_ID } from 'app/features/dashboard-scene/scene/UnconfiguredPanel'; import { getAllSuggestions } from '../../suggestions/getAllSuggestions'; +import { hasData } from '../../suggestions/utils'; import { VisualizationSuggestionCard } from './VisualizationSuggestionCard'; import { VizTypeChangeDetails } from './types'; export interface Props { - searchQuery: string; onChange: (options: VizTypeChangeDetails) => void; data?: PanelData; panel?: PanelModel; - trackSearch?: (q: string, count: number) => void; } -export function VisualizationSuggestions({ searchQuery, onChange, data, panel, trackSearch }: Props) { +const MIN_COLUMN_SIZE = 260; + +export function VisualizationSuggestions({ onChange, data, panel }: Props) { const styles = useStyles2(getStyles); const { value: suggestions } = useAsync(() => getAllSuggestions(data, panel), [data, panel]); - const filteredSuggestions = useMemo(() => { - const result = filterSuggestionsBySearch(searchQuery, suggestions); - if (trackSearch) { - trackSearch(searchQuery, result.length); + const [suggestionHash, setSuggestionHash] = useState(null); + const [firstCardRef, { width }] = useMeasure(); + const [firstCardHash, setFirstCardHash] = useState(null); + + const isNewVizSuggestionsEnabled = config.featureToggles.newVizSuggestions; + + const isUnconfiguredPanel = panel?.type === UNCONFIGURED_PANEL_PLUGIN_ID; + + const applySuggestion = useCallback( + (suggestion: PanelPluginVisualizationSuggestion, isPreview?: boolean) => { + onChange({ + pluginId: suggestion.pluginId, + options: suggestion.options, + fieldConfig: suggestion.fieldConfig, + withModKey: isPreview, + }); + + if (isPreview) { + setSuggestionHash(suggestion.hash); + } + }, + [onChange] + ); + + useEffect(() => { + if (!isNewVizSuggestionsEnabled || !suggestions || suggestions.length === 0) { + return; } - return result; - }, [searchQuery, suggestions, trackSearch]); - const hasData = data?.series && data.series.length > 0 && !data.series.every((frame) => frame.length === 0); + // if the first suggestion has changed, we're going to change the currently selected suggestion and + // set the firstCardHash to the new first suggestion's hash. We also choose the first suggestion if + // the previously selected suggestion is no longer present in the list. + const newFirstCardHash = suggestions?.[0]?.hash ?? null; + if (firstCardHash !== newFirstCardHash || suggestions.every((s) => s.hash !== suggestionHash)) { + applySuggestion(suggestions[0], true); + setFirstCardHash(newFirstCardHash); + return; + } + }, [suggestions, suggestionHash, firstCardHash, isNewVizSuggestionsEnabled, isUnconfiguredPanel, applySuggestion]); - if (config.featureToggles.newVizSuggestions && !hasData && !searchQuery) { - return ( -
- - - - Run a query to start seeing suggested visualizations - - -
- ); + const renderEmptyState = () => ( +
+ + + + Run a query to start seeing suggested visualizations + + +
+ ); + + if (isNewVizSuggestionsEnabled && (!data || !hasData(data))) { + return renderEmptyState(); + } + + if (!data) { + return null; } return ( // This div is needed in some places to make AutoSizer work
- {({ width }) => { - if (!width) { - return null; - } + {() => ( +
+
+ {suggestions?.map((suggestion, index) => { + const isCardSelected = isNewVizSuggestionsEnabled && suggestionHash === suggestion.hash; - width = width - 1; - const columnCount = Math.floor(width / 200); - const spaceBetween = 8 * (columnCount! - 1); - const previewWidth = Math.floor((width - spaceBetween) / columnCount!); - - return ( -
-
-
- Based on current data -
-
-
- {filteredSuggestions.map((suggestion, index) => ( - - ))} - {searchQuery && filteredSuggestions.length === 0 && ( -
- - No results matched your query - + return ( +
+ {isCardSelected && ( + + )} + applySuggestion(suggestion, isNewVizSuggestionsEnabled)} + />
- )} -
+ ); + })}
- ); - }} +
+ )}
); } -function filterSuggestionsBySearch( - searchQuery: string, - suggestions?: PanelPluginVisualizationSuggestion[] -): PanelPluginVisualizationSuggestion[] { - if (!searchQuery || !suggestions) { - return suggestions || []; - } - - const regex = new RegExp(searchQuery, 'i'); - - return suggestions.filter((s) => regex.test(s.name) || regex.test(s.pluginId)); -} - const getStyles = (theme: GrafanaTheme2) => { return { - heading: css({ - ...theme.typography.h5, - margin: theme.spacing(0, 0.5, 1), - }), filterRow: css({ display: 'flex', flexDirection: 'row', @@ -128,7 +147,7 @@ const getStyles = (theme: GrafanaTheme2) => { grid: css({ display: 'grid', gridGap: theme.spacing(1), - gridTemplateColumns: 'repeat(auto-fill, 144px)', + gridTemplateColumns: `repeat(auto-fit, minmax(${MIN_COLUMN_SIZE}px, 1fr))`, marginBottom: theme.spacing(1), justifyContent: 'space-evenly', }), @@ -145,5 +164,16 @@ const getStyles = (theme: GrafanaTheme2) => { color: theme.colors.text.secondary, marginBottom: theme.spacing(2), }), + cardContainer: css({ + position: 'relative', + }), + applySuggestionButton: css({ + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + zIndex: 10, + padding: '0 16px', + }), }; }; diff --git a/public/app/features/panel/suggestions/getAllSuggestions.ts b/public/app/features/panel/suggestions/getAllSuggestions.ts index 86fb0e17bde..ff6fbef50e8 100644 --- a/public/app/features/panel/suggestions/getAllSuggestions.ts +++ b/public/app/features/panel/suggestions/getAllSuggestions.ts @@ -52,6 +52,7 @@ export async function getAllSuggestions( name: plugin.name, pluginId: plugin.id, description: plugin.info.description, + hash: 'plugin-empty-' + plugin.id, cardOptions: { imgSrc: plugin.info.logos.small, }, diff --git a/public/app/features/panel/suggestions/utils.test.ts b/public/app/features/panel/suggestions/utils.test.ts index 905b59930a5..04d263415db 100644 --- a/public/app/features/panel/suggestions/utils.test.ts +++ b/public/app/features/panel/suggestions/utils.test.ts @@ -1,6 +1,14 @@ -import { createDataFrame, FieldType, getPanelDataSummary, PanelDataSummary } from '@grafana/data'; +import { + createDataFrame, + FieldType, + getPanelDataSummary, + PanelDataSummary, + PanelData, + LoadingState, + getDefaultTimeRange, +} from '@grafana/data'; -import { showDefaultSuggestion } from './utils'; +import { showDefaultSuggestion, hasData } from './utils'; describe('Suggestions utils', () => { describe('showDefaultSuggestion', () => { @@ -30,4 +38,32 @@ describe('Suggestions utils', () => { expect(result).toBeUndefined(); }); }); + + describe('hasData', () => { + it('should return false when data is undefined', () => { + expect(hasData(undefined)).toBe(false); + }); + + it('should return false when data has no series', () => { + const data: PanelData = { + series: [], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + }; + expect(hasData(data)).toBe(false); + }); + + it('should return true when at least one series has data', () => { + const data: PanelData = { + state: LoadingState.Done, + series: [ + createDataFrame({ + fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3] }], + }), + ], + timeRange: getDefaultTimeRange(), + }; + expect(hasData(data)).toBe(true); + }); + }); }); diff --git a/public/app/features/panel/suggestions/utils.ts b/public/app/features/panel/suggestions/utils.ts index 8668ec2f654..ca5cc74bd77 100644 --- a/public/app/features/panel/suggestions/utils.ts +++ b/public/app/features/panel/suggestions/utils.ts @@ -1,4 +1,4 @@ -import { PanelDataSummary } from '@grafana/data'; +import { PanelData, PanelDataSummary } from '@grafana/data'; /** * @internal @@ -9,3 +9,13 @@ import { PanelDataSummary } from '@grafana/data'; export function showDefaultSuggestion(fn: (panelDataSummary: PanelDataSummary) => boolean | void) { return (panelDataSummary: PanelDataSummary) => (fn(panelDataSummary) ? [{}] : undefined); } + +/** + * @internal + * Checks if the panel has data + * @param data - PanelData + * @returns true if data exists and has at least one non-empty series + */ +export function hasData(data?: PanelData): boolean { + return Boolean(data && data.series && data.series.length > 0 && data.series.some((frame) => frame.length > 0)); +} diff --git a/public/app/features/plugins/extensions/usePluginComponents.test.tsx b/public/app/features/plugins/extensions/usePluginComponents.test.tsx index d5740d50b5a..930e2dda359 100644 --- a/public/app/features/plugins/extensions/usePluginComponents.test.tsx +++ b/public/app/features/plugins/extensions/usePluginComponents.test.tsx @@ -23,6 +23,9 @@ import { useLoadAppPlugins } from './useLoadAppPlugins'; import { usePluginComponents } from './usePluginComponents'; import { isGrafanaDevMode } from './utils'; +// Unmock usePluginComponents to test the real implementation +jest.unmock('./usePluginComponents'); + jest.mock('./useLoadAppPlugins'); jest.mock('./utils', () => ({ diff --git a/public/app/plugins/datasource/azuremonitor/locales/cs-CZ/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/cs-CZ/grafana-azure-monitor-datasource.json index c894f2b6066..f81e1e00549 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/cs-CZ/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/cs-CZ/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Předplatné", "placeholder-resource-name": "název", "tooltip-region": "Oblast kódu zdroje. Volitelné pro jeden zdroj, ale povinné při výběru více zdrojů.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Ručně upravte <2>identifikátor URI zdroje. Podporuje použití více proměnných šablony (např.: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Funkce agregování", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Základní protokoly", - "description-basic-logs": "", + "description-basic-logs": "Povolení této funkce zapříčiní vznik nákladů Azure Monitor na dotaz na panelech nástěnek, které dotazují tabulky nakonfigurované pro <2>základní protokoly.", "label-enable-basic-logs": "Povolit základní protokoly" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/de-DE/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/de-DE/grafana-azure-monitor-datasource.json index 9c4d21c9645..180815453db 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/de-DE/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/de-DE/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Abonnement", "placeholder-resource-name": "Name", "tooltip-region": "Die Code-Region der Ressource. Optional für eine Ressource, aber zwingend notwendig bei der Auswahl mehrerer Ressourcen.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Bearbeiten Sie die <2>Ressourcen-URI manuell. Unterstützt die Verwendung mehrerer Vorlagenvariablen (Beispiel: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Aggregatfunktion", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Basis-Logs", - "description-basic-logs": "", + "description-basic-logs": "Wenn Sie diese Funktion aktivieren, fallen pro Abfrage Azure Monitor-Kosten für Dashboard-Panels an, die Tabellen abfragen, die für <2>Basis-Logs konfiguriert sind.", "label-enable-basic-logs": "Basis-Logs aktivieren" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json index 1c84f68f612..47d60ee13f7 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Subscription", "placeholder-resource-name": "name", "tooltip-region": "The code region of the resource. Optional for one resource but mandatory when selecting multiple ones.", - "tooltip-resource-uri": "Manually edit the <2>resource uri . Supports the use of multiple template variables (ex: /subscriptions/$subId/resourceGroups/$rg)" + "tooltip-resource-uri": "Manually edit the <2>resource uri. Supports the use of multiple template variables (ex: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Aggregate function", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Basic Logs", - "description-basic-logs": "Enabling this feature incurs Azure Monitor per-query costs on dashboard panels that query tables configured for <2>Basic Logs .", + "description-basic-logs": "Enabling this feature incurs Azure Monitor per-query costs on dashboard panels that query tables configured for <2>Basic Logs.", "label-enable-basic-logs": "Enable Basic Logs" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/es-ES/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/es-ES/grafana-azure-monitor-datasource.json index 34239be4c38..dc8f0cd70b8 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/es-ES/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/es-ES/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Suscripción", "placeholder-resource-name": "nombre", "tooltip-region": "La región de código del recurso. Opcional para un recurso, pero obligatorio al seleccionar varios.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Edite manualmente el <2>URI del recurso. Admite el uso de múltiples variables de plantilla (por ejemplo: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Función de agregación", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Logs básicos", - "description-basic-logs": "", + "description-basic-logs": "Habilitar esta función conlleva costes por consulta de Azure Monitor en los paneles del dashboard que consultan tablas configuradas para <2>logs básicos.", "label-enable-basic-logs": "Habilitar logs básicos" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/fr-FR/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/fr-FR/grafana-azure-monitor-datasource.json index d5db7927d85..60617f22833 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/fr-FR/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/fr-FR/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Abonnement", "placeholder-resource-name": "nom", "tooltip-region": "La région de code de la ressource. Facultatif pour une ressource, mais obligatoire lors de la sélection de plusieurs ressources.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Modifiez manuellement l’<2>URI de la ressource. Prend en charge l’utilisation de plusieurs variables de modèle (par exemple : /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Fonction d’agrégation", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Journaux de base", - "description-basic-logs": "", + "description-basic-logs": "L’activation de cette fonctionnalité entraîne des coûts Azure Monitor par requête sur les panneaux du tableau de bord qui interrogent les tables configurées pour les <2>journaux de base.", "label-enable-basic-logs": "Activer les journaux de base" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/hu-HU/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/hu-HU/grafana-azure-monitor-datasource.json index 41afe3e235e..618f41bd45e 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/hu-HU/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/hu-HU/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Előfizetés", "placeholder-resource-name": "név", "tooltip-region": "Az erőforrás kódterülete. Egy erőforrás esetén nem kötelező, de több erőforrás kiválasztásakor kötelező.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Az <2>erőforrás-uri manuálisan szerkesztendő. Több sablonváltozó használatát támogatja (pl.: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Összesítési függvény", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Alapvető naplók", - "description-basic-logs": "", + "description-basic-logs": "A funkció engedélyezése esetén lekérdezésenkénti Azure Monitor-költségek merülnek fel azon irányítópult-paneleken, amelyek lekérdezik az <2>Alapvető naplókhoz konfigurált táblákat.", "label-enable-basic-logs": "Alapvető naplók engedélyezése" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/id-ID/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/id-ID/grafana-azure-monitor-datasource.json index b11a7098cc6..8f8211bfc3b 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/id-ID/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/id-ID/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Berlangganan", "placeholder-resource-name": "nama", "tooltip-region": "Wilayah kode sumber daya. Opsional untuk satu sumber daya tetapi wajib saat memilih beberapa sumber daya.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Edit <2>uri sumber daya secara manual. Mendukung penggunaan beberapa variabel templat (misalnya: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Fungsi agregat", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Log Dasar", - "description-basic-logs": "", + "description-basic-logs": "Jika Anda mengaktifkan fitur ini, Azure Monitor akan dikenakan biaya per kueri untuk panel dasbor yang melakukan kueri pada tabel yang dikonfigurasi untuk <2>Log Dasar.", "label-enable-basic-logs": "Aktifkan Log Dasar" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/it-IT/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/it-IT/grafana-azure-monitor-datasource.json index 0b5ffcdac4c..82bfd1f9ab9 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/it-IT/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/it-IT/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Abbonamento", "placeholder-resource-name": "nome", "tooltip-region": "La parte di codice della risorsa. Facoltativo per una risorsa ma obbligatorio quando se ne selezionano più di una.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Modifica manualmente l'<2>URI della risorsa. Supporta l'uso di più variabili di modello (ad esempio: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Funzione aggregata", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Registri di base", - "description-basic-logs": "", + "description-basic-logs": "L'abilitazione di questa funzione comporta i costi per query di Azure Monitor sui pannelli della dashboard che eseguono query su tabelle configurate per <2>Registri di base.", "label-enable-basic-logs": "Abilita registri di base" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/ja-JP/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/ja-JP/grafana-azure-monitor-datasource.json index cdd23e444af..d225f928c4e 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/ja-JP/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/ja-JP/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "サブスクリプション", "placeholder-resource-name": "名前", "tooltip-region": "リソースのコード領域。単一のリソースの場合はオプションですが、複数のリソースを選択する場合は必須です。", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "<2>リソースURIを手動で編集します。複数のテンプレート変数の使用をサポートしています(例:/subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "集計関数", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "基本ログ", - "description-basic-logs": "", + "description-basic-logs": "この機能を有効にすると、<2>基本ログ用に設定されたテーブルをクエリするダッシュボードパネルで、Azure Monitorのクエリごとのコストが発生します。", "label-enable-basic-logs": "基本ログを有効にする" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/ko-KR/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/ko-KR/grafana-azure-monitor-datasource.json index 8b855a130bd..e067ea85c39 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/ko-KR/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/ko-KR/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "구독", "placeholder-resource-name": "이름", "tooltip-region": "리소스의 코드 리전입니다. 하나의 리소스에 대해서는 선택 사항이지만 여러 리소스를 선택할 때는 필수입니다.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "<2>리소스 URI를 수동으로 편집합니다. 여러 템플릿 변수 사용 지원(예: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "집계 함수", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "기본 로그", - "description-basic-logs": "", + "description-basic-logs": "이 기능을 활성화하면 <2>기본 로그에 구성된 테이블을 쿼리하는 대시보드 패널에서 Azure Monitor 쿼리당 비용이 발생합니다.", "label-enable-basic-logs": "기본 로그 활성화" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/nl-NL/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/nl-NL/grafana-azure-monitor-datasource.json index ad53fde6477..022a78b5928 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/nl-NL/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/nl-NL/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Abonnement", "placeholder-resource-name": "naam", "tooltip-region": "Het codegebied van de bron. Optioneel voor één bron, maar verplicht bij het selecteren van meerdere bronnen.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Bewerk de <2>bron-uri handmatig. Ondersteunt het gebruik van meerdere sjabloonvariabelen (bijv.: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Totaal van de functie", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Basislogs", - "description-basic-logs": "", + "description-basic-logs": "Als je deze functie inschakelt, worden Azure Monitor-kosten per query gemaakt op dashboardpanelen die tabellen opvragen die zijn geconfigureerd voor <2>Basislogboeken.", "label-enable-basic-logs": "Basislogboeken inschakelen" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/pl-PL/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/pl-PL/grafana-azure-monitor-datasource.json index 808baffda64..ad6621ca5b4 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/pl-PL/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/pl-PL/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Subskrypcja", "placeholder-resource-name": "nazwa", "tooltip-region": "Kod regionu zasobu. Opcjonalny w przypadku jednego zasobu, ale wymagany przy wielu zasobach.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Edytuj ręcznie <2>identyfikator URI zasobu. Obsługuje korzystanie z wielu zmiennych szablonu (np. /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Funkcja agregująca", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Podstawowe dzienniki", - "description-basic-logs": "", + "description-basic-logs": "Włączenie tej funkcji wiąże się z opłatami Azure Monitor za każde zapytanie w przypadku paneli pulpitu, które wysyłają zapytania do tabel skonfigurowanych dla <2>podstawowych dzienników.", "label-enable-basic-logs": "Włącz podstawowe dzienniki" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/pt-BR/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/pt-BR/grafana-azure-monitor-datasource.json index 232fdcb80c4..204a98090f0 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/pt-BR/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/pt-BR/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Assinatura", "placeholder-resource-name": "nome", "tooltip-region": "A região de código do recurso. Opcional para um recurso, mas obrigatório ao selecionar vários.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Edite o <2>URI do recurso manualmente. Compatível com o uso de múltiplas variáveis de modelo (por exemplo: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Função de agregação", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Logs básicos", - "description-basic-logs": "", + "description-basic-logs": "A ativação deste recurso incorre em custos por consulta do Azure Monitor em painéis que consultam tabelas configuradas para <2>logs básicos.", "label-enable-basic-logs": "Ativar logs básicos" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/pt-PT/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/pt-PT/grafana-azure-monitor-datasource.json index 16e63fa3e55..14eb8f6c4fb 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/pt-PT/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/pt-PT/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Subscrição", "placeholder-resource-name": "nome", "tooltip-region": "A região de código do recurso. Opcional para um recurso, mas obrigatório ao selecionar vários.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Edite manualmente o <2>URI do recurso. Suporta a utilização de múltiplas variáveis de modelo (p. ex: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Função agregada", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Registos básicos", - "description-basic-logs": "", + "description-basic-logs": "A ativação desta funcionalidade incorre em custos por consulta do Azure Monitor em painéis que consultam tabelas configuradas para <2>Registos básicos.", "label-enable-basic-logs": "Ativar registos básicos" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/ru-RU/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/ru-RU/grafana-azure-monitor-datasource.json index 3d3e4a2be3a..a483da23dcd 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/ru-RU/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/ru-RU/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Подписка", "placeholder-resource-name": "имя", "tooltip-region": "Область кода ресурса. Необязательно для одного ресурса, но обязательно при выборе нескольких.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Вручную измените <2>URI ресурса. Поддерживает использование нескольких переменных шаблона (например, /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Агрегатная функция", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Базовые журналы", - "description-basic-logs": "", + "description-basic-logs": "Включение этой функции влечет за собой затраты на каждый запрос Azure Monitor на панелях дашбордов, связанный с таблицами для <2>базовых журналов.", "label-enable-basic-logs": "Включить базовые журналы" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/sv-SE/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/sv-SE/grafana-azure-monitor-datasource.json index 07021e81d59..5b482198cce 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/sv-SE/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/sv-SE/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Prenumeration", "placeholder-resource-name": "namn", "tooltip-region": "Resursens kodregion. Valfritt för en enskild resurs men obligatoriskt vid val av flera.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "Redigera <2>resursens URI manuellt. Stöder användning av flera mallvariabler (t.ex.: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Aggregeringsfunktion", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Grundläggande loggar", - "description-basic-logs": "", + "description-basic-logs": "Om den här funktionen aktiveras medför det Azure Monitor-kostnader per fråga för instrumentpanelspaneler som frågar tabeller konfigurerade för <2>basloggar.", "label-enable-basic-logs": "Aktivera basloggar" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/tr-TR/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/tr-TR/grafana-azure-monitor-datasource.json index 1b46578214b..417fdfbe1bf 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/tr-TR/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/tr-TR/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "Abonelik", "placeholder-resource-name": "ad", "tooltip-region": "Kaynağın kod bölgesi. Bir kaynak için isteğe bağlıdır ancak birden fazla kaynak seçildiğinde zorunludur.", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "<2>Kaynak URI'sini manuel olarak düzenleyin. Birden fazla şablon değişkeni kullanımını destekler (örnek: /subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "Toplama işlevi", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "Temel Günlükler", - "description-basic-logs": "", + "description-basic-logs": "Bu özelliğin etkinleştirilmesi, <2>Temel Günlükler için yapılandırılmış tabloları sorgulayan pano panellerinde Azure Monitor sorgu başına maliyetlerinin oluşmasına neden olur.", "label-enable-basic-logs": "Temel Günlükleri Etkinleştir" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/zh-Hans/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/zh-Hans/grafana-azure-monitor-datasource.json index eb5e45aaa73..cfb1c8862f6 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/zh-Hans/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/zh-Hans/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "订阅", "placeholder-resource-name": "姓名", "tooltip-region": "资源的代码区域。对于一个资源来说是可选的,但在选择多个资源时则是必须的。", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "手动编辑<2>资源 uri。支持使用多个模板变量(例如:/subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "汇总功能", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "基本日志", - "description-basic-logs": "", + "description-basic-logs": "启用此功能后,在查询表配置为<2>基本日志的数据面板上,Azure Monitor 会产生每次查询的费用。", "label-enable-basic-logs": "启用基本日志" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/locales/zh-Hant/grafana-azure-monitor-datasource.json b/public/app/plugins/datasource/azuremonitor/locales/zh-Hant/grafana-azure-monitor-datasource.json index 29f2a139657..0b4bc313999 100644 --- a/public/app/plugins/datasource/azuremonitor/locales/zh-Hant/grafana-azure-monitor-datasource.json +++ b/public/app/plugins/datasource/azuremonitor/locales/zh-Hant/grafana-azure-monitor-datasource.json @@ -17,7 +17,7 @@ "label-subscription": "訂閱", "placeholder-resource-name": "名稱", "tooltip-region": "資源的代碼區域。對於單一資源為選填,但選取多個資源時為必填。", - "tooltip-resource-uri": "" + "tooltip-resource-uri": "手動編輯<2>資源 URI。支援使用多個範本變數(例如:/subscriptions/$subId/resourceGroups/$rg)" }, "aggregate-item": { "aria-label-aggregate-function": "彙總函式", @@ -68,7 +68,7 @@ }, "basic-logs-toggle": { "aria-label-enable-basic-logs": "基本紀錄", - "description-basic-logs": "", + "description-basic-logs": "啟用此功能會導致 Azure Monitor 在查詢為<2>基本記錄設定的表格之儀表板面板上,產生每次查詢成本。", "label-enable-basic-logs": "啟用基本紀錄" }, "config-editor": { diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 8e3a153c85f..70d873551d4 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -38,7 +38,7 @@ "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", - "i18next-cli": "1.11.12", + "i18next-cli": "^1.24.22", "jest": "29.7.0", "react-select-event": "5.5.1", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/mssql/locales/cs-CZ/mssql.json b/public/app/plugins/datasource/mssql/locales/cs-CZ/mssql.json index 198e7a85804..f48b613668f 100644 --- a/public/app/plugins/datasource/mssql/locales/cs-CZ/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/cs-CZ/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Pokud používáte starší verzi Microsoft SQL Server, jako je 2008 a 2008R2, možná budete muset zakázat šifrování, abyste se mohli připojit.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Dolní limit pro automatické seskupení podle časového intervalu. Doporučuje se nastavit frekvenci zápisu, například <1>{{exampleInterval}}, pokud jsou data zapisována každou minutu.", "description-tls-cert": "Cesta k souboru obsahujícímu certifikát veřejného klíče CA, který podepsal certifikát SQL Server. Nutné, když je certifikát serveru podepsán sám sebou.", "label-auth-settings": "Nastavení ověření Azure", "label-auth-type": "Typ ověření", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Uveďte, zda by měly být k vyhledání KDC a dalších serverů pro oblast použity záznamy DNS „SRV“. Výchozí hodnota je <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Cesta ke konfiguračnímu souboru pro <2>balíček MIT krb5. Výchozí hodnota je <4>{{default}}.", "description-udp-preference-limit": "Výchozí hodnota je <1>{{default}} (vždy použít TCP) a je volitelná.", "label-dns-lookup-kdc": "DNS pro vyhledání KDC", "label-krb5-config-file-path": "Cesta ke konfiguračnímu souboru krb5", diff --git a/public/app/plugins/datasource/mssql/locales/de-DE/mssql.json b/public/app/plugins/datasource/mssql/locales/de-DE/mssql.json index b1308275e22..1a08a22f92e 100644 --- a/public/app/plugins/datasource/mssql/locales/de-DE/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/de-DE/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Wenn Sie eine ältere Version von Microsoft SQL Server wie 2008 und 2008R2 nutzen, müssen Sie möglicherweise die Verschlüsselung deaktivieren, um eine Verbindung herstellen zu können.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Eine Untergrenze für die automatische Gruppierung je nach Zeitintervall. Es wird empfohlen, die Schreibfrequenz einzustellen, zum Beispiel <1>{{exampleInterval}}, wenn Ihre Daten jede Minute geschrieben werden.", "description-tls-cert": "Pfad zu einer Datei, die das öffentliche Schlüsselzertifikat der Zertifizierungsstelle enthält, die das SQL Server-Zertifikat signiert hat. Wird benötigt, wenn es sich um ein selbstsigniertes Serverzertifikat handelt.", "label-auth-settings": "Einstellungen für Azure Authentication", "label-auth-type": "Authentifizierungstyp", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Geben Sie an, ob DNS-`SRV`-Einträge verwendet werden sollen, um die KDCs und andere Server für einen Bereich zu lokalisieren. Der Standardwert ist <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Der Pfad zur Konfigurationsdatei für das <2>Paket MIT krb5. Der Standardwert ist <4>{{default}}.", "description-udp-preference-limit": "Der Standardwert ist <1>{{default}} und bedeutet, dass immer TCP verwendet wird. Dies ist optional.", "label-dns-lookup-kdc": "DNS-Suche KDC", "label-krb5-config-file-path": "Konfigurationsdateipfad für krb5", diff --git a/public/app/plugins/datasource/mssql/locales/en-US/mssql.json b/public/app/plugins/datasource/mssql/locales/en-US/mssql.json index 4e20b5d10c1..c495e66b179 100644 --- a/public/app/plugins/datasource/mssql/locales/en-US/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/en-US/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "{{encryptionValue}} - Data sent between client and server is not encrypted beyond the login packet. (default)", "description-encrypt-older-version": "If you're using an older version of Microsoft SQL Server like 2008 and 2008R2 you may need to disable encryption to be able to connect.", "description-encrypt-true": "{{encryptionValue}} - Data sent between client and server is encrypted.", - "description-min-interval": "A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example <1>{{exampleInterval}} if your data is written every minute.", + "description-min-interval": "A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example<1>{{exampleInterval}} if your data is written every minute.", "description-tls-cert": "Path to file containing the public key certificate of the CA that signed the SQL Server certificate. Needed when the server certificate is self signed.", "label-auth-settings": "Azure Authentication Settings", "label-auth-type": "Authentication Type", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Indicate whether DNS `SRV` records should be used to locate the KDCs and other servers for a realm. The default is <1>{{default}}.", - "description-krb5-config-file-path": "The path to the configuration file for the <2>MIT krb5 package . The default is <4>{{default}}.", + "description-krb5-config-file-path": "The path to the configuration file for the <2>MIT krb5 package. The default is <4>{{default}}.", "description-udp-preference-limit": "The default is <1>{{default}} and means always use TCP and is optional.", "label-dns-lookup-kdc": "DNS Lookup KDC", "label-krb5-config-file-path": "krb5 config file path", diff --git a/public/app/plugins/datasource/mssql/locales/es-ES/mssql.json b/public/app/plugins/datasource/mssql/locales/es-ES/mssql.json index a3beb6a86f4..44bdc0cd695 100644 --- a/public/app/plugins/datasource/mssql/locales/es-ES/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/es-ES/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Si está utilizando una versión anterior de Microsoft SQL Server, como 2008 y 2008R2, es posible que deba desactivar el cifrado para poder conectarse.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Un límite inferior para el grupo automático por intervalo de tiempo. Se recomienda configurar la frecuencia de escritura, por ejemplo<1>{{exampleInterval}}, si sus datos se escriben cada minuto.", "description-tls-cert": "Ruta al archivo que contiene el certificado de clave pública de la CA que firmó el certificado de SQL Server. Necesario cuando el certificado del servidor está autofirmado.", "label-auth-settings": "Configuración de autenticación de Azure", "label-auth-type": "Tipo de autenticación", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Indique si los registros DNS «SRV» deben utilizarse para localizar los KDC y otros servidores de un dominio. El valor predeterminado es <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "La ruta del archivo de configuración para el <2>paquete MIT krb5. El valor predeterminado es <4>{{default}}.", "description-udp-preference-limit": "El valor predeterminado es <1>{{default}} y significa que siempre se utiliza TCP y es opcional.", "label-dns-lookup-kdc": "KDC de búsqueda de DNS", "label-krb5-config-file-path": "Ruta del archivo de configuración krb5", diff --git a/public/app/plugins/datasource/mssql/locales/fr-FR/mssql.json b/public/app/plugins/datasource/mssql/locales/fr-FR/mssql.json index 8e706658691..4bbc3d03912 100644 --- a/public/app/plugins/datasource/mssql/locales/fr-FR/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/fr-FR/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Si vous utilisez une ancienne version de Microsoft SQL Server, comme 2008 et 2008R2, vous devrez peut-être désactiver le chiffrement pour pouvoir vous connecter.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Une limite inférieure pour le groupe automatique par intervalle de temps. Il est recommandé de définir la fréquence d’écriture, par exemple<1>{{exampleInterval}} si vos données sont écrites toutes les minutes.", "description-tls-cert": "Chemin d’accès au fichier contenant le certificat de clé publique de l’autorité de certification qui a signé le certificat SQL Server. Nécessaire lorsque le certificat du serveur est auto-signé.", "label-auth-settings": "Paramètres d’authentification Azure", "label-auth-type": "Type d’authentification", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Indiquez si les enregistrements DNS « SRV » doivent être utilisés pour localiser les KDC et d’autres serveurs pour un domaine. La valeur par défaut est <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Le chemin d’accès au fichier de configuration pour le <2>paquet MIT krb5. La valeur par défaut est <4>{{default}}.", "description-udp-preference-limit": "La valeur par défaut est <1>{{default}} et signifie toujours utiliser TCP et est facultative.", "label-dns-lookup-kdc": "Recherche DNS KDC", "label-krb5-config-file-path": "chemin du fichier de configuration krb5", diff --git a/public/app/plugins/datasource/mssql/locales/hu-HU/mssql.json b/public/app/plugins/datasource/mssql/locales/hu-HU/mssql.json index d742078e2af..d2ab6b0d749 100644 --- a/public/app/plugins/datasource/mssql/locales/hu-HU/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/hu-HU/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Ha a Microsoft SQL Server régebbi verzióját használja, például a 2008-at vagy a 2008R2-t, akkor előfordulhat, hogy le kell tiltania a titkosítást, hogy csatlakozhasson.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Az automatikus csoportosítás időintervallumának alsó határa. Javasoljuk, hogy az írási gyakoriságra állítsa be, például <1>{{exampleInterval}} értékre, ha az adatait percenként írja.", "description-tls-cert": "Az SQL Server-tanúsítványt aláíró hitelesítésszolgáltató nyilvános kulcsos tanúsítványát tartalmazó fájl elérési útvonala. Akkor szükséges, ha a kiszolgáló tanúsítványa saját aláírású.", "label-auth-settings": "Azure-hitelesítési beállítások", "label-auth-type": "Hitelesítés típusa", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Jelölje meg, hogy a DNS „SRV” rekordjait kell-e használni a KDC-k és más kiszolgálók keresésére egy tartományban. Az alapértelmezett érték <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "A <2>MIT krb5 csomag konfigurációs fájljának elérési útvonala. Az alapértelmezett érték <4>{{default}}.", "description-udp-preference-limit": "Az alapértelmezett érték <1>{{default}}, ami azt jelenti, hogy mindig TCP-t használ, és opcionális.", "label-dns-lookup-kdc": "DNS-keresés – KDC", "label-krb5-config-file-path": "krb5 konfigurációs fájl útvonala", diff --git a/public/app/plugins/datasource/mssql/locales/id-ID/mssql.json b/public/app/plugins/datasource/mssql/locales/id-ID/mssql.json index e61a202f711..ea5951aff48 100644 --- a/public/app/plugins/datasource/mssql/locales/id-ID/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/id-ID/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Jika Anda menggunakan Microsoft SQL Server versi lama seperti 2008 dan 2008R2, Anda mungkin perlu menonaktifkan enkripsi untuk dapat terhubung.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Batas bawah untuk grup otomatis berdasarkan interval waktu. Direkomendasikan untuk diatur ke frekuensi tulis, misalnya <1>{{exampleInterval}} jika data Anda ditulis setiap menit.", "description-tls-cert": "Jalur ke file yang berisi sertifikat kunci publik CA yang menandatangani sertifikat SQL Server. Diperlukan ketika sertifikat server ditandatangani sendiri.", "label-auth-settings": "Pengaturan Autentikasi Azure", "label-auth-type": "Jenis Autentikasi", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Tunjukkan apakah catatan DNS 'SRV' harus digunakan untuk menemukan KDC dan server lain untuk suatu realm. Defaultnya adalah <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Jalur ke file konfigurasi untuk <2>paket MIT krb5. Defaultnya adalah <4>{{default}}.", "description-udp-preference-limit": "Defaultnya adalah <1>{{default}} dan berarti selalu menggunakan TCP dan bersifat opsional.", "label-dns-lookup-kdc": "KDC Pencarian DNS", "label-krb5-config-file-path": "jalur file konfigurasi krb5", diff --git a/public/app/plugins/datasource/mssql/locales/it-IT/mssql.json b/public/app/plugins/datasource/mssql/locales/it-IT/mssql.json index 13291c7e524..958f8f1e732 100644 --- a/public/app/plugins/datasource/mssql/locales/it-IT/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/it-IT/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Se utilizzi una versione precedente di Microsoft SQL Server come 2008 e 2008R2, potrebbe essere necessario disabilitare la crittografia per potersi connettere.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Un limite inferiore per il raggruppamento automatico per intervallo di tempo. Si consiglia di impostare la frequenza di scrittura, ad esempio <1>{{exampleInterval}} se i dati vengono scritti ogni minuto.", "description-tls-cert": "Percorso al file contenente il certificato di chiave pubblica della CA che ha firmato il certificato di SQL Server. Necessario quando il certificato del server è autofirmato.", "label-auth-settings": "Impostazioni di autenticazione di Azure", "label-auth-type": "Tipo di autenticazione", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Indica se i record DNS \"SRV\" devono essere utilizzati per individuare i KDC e altri server per un'area di autenticazione. Il valore predefinito è <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Il percorso al file di configurazione per il <2>pacchetto MIT krb5. Il valore predefinito è <4>{{default}}.", "description-udp-preference-limit": "Il valore predefinito è <1>{{default}} e significa che si utilizza sempre TCP ed è facoltativo.", "label-dns-lookup-kdc": "KDC di ricerca DNS", "label-krb5-config-file-path": "percorso del file di configurazione krb5", diff --git a/public/app/plugins/datasource/mssql/locales/ja-JP/mssql.json b/public/app/plugins/datasource/mssql/locales/ja-JP/mssql.json index 71971ae17ea..b806f193412 100644 --- a/public/app/plugins/datasource/mssql/locales/ja-JP/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/ja-JP/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "2008や2008R2などの古いバージョンのMicrosoft SQL Serverを使用している場合、接続できるようにするためには、暗号化を無効にする必要がある場合があります。", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "時間間隔による自動グループ化の下限。データが毎分書き込まれる場合は、書き込み頻度に合わせて<1>{{exampleInterval}}のようにに設定することをお勧めします。", "description-tls-cert": "SQL Server証明書に署名したCAの公開鍵証明書を含むファイルへのパス。サーバー証明書が自己署名されている場合に必要です。", "label-auth-settings": "Azure認証設定", "label-auth-type": "認証タイプ", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "DNS「SRV」レコードを使用して、レルムのKDCやその他のサーバーを特定するかどうかを示します。デフォルトは<1>{{default}}です。", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "<2>MIT krb5パッケージの設定ファイルへのパス。デフォルトは<4>{{default}}です。", "description-udp-preference-limit": "デフォルトは<1>{{default}}で、常にTCPを使用することを意味します。オプションです。", "label-dns-lookup-kdc": "DNS検索KDC", "label-krb5-config-file-path": "krb5設定ファイルパス", diff --git a/public/app/plugins/datasource/mssql/locales/ko-KR/mssql.json b/public/app/plugins/datasource/mssql/locales/ko-KR/mssql.json index d88f5e897f1..fbef047ba28 100644 --- a/public/app/plugins/datasource/mssql/locales/ko-KR/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/ko-KR/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "2008 및 2008R2와 같은 이전 버전의 Microsoft SQL Server를 사용하는 경우 연결을 위해 암호화를 비활성화해야 할 수 있습니다.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "시간 간격을 기준으로 자동 그룹화할 때의 하한입니다. 데이터 기록 빈도에 맞춰 설정하는 것이 좋습니다. 예를 들어, 데이터가 1분마다 기록된다면 <1>{{exampleInterval}}으로 설정하세요. ", "description-tls-cert": "SQL 서버 인증서에 서명한 CA의 공개 키 인증서가 포함된 파일의 경로입니다. 서버 인증서가 자체 서명된 경우 필요합니다.", "label-auth-settings": "Azure 인증 설정", "label-auth-type": "인증 유형", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "DNS `SRV` 레코드를 사용하여 Realm의 KDC 및 기타 서버를 찾을지 여부를 나타냅니다. 기본값은 <1>{{default}}입니다.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "<2>MIT krb5 패키지의 구성 파일에 대한 경로입니다. 기본값은 <4>{{default}}입니다.", "description-udp-preference-limit": "기본값은 <1>{{default}}(으)로 항상 TCP를 사용한다는 의미이며, 선택 사항입니다.", "label-dns-lookup-kdc": "DNS 조회 KDC", "label-krb5-config-file-path": "krb5 구성 파일 경로", diff --git a/public/app/plugins/datasource/mssql/locales/nl-NL/mssql.json b/public/app/plugins/datasource/mssql/locales/nl-NL/mssql.json index 6f5631edda2..73a500eef02 100644 --- a/public/app/plugins/datasource/mssql/locales/nl-NL/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/nl-NL/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Als je een oudere versie van Microsoft SQL Server gebruikt, zoals 2008 en 2008R2, moet je mogelijk versleuteling uitschakelen om verbinding te kunnen maken.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Een ondergrens voor de automatische groepering op tijdsinterval. Aanbevolen om in te stellen op schrijffrequentie, bijvoorbeeld <1>{{exampleInterval}} als je gegevens elke minuut worden geschreven.", "description-tls-cert": "Pad naar bestand met het publieke sleutelcertificaat van de CA die het SQL Server-certificaat heeft ondertekend. Nodig wanneer het servercertificaat zelfondertekend is.", "label-auth-settings": "Azure-verificatie-instellingen", "label-auth-type": "Authenticatietype", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Geef aan of DNS 'SRV'-records moeten worden gebruikt om de KDC's en andere servers voor een realm te lokaliseren. De standaardwaarde is <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Het pad naar het configuratiebestand voor het <2>MIT krb5-pakket. De standaardwaarde is <4>{{default}}.", "description-udp-preference-limit": "De standaardwaarde is <1>{{default}} en betekent altijd TCP gebruiken en is optioneel.", "label-dns-lookup-kdc": "DNS Lookup KDC", "label-krb5-config-file-path": "pad naar het krb5-config-bestand", diff --git a/public/app/plugins/datasource/mssql/locales/pl-PL/mssql.json b/public/app/plugins/datasource/mssql/locales/pl-PL/mssql.json index 98329b765fb..404e2ffed33 100644 --- a/public/app/plugins/datasource/mssql/locales/pl-PL/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/pl-PL/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Jeśli używasz starszej wersji Microsoft SQL Server, takiej jak 2008 lub 2008 R2, może być konieczne wyłączenie szyfrowania w celu nawiązania połączenia.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Dolna granica automatycznego grupowania według odstępu czasu. Zalecane jest ustawienie częstotliwości zapisu, na przykład <1>{{exampleInterval}}, jeśli dane są zapisywane co minutę. ", "description-tls-cert": "Ścieżka do pliku zawierającego certyfikat klucza publicznego urzędu certyfikacji, który podpisał certyfikat SQL Server. Wymagane, gdy certyfikat serwera jest samodzielnie podpisany.", "label-auth-settings": "Ustawienia uwierzytelniania Azure", "label-auth-type": "Typ uwierzytelniania", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Określ, czy rekordy DNS „SRV” powinny być używane do lokalizowania KDC i innych serwerów dla domeny. Domyślnie jest to <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Ścieżka do pliku konfiguracyjnego dla <2>pakietu MIT krb5. Domyślnie jest to <4>{{default}}.", "description-udp-preference-limit": "Domyślnie jest to <1>{{default}}, co oznacza „zawsze używaj TCP”. Jest to ustawienie opcjonalne.", "label-dns-lookup-kdc": "Wyszukiwanie DNS KDC", "label-krb5-config-file-path": "Ścieżka do pliku konfiguracyjnego krb5", diff --git a/public/app/plugins/datasource/mssql/locales/pt-BR/mssql.json b/public/app/plugins/datasource/mssql/locales/pt-BR/mssql.json index 2c99bffee93..7b79a793e0f 100644 --- a/public/app/plugins/datasource/mssql/locales/pt-BR/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/pt-BR/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Se você estiver usando uma versão mais antiga do Microsoft SQL Server, como 2008 e 2008R2, talvez seja necessário desativar a criptografia para poder se conectar.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Um limite inferior para o grupo automático por intervalo de tempo. Recomendamos definir a frequência de gravação — por exemplo, <1>{{exampleInterval}}, se seus dados forem gravados a cada minuto.", "description-tls-cert": "Caminho para o arquivo que contém o certificado de chave pública da CA que assinou o certificado do SQL Server. Necessário quando o certificado do servidor é autoassinado.", "label-auth-settings": "Configurações de autenticação do Azure", "label-auth-type": "Tipo de autenticação", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Indique se os registros DNS \"SRV\" devem ser usados para localizar os KDCs e outros servidores de um domínio. O padrão é <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "O caminho para o arquivo de configuração do <2>pacote MIT krb5. O padrão é <4>{{default}}.", "description-udp-preference-limit": "O valor padrão é <1>{{default}}, indicando que o TCP será usado sempre e é opcional.", "label-dns-lookup-kdc": "KDC de pesquisa de DNS", "label-krb5-config-file-path": "Caminho do arquivo de configuração krb5", diff --git a/public/app/plugins/datasource/mssql/locales/pt-PT/mssql.json b/public/app/plugins/datasource/mssql/locales/pt-PT/mssql.json index e41988ed380..0b2ef269e12 100644 --- a/public/app/plugins/datasource/mssql/locales/pt-PT/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/pt-PT/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Se estiver a utilizar uma versão mais antiga do Microsoft SQL Server, como 2008 e 2008R2, talvez tenha de desativar a encriptação para poder estabelecer ligação.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Um limite inferior para o agrupamento automático por intervalo de tempo. É recomendado que seja definido para frequência de escrita, por exemplo, <1>{{exampleInterval}} se os seus dados forem escritos a cada minuto.", "description-tls-cert": "Caminho para o ficheiro que contém o certificado de chave pública da AC que assinou o certificado do SQL Server. Necessário quando o certificado do servidor é assinado automaticamente.", "label-auth-settings": "Definições de autenticação do Azure", "label-auth-type": "Tipo de autenticação", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Indique se os registos DNS \"SRV\" devem ser utilizados para localizar os KDC e outros servidores para um âmbito. A predefinição é <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "O caminho para o ficheiro de configuração do <2>pacote MIT krb5. A predefinição é <4>{{default}}.", "description-udp-preference-limit": "A predefinição é <1>{{default}}, que significa utilizar sempre TCP, e é opcional.", "label-dns-lookup-kdc": "KDC de pesquisa de DNS", "label-krb5-config-file-path": "Caminho do ficheiro de configuração krb5", diff --git a/public/app/plugins/datasource/mssql/locales/ru-RU/mssql.json b/public/app/plugins/datasource/mssql/locales/ru-RU/mssql.json index 2a8cc95e103..0cd1eaf499f 100644 --- a/public/app/plugins/datasource/mssql/locales/ru-RU/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/ru-RU/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Если вы используете более старую версию Microsoft SQL Server, например 2008 или 2008R2, для подключения может требоваться отключение шифрования.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Нижний предел для автоматической группировки по временному интервалу. Рекомендуется установить частоту записи, например <1>{{exampleInterval}}, если ваши данные записываются каждую минуту.", "description-tls-cert": "Путь к файлу, содержащему сертификат открытого ключа центра сертификации, который подписал сертификат SQL Server. Требуется, когда сертификат сервера является самозаверяющим.", "label-auth-settings": "Параметры аутентификации Azure", "label-auth-type": "Тип аутентификации", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Укажите, следует ли использовать служебные записи DNS для поиска KDC и других серверов для области. Значение по умолчанию — <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Путь к файлу конфигурации для <2>пакета MIT krb5. Значение по умолчанию — <4>{{default}}.", "description-udp-preference-limit": "Значение по умолчанию — <1>{{default}}, что означает всегда использовать TCP (необязательно).", "label-dns-lookup-kdc": "DNS-поиск KDC", "label-krb5-config-file-path": "Путь к файлу конфигурации krb5", diff --git a/public/app/plugins/datasource/mssql/locales/sv-SE/mssql.json b/public/app/plugins/datasource/mssql/locales/sv-SE/mssql.json index f5fad3ac672..78c4fe3450f 100644 --- a/public/app/plugins/datasource/mssql/locales/sv-SE/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/sv-SE/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Om du använder en äldre version av Microsoft SQL Server, exempelvis 2008 eller 2008R2, kan du behöva inaktivera kryptering för att kunna ansluta.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "En nedre gräns för den automatiska grupperingen efter tidsintervall. Rekommenderas att ställas in på skrivfrekvens, till exempel<1>{{exampleInterval}} om data skrivs varje minut.", "description-tls-cert": "Sökväg till fil som innehåller det offentliga nyckelcertifikatet för den certifikatutfärdare som undertecknade SQL Server-certifikatet. Behövs när servercertifikatet är självsignerat.", "label-auth-settings": "Inställningar för Azure-autentisering", "label-auth-type": "Behörighetskontrolltyp", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Ange om DNS \"SRV\"-poster ska användas för att hitta KDC:er och andra servrar för en realm. Standard är <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "Sökvägen till konfigurationsfilen för <2>MIT krb5-paketet. Standard är <4>{{default}}.", "description-udp-preference-limit": "Standard är <1>{{default}} och innebär att alltid använda TCP och är valfritt.", "label-dns-lookup-kdc": "DNS-sökning KDC", "label-krb5-config-file-path": "Sökväg till krb5-konfigurationsfil", diff --git a/public/app/plugins/datasource/mssql/locales/tr-TR/mssql.json b/public/app/plugins/datasource/mssql/locales/tr-TR/mssql.json index 585a8541064..cff53a13e08 100644 --- a/public/app/plugins/datasource/mssql/locales/tr-TR/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/tr-TR/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "Microsoft SQL Server'ın 2008 ve 2008R2 gibi eski bir sürümünü kullanıyorsanız bağlanabilmek için şifrelemeyi devre dışı bırakmanız gerekebilir.", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "Otomatik gruplama zaman aralığı için alt sınır. Verileriniz her dakika yazılıyorsa (<1>{{exampleInterval}} gibi) yazma sıklığına ayarlanması önerilir.", "description-tls-cert": "SQL Server sertifikasını imzalayan Sertifika Yetkilisinin (CA) genel anahtar sertifikasını içeren dosyanın yolu. Sunucu sertifikası kendi kendine imzalanmışsa gereklidir.", "label-auth-settings": "Azure Kimlik Doğrulama Ayarları", "label-auth-type": "Kimlik Doğrulama Türü", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "Bir alan için KDC'leri ve diğer sunucuları bulmak için DNS SRV kayıtlarının kullanılıp kullanılmayacağını belirtir. Varsayılan değer <1>{{default}}.", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "<2>MIT krb5 paketi için yapılandırma dosyasının yolu. Varsayılan değer <4>{{default}}.", "description-udp-preference-limit": "Varsayılan değer <1>{{default}} olduğu için her zaman TCP kullanılır ve isteğe bağlıdır.", "label-dns-lookup-kdc": "DNS Arama KDC", "label-krb5-config-file-path": "krb5 yapılandırma dosyasının yolu", diff --git a/public/app/plugins/datasource/mssql/locales/zh-Hans/mssql.json b/public/app/plugins/datasource/mssql/locales/zh-Hans/mssql.json index e13b1998e93..406d43f5fbe 100644 --- a/public/app/plugins/datasource/mssql/locales/zh-Hans/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/zh-Hans/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "如果您使用的是 2008 和 2008R2 等旧版本的 Microsoft SQL Server,可能需要禁用加密才能连接。", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "按时间间隔划分的自动分组下限。如果数据每分钟写入一次,建议设置为写入频率,例如<1>{{exampleInterval}}。", "description-tls-cert": "包含签署 SQL Server 证书的 CA 的公钥证书的文件路径。服务器证书为自签名时需要。", "label-auth-settings": "Azure 身份验证设置", "label-auth-type": "身份验证类型", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "指明是否应使用 DNS `SRV`记录来定位领域的 KDC 和其他服务器。默认值为 <1>{{default}}。", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "<2>MIT krb5 软件包配置文件的路径。默认值为 <4>{{default}}。", "description-udp-preference-limit": "默认值为 <1>{{default}},表示始终使用 TCP,为可选项。", "label-dns-lookup-kdc": "DNS 查询 KDC", "label-krb5-config-file-path": "krb5 配置文件路径", diff --git a/public/app/plugins/datasource/mssql/locales/zh-Hant/mssql.json b/public/app/plugins/datasource/mssql/locales/zh-Hant/mssql.json index bdcd2b1831b..bc4da26a720 100644 --- a/public/app/plugins/datasource/mssql/locales/zh-Hant/mssql.json +++ b/public/app/plugins/datasource/mssql/locales/zh-Hant/mssql.json @@ -69,7 +69,7 @@ "description-encrypt-false": "", "description-encrypt-older-version": "如果您使用的是舊版本的 Microsoft SQL Server(如 2008 和 2008R2),則可能需要停用加密才能連線。", "description-encrypt-true": "", - "description-min-interval": "", + "description-min-interval": "按時間間隔自動分組的下限。建議設定為寫入頻率,例如 <1>{{exampleInterval}},如果您的資料每分鐘寫入一次,則設定為 <1>1 分鐘。", "description-tls-cert": "包含簽署 SQL Server 憑證的 CA 之公開金鑰憑證的檔案路徑。伺服器憑證自行簽署時需要。", "label-auth-settings": "Azure 驗證設定", "label-auth-type": "身分驗證類型", @@ -104,7 +104,7 @@ }, "kerberos-advanced-settings": { "description-dns-lookup-kdc": "指明是否應使用 DNS「SRV」紀錄,來定位領域的 KDC 和其他伺服器。預設值為 <1>{{default}}。", - "description-krb5-config-file-path": "", + "description-krb5-config-file-path": "<2>MIT krb5 套件的設定檔案路徑。預設值為 <4>{{default}}。", "description-udp-preference-limit": "預設值為 <1>{{default}},表示一律使用 TCP,且為選用。", "label-dns-lookup-kdc": "DNS 查找 KDC", "label-krb5-config-file-path": "krb5 設定檔案路徑", diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index cb766af057f..48b210ca0f5 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -26,7 +26,7 @@ "@types/lodash": "4.17.20", "@types/node": "24.10.1", "@types/react": "18.3.18", - "i18next-cli": "1.11.12", + "i18next-cli": "^1.24.22", "ts-node": "10.9.2", "typescript": "5.9.2", "webpack": "5.101.0" diff --git a/public/app/plugins/panel/timeseries/suggestions.ts b/public/app/plugins/panel/timeseries/suggestions.ts index 8ca7d6148ed..d41179d3f36 100644 --- a/public/app/plugins/panel/timeseries/suggestions.ts +++ b/public/app/plugins/panel/timeseries/suggestions.ts @@ -231,6 +231,7 @@ export function getPrepareTimeseriesSuggestion(panelId: number): PanelPluginVisu return { name: 'Transform to wide time series format', + hash: 'timeseries-transform-prepare-wide', pluginId: 'timeseries', transformations, }; diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 1e2530c7e6f..ac6538d3719 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -2093,7 +2093,7 @@ "warning-1": "Odstraněním těchto zásad oznamování dojde k jejich trvalému odebrání.", "warning-2": "Určitě chcete zásady odstranit?" }, - "filter-description": "", + "filter-description": "Filtrujte zásady oznamování pomocí seznamu odpovídajících osob oddělených čárkami, např.:<1>závažnost = kritická, region = EMEA", "generated-policies": "Automaticky generované zásady", "matchers": "Srovnávače", "metadata": { @@ -2832,7 +2832,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Data šablony výstrah", - "only-in-alert": "K dispozici pouze v kontextu výstrahy (např. v rámcismyčky výstrahy)" + "only-in-alert": "" }, "available-context-notification": "K dispozici v kontextu oznámení.", "notification-template-data": "Data šablony oznámení" @@ -3269,7 +3269,7 @@ "auth-settings": "Nastavení ověření" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Konfigurovat nastavení ověření. Další informace najdete v naší <2>dokumentaci." }, "auth-drawer-unconnected": { "advanced-auth": "Pokročilé ověření", @@ -3303,7 +3303,7 @@ "allowed-organizations-description": "Seznam organizací oddělených čárkou nebo mezerou. Uživatel musí být členem alespoň\njedné organizace, aby se mohl přihlásit.", "allowed-organizations-label": "Povolené organizace", "allowed-organizations-placeholder": "Zadejte organizace (my-team, myteam…) a stiskněte klávesu Enter pro přidání", - "api-url-description": "", + "api-url-description": "Koncový bod uživatelských informací vašeho poskytovatele OAuth2. Informace vrácené tímto koncovým bodem musí být kompatibilní s <2>OpenID UserInfo.", "api-url-required": "Toto pole musí být platná adresa URL, pokud je nastavena.", "auth-style-description": "Určuje způsob odesílání „{{ clientIDLabel }}“ a „{{ clientSecretLabel }}“ poskytovateli Oauth2. Výchozí hodnota je AutoDetect.", "auth-style-label": "Styl autorizace", @@ -3448,7 +3448,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Spravujte nastavení ověření a konfigurujte jednotné přihlašování. Další informace najdete v naší <2>dokumentaci." }, "bar-chart": { "warn": { @@ -4884,7 +4884,7 @@ "add-visualization-body": "Vyberte zdroj dat a poté se dotazujte a vizualizujte data pomocí grafů, statistik a tabulek nebo vytvářejte seznamy, přehledy a další widgety.", "add-visualization-button": "Přidat vizualizaci", "add-visualization-header": "Začněte novou nástěnku přidáním vizualizace", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importujte nástěnky ze souborů nebo z <2>grafana.com.", "import-a-dashboard-header": "Importovat nástěnku", "import-dashboard-button": "Importovat nástěnku", "show-less-dashboards": "", @@ -6671,7 +6671,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Klikněte <2>tady a zjistěte víc o této chybě.", - "success-more-details-links": "" + "success-more-details-links": "Následně můžete začít vizualizovat data <2>vytvořením nástěnky nebo dotazováním na data v <5>Prozkoumat zobrazení." }, "data-sources": { "datasource-add-button": { @@ -6767,7 +6767,7 @@ "test": "Test" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Nebo si to zjednodušte a získejte {{mainDS}} (a {{extraDS}}) – plně spravované, škálovatelné a hostované zdroje dat od Grafana Labs s <6>celoživotním bezplatným plánem Grafana Cloud.", "title-alert": "Nakonfigurujte zdroje dat {{mainDS}} níže" }, "dashboards-table": { @@ -7756,7 +7756,7 @@ "math": { "available-math-functions": "Dostupné matematické funkce", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Podívejte se na naši další dokumentaci o <2>matematických výrazech.", "tooltip-title": "Matematický operátor", "tooltip-trigger": "Výraz" }, @@ -7782,11 +7782,24 @@ "label-upsample": "Zvýšit vzorkovací frekvenci", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Spustit dotaz", "modal-title": "Editor SQL", "tooltip-experimental": "Integrace LLM pro výrazy jazyka SQL je experimentální. Jakékoli problémy nahlaste týmu Grafana." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Vstup" } @@ -11022,10 +11035,10 @@ "placeholder-optional": "(volitelné)", "role": "Role", "submit": "Odeslat", - "tooltip": "" + "tooltip": "Teď můžete vybrat možnost „Žádná základní role“ a přidat oprávnění k vlastním potřebám. Více informací najdete v <1>naší dokumentaci." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Pošlete pozvánku nebo přidejte stávajícího uživatele Grafany do organizace.<1> {{orgName}}", "text": { "invite-user": "Pozvat uživatele" } @@ -12493,7 +12506,7 @@ }, "menu": { "clear-button": "Vymazat vše", - "tooltip": "" + "tooltip": "Teď můžete vybrat možnost „Žádná základní role“ a přidat oprávnění k vlastním potřebám. Více informací najdete v <1>naší dokumentaci." }, "menu-aria-label": "Nabídka voliče role", "menu-group-option-aria-label": "Možnost voliče role", @@ -12821,7 +12834,7 @@ "info-text": "Vytvořte přímý odkaz na tuto nástěnku nebo panel přizpůsobený možnostmi dole.", "link-url": "Odkaz adresy URL", "render-alert": "Plugin pro renderování obrázků není nainstalován", - "render-instructions": "", + "render-instructions": "Chcete-li vyrenderovat obrázek, musíte si nainstalovat <2>plugin Grafana pro renderování obrázků. Chcete-li nainstalovat plugin, obraťte se na správce Grafany.", "rendered-image": "Přímý odkaz na vyrenderovaný obrázek", "save-alert": "Nástěnka není uložena", "save-dashboard": "Chcete-li vyrenderovat obrázek panelu, musíte nejprve uložit nástěnku.", @@ -13864,7 +13877,7 @@ "label-format": "Formát", "label-set-timezone": "Nastavit časové pásmo", "label-time-field": "Pole času", - "tooltip-format": "", + "tooltip-format": "Výstupní formát pro pole zadané jako <2>řetězec formátu Moment.js.", "tooltip-timezone-manually": "Nastavit časové pásmo data manuálně" }, "format-time-transformer-editor": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 546fb6800d6..47445cd97ed 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "Wenn Sie diese Benachrichtigungsrichtlinie löschen, wird sie dauerhaft entfernt.", "warning-2": "Möchten Sie diese Richtlinie wirklich löschen?" }, - "filter-description": "", + "filter-description": "Filtern Sie Benachrichtigungsrichtlinien und trennen Sie die Matchers durch Kommata, z. B.:<1>Schweregrad=kritisch, Region=EMEA", "generated-policies": "Automatisch generierte Richtlinien", "matchers": "Matcher", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Vorlagendaten für Warnungen", - "only-in-alert": "Nur verfügbar im Rahmen einer Warnung (z. B. in einerAlarmschleife)" + "only-in-alert": "" }, "available-context-notification": "Verfügbar im Rahmen einer Benachrichtigung.", "notification-template-data": "Vorlagendaten für Benachrichtigungen" @@ -3245,7 +3245,7 @@ "auth-settings": "Authentifizierungseinstellungen" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Konfigurieren Sie die Authentifizierungseinstellungen. Erfahren Sie mehr in unserer <2>Dokumentation." }, "auth-drawer-unconnected": { "advanced-auth": "Erweiterte Authentifizierung", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Durch Komma oder Leerzeichen getrennte Liste der Organisationen. Der Nutzer sollte Mitglied von mindestens\neiner Organisation sein, um sich anzumelden.", "allowed-organizations-label": "Erlaubte Organisationen", "allowed-organizations-placeholder": "Geben Sie Organisationen (my-team, myteam …) ein und drücken Sie zum Hinzufügen die Eingabetaste", - "api-url-description": "", + "api-url-description": "Der Endpunkt der Benutzerinformationen Ihres OAuth2-Anbieters. Informationen, die von diesem Endpunkt zurückgegeben werden, müssen mit <2>OpenID UserInfo kompatibel sein.", "api-url-required": "Dieses Feld muss – sofern festgelegt – eine gültige URL sein.", "auth-style-description": "Es bestimmt, wie „{{ clientIDLabel }}“ und „{{ clientSecretLabel }}“ an den Oauth2-Provider gesendet werden. Die Standardeinstellung ist AutoDetect.", "auth-style-label": "Authentifizierungsart", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Verwalten Sie Ihre Authentifizierungseinstellungen und konfigurieren Sie einmalige Anmeldungen. Erfahren Sie mehr in unserer <2>Dokumentation." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Wählen Sie eine Datenquelle aus und visualisieren und fragen Sie dann Ihre Daten mit Diagrammen, Statistiken und Tabellen ab oder erstellen Sie Listen, Markierungen und andere Widgets.", "add-visualization-button": "Visualisierung hinzufügen", "add-visualization-header": "Starten Sie Ihr neues Dashboard, indem Sie eine Visualisierung hinzufügen", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importieren Sie Dashboards aus Dateien oder von <2>grafana.com.", "import-a-dashboard-header": "Dashboard importieren", "import-dashboard-button": "Dashboard importieren", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Klicken Sie <2>hier, um mehr über diesen Fehler zu erfahren.", - "success-more-details-links": "" + "success-more-details-links": "Als Nächstes können Sie damit beginnen, Daten zu visualisieren, indem Sie <2>ein Dashboard erstellen oder Daten in der <5>Explore-Ansicht abfragen." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Test" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Oder sparen Sie sich den Aufwand und erhalten Sie {{mainDS}} (und {{extraDS}}) als vollständig verwaltete, skalierbare und gehostete Datenquellen von Grafana Labs – mit dem <6>jederzeit kostenlosen Grafana-Cloud-Plan.", "title-alert": "Konfigurieren Sie unten Ihre {{mainDS}} Datenquelle" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Verfügbare mathematische Funktionen", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Siehe unsere zusätzliche Dokumentation zu <2>Mathematischen Ausdrücken.", "tooltip-title": "Mathematik-Operator", "tooltip-trigger": "Ausdruck" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Upsample", "tooltip-s-m-h": "10 s, 1 min., 30 min., 1 h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Abfrage ausführen", "modal-title": "SQL Editor", "tooltip-experimental": "Die Integration des SQL Expressions LLM ist experimentell. Bitte melden Sie Probleme dem Grafana-Team." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Eingabe" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(optional)", "role": "Rolle", "submit": "Absenden", - "tooltip": "" + "tooltip": "Sie können jetzt die Option „Keine Basisrolle“ auswählen und Berechtigungen zu Ihren benutzerdefinierten Anforderungen hinzufügen. Weitere Informationen finden Sie in <1>unserer Dokumentation." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Versenden Sie eine Einladung oder fügen Sie einen bestehenden Grafana-Nutzer zur Organisation hinzu.<1> {{orgName}}", "text": { "invite-user": "Nutzer einladen" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Alles löschen", - "tooltip": "" + "tooltip": "Sie können jetzt die Option „Keine Basisrolle“ auswählen und Berechtigungen zu Ihren benutzerdefinierten Anforderungen hinzufügen. Weitere Informationen finden Sie in <1>unserer Dokumentation." }, "menu-aria-label": "Rollenauswahlmenü", "menu-group-option-aria-label": "Rollenauswahloption", @@ -12709,7 +12722,7 @@ "info-text": "Erstelle einen direkten Link zu diesem Dashboard oder Panel mit den folgenden angepassten Optionen.", "link-url": "Link-URL", "render-alert": "Bild-Render-Plug-in nicht installiert", - "render-instructions": "", + "render-instructions": "Um ein Bild zu rendern, müssen Sie das <2>Grafana Image Renderer Plugin installieren. Bitte kontaktieren Sie Ihren Grafana-Administrator, um das Plugin zu installieren.", "rendered-image": "Direktlink zum gerenderten Bild", "save-alert": "Dashboard nicht gespeichert", "save-dashboard": "Um ein Panel-Bild zu rendern, muss zuerst das Dashboard gespeichert werden.", @@ -13750,7 +13763,7 @@ "label-format": "Format", "label-set-timezone": "Zeitzone festlegen", "label-time-field": "Zeitfeld", - "tooltip-format": "", + "tooltip-format": "Das Ausgabeformat für das Feld, das als <2>Moment.js-Format-String angegeben ist.", "tooltip-timezone-manually": "Stellen Sie die Zeitzone des Datums manuell ein" }, "format-time-transformer-editor": { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 874be92a233..e8d4b15d05f 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -209,7 +209,7 @@ "title-delete": "Delete" }, "orgs": { - "delete-body": "Are you sure you want to delete '{{deleteOrgName}}'?
<5>All dashboards for this organization will be removed!", + "delete-body": "Are you sure you want to delete '{{deleteOrgName}}'?
<5>All dashboards for this organization will be removed!", "id-header": "ID", "name-header": "Name", "new-org-button": "New org" @@ -1145,7 +1145,7 @@ "title-something-wrong-trying-fetch-group-details": "Something went wrong when trying to fetch group details" }, "evaluation-interval-limit-exceeded": { - "body-minimum-interval": "A minimum evaluation interval of {{minInterval}} has been configured in Grafana.
Please contact the administrator to configure a lower interval.", + "body-minimum-interval": "A minimum evaluation interval of {{minInterval}} has been configured in Grafana.
Please contact the administrator to configure a lower interval.", "title-global-evaluation-interval-limit-exceeded": "Global evaluation interval limit exceeded" }, "existing-rule-editor": { @@ -1417,7 +1417,7 @@ "title-add-folder-and-labels": "Add folder and labels" }, "grafana-managed-rule-type": { - "description": "Supports multiple data sources of any kind.
Transform data with expressions." + "description": "Supports multiple data sources of any kind.
Transform data with expressions." }, "grafana-modify-export": { "body-invalid-rule-id": "The rule UID in the page URL is invalid. Please check the URL and try again.", @@ -1848,7 +1848,7 @@ "aria-label-new": "new" }, "mimir-flavored-type": { - "description": "Use a Mimir, Loki or Cortex datasource.
Expressions are not supported." + "description": "Use a Mimir, Loki or Cortex datasource.
Expressions are not supported." }, "min-interval-option": { "label-interval": "Interval", @@ -2081,7 +2081,7 @@ "warning-1": "Deleting this notification policy will permanently remove it.", "warning-2": "Are you sure you want to delete this policy?" }, - "filter-description": "Filter notification policies by using a comma separated list of matchers, e.g.: <1>severity=critical, region=EMEA", + "filter-description": "Filter notification policies by using a comma separated list of matchers, e.g.:<1>severity=critical, region=EMEA", "generated-policies": "Auto-generated policies", "matchers": "Matchers", "metadata": { @@ -2244,7 +2244,7 @@ "error-no-query-editor": "Could not load query editor due to: {{errorMessage}}" }, "recording-rule-type": { - "description": "Precompute expressions.
Should be combined with an alert rule." + "description": "Precompute expressions.
Should be combined with an alert rule." }, "recording-rules": { "description-target-data-source": "The Prometheus data source to store recording rules in", @@ -2624,7 +2624,9 @@ "placeholder-search-input": "Search by name or enter filter query..." }, "grafana-folder": "Grafana folder", - "group-autocomplete-unavailable": "Due to large number of groups, autocomplete is not available", + "group-no-results": "No groups found matching \"{{search}}\"", + "group-search-error": "Error searching groups", + "group-search-prompt": "Type at least 3 characters to search groups", "health": "Health", "label": { "hide": "Hide", @@ -2635,6 +2637,7 @@ "placeholder-all-data-sources": "All data sources", "placeholder-contact-point": "Select contact point", "placeholder-data-sources": "Select data sources", + "placeholder-group-search": "Search group", "placeholder-labels": "Select labels", "plugin-rules": "Plugin rules", "rule-source": { @@ -2808,7 +2811,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Alert template data", - "only-in-alert": "Available only when in the context of an Alert (e.g. inside .Alerts loop)" + "only-in-alert": "Available only when in the context of an Alert (e.g. inside.Alerts loop)" }, "available-context-notification": "Available in the context of a notification.", "notification-template-data": "Notification template data" @@ -3093,7 +3096,7 @@ "title-notification-policies": "Notification policies" }, "yaml-content-info": { - "body": "The YAML content in the editor only contains alert rule configuration
To configure Prometheus, you need to provide the rest of the <4>configuration file content." + "body": "The YAML content in the editor only contains alert rule configuration
To configure Prometheus, you need to provide the rest of the <4>configuration file content." } }, "alertlist": { @@ -3169,7 +3172,7 @@ "no-annotations-found": "No annotations found" }, "annotation-list-item": { - "tooltip-created-by": "Created by:
{{email}}" + "tooltip-created-by": "Created by:
{{email}}" }, "category-annotation-query": "Annotation query", "category-display": "Display", @@ -3207,7 +3210,7 @@ }, "empty-state": { "button-title": "Add annotation query", - "info-box-content": "

Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines and icons on all graph panels. When you hover over an annotation icon you can get event text & tags for the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or drag region). These will be stored in Grafana's annotation database.

", + "info-box-content": "

Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines and icons on all graph panels. When you hover over an annotation icon you can get event text & tags for the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or drag region). These will be stored in Grafana's annotation database.

", "info-box-content-2": "Checkout the <2>Annotations documentation for more information.", "title": "There are no custom annotation queries added yet" }, @@ -3245,7 +3248,7 @@ "auth-settings": "Auth settings" }, "auth-drawer-unconneced": { - "subtitle": "Configure auth settings. Find out more in our <2>documentation ." + "subtitle": "Configure auth settings. Find out more in our <2>documentation." }, "auth-drawer-unconnected": { "advanced-auth": "Advanced Auth", @@ -3279,7 +3282,7 @@ "allowed-organizations-description": "List of comma- or space-separated organizations. The user should be a member \nof at least one organization to log in.", "allowed-organizations-label": "Allowed organizations", "allowed-organizations-placeholder": "Enter organizations (my-team, myteam...) and press Enter to add", - "api-url-description": "The user information endpoint of your OAuth2 provider. Information returned by this endpoint must be compatible with <2>OpenID UserInfo .", + "api-url-description": "The user information endpoint of your OAuth2 provider. Information returned by this endpoint must be compatible with <2>OpenID UserInfo.", "api-url-required": "This field must be a valid URL if set.", "auth-style-description": "It determines how \"{{ clientIDLabel }}\" and \"{{ clientSecretLabel }}\" are sent to Oauth2 provider. Default is AutoDetect.", "auth-style-label": "Auth style", @@ -3424,7 +3427,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "Manage your auth settings and configure single sign-on. Find out more in our <2>documentation ." + "subtitle": "Manage your auth settings and configure single sign-on. Find out more in our <2>documentation." }, "bar-chart": { "warn": { @@ -4290,7 +4293,7 @@ "okay": "Okay" }, "not-found-datasource": { - "body": "Maybe you mistyped the URL or the plugin with the id <1> is unavailable.
To see a list of available datasources please <5>click here." + "body": "Maybe you mistyped the URL or the plugin with the id <1> is unavailable.
To see a list of available datasources please <5>click here." }, "oss": { "connections-home-page": { @@ -4403,7 +4406,7 @@ }, "source-form": { "control-required": "This field is required.", - "description": "A data point needs to provide values to all variables as fields or as transformations output to make the correlation button appear in the visualization.
Note: Not every variable needs to be explicitly defined below. A transformation such as <4>logfmt will create variables for every key/value pair.", + "description": "A data point needs to provide values to all variables as fields or as transformations output to make the correlation button appear in the visualization.
Note: Not every variable needs to be explicitly defined below. A transformation such as <4>logfmt will create variables for every key/value pair.", "description-external-pre": "You have used following variables in the target URL:", "description-query-pre": "You have used following variables in the target query:", "external-title": "Configure the data source that will use the URL (Step 3 of 3)", @@ -4415,12 +4418,12 @@ "results-required": "This field is required.", "source-description": "Results from selected source data source have links displayed in the panel", "source-label": "Source", - "sub-text": "

Define what data source will display the correlation, and what data will replace previously defined variables.

" + "sub-text": "

Define what data source will display the correlation, and what data will replace previously defined variables.

" }, "sub-title": "Define how data living in different data sources relates to each other. Read more in the <2>documentation", "target-form": { "control-rules": "This field is required.", - "sub-text": "

Define what the correlation will link to. With the query type, a query will run when the correlation is clicked. With the external type, clicking the correlation will open a URL.

", + "sub-text": "

Define what the correlation will link to. With the query type, a query will run when the correlation is clicked. With the external type, clicking the correlation will open a URL.

", "target-description-external": "Specify the URL that will open when the link is clicked", "target-description-query": "Specify which data source is queried when the link is clicked", "target-label": "Target", @@ -4627,7 +4630,7 @@ } }, "confirm-plugin-dashboard-save-modal": { - "body-plugin-dashboard": "Your changes will be lost when you update the plugin.
<2>Use Save As to create custom version.", + "body-plugin-dashboard": "Your changes will be lost when you update the plugin.
<2>Use Save As to create custom version.", "cancel": "Cancel", "overwrite": "Overwrite", "title-plugin-dashboard": "Plugin dashboard" @@ -4844,7 +4847,7 @@ "add-visualization-body": "Select a data source and then query and visualize your data with charts, stats and tables or create lists, markdowns and other widgets.", "add-visualization-button": "Add visualization", "add-visualization-header": "Start your new dashboard by adding a visualization", - "import-a-dashboard-body": "Import dashboards from files or <2>grafana.com .", + "import-a-dashboard-body": "Import dashboards from files or <2>grafana.com.", "import-a-dashboard-header": "Import a dashboard", "import-dashboard-button": "Import dashboard", "show-less-dashboards": "Show less", @@ -5319,8 +5322,8 @@ "title-provisioned": "Provisioned dashboard" }, "save-dashboard-error-proxy": { - "body-name-exists": "A dashboard with the same name in selected folder already exists.
<2>Would you still like to save this dashboard?", - "body-version-mismatch": "Someone else has updated this dashboard
<2>Would you still like to save this dashboard?", + "body-name-exists": "A dashboard with the same name in selected folder already exists.
<2>Would you still like to save this dashboard?", + "body-version-mismatch": "Someone else has updated this dashboard
<2>Would you still like to save this dashboard?", "confirmText-save-and-overwrite": "Save and overwrite", "title-name-exists": "Conflict", "title-version-mismatch": "Conflict" @@ -6182,8 +6185,8 @@ "placeholder-search-for": "Search for...", "radio-options": { "label": { - "suggestions": "Suggestions", - "visualizations": "Visualizations" + "all-visualizations": "All visualizations", + "suggestions": "Suggestions" } }, "title-close": "Close" @@ -6199,7 +6202,7 @@ "query": "Query" }, "query-variable-editor-form": { - "description-examples": "Named capture groups can be used to separate the display text and value ( <1>see examples ).", + "description-examples": "Named capture groups can be used to separate the display text and value (<1>see examples ).", "description-optional": "Optional, if you want to extract part of a series name or metric node segment.", "label-data-source": "Data source", "label-static-options-sort": "Static options sort", @@ -6623,7 +6626,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Click <2>here to learn more about this error.", - "success-more-details-links": "Next, you can start to visualize data by <2>building a dashboard , or by querying data in the <5>Explore view ." + "success-more-details-links": "Next, you can start to visualize data by <2>building a dashboard, or by querying data in the <5>Explore view." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6722,7 @@ "test": "Test" }, "cloud-info-box": { - "body-alert": "Or skip the effort and get {{mainDS}} (and {{extraDS}}) as fully-managed, scalable, and hosted data sources from Grafana Labs with the <6>free-forever Grafana Cloud plan .", + "body-alert": "Or skip the effort and get {{mainDS}} (and {{extraDS}}) as fully-managed, scalable, and hosted data sources from Grafana Labs with the <6>free-forever Grafana Cloud plan.", "title-alert": "Configure your {{mainDS}} data source below" }, "dashboards-table": { @@ -7703,8 +7706,8 @@ }, "math": { "available-math-functions": "Available math functions", - "run-math-operations": "Run math operations on one or more queries. You reference the query by {{refExample}} ie. {{ref1}}, {{ref2}}, {{ref3}} etc.
Example: <12>{{example}}", - "tooltip-footer": "See our additional documentation on <2>Math expressions .", + "run-math-operations": "Run math operations on one or more queries. You reference the query by {{refExample}} ie. {{ref1}}, {{ref2}}, {{ref3}}etc.
Example: <12>{{example}}", + "tooltip-footer": "See our additional documentation on <2>Math expressions.", "tooltip-title": "Math operator", "tooltip-trigger": "Expression" }, @@ -7730,11 +7733,24 @@ "label-upsample": "Upsample", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "Close schema inspector" + }, "sql-expr": { "button-run-query": "Run query", "modal-title": "SQL Editor", "tooltip-experimental": "SQL Expressions LLM integration is experimental. Please report any issues to the Grafana team." }, + "sql-schema": { + "close-schema-inspector": "Close schema inspector", + "error-title": "Error", + "inspect-button": "Inspect schema", + "loading": "Loading schema information...", + "no-data-title": "No schema information available", + "no-fields-desc": "This query returned no schema information.", + "no-fields-title": "No schema information", + "query-error-title": "Query error" + }, "threshold": { "label-input": "Input" } @@ -8110,8 +8126,7 @@ "edit-pane": { "go-back": "Go back" } - }, - "select-group": "Select group" + } }, "grafana-data": { "datetime": { @@ -9532,7 +9547,7 @@ "label": "Search base DNS", "placeholder": "example: dc=grafana,dc=org" }, - "subtitle": "The LDAP integration in Grafana allows your Grafana users to log in with their LDAP credentials. Find out more in our <2><0>documentation .", + "subtitle": "The LDAP integration in Grafana allows your Grafana users to log in with their LDAP credentials. Find out more in our <2><1>documentation.", "title": "Basic Settings" }, "library-panel": { @@ -10928,10 +10943,10 @@ "placeholder-optional": "(optional)", "role": "Role", "submit": "Submit", - "tooltip": "You can now select the \"No basic role\" option and add permissions to your custom needs. You can find more information in <1>our documentation ." + "tooltip": "You can now select the \"No basic role\" option and add permissions to your custom needs. You can find more information in <1>our documentation." }, "user-invite-page": { - "sub-title": "Send invitation or add existing Grafana user to the organization. <1> {{orgName}}", + "sub-title": "Send invitation or add existing Grafana user to the organization.<1> {{orgName}}", "text": { "invite-user": "Invite user" } @@ -11021,7 +11036,7 @@ "switch-to-table": "Switch to table" }, "panel-plugin-error": { - "text-load-error": "Check the server startup logs for more information.
If this plugin was loaded from Git, then make sure it was compiled.", + "text-load-error": "Check the server startup logs for more information.
If this plugin was loaded from Git, then make sure it was compiled.", "title-load-error": "Error loading: {{panelId}}", "title-not-found": "Panel plugin not found: {{id}}" }, @@ -11037,8 +11052,8 @@ "tooltip-delete": "Delete" }, "visualization-suggestions": { - "based-on-current-data": "Based on current data", - "no-results-matched-your-query": "No results matched your query" + "apply-suggestion-aria-label": "Apply {{suggestionName}} visualization", + "use-this-suggestion": "Use this suggestion" }, "viz-type-picker": { "could-anything-matching-query": "Could not find anything matching your query" @@ -11322,7 +11337,7 @@ "message": "All plugins are up to date" }, "not-found-plugin": { - "body-plugin-not-found": "That plugin cannot be found. Please check the url is correct or
go to the <3>plugin catalog.", + "body-plugin-not-found": "That plugin cannot be found. Please check the url is correct or
go to the <3>plugin catalog.", "title-plugin-not-found": "Plugin not found" }, "plugin-actions": { @@ -11637,7 +11652,7 @@ "actions": { "set-up-required-feature-toggles": "Set up required feature toggles" }, - "learn-more-documentation": "Want to learn more? See our <2>documentation .", + "learn-more-documentation": "Want to learn more? See our <2>documentation.", "manage-dashboards-provision-updates-automatically": "Manage dashboards as code in Git and provision updates automatically", "manage-your-dashboards-with-remote-provisioning": "Get started with Git Sync", "store-dashboards-in-version-controlled-storage": "Store dashboards in version-controlled storage for better organization and history tracking" @@ -12383,7 +12398,7 @@ }, "menu": { "clear-button": "Clear all", - "tooltip": "You can now select the \"No basic role\" option and add permissions to your custom needs. You can find more information in <1>our documentation ." + "tooltip": "You can now select the \"No basic role\" option and add permissions to your custom needs. You can find more information in <1>our documentation." }, "menu-aria-label": "Role picker menu", "menu-group-option-aria-label": "Role picker option", @@ -12393,7 +12408,7 @@ }, "sub-menu-aria-label": "Role picker submenu", "title": { - "description": "Assign roles to users to ensure granular control over access to Grafana‘s features and resources. Find out more in our <2>documentation ." + "description": "Assign roles to users to ensure granular control over access to Grafana‘s features and resources. Find out more in our <2>documentation." } }, "role-picker-drawer": { @@ -12709,7 +12724,7 @@ "info-text": "Create a direct link to this dashboard or panel, customized with the options below.", "link-url": "Link URL", "render-alert": "Image renderer plugin not installed", - "render-instructions": "To render an image, you must install the <2>Grafana image renderer plugin . Please contact your Grafana administrator to install the plugin.", + "render-instructions": "To render an image, you must install the <2>Grafana image renderer plugin. Please contact your Grafana administrator to install the plugin.", "rendered-image": "Direct link rendered image", "save-alert": "Dashboard is not saved", "save-dashboard": "To render a panel image, you must save the dashboard first.", @@ -13355,7 +13370,7 @@ "forwards-time-aria-label": "Move time range forwards", "to": "to", "zoom-out-button": "Zoom out time range", - "zoom-out-tooltip": "Time range zoom out
CTRL+Z" + "zoom-out-tooltip": "Time range zoom out
CTRL+Z" }, "time-range": { "apply": "Apply time range", @@ -13483,7 +13498,7 @@ }, "transformations": { "empty": { - "add-transformation-body": "Transformations allow data to be changed in various ways before your visualization is shown.
This includes joining data together, renaming fields, making calculations, formatting data for display, and more.", + "add-transformation-body": "Transformations allow data to be changed in various ways before your visualization is shown.
This includes joining data together, renaming fields, making calculations, formatting data for display, and more.", "add-transformation-header": "Start transforming data" } }, @@ -13750,7 +13765,7 @@ "label-format": "Format", "label-set-timezone": "Set timezone", "label-time-field": "Time field", - "tooltip-format": "The output format for the field specified as a <2>Moment.js format string .", + "tooltip-format": "The output format for the field specified as a <2>Moment.js format string.", "tooltip-timezone-manually": "Set the timezone of the date manually" }, "format-time-transformer-editor": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 35b1730e477..653d7942c02 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "La eliminación de esta política de notificación la eliminará de forma permanente.", "warning-2": "¿Seguro que quieres eliminar esta política?" }, - "filter-description": "", + "filter-description": "Filtra las políticas de notificación mediante una lista de buscadores de coincidencias separados por comas, por ejemplo:<1>severidad=crítica, región=EMEA", "generated-policies": "Políticas generadas automáticamente", "matchers": "Buscadores de coincidencias", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Datos de la plantilla de alerta", - "only-in-alert": "Disponible solo cuando está en el contexto de una alerta (por ejemplo, dentro de .Bucle de alertas" + "only-in-alert": "" }, "available-context-notification": "Disponible en el contexto de una notificación.", "notification-template-data": "Datos de la plantilla de notificación" @@ -3245,7 +3245,7 @@ "auth-settings": "Ajustes de autenticación" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Configura los ajustes de autenticación. Encuentra más información en nuestra <2><0>documentación." }, "auth-drawer-unconnected": { "advanced-auth": "Autenticación avanzada", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Lista de organizaciones separadas por comas o espacios. El usuario debe ser miembro \nde al menos una organización para iniciar sesión.", "allowed-organizations-label": "Organizaciones permitidas", "allowed-organizations-placeholder": "Introduzca organizaciones (mi-equipo, miequipo...) y pulse Intro para añadirlas", - "api-url-description": "", + "api-url-description": "El punto final de información del usuario de tu proveedor de OAuth2. La información devuelta por este punto final debe ser compatible con <2>OpenID UserInfo.", "api-url-required": "Este campo debe ser una URL válida si se configura.", "auth-style-description": "Determina cómo se envían los valores de «{{ clientIDLabel }}» y «{{ clientSecretLabel }}» al proveedor de Oauth2. El valor predeterminado es «Detección automática».", "auth-style-label": "Estilo de autenticación", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Gestiona tus ajustes de autenticación y configura el inicio de sesión único. Encuentra más información en nuestra <2><0>documentación." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Selecciona una fuente de datos y luego consulta y visualiza tus datos con gráficos, estadísticas y tablas o cree listas, anotaciones y otros widgets.", "add-visualization-button": "Añadir visualización", "add-visualization-header": "Comienza tu nuevo panel de control añadiendo una visualización", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importa dashboards desde archivos o <2>grafana.com.", "import-a-dashboard-header": "Importar un tablero", "import-dashboard-button": "Importar panel de control", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Haz clic <2>aquí para obtener más información sobre este error.", - "success-more-details-links": "" + "success-more-details-links": "A continuación, puedes empezar a visualizar los datos <2>creando un panel de control o consultando los datos en la <5>vista Explorar." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Prueba" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "O ahórrate el esfuerzo y consigue {{mainDS}} (y{{extraDS}}) como fuentes de datos totalmente gestionadas, escalables y alojadas de Grafana Labs con el <6>plan Grafana Cloud gratuito para siempre.", "title-alert": "Configura tu fuente de datos {{mainDS}} a continuación" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Funciones matemáticas disponibles", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Consulta nuestra documentación adicional sobre <2>Expresiones matemáticas.", "tooltip-title": "Operador matemático", "tooltip-trigger": "Expresión" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Aumentar tamaño", "tooltip-s-m-h": "10 s, 1 m, 30 m, 1 h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Ejecutar consulta", "modal-title": "Editor de SQL", "tooltip-experimental": "La integración de LLM de expresiones SQL es experimental. Avisa de cualquier problema al equipo de Grafana." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Entrada" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(opcional)", "role": "Rol", "submit": "Enviar", - "tooltip": "" + "tooltip": "Ahora puedes seleccionar la opción «Sin función básica» y añadir permisos según tus necesidades específicas. Puedes encontrar más información en <1>nuestra documentación." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Envía una invitación o añade un usuario de Grafana existente a la organización.<1> {{orgName}}", "text": { "invite-user": "Invitar a usuario" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Borrar todo", - "tooltip": "" + "tooltip": "Ahora puedes seleccionar la opción «Sin función básica» y añadir permisos según tus necesidades específicas. Puedes encontrar más información en <1>nuestra documentación." }, "menu-aria-label": "Menú del selector de roles", "menu-group-option-aria-label": "Opción del selector de roles", @@ -12709,7 +12722,7 @@ "info-text": "Cree un enlace directo a este panel de control o panel, personalizado con las siguientes opciones.", "link-url": "URL del enlace", "render-alert": "Complemento de renderizador de imagen no instalado", - "render-instructions": "", + "render-instructions": "Para renderizar una imagen, debes instalar el <2>complemento de renderizado de imágenes Grafana. Ponte en contacto con tu administrador de Grafana para instalarlo.", "rendered-image": "Enlace directo a la imagen renderizada", "save-alert": "El panel de control no se ha guardado", "save-dashboard": "Para renderizar una imagen de panel, primero debe guardar el panel de control.", @@ -13750,7 +13763,7 @@ "label-format": "Formato", "label-set-timezone": "Establecer zona horaria", "label-time-field": "Campo de tiempo", - "tooltip-format": "", + "tooltip-format": "El formato de salida para el campo especificado como una <2>cadena de formato Moment.js.", "tooltip-timezone-manually": "Establecer la zona horaria de la fecha manualmente" }, "format-time-transformer-editor": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 74e96034e55..fecfc76d3ea 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "La suppression de cette politique de notification la supprimera définitivement.", "warning-2": "Voulez-vous vraiment supprimer cette politique ?" }, - "filter-description": "", + "filter-description": "Filtrez les politiques de notification en utilisant une liste de correspondances séparées par des virgules, par exemple :<1>gravité=critique, région=EMEA", "generated-policies": "Politiques générées automatiquement", "matchers": "Correspondants", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Données du modèle d’alerte", - "only-in-alert": "Disponible uniquement dans le contexte d’une alerte (par exemple à l’intérieur de .Boucle d’alertes)" + "only-in-alert": "" }, "available-context-notification": "Disponible dans le contexte d’une notification.", "notification-template-data": "Données du modèle de notification" @@ -3245,7 +3245,7 @@ "auth-settings": "Paramètres d’authentification" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Configurez les paramètres d’authentification. Pour en savoir plus, consultez notre <2>documentation." }, "auth-drawer-unconnected": { "advanced-auth": "Authentification avancée", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Liste des organisations séparées par des virgules ou des espaces. L’utilisateur doit être membre\nd’au moins une organisation pour se connecter.", "allowed-organizations-label": "Organisations autorisées", "allowed-organizations-placeholder": "Saisissez les organisations (my-team, myteam...) et appuyez sur Entrée pour ajouter", - "api-url-description": "", + "api-url-description": "Le point de terminaison des informations utilisateur de votre fournisseur OAuth2. Les informations renvoyées par ce point de terminaison doivent être compatibles avec <2>OpenID UserInfo.", "api-url-required": "Ce champ doit être une URL valide s’il est défini.", "auth-style-description": "Il détermine comment « {{ clientIDLabel }} » et « {{ clientSecretLabel }} » sont envoyés au fournisseur Oauth2. La valeur par défaut est AutoDetect.", "auth-style-label": "Style d’authentification", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Gérez vos paramètres d’authentification et configurez l’authentification unique. Pour en savoir plus, consultez notre <2>documentation." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Sélectionnez une source de données, puis examinez et visualisez vos données avec des graphiques, des statistiques et des tableaux ou créez des listes, des markdowns et d'autres widgets.", "add-visualization-button": "Ajouter une visualisation", "add-visualization-header": "Commencez votre nouveau tableau de bord en ajoutant une visualisation", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importez des tableaux de bord à partir de fichiers ou du site <2>grafana.com.", "import-a-dashboard-header": "Importer un tableau de bord", "import-dashboard-button": "Importer un tableau de bord", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Cliquez <2>ici pour en savoir plus sur cette erreur.", - "success-more-details-links": "" + "success-more-details-links": "Ensuite, vous pouvez commencer à visualiser les données en <2>créant un tableau de bord ou en interrogeant les données dans la <5>vue Explorer." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Test" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Ou simplifiez-vous la tâche et obtenez {{mainDS}} (et {{extraDS}}) sous forme de sources de données entièrement gérées, évolutives et hébergées par Grafana Labs avec le <6>plan Grafana Cloud gratuit à vie.", "title-alert": "Configurer la source de données {{mainDS}} ci-dessous" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Fonctions mathématiques disponibles", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Consultez notre documentation supplémentaire sur les <2>expressions mathématiques.", "tooltip-title": "Opérateur mathématique", "tooltip-trigger": "Expression" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Sur-échantillonner", "tooltip-s-m-h": "10 s, 1 m, 30 m, 1 h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Exécuter la requête", "modal-title": "Éditeur SQL", "tooltip-experimental": "L’intégration des expressions SQL avec les LLM est expérimentale. Merci de signaler tout problème à l’équipe Grafana." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Entrée" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(facultatif)", "role": "Rôle", "submit": "Envoyer", - "tooltip": "" + "tooltip": "Vous pouvez maintenant sélectionner l'option « Aucun rôle de base » et ajouter des autorisations à vos besoins personnalisés. Vous trouverez plus d'informations dans <1>notre documentation." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Envoyez une invitation ou ajoutez un utilisateur Grafana existant à l’organisation.<1>{{orgName}}", "text": { "invite-user": "Inviter un utilisateur" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Tout effacer", - "tooltip": "" + "tooltip": "Vous pouvez maintenant sélectionner l'option « Aucun rôle de base » et ajouter des autorisations à vos besoins personnalisés. Vous trouverez plus d'informations dans <1>notre documentation." }, "menu-aria-label": "Menu du sélecteur de rôle", "menu-group-option-aria-label": "Option du sélecteur de rôle", @@ -12709,7 +12722,7 @@ "info-text": "Créez un lien direct vers ce tableau de bord ou ce panneau, personnalisé avec les options ci-dessous.", "link-url": "Lien URL", "render-alert": "Plugin de rendu d'image non installé", - "render-instructions": "", + "render-instructions": "Pour afficher une image, vous devez installer le <2>plugin de rendu d’images Grafana. Veuillez contacter votre administrateur Grafana pour installer le plugin.", "rendered-image": "Lien direct vers l'image dont il faut améliorer le rendu", "save-alert": "Le tableau de bord n'est pas enregistré", "save-dashboard": "Pour améliorer le rendu d'une image de panneau, vous devez d'abord enregistrer le tableau de bord.", @@ -13750,7 +13763,7 @@ "label-format": "Format", "label-set-timezone": "Définir le fuseau horaire", "label-time-field": "Champ horaire", - "tooltip-format": "", + "tooltip-format": "Le format de sortie pour le champ spécifié en tant que <2>chaîne de format Moment.js.", "tooltip-timezone-manually": "Définir manuellement le fuseau horaire de la date" }, "format-time-transformer-editor": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 02d75808711..e691d36eff2 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "Az értesítési házirendet a törlésével véglegesen eltávolítja.", "warning-2": "Biztosan törli ezt a házirendet?" }, - "filter-description": "", + "filter-description": "Az értesítési házirendek szűrése az egyezések vesszővel elválasztott listájának használatával, pl.:<1>súlyosság=kritikus, régió=EMEA", "generated-policies": "Automatikusan generált házirendek", "matchers": "Egyezések", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Riasztási sablon adatai", - "only-in-alert": "Csak egy riasztás kontextusában érhető el (pl. Riasztási hurok)" + "only-in-alert": "" }, "available-context-notification": "Egy értesítés kontextusában érhető el.", "notification-template-data": "Értesítési sablon adatai" @@ -3245,7 +3245,7 @@ "auth-settings": "Hitelesítési beállítások" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Hitelesítési beállítások konfigurálása. További információkat a <2>dokumentációnkban talál." }, "auth-drawer-unconnected": { "advanced-auth": "Speciális hitelesítés", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Vesszővel vagy szóközzel elválasztott szervezetek listája. A bejelentkezéshez a felhasználónak legalább \negy szervezet tagjának kell lennie.", "allowed-organizations-label": "Engedélyezett szervezetek", "allowed-organizations-placeholder": "Adja meg a szervezeteket (my-team, myteam…), és nyomja le az Enter billentyűt a hozzáadáshoz", - "api-url-description": "", + "api-url-description": "Az OAuth2-szolgáltató felhasználói információs végpontja. Az ezen végpont által visszaadott információknak kompatibilisnek kell lenniük az <2>OpenID UserInfóval.", "api-url-required": "Ennek a mezőnek érvényes URL-címnek kell lennie, ha be van állítva.", "auth-style-description": "Ez határozza meg, hogy a(z) „{{ clientIDLabel }}” és a(z) „{{ clientSecretLabel }}” hogyan lesz elküldve az Oauth2-szolgáltatónak. Az alapértelmezett beállítás az AutoDetect (Automatikus észlelés).", "auth-style-label": "Hitelesítési stílus", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Saját hitelesítési beállítások kezelése és az egyszeri bejelentkezés konfigurálása. További információkat a <2>dokumentációnkban talál." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Válasszon ki egy adatforrást, majd kérdezze le és jelenítse meg az adatait diagramokkal, statisztikákkal és táblázatokkal, vagy hozzon létre listákat, Markdown-elemeket és egyéb widgeteket.", "add-visualization-button": "Vizualizáció hozzáadása", "add-visualization-header": "Indítsa el az új irányítópultot vizualizáció hozzáadásával", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Irányítópultok importálása fájlokból vagy a <2>grafana.com webhelyről.", "import-a-dashboard-header": "Irányítópult importálása", "import-dashboard-button": "Irányítópult importálása", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Ha többet szeretne megtudni erről a hibáról, kattintson <2>ide.", - "success-more-details-links": "" + "success-more-details-links": "Ezután < 2 >létrehozhat egy irányítópultot, vagy lekérdezheti az adatokat az <5>Explore nézetben." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Teszt" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Vagy hagyja ki a próbálkozást, és szerezze be a(z) {{mainDS}} adatforrást (és {{extraDS}}) a Grafana Labs teljes körűen felügyelt, skálázható és hosztolt adatforrásait az <6>örökre ingyenes Grafana Cloud-előfizetéssel.", "title-alert": "Konfigurálja {{mainDS}} adatforrását alább" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Elérhető matematikai függvények", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Lásd a további dokumentációt a <2>matematikai kifejezésekről.", "tooltip-title": "Matematikai operátor", "tooltip-trigger": "Kifejezés" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Mintasűrűség növelése", "tooltip-s-m-h": "10 mp., 1 p., 30 p., 1 ó." }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Lekérdezés futtatása", "modal-title": "SQL-szerkesztő", "tooltip-experimental": "Az SQL-kifejezések LLM-integrációja kísérleti jellegű. Kérjük, jelentse az esetleges problémákat a Grafana csapatának." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Bemenet" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(opcionális)", "role": "Szerepkör", "submit": "Küldés", - "tooltip": "" + "tooltip": "Most kiválaszthatja a „Nincs alapvető szerepkör” opciót, és az egyéni igényeinek megfelelő engedélyeket adhat hozzá. További információt a <1>dokumentációnkban talál." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Meghívó küldése vagy meglévő Grafana-felhasználó hozzáadása a szervezethez.<1>{{orgName}}", "text": { "invite-user": "Felhasználó meghívása" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Összes törlése", - "tooltip": "" + "tooltip": "Most kiválaszthatja a „Nincs alapvető szerepkör” opciót, és az egyéni igényeinek megfelelő engedélyeket adhat hozzá. További információt a <1>dokumentációnkban talál." }, "menu-aria-label": "Szerepkörválasztó menü", "menu-group-option-aria-label": "Szerepkörválasztási beállítás", @@ -12709,7 +12722,7 @@ "info-text": "Létrehozhat egy közvetlen hivatkozást ehhez az irányítópulthoz vagy panelhez, az alábbi lehetőségekkel testreszabva.", "link-url": "Hivatkozás URL-címe", "render-alert": "Az Image Renderer bővítmény nincs telepítve", - "render-instructions": "", + "render-instructions": "Képek rendereléséhez telepítenie kell a <2>Grafana Image Renderer bővítményt. Kérjük, vegye fel a kapcsolatot Grafana-rendszergazdájával a bővítmény telepítéséhez.", "rendered-image": "Renderelt kép közvetlen hivatkozása", "save-alert": "Az irányítópult nincs mentve", "save-dashboard": "Panelkép rendereléséhez először mentenie kell az irányítópultot.", @@ -13750,7 +13763,7 @@ "label-format": "Formátum", "label-set-timezone": "Időzóna beállítása", "label-time-field": "Időmező", - "tooltip-format": "", + "tooltip-format": "A <2>Moment.js formátumú karakterlánc mező kimeneti formátuma.", "tooltip-timezone-manually": "A dátum időzónájának manuális beállítása" }, "format-time-transformer-editor": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index d9b58b75791..ff5ecf95800 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -2075,7 +2075,7 @@ "warning-1": "Menghapus kebijakan pemberitahuan ini akan menghapusnya secara permanen.", "warning-2": "Apa Anda yakin ingin menghapus kebijakan ini?" }, - "filter-description": "", + "filter-description": "Filter kebijakan pemberitahuan menggunakan daftar pencocok yang dipisahkan koma, misalnya:<1>severity=critical, region=EMEA", "generated-policies": "Kebijakan yang dibuat otomatis", "matchers": "Pencocok", "metadata": { @@ -2796,7 +2796,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Data templat peringatan", - "only-in-alert": "Hanya tersedia ketika dalam konteks Peringatan (misalnya di dalam .loop Peringatan)" + "only-in-alert": "" }, "available-context-notification": "Tersedia dalam konteks pemberitahuan.", "notification-template-data": "Data templat pemberitahuan" @@ -3233,7 +3233,7 @@ "auth-settings": "Pengaturan autentikasi" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Konfigurasikan pengaturan autentikasi. Cari tahu selengkapnya di <2>dokumentasi kami." }, "auth-drawer-unconnected": { "advanced-auth": "Autentikasi Lanjutan", @@ -3267,7 +3267,7 @@ "allowed-organizations-description": "Daftar organisasi yang dipisahkan koma atau spasi. Pengguna harus menjadi anggota\ndari setidaknya satu organisasi untuk masuk.", "allowed-organizations-label": "Organisasi yang diizinkan", "allowed-organizations-placeholder": "Masukkan organisasi (my-team, myteam...) dan tekan Enter untuk menambahkan", - "api-url-description": "", + "api-url-description": "Endpoint informasi pengguna penyedia OAuth2 Anda. Informasi yang dikembalikan oleh endpoint ini harus kompatibel dengan <2>OpenID UserInfo.", "api-url-required": "Bidang ini harus berupa URL yang valid jika ditetapkan.", "auth-style-description": "Ini menentukan cara \"{{ clientIDLabel }}\" dan \"{{ clientSecretLabel }}\" dikirim ke penyedia Oauth2. Default adalah AutoDetect.", "auth-style-label": "Metode autentikasi", @@ -3412,7 +3412,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Kelola pengaturan autentikasi Anda dan konfigurasikan single sign-on. Cari tahu selengkapnya di <2>dokumentasi kami." }, "bar-chart": { "warn": { @@ -4824,7 +4824,7 @@ "add-visualization-body": "Pilih sumber data, kemudian kueri dan visualisasikan data Anda dengan bagan, statistik, dan tabel atau buat daftar, markdown, dan widget lainnya.", "add-visualization-button": "Tambahkan visualisasi", "add-visualization-header": "Mulai dasbor baru Anda dengan menambahkan visualisasi", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Impor dasbor dari file atau <2>grafana.com.", "import-a-dashboard-header": "Impor dasbor", "import-dashboard-button": "Impor dasbor", "show-less-dashboards": "", @@ -6599,7 +6599,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Klik <2>di sini untuk mempelajari selengkapnya tentang kesalahan ini.", - "success-more-details-links": "" + "success-more-details-links": "Selanjutnya, Anda dapat mulai memvisualisasikan data dengan <2>membuat dasbor, atau dengan melakukan kueri data di <5>tampilan Explore." }, "data-sources": { "datasource-add-button": { @@ -6695,7 +6695,7 @@ "test": "Tes" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Atau lewati proses dan dapatkan {{mainDS}} (dan {{extraDS}}) sebagai sumber data yang dikelola sepenuhnya, dapat diskalakan, dan di-host dari Grafana Labs dengan <6>paket Grafana Cloud gratis selamanya.", "title-alert": "Konfigurasikan sumber data {{mainDS}} Anda di bawah ini" }, "dashboards-table": { @@ -7678,7 +7678,7 @@ "math": { "available-math-functions": "Fungsi matematika yang tersedia", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Lihat dokumentasi tambahan kami di <2>Pola matematika.", "tooltip-title": "Operator matematika", "tooltip-trigger": "Ekspresi" }, @@ -7704,11 +7704,24 @@ "label-upsample": "Tingkatkan sample", "tooltip-s-m-h": "10 dtk, 1 mnt, 30 mnt, 1 jm" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Jalankan kueri", "modal-title": "Editor SQL", "tooltip-experimental": "Integrasi LLM Ekspresi SQL bersifat eksperimental. Harap laporkan masalah apa pun kepada tim Grafana." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Input" } @@ -10881,10 +10894,10 @@ "placeholder-optional": "(opsional)", "role": "Peran", "submit": "Kirim", - "tooltip": "" + "tooltip": "Anda sekarang dapat memilih opsi \"Tidak ada peran dasar\" dan menambahkan izin ke kebutuhan kustom Anda. Anda dapat menemukan informasi lebih lanjut di <1> dokumentasi kami ." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Kirim undangan atau tambahkan pengguna Grafana yang ada ke organisasi.<1> {{orgName}}", "text": { "invite-user": "Undang pengguna" } @@ -12328,7 +12341,7 @@ }, "menu": { "clear-button": "Hapus semua", - "tooltip": "" + "tooltip": "Anda sekarang dapat memilih opsi \"Tidak ada peran dasar\" dan menambahkan izin ke kebutuhan kustom Anda. Anda dapat menemukan informasi lebih lanjut di <1> dokumentasi kami ." }, "menu-aria-label": "Menu pemilih peran", "menu-group-option-aria-label": "Opsi pemilih peran", @@ -12653,7 +12666,7 @@ "info-text": "Buat tautan langsung ke dasbor atau panel ini, disesuaikan dengan opsi di bawah ini.", "link-url": "Tautkan URL", "render-alert": "Plugin perender gambar belum diinstal", - "render-instructions": "", + "render-instructions": "Untuk merender gambar, Anda harus menginstal <2>plugin perender gambar Grafana. Hubungi administrator Grafana Anda untuk menginstal plugin.", "rendered-image": "Tautan langsung gambar yang dirender", "save-alert": "Dasbor tidak disimpan", "save-dashboard": "Untuk merender gambar panel, Anda harus menyimpan dasbor terlebih dahulu.", @@ -13693,7 +13706,7 @@ "label-format": "Format", "label-set-timezone": "Atur zona waktu", "label-time-field": "Bidang waktu", - "tooltip-format": "", + "tooltip-format": "Format output untuk bidang yang ditentukan sebagai <2>string format Moment.js.", "tooltip-timezone-manually": "Atur zona waktu tanggal secara manual" }, "format-time-transformer-editor": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 7ada28c6693..d70278c2c40 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "L'eliminazione di questo criterio di notifica lo rimuoverà in modo permanente.", "warning-2": "Vuoi davvero eliminare questo criterio?" }, - "filter-description": "", + "filter-description": "Filtra i criteri di notifica utilizzando un elenco di corrispondenti separati da virgole, ad esempio:<1>gravità=critica, regione=EMEA", "generated-policies": "Criteri generati automaticamente", "matchers": "Corrispondenze", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Dati del modello di avviso", - "only-in-alert": "Disponibile solo nel contesto di un avviso (ad es. all'interno di .Ciclo di avvisi)" + "only-in-alert": "" }, "available-context-notification": "Disponibile nel contesto di una notifica.", "notification-template-data": "Dati del modello di notifica" @@ -3245,7 +3245,7 @@ "auth-settings": "Impostazioni di autenticazione" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Configura le impostazioni di autenticazione. Scopri di più nella nostra <2>documentazione." }, "auth-drawer-unconnected": { "advanced-auth": "Autenticazione avanzata", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Elenco di organizzazioni separate da virgole o spazi. L'utente deve essere membro\ndi almeno un'organizzazione per accedere.", "allowed-organizations-label": "Organizzazioni consentite", "allowed-organizations-placeholder": "Inserisci le organizzazioni (my-team, myteam...) e premi Invio per aggiungerle", - "api-url-description": "", + "api-url-description": "L'endpoint delle informazioni utente del tuo provider OAuth2. Le informazioni restituite da questo endpoint devono essere compatibili con <2>OpenID UserInfo.", "api-url-required": "Questo campo deve essere un URL valido se impostato.", "auth-style-description": "Determina come \"{{ clientIDLabel }}\" e \"{{ clientSecretLabel }}\" vengono inviati al provider Oauth2. L'impostazione predefinita è Rilevamento automatico.", "auth-style-label": "Stile di autenticazione", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Gestisci le impostazioni di autenticazione e configura il Single Sign-On. Scopri di più nella nostra <2>documentazione." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Seleziona un'origine dati, quindi esegui query e visualizza i dati con grafici, statistiche e tabelle o crea elenchi, markdown e altri widget.", "add-visualization-button": "Aggiungi visualizzazione", "add-visualization-header": "Avvia il tuo nuovo dashboard aggiungendo una visualizzazione", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importa dashboard da file o <2>grafana.com.", "import-a-dashboard-header": "Importa un dashboard", "import-dashboard-button": "Importa dashboard", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Fai clic <2>qui per saperne di più su questo errore.", - "success-more-details-links": "" + "success-more-details-links": "Successivamente, puoi iniziare a visualizzare i dati <2>creando un dashboard o eseguendo query sui dati nella <5>vista Esplora." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Prova" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Oppure puoi ignorare tutto e utilizzare {{mainDS}} (e {{extraDS}}) come origini dei dati completamente gestite, scalabili e ospitate da Grafana Labs con il <6>piano Grafana Cloud gratuito per sempre.", "title-alert": "Configura la tua origine dei dati {{mainDS}} qui sotto" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Funzioni matematiche disponibili", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Consulta la nostra documentazione aggiuntiva su <2>Espressioni matematiche.", "tooltip-title": "Operatore matematico", "tooltip-trigger": "Espressione" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Aumenta campionamento", "tooltip-s-m-h": "10 s, 1 m, 30 m, 1 h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Esegui query", "modal-title": "Editor SQL", "tooltip-experimental": "L'integrazione LLM delle Espressioni SQL è sperimentale. Segnala eventuali problemi al team Grafana." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Inserisci" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(opzionale)", "role": "Ruolo", "submit": "Invia", - "tooltip": "" + "tooltip": "Ora puoi selezionare l'opzione \"Nessun ruolo di base\" e aggiungere autorizzazioni in base alle tue esigenze personalizzate. Puoi trovare maggiori informazioni nella <1>nostra documentazione." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Invia un invito o aggiungi un utente Grafana esistente all'organizzazione.<1> {{orgName}}", "text": { "invite-user": "Invita un utente" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Cancella tutto", - "tooltip": "" + "tooltip": "Ora puoi selezionare l'opzione \"Nessun ruolo di base\" e aggiungere autorizzazioni in base alle tue esigenze personalizzate. Puoi trovare maggiori informazioni nella <1>nostra documentazione." }, "menu-aria-label": "Menu selettore ruoli", "menu-group-option-aria-label": "Opzione selettore ruoli", @@ -12709,7 +12722,7 @@ "info-text": "Crea un link diretto a questo dashboard o pannello, personalizzato con le opzioni seguenti.", "link-url": "Collega URL", "render-alert": "Plug-in del renderer di immagini non installato", - "render-instructions": "", + "render-instructions": "Per eseguire il rendering di un'immagine, è necessario installare il <2>plug-in del renderer di immagini Grafana. Contatta l'amministratore di Grafana per installare il plug-in.", "rendered-image": "Immagine con rendering del collegamento diretto", "save-alert": "Il dashboard non è stato salvato", "save-dashboard": "Per eseguire il rendering di un'immagine del pannello, è necessario prima salvare il dashboard.", @@ -13750,7 +13763,7 @@ "label-format": "Formato", "label-set-timezone": "Imposta fuso orario", "label-time-field": "Campo dell'ora", - "tooltip-format": "", + "tooltip-format": "Il formato di output per il campo specificato come <2>stringa di formato Moment.js.", "tooltip-timezone-manually": "Imposta manualmente il fuso orario della data" }, "format-time-transformer-editor": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 5043d63283e..9667ae7ea41 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -2075,7 +2075,7 @@ "warning-1": "この通知ポリシーを削除すると、完全に削除されます。", "warning-2": "本当にこれを削除してもよろしいですか?" }, - "filter-description": "", + "filter-description": "通知ポリシーを、カンマで区切られたマッチャーのリストを使用してフィルタリングします。例:<1>severity=critical, region=EMEA", "generated-policies": "自動生成されたポリシー", "matchers": "マッチャー", "metadata": { @@ -2796,7 +2796,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "アラートテンプレートデータ", - "only-in-alert": "アラートのコンテキストでのみ使用できます(アラートループ内など)" + "only-in-alert": "" }, "available-context-notification": "通知のコンテキストで利用可能です。", "notification-template-data": "通知テンプレートデータ" @@ -3233,7 +3233,7 @@ "auth-settings": "認証設定" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "認証設定を行ってください。詳細については、<2>ドキュメントをご覧ください。" }, "auth-drawer-unconnected": { "advanced-auth": "高度な認証", @@ -3267,7 +3267,7 @@ "allowed-organizations-description": "カンマまたはスペースで区切られた組織のリスト。ユーザーがログインするには、少なくとも\n1つの組織のメンバーである必要があります。", "allowed-organizations-label": "許可された組織", "allowed-organizations-placeholder": "組織(my-team、myteam...)を入力し、Enterキーを押して追加します", - "api-url-description": "", + "api-url-description": "OAuth2プロバイダーのユーザー情報エンドポイント。このエンドポイントから返される情報は<2>OpenID UserInfoと互換性がある必要があります。", "api-url-required": "このフィールドを設定する場合、有効なURLを入力する必要があります。", "auth-style-description": "「{{ clientIDLabel }}」と「{{ clientSecretLabel }}」がOAuth2プロバイダーに送信される方法を決定します。デフォルトは自動検出です。", "auth-style-label": "認証形式", @@ -3412,7 +3412,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "認証設定を管理し、シングルサインオンを設定します。詳細については、<2>ドキュメントをご覧ください。" }, "bar-chart": { "warn": { @@ -4824,7 +4824,7 @@ "add-visualization-body": "データソースを選択し、グラフ、統計、テーブルを使用してデータをクエリおよび視覚化するか、リスト、マークダウン、その他のウィジェットを作成します。", "add-visualization-button": "視覚化を追加", "add-visualization-header": "視覚化を追加して新しいダッシュボードを開始します", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "ファイルまたは<1>grafana.comからダッシュボードをインポートします。", "import-a-dashboard-header": "ダッシュボードをインポートする", "import-dashboard-button": "ダッシュボードをインポート", "show-less-dashboards": "", @@ -6599,7 +6599,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "このエラーの詳細については、<2>こちらをクリックしてください。", - "success-more-details-links": "" + "success-more-details-links": "次に、<2>ダッシュボードを構築するか、<5>Exploreビューでデータをクエリすることで、データの視覚化を開始できます。" }, "data-sources": { "datasource-add-button": { @@ -6695,7 +6695,7 @@ "test": "テスト" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "または、<6>永久無料のGrafana Cloudプランで、Grafana Labsからフルマネージドでスケーラブルなホスト済みデータソースの{{mainDS}}(および{{extraDS}})を入手して、手間を省くことも可能です。", "title-alert": "以下で{{mainDS}}データソースを設定します" }, "dashboards-table": { @@ -7678,7 +7678,7 @@ "math": { "available-math-functions": "利用可能な数学関数", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "<2>数式に関する追加ドキュメントをご覧ください。", "tooltip-title": "数学演算子", "tooltip-trigger": "式" }, @@ -7704,11 +7704,24 @@ "label-upsample": "アップサンプリング", "tooltip-s-m-h": "10秒、1分、30分、1時間" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "クエリを実行", "modal-title": "SQLエディター", "tooltip-experimental": "SQL Expressions LLMの統合は実験的です。問題が発生した場合は、Grafanaチームに報告してください。" }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "入力" } @@ -10881,10 +10894,10 @@ "placeholder-optional": "(任意)", "role": "ロール", "submit": "送信", - "tooltip": "" + "tooltip": "「基本的な役割なし」オプションを選択し、カスタムニーズに権限を追加できるようになりました。詳細については、<1>ドキュメントをご覧ください。" }, "user-invite-page": { - "sub-title": "", + "sub-title": "招待状を送信するか、既存のGrafanaユーザーを組織<1>{{orgName}}に追加します。", "text": { "invite-user": "ユーザーを招待" } @@ -12328,7 +12341,7 @@ }, "menu": { "clear-button": "すべてをクリア", - "tooltip": "" + "tooltip": "「基本的な役割なし」オプションを選択し、カスタムニーズに権限を追加できるようになりました。詳細については、<1>ドキュメントをご覧ください。" }, "menu-aria-label": "ロール選択ツールメニュー", "menu-group-option-aria-label": "ロール選択オプション", @@ -12653,7 +12666,7 @@ "info-text": "以下のオプションでカスタマイズした、このダッシュボードまたはパネルへの直接リンクを作成します。", "link-url": "URLをリンク", "render-alert": "画像レンダラープラグインがインストールされていません", - "render-instructions": "", + "render-instructions": "画像をレンダリングするには、<2>Grafana画像レンダラープラグインをインストールする必要があります。プラグインをインストールするには、Grafana管理者にお問い合わせください。", "rendered-image": "レンダリングされた画像への直接リンク", "save-alert": "ダッシュボードは保存されていません", "save-dashboard": "パネル画像をレンダリングするには、最初にダッシュボードを保存する必要があります。", @@ -13693,7 +13706,7 @@ "label-format": "形式", "label-set-timezone": "タイムゾーンを設定", "label-time-field": "時間フィールド", - "tooltip-format": "", + "tooltip-format": "<2>Moment.js形式文字列として指定されたフィールドの出力形式。", "tooltip-timezone-manually": "日付のタイムゾーンを手動で設定" }, "format-time-transformer-editor": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 0b205d6c624..115f653a526 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -2075,7 +2075,7 @@ "warning-1": "이 알림 정책을 삭제하면 영구적으로 제거됩니다.", "warning-2": "정말로 이 정책을 삭제하시겠어요?" }, - "filter-description": "", + "filter-description": "다음과 같이 쉼표로 구분된 일치 조건의 목록을 사용하여 알림 정책을 필터링합니다. <1>심각도=심각, 지역=EMEA", "generated-policies": "자동 생성된 정책", "matchers": "일치 조건", "metadata": { @@ -2796,7 +2796,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "경고 템플릿 데이터", - "only-in-alert": "경고 콘텍스트에서만 사용할 수 있습니다(예: 내부 .경고 루프)" + "only-in-alert": "" }, "available-context-notification": "알림 컨텍스트에서 사용할 수 있습니다.", "notification-template-data": "알림 템플릿 데이터" @@ -3233,7 +3233,7 @@ "auth-settings": "인증 설정" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "인증 설정을 구성합니다. 자세한 내용은 Grafana의 <2>문서를 확인하세요." }, "auth-drawer-unconnected": { "advanced-auth": "고급 인증", @@ -3267,7 +3267,7 @@ "allowed-organizations-description": "쉼표 또는 공백으로 구분된 조직 목록입니다. 사용자가 로그인하려면 하나 이상의\n조직의 멤버여야 합니다.", "allowed-organizations-label": "허용된 조직", "allowed-organizations-placeholder": "조직(my-team, myteam...)을 입력하고 엔터 키를 눌러 추가", - "api-url-description": "", + "api-url-description": "OAuth2 제공자의 사용자 정보 엔드포인트입니다. 이 엔드포인트에서 반환되는 정보는 <2>OpenID UserInfo와 호환되어야 합니다.", "api-url-required": "설정된 경우 이 필드는 유효한 URL이어야 합니다.", "auth-style-description": "\"{{ clientIDLabel }}\" 및 \"{{ clientSecretLabel }}\"이(가) Oauth2 제공자에 전송되는 방식을 결정합니다. 기본값은 AutoDetect입니다.", "auth-style-label": "인증 스타일", @@ -3412,7 +3412,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "인증 설정을 관리하고 싱글 사인온을 구성하세요. 자세한 내용은 Grafana의 <2>문서를 확인하세요." }, "bar-chart": { "warn": { @@ -4824,7 +4824,7 @@ "add-visualization-body": "데이터 소스를 선택한 다음 차트, 통계, 표를 사용하여 데이터를 쿼리하고 시각화하거나 목록, 마크다운 및 기타 위젯을 생성합니다.", "add-visualization-button": "시각화 추가", "add-visualization-header": "시각화 추가하여 새 대시보드 시작하기", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "파일 또는 <1>grafana.com에서 대시보드 가져오기", "import-a-dashboard-header": "대시보드 가져오기", "import-dashboard-button": "대시보드 가져오기", "show-less-dashboards": "", @@ -6599,7 +6599,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "이 오류에 대해 자세히 알아보려면 <2>여기를 클릭하세요.", - "success-more-details-links": "" + "success-more-details-links": "그런 다음 <2>대시보드를 구축하거나 <5>탐색 보기에서 데이터를 쿼리하여 데이터 시각화를 시작할 수 있습니다." }, "data-sources": { "datasource-add-button": { @@ -6695,7 +6695,7 @@ "test": "테스트" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "또는 이 작업을 건너뛰고 <6>Grafana Cloud 평생 무료 플랜을 통해 Grafana Labs에서 완전 관리되고 확장 가능하며 호스팅되는 데이터 소스인 {{mainDS}}(및 {{extraDS}})을(를) 받으세요.", "title-alert": "아래에서 {{mainDS}} 데이터 소스를 구성하세요" }, "dashboards-table": { @@ -7678,7 +7678,7 @@ "math": { "available-math-functions": "사용 가능한 수학 함수", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "<2>수식에 대한 추가 문서를 참조하세요.", "tooltip-title": "수학 연산자", "tooltip-trigger": "표현식" }, @@ -7704,11 +7704,24 @@ "label-upsample": "업샘플", "tooltip-s-m-h": "10초, 1분, 30분, 1시간" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "쿼리 실행", "modal-title": "SQL 편집기", "tooltip-experimental": "SQL 표현식 LLM 통합 기능은 실험 단계입니다. 문제가 발생하면 Grafana 팀에 보고해 주세요." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "입력" } @@ -10881,10 +10894,10 @@ "placeholder-optional": "(선택 사항)", "role": "역할", "submit": "제출", - "tooltip": "" + "tooltip": "이제 '기본 역할 없음' 옵션을 선택하고 사용자의 필요에 맞게 권한을 추가할 수 있습니다. 자세한 내용은 <1>문서를 참조하세요." }, "user-invite-page": { - "sub-title": "", + "sub-title": "초대장을 보내거나 기존 Grafana 사용자를<1> {{orgName}} 조직에 추가하세요.", "text": { "invite-user": "사용자 초대" } @@ -12328,7 +12341,7 @@ }, "menu": { "clear-button": "전체 초기화", - "tooltip": "" + "tooltip": "이제 '기본 역할 없음' 옵션을 선택하고 사용자의 필요에 맞게 권한을 추가할 수 있습니다. 자세한 내용은 <1>문서를 참조하세요." }, "menu-aria-label": "역할 선택기 메뉴", "menu-group-option-aria-label": "역할 선택기 옵션", @@ -12653,7 +12666,7 @@ "info-text": "아래 옵션으로 사용자 정의된 이 대시보드 또는 패널에 대한 직접 링크를 생성합니다.", "link-url": "링크 URL", "render-alert": "이미지 렌더러 플러그인 미설치", - "render-instructions": "", + "render-instructions": "이미지를 렌더링하려면 <2>Grafana 이미지 렌더러 플러그인을 설치해야 합니다. 플러그인을 설치하려면 Grafana 관리자에게 문의하세요.", "rendered-image": "렌더링된 이미지에 직접 연결", "save-alert": "대시보드 저장되지 않음", "save-dashboard": "패널 이미지를 렌더링하려면 먼저 대시보드를 저장해야 합니다.", @@ -13693,7 +13706,7 @@ "label-format": "형식", "label-set-timezone": "시간대 설정", "label-time-field": "시간 필드", - "tooltip-format": "", + "tooltip-format": "<2>Moment.js 형식 문자열로 지정된 필드의 출력 형식입니다.", "tooltip-timezone-manually": "날짜의 시간대를 수동으로 설정합니다" }, "format-time-transformer-editor": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index fae593379b6..f1236285a6b 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "Als je dit meldingsbeleid verwijdert, wordt dit permanent verwijderd.", "warning-2": "Weet je zeker dat je dit beleid wil verwijderen?" }, - "filter-description": "", + "filter-description": "Filter meldingsbeleid met behulp van een door komma's gescheiden lijst met matchers, bijv.:<1>severity=critical, region=EMEA", "generated-policies": "Automatisch gegenereerd beleid", "matchers": "Matchers", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Waarschuwingssjabloongegevens", - "only-in-alert": "Alleen beschikbaar in de context van een waarschuwing (bijv. in .Waarschuwingslus)" + "only-in-alert": "" }, "available-context-notification": "Beschikbaar in het kader van een melding.", "notification-template-data": "Meldingssjabloongegevens" @@ -3245,7 +3245,7 @@ "auth-settings": "Auth-instellingen" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Auth-instellingen configureren. Lees meer in onze <2>documentatie." }, "auth-drawer-unconnected": { "advanced-auth": "Geavanceerde auth", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Lijst van door komma's of spaties gescheiden organisaties. De gebruiker moet lid zijn van ten minste\néén organisatie om in te loggen.", "allowed-organizations-label": "Toegestane organisaties", "allowed-organizations-placeholder": "Voer organisaties in (my-team, myteam...) en druk op Enter om toe te voegen", - "api-url-description": "", + "api-url-description": "Het eindpunt voor gebruikersinformatie van je OAuth2-provider. Informatie die door dit eindpunt wordt geretourneerd, moet compatibel zijn met <2>OpenID-gebruikersinformatie.", "api-url-required": "Dit veld moet een geldige URL zijn, indien ingesteld.", "auth-style-description": "Het bepaalt hoe '{{ clientIDLabel }}' en '{{ clientSecretLabel }}' worden verzonden naar de Oauth2-provider. Standaard is AutoDetect.", "auth-style-label": "Auth-stijl", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Je auth-instellingen beheren en single sign-on configureren. Lees meer in onze <2>documentatie." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Selecteer een gegevensbron en zoek en visualiseer je gegevens met grafieken, statistieken en tabellen of maak lijsten, prijsverlagingen en andere widgets.", "add-visualization-button": "Visualisatie toevoegen", "add-visualization-header": "Begin je nieuwe dashboard door een visualisatie toe te voegen", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importeer dashboards uit bestanden of <1>grafana.com.", "import-a-dashboard-header": "Dashboard importeren", "import-dashboard-button": "Dashboard importeren", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Klik <2>hier voor meer informatie over deze fout.", - "success-more-details-links": "" + "success-more-details-links": "Vervolgens kun je beginnen met het visualiseren van gegevens door <2> een dashboard te bouwen of door gegevens op te vragen in de <5> Verkennen-weergave." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Testen" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Of bespaar je de moeite en krijg {{mainDS}} (en {{extraDS}}) als volledig beheerde, schaalbare en gehoste gegevensbronnen van Grafana Labs met het <6>altijd gratis Grafana Cloud-abonnement.", "title-alert": "Configureer hieronder je gegevensbron voor {{mainDS}}" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Beschikbare wiskundige functies", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Zie onze aanvullende documentatie over <2>wiskundige expressies.", "tooltip-title": "Wiskundige operator", "tooltip-trigger": "Expressie" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Upsample", "tooltip-s-m-h": "10s, 1m, 30m, 1u" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Query uitvoeren", "modal-title": "SQL-editor", "tooltip-experimental": "SQL Expressions LLM-integratie is experimenteel. Meld eventuele problemen aan het Grafana-team." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Invoer" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(optioneel)", "role": "Rol", "submit": "Verzenden", - "tooltip": "" + "tooltip": "Je kunt nu de optie 'Geen basisrol' selecteren en toestemmingen toevoegen aan je aangepaste behoeften. Meer informatie vind je in <1>onze documentatie." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Stuur uitnodiging of voeg een bestaande Grafana-gebruiker toe aan de organisatie.<1> {{orgName}}", "text": { "invite-user": "Gebruiker uitnodigen" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Alles wissen", - "tooltip": "" + "tooltip": "Je kunt nu de optie 'Geen basisrol' selecteren en toestemmingen toevoegen aan je aangepaste behoeften. Meer informatie vind je in <1>onze documentatie." }, "menu-aria-label": "Menu rolkiezer", "menu-group-option-aria-label": "Optie rolkiezer", @@ -12709,7 +12722,7 @@ "info-text": "Maak een directe link naar dit dashboard of paneel, aangepast met de onderstaande opties.", "link-url": "Link-URL", "render-alert": "Afbeeldingweergave-plug-in niet geïnstalleerd", - "render-instructions": "", + "render-instructions": "Om een afbeelding weer te geven, moet je de <2>Grafana- afbeeldingsweergave-plug-in installeren. Neem contact op met je Grafana-beheerder om de plug-in te installeren.", "rendered-image": "Directe link naar gerenderde afbeelding", "save-alert": "Dashboard is niet opgeslagen", "save-dashboard": "Om een paneelafbeelding weer te geven, moet je eerst het dashboard opslaan.", @@ -13750,7 +13763,7 @@ "label-format": "Formaat", "label-set-timezone": "Tijdzone instellen", "label-time-field": "Tijdveld", - "tooltip-format": "", + "tooltip-format": "De uitvoerindeling voor het veld dat is opgegeven als een <2>Moment.js-indelingsreeks.", "tooltip-timezone-manually": "Stel de tijdzone van de datum handmatig in" }, "format-time-transformer-editor": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 581e3de181b..1a5897fd7ed 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -2093,7 +2093,7 @@ "warning-1": "Usunięcie tego zbioru zasad dot. powiadomień jest nieodwracalne.", "warning-2": "Na pewno chcesz usunąć te zasady?" }, - "filter-description": "", + "filter-description": "Filtruj zasady powiadomień, używając oddzielonej przecinkami listy dopasowań, np.:<1>severity=critical, region=EMEA", "generated-policies": "Zasady generowane automatycznie", "matchers": "Dopasowania", "metadata": { @@ -2832,7 +2832,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Dane szablonu alertu", - "only-in-alert": "Dostępne tylko w kontekście alertu (np. wewnątrz pętli .Alerts)" + "only-in-alert": "" }, "available-context-notification": "Dostępne w kontekście powiadomienia.", "notification-template-data": "Dane szablonu powiadomienia" @@ -3269,7 +3269,7 @@ "auth-settings": "Ustawienia uwierzytelniania" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Skonfiguruj ustawienia uwierzytelniania. Więcej informacji znajdziesz w <2>dokumentacji." }, "auth-drawer-unconnected": { "advanced-auth": "Zaawansowane uwierzytelnianie", @@ -3303,7 +3303,7 @@ "allowed-organizations-description": "Lista organizacji rozdzielona przecinkami lub spacjami. Użytkownik musi należeć do co najmniej \njednej organizacji, aby się zalogować.", "allowed-organizations-label": "Dozwolone organizacje", "allowed-organizations-placeholder": "Wprowadź organizacje (mój-zespół, mój_zespół itp.) i naciśnij klawisz Enter, aby je dodać", - "api-url-description": "", + "api-url-description": "Punkt końcowy dostawcy OAuth2 udostępniający informacje o użytkownikach. Informacje zwracane przez ten punkt końcowy muszą być zgodne z formatem <2>OpenID UserInfo.", "api-url-required": "To pole musi zawierać prawidłowy adres URL, jeśli jest ustawiony.", "auth-style-description": "Określa, w jaki sposób „{{ clientIDLabel }}” i „{{ clientSecretLabel }}” są wysyłane do dostawcy OAuth2. Domyślnie jest to tryb automatycznego wykrywania.", "auth-style-label": "Styl uwierzytelniania", @@ -3448,7 +3448,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Zarządzaj ustawieniami uwierzytelniania i skonfiguruj logowanie jednokrotne. Więcej informacji znajdziesz w <2>dokumentacji." }, "bar-chart": { "warn": { @@ -4884,7 +4884,7 @@ "add-visualization-body": "Wybierz źródło danych, a następnie wyszukaj i zwizualizuj dane na wykresach, w statystykach i tabelach lub utwórz listy, korzystaj ze znaczników markdown i innych widżetów.", "add-visualization-button": "Dodaj wizualizację", "add-visualization-header": "Dodaj wizualizację, aby rozpocząć tworzenie nowego pulpitu", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Zaimportuj pulpity z plików lub witryny <2>grafana.com.", "import-a-dashboard-header": "Importuj pulpit", "import-dashboard-button": "Importuj pulpit", "show-less-dashboards": "", @@ -6671,7 +6671,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Kliknij <2>tutaj, aby dowiedzieć się więcej o tym błędzie.", - "success-more-details-links": "" + "success-more-details-links": "Następnie możesz rozpocząć wizualizację danych, <2>tworząc pulpit lub tworząc zapytanie dotyczące danych w widoku <5>Eksploruj." }, "data-sources": { "datasource-add-button": { @@ -6767,7 +6767,7 @@ "test": "Test" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Możesz ułatwić sobie życie i wybrać {{mainDS}} (oraz {{extraDS}}) jako w pełni zarządzane, skalowalne źródła danych hostowane przez Grafana Labs w ramach <6>planu Grafana Cloud, który będzie zawsze bezpłatny.", "title-alert": "Skonfiguruj źródło danych {{mainDS}} poniżej" }, "dashboards-table": { @@ -7756,7 +7756,7 @@ "math": { "available-math-functions": "Dostępne funkcje matematyczne", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Zapoznaj się z naszą dodatkową dokumentacją na temat <2>wyrażeń matematycznych.", "tooltip-title": "Operator matematyczny", "tooltip-trigger": "Wyrażenie" }, @@ -7782,11 +7782,24 @@ "label-upsample": "Zwiększ próbkowanie", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Uruchom zapytanie", "modal-title": "Edytor SQL", "tooltip-experimental": "Integracja LLM z wyrażeniami SQL jest eksperymentalna. Wszelkie problemy zgłaszaj zespołowi Grafana." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Wejście" } @@ -11022,10 +11035,10 @@ "placeholder-optional": "(opcjonalnie)", "role": "Rola", "submit": "Prześlij", - "tooltip": "" + "tooltip": "Możesz teraz wybrać opcję „Brak podstawowej roli” i dodać uprawnienia zgodnie z własnymi potrzebami. Więcej informacji znajdziesz w <1>dokumentacji." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Wyślij zaproszenie lub dodaj istniejącego użytkownika Grafany do organizacji.<1> {{orgName}}", "text": { "invite-user": "Zaproś użytkownika" } @@ -12493,7 +12506,7 @@ }, "menu": { "clear-button": "Wyczyść wszystko", - "tooltip": "" + "tooltip": "Możesz teraz wybrać opcję „Brak podstawowej roli” i dodać uprawnienia zgodnie z własnymi potrzebami. Więcej informacji znajdziesz w <1>dokumentacji." }, "menu-aria-label": "Menu selektora ról", "menu-group-option-aria-label": "Opcja selektora ról", @@ -12821,7 +12834,7 @@ "info-text": "Utwórz bezpośredni link do tego pulpitu lub panelu, dostosowany za pomocą poniższych opcji.", "link-url": "Link URL", "render-alert": "Nie zainstalowano wtyczki renderowania obrazów", - "render-instructions": "", + "render-instructions": "Aby wyrenderować obraz, musisz zainstalować <2>wtyczkę do renderowania obrazów w Grafanie. Skontaktuj się z administratorem usługi Grafana, aby zainstalować wtyczkę.", "rendered-image": "Obraz renderowany z bezpośredniego linku", "save-alert": "Nie zapisano pulpitu", "save-dashboard": "Aby wyrenderować obraz panelu, musisz najpierw zapisać pulpit.", @@ -13864,7 +13877,7 @@ "label-format": "Format", "label-set-timezone": "Ustaw strefę czasową", "label-time-field": "Pole czasu", - "tooltip-format": "", + "tooltip-format": "Format wyjściowy pola określonego jako <2>Ciąg formatowania Moment.js.", "tooltip-timezone-manually": "Ręcznie ustaw strefę czasową daty" }, "format-time-transformer-editor": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 9fb0c60ec94..419d550fe09 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "A exclusão desta política de notificação irá removê-la permanentemente.", "warning-2": "Tem certeza de que deseja excluir esta política?" }, - "filter-description": "", + "filter-description": "Filtre as políticas de notificação usando uma lista de correspondências separadas por vírgulas, por exemplo:<1>severity=critical, region=EMEA", "generated-policies": "Políticas geradas automaticamente", "matchers": "Correspondências", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Dados do modelo de alerta", - "only-in-alert": "Disponível apenas quando estiver no contexto de um Alerta (por exemplo, dentro do .Loop de alertas)" + "only-in-alert": "" }, "available-context-notification": "Disponível no contexto de uma notificação.", "notification-template-data": "Dados do modelo de notificação" @@ -3245,7 +3245,7 @@ "auth-settings": "Configurações de autenticação" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Defina as configurações de autenticação. Saiba mais na nossa <2>documentação." }, "auth-drawer-unconnected": { "advanced-auth": "Autenticação avançada", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Lista de organizações separadas por vírgulas ou espaços. O usuário deve ser membro \nde pelo menos uma organização para fazer login.", "allowed-organizations-label": "Organizações permitidas", "allowed-organizations-placeholder": "Insira as organizações (minha-equipe, minhaequipe...) e pressione Enter para adicioná-las", - "api-url-description": "", + "api-url-description": "O endpoint de informações do usuário do seu provedor OAuth2. As informações que retornaram com este endpoint devem ser compatíveis com <2>OpenID UserInfo.", "api-url-required": "Este campo deve ser uma URL válida, se definido.", "auth-style-description": "Ele determina como \"{{ clientIDLabel }}\" e \"{{ clientSecretLabel }}\" são enviados para o provedor Oauth2. O padrão é AutoDetect.", "auth-style-label": "Estilo de autenticação", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Gerencie suas configurações de autenticação e configure a autenticação única. Saiba mais na nossa <2>documentação." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Selecione uma fonte de dados e consulte e visualize seus dados com gráficos, estatísticas e tabelas ou crie listas, markdowns e outros widgets.", "add-visualization-button": "Adicionar visualização", "add-visualization-header": "Comece seu novo painel de controle adicionando uma visualização", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importe painéis por meio de arquivos ou do <2>grafana.com.", "import-a-dashboard-header": "Importar um painel de controle", "import-dashboard-button": "Importar painel de controle", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Clique <2>aqui para saber mais sobre esse erro.", - "success-more-details-links": "" + "success-more-details-links": "Em seguida, você pode começar a visualizar os dados <2>criando um painel de controle ou consultando os dados na <5>Visualização de exploração." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Teste" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Ou evite essa tarefa e obtenha {{mainDS}} (e {{extraDS}}) como fontes de dados totalmente gerenciadas, escaláveis e hospedadas da Grafana Labs com o <6>plano gratuito vitalício da Grafana Cloud.", "title-alert": "Configure sua fonte de {{mainDS}} dados abaixo" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Funções matemáticas disponíveis", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Consulte nossa documentação adicional sobre <2>expressões matemáticas.", "tooltip-title": "Operador matemático", "tooltip-trigger": "Expressão" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Aumentar taxa de amostragem", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Executar consulta", "modal-title": "Editor de SQL", "tooltip-experimental": "A integração do LLM de expressões SQL está em fase de testes. Informe à equipe do Grafana se surgir algum problema." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Entrada" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(opcional)", "role": "Função", "submit": "Enviar", - "tooltip": "" + "tooltip": "Agora você pode selecionar a opção \"Sem função básica\" e adicionar permissões personalizadas com base nas suas necessidades. Você pode encontrar mais informações em <1>nossa documentação." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Envie um convite ou adicione um usuário que já possui conta na Grafana à organização.<1> {{orgName}}", "text": { "invite-user": "Convidar usuário" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Limpar tudo", - "tooltip": "" + "tooltip": "Agora você pode selecionar a opção \"Sem função básica\" e adicionar permissões personalizadas com base nas suas necessidades. Você pode encontrar mais informações em <1>nossa documentação." }, "menu-aria-label": "Menu do seletor de função", "menu-group-option-aria-label": "Opção do seletor de função", @@ -12709,7 +12722,7 @@ "info-text": "Crie um link direto para este painel de controle ou painel, personalizado com as opções abaixo.", "link-url": "URL do link", "render-alert": "Plug-in renderizador de imagem não instalado", - "render-instructions": "", + "render-instructions": "Para renderizar uma imagem, você precisa instalar o <2>plug-in renderizador de imagens do Grafana. Entre em contato com o administrador do Grafana para instalar o plug-in.", "rendered-image": "Link direto de imagem renderizada", "save-alert": "O painel de controle não está salvo", "save-dashboard": "Para renderizar uma imagem do painel, você deve salvar o painel de controle primeiro.", @@ -13750,7 +13763,7 @@ "label-format": "Formato", "label-set-timezone": "Definir fuso horário", "label-time-field": "Campo de hora", - "tooltip-format": "", + "tooltip-format": "O formato de saída para o campo especificado como uma <2>sequência de caracteres de formato Moment.js.", "tooltip-timezone-manually": "Defina o fuso horário da data manualmente" }, "format-time-transformer-editor": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index edb3fcc3a16..28b728aee0f 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "A eliminação desta política de notificação irá removê-la permanentemente.", "warning-2": "Tem a certeza de que pretende eliminar esta política?" }, - "filter-description": "", + "filter-description": "Filtre as políticas de notificação utilizando uma lista de correspondências separadas por vírgulas, por ex.:<1>gravidade=crítica, região=EMEA", "generated-policies": "Políticas geradas automaticamente", "matchers": "Correspondências", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Dados do modelo de alerta", - "only-in-alert": "Disponível apenas quando está no contexto de um alerta (por exemplo, dentro de .Ciclo de alertas)" + "only-in-alert": "" }, "available-context-notification": "Disponível no contexto de uma notificação.", "notification-template-data": "Dados do modelo de notificação" @@ -3245,7 +3245,7 @@ "auth-settings": "Definições de autenticação" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Configurar as definições de autenticação. Saiba mais na nossa <2>documentação." }, "auth-drawer-unconnected": { "advanced-auth": "Autenticação avançada", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Lista de organizações separadas por vírgulas ou espaços. O utilizador deve ser membro \nde pelo menos uma organização para iniciar sessão.", "allowed-organizations-label": "Organizações permitidas", "allowed-organizations-placeholder": "Introduza organizações (my-team, myteam...) e prima Enter para adicionar", - "api-url-description": "", + "api-url-description": "O ponto final de informações do utilizador do seu fornecedor OAuth2. As informações obtidas por este ponto final devem ser compatíveis com <2>OpenID UserInfo.", "api-url-required": "Este campo deve ser um URL válido, se definido.", "auth-style-description": "Determina como \"{{ clientIDLabel }}\" e \"{{ clientSecretLabel }}\" são enviados para o fornecedor Oauth2. A predefinição é AutoDetect.", "auth-style-label": "Estilo de autenticação", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Faça a gestão das suas definições de autenticação e configure o início de sessão único. Saiba mais na nossa <2>documentação." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Selecione uma origem de dados e, em seguida, consulte e visualize os seus dados com gráficos, estatísticas e tabelas ou crie listas, remarcações e outros widgets.", "add-visualization-button": "Adicionar visualização", "add-visualization-header": "Inicie o seu novo painel de controlo ao adicionar uma visualização", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importe painéis de controlo de ficheiros ou de <2>grafana.com.", "import-a-dashboard-header": "Importar um painel de controlo", "import-dashboard-button": "Importar painel de controlo", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Clique <2>aqui para saber mais sobre este erro.", - "success-more-details-links": "" + "success-more-details-links": "Em seguida, pode começar a visualizar os dados <2>criando um painel de controlo ou consultando os dados na <5>vista Explorar." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Teste" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Ou evite o esforço e obtenha {{mainDS}} (e {{extraDS}}) como origens de dados totalmente geridas, escaláveis e hospedadas da Grafana Labs com o <6>plano Grafana Cloud gratuito para sempre.", "title-alert": "Configurar a sua origem de dados {{mainDS}} abaixo" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Funções matemáticas disponíveis", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Consulte a nossa documentação adicional sobre <2>expressões matemáticas.", "tooltip-title": "Operador matemático", "tooltip-trigger": "Expressão" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Aumentar a quantidade de amostras", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Executar consulta", "modal-title": "Editor SQL", "tooltip-experimental": "A integração de LLM de expressões SQL é experimental. Comunique quaisquer problemas à equipa da Grafana." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Entrada" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(opcional)", "role": "Função", "submit": "Enviar", - "tooltip": "" + "tooltip": "Agora pode selecionar a opção \"Sem função básica\" e adicionar permissões às suas necessidades personalizadas. Pode encontrar mais informações na <1>nossa documentação." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Envie um convite ou adicione um utilizador Grafana existente à organização.<1> {{orgName}}", "text": { "invite-user": "Convidar utilizador" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Limpar tudo", - "tooltip": "" + "tooltip": "Agora pode selecionar a opção \"Sem função básica\" e adicionar permissões às suas necessidades personalizadas. Pode encontrar mais informações na <1>nossa documentação." }, "menu-aria-label": "Menu do selecionador de funções", "menu-group-option-aria-label": "Opção do selecionador de funções", @@ -12709,7 +12722,7 @@ "info-text": "Crie um link direto para este painel de controlo ou painel, personalizado com as opções abaixo.", "link-url": "URL do link", "render-alert": "Plugin de renderização de imagem não instalado", - "render-instructions": "", + "render-instructions": "Para renderizar uma imagem, tem de instalar o <2>plugin de renderização de imagens Grafana. Entre em contacto com o seu administrador Grafana para instalar o plugin.", "rendered-image": "Link direto para imagem renderizada", "save-alert": "O painel de controlo não está guardado", "save-dashboard": "Para processar uma imagem de painel, tem de guardar o painel de controlo primeiro.", @@ -13750,7 +13763,7 @@ "label-format": "Formato", "label-set-timezone": "Definir o fuso horário", "label-time-field": "Campo de hora", - "tooltip-format": "", + "tooltip-format": "O formato de saída para o campo especificado como uma <2>sequência de caracteres de formato Moment.js.", "tooltip-timezone-manually": "Defina o fuso horário da data manualmente" }, "format-time-transformer-editor": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index a7a264ece15..1d05879899a 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -2093,7 +2093,7 @@ "warning-1": "Политика уведомления будет безвозвратно удалена.", "warning-2": "Действительно удалить политику?" }, - "filter-description": "", + "filter-description": "Фильтруйте политики уведомления, используя список сопоставлений, разделенных запятыми, например, <1>серьезность=критическая, регион=EMEA", "generated-policies": "Автоматически генерируемые политики", "matchers": "Средства сопоставления", "metadata": { @@ -2832,7 +2832,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Данные шаблона оповещения", - "only-in-alert": "Доступно только в контексте оповещения (например, внутрицикла оповещений)" + "only-in-alert": "" }, "available-context-notification": "Доступно в контексте уведомления.", "notification-template-data": "Данные шаблона уведомления" @@ -3269,7 +3269,7 @@ "auth-settings": "Параметры аутентификации" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Настройка параметров аутентификации Подробнее см. в нашей <2>документации." }, "auth-drawer-unconnected": { "advanced-auth": "Расширенная аутентификация", @@ -3303,7 +3303,7 @@ "allowed-organizations-description": "Список организаций, разделенных запятыми или пробелами. Чтобы войти в систему, пользователь должен \nбыть участником хотя бы одной организации.", "allowed-organizations-label": "Разрешенные организации", "allowed-organizations-placeholder": "Введите названия организаций (my-team, myteam...) и нажмите «Enter», чтобы их добавить", - "api-url-description": "", + "api-url-description": "Конечная точка информации о пользователе вашего поставщика OAuth2. Информация, возвращаемая этой конечной точкой, должна быть совместима с <2>OpenID UserInfo.", "api-url-required": "В поле должен быть указан действительный URL-адрес (если он настроен).", "auth-style-description": "Определяет, каким образом «{{ clientIDLabel }}» и «{{ clientSecretLabel }}» отправляются поставщику Oauth2. По умолчанию используется автоопределение.", "auth-style-label": "Метод авторизации", @@ -3448,7 +3448,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Управление параметрами аутентификации и настройка единого входа Подробнее см. в нашей <2>документации." }, "bar-chart": { "warn": { @@ -4884,7 +4884,7 @@ "add-visualization-body": "Выберите источник данных, а затем запрашивайте и визуализируйте свои данные с помощью диаграмм, статистики и таблиц или создавайте списки, разметки и другие виджеты.", "add-visualization-button": "Добавить визуализацию", "add-visualization-header": "Создание нового дашборда с добавлением визуализации", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Импортируйте дашборды из файлов или с <1>grafana.com.", "import-a-dashboard-header": "Импорт дашборда", "import-dashboard-button": "Импорт дашборда", "show-less-dashboards": "", @@ -6671,7 +6671,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Чтобы узнать больше об этой ошибке, нажмите <2>здесь.", - "success-more-details-links": "" + "success-more-details-links": "Затем вы можете начать визуализировать данные, <2>создав панель или запросив данные в <5>представлении Explore." }, "data-sources": { "datasource-add-button": { @@ -6767,7 +6767,7 @@ "test": "Тестирование" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Или не тратьте время и получите {{mainDS}} (и {{extraDS}}) в качестве полностью управляемых, масштабируемых и размещенных источников данных от Grafana Labs с <6>бессрочным бесплатным планом Grafana Cloud.", "title-alert": "Настройте свой источник данных {{mainDS}} ниже" }, "dashboards-table": { @@ -7756,7 +7756,7 @@ "math": { "available-math-functions": "Доступные математические функции", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Подробнее о <2>математических выражениях см. в нашей дополнительной документации.", "tooltip-title": "Математический оператор", "tooltip-trigger": "Выражение" }, @@ -7782,11 +7782,24 @@ "label-upsample": "Увеличить частоту выборки", "tooltip-s-m-h": "10 с, 1 мин, 30 мин, 1 ч" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Выполнить запрос", "modal-title": "Редактор SQL", "tooltip-experimental": "Интеграция LLM с SQL-выражениями является экспериментальной. При обнаружении проблем свяжитесь с командой Grafana." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Ввод" } @@ -11022,10 +11035,10 @@ "placeholder-optional": "(необязательно)", "role": "Роль", "submit": "Отправить", - "tooltip": "" + "tooltip": "Теперь вы можете выбрать параметр «Нет основных ролей» и добавить разрешения в соответствии со своими потребностями. Дополнительную информацию можно найти в <1>нашей документации." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Отправьте приглашение или добавьте существующего пользователя Grafana в организацию <1>{{orgName}}", "text": { "invite-user": "Пригласить пользователя" } @@ -12493,7 +12506,7 @@ }, "menu": { "clear-button": "Очистить всё", - "tooltip": "" + "tooltip": "Теперь вы можете выбрать параметр «Нет основных ролей» и добавить разрешения в соответствии со своими потребностями. Дополнительную информацию можно найти в <1>нашей документации." }, "menu-aria-label": "Меню указателя роли", "menu-group-option-aria-label": "Параметр указателя роли", @@ -12821,7 +12834,7 @@ "info-text": "Создайте прямую ссылку на дашборд или панель, настроенные с помощью приведенных ниже параметров.", "link-url": "Привязать URL-адрес", "render-alert": "Плагин визуализации изображений не установлен", - "render-instructions": "", + "render-instructions": "Чтобы визуализировать изображение, необходимо установить <2>плагин визуализации изображений Grafana. Чтобы установить плагин, обратитесь к своему администратору Grafana.", "rendered-image": "Прямая ссылка на визуализированное изображение", "save-alert": "Дашборд не сохранен", "save-dashboard": "Чтобы визуализировать изображение панели, сначала необходимо сохранить дашборд.", @@ -13864,7 +13877,7 @@ "label-format": "Формат", "label-set-timezone": "Установить часовой пояс", "label-time-field": "Поле времени", - "tooltip-format": "", + "tooltip-format": "Формат вывода для поля, указанного в виде <2>строки в формате Moment.js.", "tooltip-timezone-manually": "Установите часовой пояс вручную" }, "format-time-transformer-editor": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 9a1940aa2b4..95e2ae6130e 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "Om du tar bort denna aviseringspolicy tas den bort permanent.", "warning-2": "Är du säker på att du vill ta bort denna policy?" }, - "filter-description": "", + "filter-description": "Filtrera aviseringspolicyer med hjälp av en kommaseparerad lista över matchare, till exempel:<1>severity=critical, region=EMEA", "generated-policies": "Automatiskt genererade policyer", "matchers": "Matchare", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Larmmalldata", - "only-in-alert": "Endast tillgängligt när det är i samband med ett larm (t.ex. inuti .Alerts-loop)" + "only-in-alert": "" }, "available-context-notification": "Tillgängligt i samband med ett meddelande.", "notification-template-data": "Aviseringsmalldata" @@ -3245,7 +3245,7 @@ "auth-settings": "Autentiseringsinställningar" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Konfigurera autentiseringsinställningar. Läs mer i vår <2>dokumentation." }, "auth-drawer-unconnected": { "advanced-auth": "Avancerad autentisering", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Lista över kommaseparerade eller mellanslagsavgränsade organisationer. Användaren bör vara medlem \n i minst en organisation för att logga in.", "allowed-organizations-label": "Tillåtna organisationer", "allowed-organizations-placeholder": "Ange organisationer (mitt-team, mittteam …) och tryck på returtangenten för att lägga till", - "api-url-description": "", + "api-url-description": "Slutpunkten för användarinformation för din OAuth2-leverantör. Information som returneras av denna slutpunkt måste vara kompatibel med <2>OpenID UserInfo.", "api-url-required": "Om detta fält är inställt måste det vara en giltig webbadress.", "auth-style-description": "Det avgör hur ”{{ clientIDLabel }}” och ”{{ clientSecretLabel }}” skickas till Oauth2-leverantören. Standard är AutoDetect.", "auth-style-label": "Autentiseringsstil", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Hantera dina autentiseringsinställningar och konfigurera enkel inloggning. Läs mer i vår <2>dokumentation." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Välj en datakälla och sedan en fråga och visualisera dina data med diagram, statistik och tabeller eller skapa listor, markdown-dokument och andra widgetar.", "add-visualization-button": "Lägg till visualisering", "add-visualization-header": "Starta din nya instrumentpanel genom att lägga till en visualisering", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Importera instrumentpaneler från filer eller från <2>grafana.com.", "import-a-dashboard-header": "Importera en instrumentpanel", "import-dashboard-button": "Importera instrumentpanel", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Klicka <2>här för att läsa mer om detta fel.", - "success-more-details-links": "" + "success-more-details-links": "Därefter kan du börja visualisera data genom att <2>bygga en instrumentpanel eller genom att fråga efter data i <5>Utforska vy." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Test" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Eller hoppa över ansträngningen och få {{mainDS}} (och {{extraDS}}) som fullt hanterade, skalbara och värdbaserade datakällor från Grafana Labs med <6>Grafana Cloud-prenumerationen gratis för alltid.", "title-alert": "Konfigurera din {{mainDS}}-datakälla nedan" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Tillgängliga matematikfunktioner", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "Se vår ytterligare dokumentation om <2>matematiska uttryck.", "tooltip-title": "Matematisk operator", "tooltip-trigger": "Uttryck" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Sampla upp", "tooltip-s-m-h": "10 s, 1 m, 30 m, 1 h" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "Kör fråga", "modal-title": "SQL-redigerare", "tooltip-experimental": "Integreringen för SQL Expressions LLM är i ett experimentellt skede. Rapportera alla problem du stöter på till Grafana-teamet." }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Ingång" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(valfritt)", "role": "Roll", "submit": "Skicka in", - "tooltip": "" + "tooltip": "Du kan nu välja alternativet ”Ingen grundläggande roll” och lägga till behörigheter för dina anpassade behov. Du hittar mer information i <1>vår dokumentation." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Skicka inbjudan eller lägg till en befintlig Grafana-användare i organisationen.<1> {{orgName}}", "text": { "invite-user": "Bjud in användare" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Rensa alla", - "tooltip": "" + "tooltip": "Du kan nu välja alternativet ”Ingen grundläggande roll” och lägga till behörigheter för dina anpassade behov. Du hittar mer information i <1>vår dokumentation." }, "menu-aria-label": "Menyn Rollväljare", "menu-group-option-aria-label": "Alternativ för rollväljare", @@ -12709,7 +12722,7 @@ "info-text": "Skapa en direktlänk till denna instrumentpanel eller panel, anpassad med alternativen nedan.", "link-url": "Länkens webbadress", "render-alert": "Tilläggsprogram för bildrendering inte installerat", - "render-instructions": "", + "render-instructions": "För att rendera en bild måste du installera <2>Grafanas bildrenderingstillägg. Kontakta din Grafana-administratör för installation av tillägget.", "rendered-image": "Direktlänk till renderad bild", "save-alert": "Instrumentpanelen sparades inte", "save-dashboard": "För att rendera en panelbild måste du spara instrumentpanelen först.", @@ -13750,7 +13763,7 @@ "label-format": "Format", "label-set-timezone": "Ange tidszon", "label-time-field": "Tidsfält", - "tooltip-format": "", + "tooltip-format": "Utdataformatet för fältet anges som en <2>Moment.js-formatsträng.", "tooltip-timezone-manually": "Ställ in tidszonen för datumet manuellt" }, "format-time-transformer-editor": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index a081fdbd8d3..16e0520c9a7 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -2081,7 +2081,7 @@ "warning-1": "Bu bildirim politikası silindiğinde kalıcı olarak kaldırılacaktır.", "warning-2": "Bu politikayı silmek istediğinizden emin misiniz?" }, - "filter-description": "", + "filter-description": "Bildirim politikalarını, virgülle ayrılmış eşleşenler listesi kullanarak filtreleyin, örneğin: <1>severity=critical, region=EMEA", "generated-policies": "Otomatik olarak oluşturulan politikalar", "matchers": "Eşleşenler", "metadata": { @@ -2808,7 +2808,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "Uyarı şablonu verileri", - "only-in-alert": "Yalnızca bir Uyarı bağlamında kullanılabilir (örn.Alerts döngüsü içinde)" + "only-in-alert": "" }, "available-context-notification": "Bir bildirim bağlamında kullanılabilir.", "notification-template-data": "Bildirim şablonu verileri" @@ -3245,7 +3245,7 @@ "auth-settings": "Kimlik doğrulaması ayarları" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "Kimlik doğrulama ayarlarını yapılandırın. Daha fazla bilgi için <2>belgelerimize bakın." }, "auth-drawer-unconnected": { "advanced-auth": "Gelişmiş Kimlik Doğrulaması", @@ -3279,7 +3279,7 @@ "allowed-organizations-description": "Virgül veya boşlukla ayrılmış kuruluş listesi. Kullanıcının giriş yapabilmesi için en az bir\ngrubun üyesi olması gerekir.", "allowed-organizations-label": "İzin verilen kuruluşlar", "allowed-organizations-placeholder": "Kuruluşları girin (benim-ekibim, benimekibim...) ve eklemek için Enter tuşuna basın", - "api-url-description": "", + "api-url-description": "OAuth2 sağlayıcınızın kullanıcı bilgisi uç noktası. Bu uç noktadan dönen bilgiler <2>OpenID UserInfo ile uyumlu olmalıdır.", "api-url-required": "Bu alan ayarlanmışsa geçerli bir URL olmalıdır", "auth-style-description": "\"{{ clientIDLabel }}\" ve \"{{ clientSecretLabel }}\" bilgilerinin OAuth2 sağlayıcısına nasıl gönderileceğini belirler. Varsayılan Otomatik Algıla'dır.", "auth-style-label": "Kimlik doğrulama stili", @@ -3424,7 +3424,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "Kimlik doğrulama ayarlarınızı yönetin ve çoklu oturum açmayı yapılandırın. Daha fazla bilgi için <2>belgelerimize bakın." }, "bar-chart": { "warn": { @@ -4844,7 +4844,7 @@ "add-visualization-body": "Bir veri kaynağı seçin ve ardından grafikler, istatistikler ve tablolarla verilerinizi sorgulayın ve görselleştirin veya listeler, markdown'lar ve diğer widget'lar oluşturun.", "add-visualization-button": "Görselleştirme ekle", "add-visualization-header": "Bir görselleştirme ekleyerek yeni panonuzu başlatın", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "Panoları dosyalardan veya <2>grafana.com'dan içe aktarın.", "import-a-dashboard-header": "Bir panoyu içe aktar", "import-dashboard-button": "Panoyu içe aktar", "show-less-dashboards": "", @@ -6623,7 +6623,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "Bu hata hakkında daha fazla bilgi edinmek için <2>buraya tıklayın.", - "success-more-details-links": "" + "success-more-details-links": "Ardından, bir <2>pano oluşturarak veya <5>Keşfet görünümünde verileri sorgulayarak verileri görselleştirmeye başlayabilirsiniz." }, "data-sources": { "datasource-add-button": { @@ -6719,7 +6719,7 @@ "test": "Test" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "Veya uğraşmadan {{mainDS}} (ve {{extraDS}}) veri kaynaklarını Grafana Labs'tan tamamen yönetilen, ölçeklenebilir ve barındırılan <6>daima ücretsiz Grafana Cloud planıyla edinin.", "title-alert": "Aşağıdan {{mainDS}} veri kaynağınızı yapılandırın" }, "dashboards-table": { @@ -7704,7 +7704,7 @@ "math": { "available-math-functions": "Kullanılabilir matematiksel işlevler", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": " <2>Matematiksel ifadeler hakkında ek belgelerimize bakın.", "tooltip-title": "Matematik işleci", "tooltip-trigger": "İfade" }, @@ -7730,11 +7730,24 @@ "label-upsample": "Örneklemeyi artır", "tooltip-s-m-h": "10 sn, 1 dk, 30 dk, 1 sa" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "", "modal-title": "", "tooltip-experimental": "" }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "Girdi" } @@ -10928,10 +10941,10 @@ "placeholder-optional": "(isteğe bağlı)", "role": "Rol", "submit": "Gönder", - "tooltip": "" + "tooltip": "Artık \"Temel rol yok\" seçeneğini seçebilir ve izinleri özel ihtiyaçlarınıza göre ekleyebilirsiniz. Daha fazla bilgi için <1>belgelerimize göz atabilirsiniz." }, "user-invite-page": { - "sub-title": "", + "sub-title": "Davet gönderin veya mevcut bir Grafana kullanıcısını <1> {{orgName}} kuruluşuna ekleyin.", "text": { "invite-user": "Kullanıcı davet et" } @@ -12383,7 +12396,7 @@ }, "menu": { "clear-button": "Tümünü temizle", - "tooltip": "" + "tooltip": "Artık \"Temel rol yok\" seçeneğini seçebilir ve izinleri özel ihtiyaçlarınıza göre ekleyebilirsiniz. Daha fazla bilgi için <1>belgelerimize göz atabilirsiniz." }, "menu-aria-label": "Rol seçici menüsü", "menu-group-option-aria-label": "Rol seçme opsiyonu", @@ -12709,7 +12722,7 @@ "info-text": "Aşağıdaki seçeneklerle özelleştirilen bu panoya veya panele doğrudan bir bağlantı oluşturun.", "link-url": "Bağlantı URL'si", "render-alert": "Görüntü işleme eklentisi yüklü değil", - "render-instructions": "", + "render-instructions": "Bir görsel oluşturmak için <2>Grafana görüntü işleme eklentisini yüklemeniz gerekir. Lütfen eklentiyi yüklemek için Grafana yöneticinizle iletişime geçin.", "rendered-image": "Doğrudan bağlantılı oluşturulmuş görüntü", "save-alert": "Pano kaydedilmedi", "save-dashboard": "Panel görüntüsü oluşturmak için önce panoyu kaydetmelisiniz.", @@ -13750,7 +13763,7 @@ "label-format": "Biçim", "label-set-timezone": "Zaman dilimini ayarla", "label-time-field": "Zaman alanı", - "tooltip-format": "", + "tooltip-format": "Alan için çıktı biçimi, <2>Moment.js biçimlendirme dizesi olarak belirtilir.", "tooltip-timezone-manually": "Tarih için saat dilimini manuel olarak ayarlayın" }, "format-time-transformer-editor": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 481399a0bcb..4dccf320255 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -2075,7 +2075,7 @@ "warning-1": "删除此通知策略将永久移除它。", "warning-2": "您确定要删除此策略吗?" }, - "filter-description": "", + "filter-description": "使用半角逗号分隔的匹配器列表来筛选通知策略,例如:<1>severity=critical, region=EMEA", "generated-policies": "自动生成的策略", "matchers": "匹配器", "metadata": { @@ -2796,7 +2796,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "警报模板数据", - "only-in-alert": "仅在警报上下文中可用(例如在警报循环中)" + "only-in-alert": "" }, "available-context-notification": "在通知上下文中可用。", "notification-template-data": "通知模板数据" @@ -3233,7 +3233,7 @@ "auth-settings": "身份验证设置" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "配置身份验证设置。不妨在我们的<2>文档中了解更多信息。" }, "auth-drawer-unconnected": { "advanced-auth": "高级身份验证", @@ -3267,7 +3267,7 @@ "allowed-organizations-description": "以逗号或空格分隔的组织列表。用户应至少是一个\n组织的成员才能登录。", "allowed-organizations-label": "允许的组织", "allowed-organizations-placeholder": "输入组织(my-team、myteam...)并按 Enter 键添加", - "api-url-description": "", + "api-url-description": "OAuth2 提供商的用户信息端点。此端点返回的信息必须与 <2>OpenID UserInfo 兼容。", "api-url-required": "如果设置,此字段必须是有效的网址。", "auth-style-description": "它决定了如何将“{{ clientIDLabel }}”和“{{ clientSecretLabel }}”发送到 Oauth2 提供程序。默认值为 AutoDetect。", "auth-style-label": "身份验证样式", @@ -3412,7 +3412,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "管理身份验证设置并配置单点登录。不妨在我们的<2>文档中了解更多信息。" }, "bar-chart": { "warn": { @@ -4824,7 +4824,7 @@ "add-visualization-body": "选择一个数据源,然后用图表、统计信息和表格查询您的数据以及将其可视化,或创建列表、Markdown 和其他小部件。", "add-visualization-button": "添加可视化", "add-visualization-header": "通过添加可视化开始您的新仪表板", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "从文件或 <2>grafana.com 导入数据面板。", "import-a-dashboard-header": "导入仪表板", "import-dashboard-button": "导入仪表板", "show-less-dashboards": "", @@ -6599,7 +6599,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "点击<2>此处了解有关此错误的更多信息。", - "success-more-details-links": "" + "success-more-details-links": "接下来,您可以通过<2>构建数据面板或在<5>Explore 视图中查询数据来开始可视化数据。" }, "data-sources": { "datasource-add-button": { @@ -6695,7 +6695,7 @@ "test": "测试" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "您也可以省去这些步骤,通过<6>永久免费的 Grafana Cloud 计划,从 Grafana Labs 获取完全托管、可扩展和托管式数据源 {{mainDS}}(和 {{extraDS}})。", "title-alert": "在下方配置您的 {{mainDS}} 数据源" }, "dashboards-table": { @@ -7678,7 +7678,7 @@ "math": { "available-math-functions": "可用的数学函数", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "请参阅我们关于<2>数学表达式的其他文档。", "tooltip-title": "数学运算符", "tooltip-trigger": "表达式" }, @@ -7704,11 +7704,24 @@ "label-upsample": "升高采样", "tooltip-s-m-h": "10 秒、1 分钟、30 分钟、1 小时" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "运行查询", "modal-title": "SQL 编辑器", "tooltip-experimental": "SQL 表达式 LLM 集成是实验性的。若有任何问题,请向 Grafana 团队报告。" }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "输入" } @@ -10881,10 +10894,10 @@ "placeholder-optional": "(可选)", "role": "角色", "submit": "提交", - "tooltip": "" + "tooltip": "您现在可以选择“无基本角色”选项,并根据自定义需求添加权限。您可以在<1>我们的文档中找到更多信息。" }, "user-invite-page": { - "sub-title": "", + "sub-title": "发送邀请或将现有 Grafana 用户添加到组织。<1>{{orgName}}", "text": { "invite-user": "邀请用户" } @@ -12328,7 +12341,7 @@ }, "menu": { "clear-button": "全部清除", - "tooltip": "" + "tooltip": "您现在可以选择“无基本角色”选项,并根据自定义需求添加权限。您可以在<1>我们的文档中找到更多信息。" }, "menu-aria-label": "角色选择器菜单", "menu-group-option-aria-label": "角色选择器选项", @@ -12653,7 +12666,7 @@ "info-text": "创建指向此仪表板或面板的直接链接,通过以下选项自定义。", "link-url": "链接网址", "render-alert": "未安装图像渲染器插件", - "render-instructions": "", + "render-instructions": "要渲染图像,您必须安装 <2>Grafana 图像渲染器插件。请与您的 Grafana 管理员联系以安装插件。", "rendered-image": "直接链接至渲染后的图像", "save-alert": "仪表板未保存", "save-dashboard": "要渲染面板图像,需要先保存仪表板。", @@ -13693,7 +13706,7 @@ "label-format": "格式", "label-set-timezone": "设置时区", "label-time-field": "时间字段", - "tooltip-format": "", + "tooltip-format": "指定为 <2>Moment.js 格式字符串的字段的输出格式。", "tooltip-timezone-manually": "手动设置日期的时区" }, "format-time-transformer-editor": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 061e2cb2f50..dec7715e1fd 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -2075,7 +2075,7 @@ "warning-1": "刪除此通知政策將永久移除該政策。", "warning-2": "您確定要刪除這個政策嗎?" }, - "filter-description": "", + "filter-description": "使用以逗號分隔的比對條件清單來篩選通知政策,例如:<1>severity=critical, region=EMEA", "generated-policies": "自動產生的政策", "matchers": "匹配條件", "metadata": { @@ -2796,7 +2796,7 @@ "template-data-docs": { "alert-template-data-table": { "alert-template-data": "警報範本資料", - "only-in-alert": "僅在警報內容中才可用(例如在警報迴圈中)" + "only-in-alert": "" }, "available-context-notification": "可用於通知內容。", "notification-template-data": "通知範本資料" @@ -3233,7 +3233,7 @@ "auth-settings": "驗證設定" }, "auth-drawer-unconneced": { - "subtitle": "" + "subtitle": "設定驗證設定。請在我們的<2>文件中閱讀更多資訊。" }, "auth-drawer-unconnected": { "advanced-auth": "進階驗證", @@ -3267,7 +3267,7 @@ "allowed-organizations-description": "以逗號或空格分隔的組織清單。使用者應至少是\n一個組織的成員才能登入。", "allowed-organizations-label": "允許的組織", "allowed-organizations-placeholder": "輸入組織 (my-team、myteam...),然後按 Enter 新增", - "api-url-description": "", + "api-url-description": "OAuth2 提供者的使用者資訊端點。此端點傳回的資訊必須與 <2>OpenID UserInfo 相容。", "api-url-required": "如果設定,則此欄位必須為有效的網址。", "auth-style-description": "此設定決定了如何將「{{ clientIDLabel }}」和「{{ clientSecretLabel }}」傳送給 Oauth2 提供者。預設為 AutoDetect。", "auth-style-label": "驗證樣式", @@ -3412,7 +3412,7 @@ } }, "auth-config-auth-config-page-unconnected": { - "subtitle": "" + "subtitle": "管理您的驗證設定並設定單一登入。請在我們的<2>文件中閱讀更多資訊。" }, "bar-chart": { "warn": { @@ -4824,7 +4824,7 @@ "add-visualization-body": "選取資料來源,然後使用圖表、統計資料及表格查詢並將資料可視化,或建立清單、標記及其他小工具。", "add-visualization-button": "新增可視化", "add-visualization-header": "透過新增可視化來啟動您的新儀表板", - "import-a-dashboard-body": "", + "import-a-dashboard-body": "從檔案或 <2>grafana.com 匯入儀表板。", "import-a-dashboard-header": "匯入儀表板", "import-dashboard-button": "匯入儀表板", "show-less-dashboards": "", @@ -6599,7 +6599,7 @@ }, "data-source-testing-status-page": { "error-more-details-link": "點選<2>此處,了解更多關於此錯誤的資訊。", - "success-more-details-links": "" + "success-more-details-links": "接下來,您可以透過<2>建立儀表板或在<5>瀏覽檢視中查詢資料,開始將資料視覺化。" }, "data-sources": { "datasource-add-button": { @@ -6695,7 +6695,7 @@ "test": "測試" }, "cloud-info-box": { - "body-alert": "", + "body-alert": "或者,跳過這個步驟,並透過<6>永久免費的 Grafana Cloud 方案,從 Grafana Labs 取得完全受控、可擴展及託管的資料來源 {{mainDS}}(和 {{extraDS}})。", "title-alert": "在下方設定您的 {{mainDS}} 資料來源" }, "dashboards-table": { @@ -7678,7 +7678,7 @@ "math": { "available-math-functions": "可用的數學函數", "run-math-operations": "", - "tooltip-footer": "", + "tooltip-footer": "請參閱我們關於<2>數學表達式的其他文件。", "tooltip-title": "數學運算子", "tooltip-trigger": "表達式" }, @@ -7704,11 +7704,24 @@ "label-upsample": "向上取樣", "tooltip-s-m-h": "10 秒、1 分鐘、30 分鐘、1 小時" }, + "schema-inspector-panel": { + "aria-label-close-schema-inspector": "" + }, "sql-expr": { "button-run-query": "執行查詢", "modal-title": "SQL 編輯器", "tooltip-experimental": "SQL 運算式 LLM 整合為實驗性功能。如有任何問題,請向 Grafana 團隊回報。" }, + "sql-schema": { + "close-schema-inspector": "", + "error-title": "", + "inspect-button": "", + "loading": "", + "no-data-title": "", + "no-fields-desc": "", + "no-fields-title": "", + "query-error-title": "" + }, "threshold": { "label-input": "輸入" } @@ -10881,10 +10894,10 @@ "placeholder-optional": "(選填)", "role": "角色", "submit": "提交", - "tooltip": "" + "tooltip": "您現在可以選擇「無基本角色」選項,並根據您的自訂需求新增權限。您可以在<1>我們的文件中找到更多資訊。" }, "user-invite-page": { - "sub-title": "", + "sub-title": "傳送邀請或將現有 Grafana 使用者新增至組織。<1>{{orgName}}", "text": { "invite-user": "邀請使用者" } @@ -12328,7 +12341,7 @@ }, "menu": { "clear-button": "全部清除", - "tooltip": "" + "tooltip": "您現在可以選擇「無基本角色」選項,並根據您的自訂需求新增權限。您可以在<1>我們的文件中找到更多資訊。" }, "menu-aria-label": "角色選擇器選單", "menu-group-option-aria-label": "角色選擇器選項", @@ -12653,7 +12666,7 @@ "info-text": "建立指向此儀表板或面板的直接連結,並使用以下選項自訂。", "link-url": "連結網址", "render-alert": "未安裝圖片轉譯器外掛程式", - "render-instructions": "", + "render-instructions": "若要轉譯圖片,您必須安裝 <2>Grafana 圖片轉譯器外掛程式。請聯絡您的 Grafana 管理員以安裝外掛程式。", "rendered-image": "直接連結轉譯的圖片", "save-alert": "儀表板未儲存", "save-dashboard": "若要呈現面板圖片,您必須先儲存儀表板。", @@ -13693,7 +13706,7 @@ "label-format": "格式", "label-set-timezone": "設定時區", "label-time-field": "時間欄位", - "tooltip-format": "", + "tooltip-format": "指定為 <2>Moment.js 格式字串的欄位輸出格式。", "tooltip-timezone-manually": "手動設定日期的時區" }, "format-time-transformer-editor": { diff --git a/public/openapi3.json b/public/openapi3.json index 706b89abb32..20b97b3f14d 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -6207,6 +6207,10 @@ "is_paused": { "type": "boolean" }, + "message": { + "description": "Field is only populated when listing alert rule versions.", + "type": "string" + }, "metadata": { "$ref": "#/components/schemas/AlertRuleMetadata" }, @@ -7759,6 +7763,28 @@ "TLSConfig": { "$ref": "#/components/schemas/TLSConfig" }, + "audience": { + "description": "Audience optionally specifies the intended audience of the\nrequest. If empty, the value of TokenURL is used as the\nintended audience. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, + "claims": { + "additionalProperties": {}, + "description": "Claims is a map of claims to be added to the JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "object" + }, + "client_certificate_key": { + "$ref": "#/components/schemas/Secret" + }, + "client_certificate_key_file": { + "type": "string" + }, + "client_certificate_key_id": { + "type": "string" + }, + "client_certificate_key_ref": { + "description": "ClientCertificateKeyRef is the name of the secret within the secret manager to use as the client\nsecret.", + "type": "string" + }, "client_id": { "type": "string" }, @@ -7778,6 +7804,14 @@ }, "type": "object" }, + "grant_type": { + "description": "GrantType is the OAuth2 grant type to use. It can be one of\n\"client_credentials\" or \"urn:ietf:params:oauth:grant-type:jwt-bearer\" (RFC 7523).\nDefault value is \"client_credentials\"", + "type": "string" + }, + "iss": { + "description": "Iss is the OAuth client identifier used when communicating with\nthe configured OAuth provider. Default value is client_id. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".", + "type": "string" + }, "no_proxy": { "description": "NoProxy contains addresses that should not use a proxy.", "type": "string" @@ -7798,6 +7832,10 @@ }, "type": "array" }, + "signature_algorithm": { + "description": "SignatureAlgorithm is the RSA algorithm used to sign JWT token. Only used if\nGrantType is set to \"urn:ietf:params:oauth:grant-type:jwt-bearer\".\nDefault value is RS256 and valid values RS256, RS384, RS512", + "type": "string" + }, "token_url": { "type": "string" } @@ -11571,6 +11609,9 @@ "teamId": { "format": "int64", "type": "integer" + }, + "uid": { + "type": "string" } }, "type": "object" @@ -17019,7 +17060,7 @@ }, { "in": "header", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "schema": { "type": "string" } @@ -17378,7 +17419,7 @@ }, { "in": "header", - "name": "x-grafana-alerting-notification-receiver", + "name": "x-grafana-alerting-notification-settings", "schema": { "type": "string" } @@ -17567,56 +17608,6 @@ ] } }, - "/dashboards/calculate-diff": { - "post": { - "operationId": "calculateDashboardDiff", - "requestBody": { - "content": { - "application/json": { - "schema": { - "properties": { - "base": { - "$ref": "#/components/schemas/CalculateDiffTarget" - }, - "diffType": { - "description": "The type of diff to return\nDescription:\n`basic`\n`json`", - "enum": [ - "basic", - "json" - ], - "type": "string" - }, - "new": { - "$ref": "#/components/schemas/CalculateDiffTarget" - } - }, - "type": "object" - } - } - }, - "required": true, - "x-originalParamName": "Body" - }, - "responses": { - "200": { - "$ref": "#/components/responses/calculateDashboardDiffResponse" - }, - "401": { - "$ref": "#/components/responses/unauthorisedError" - }, - "403": { - "$ref": "#/components/responses/forbiddenError" - }, - "500": { - "$ref": "#/components/responses/internalServerError" - } - }, - "summary": "Perform diff on two dashboards.", - "tags": [ - "dashboards" - ] - } - }, "/dashboards/db": { "post": { "description": "Creates a new dashboard or updates an existing dashboard.\nNote: This endpoint is not intended for creating folders, use `POST /api/folders` for that.", diff --git a/public/test/setupTests.ts b/public/test/setupTests.ts index d6c97a0d1c8..2c21f3a8a1e 100644 --- a/public/test/setupTests.ts +++ b/public/test/setupTests.ts @@ -34,6 +34,18 @@ i18next.use(initReactI18next).init({ // the factory uses import.meta.url so we can't use it in CommonJS modules. jest.mock('app/features/dashboard-scene/saving/createDetectChangesWorker.ts'); +// Mock useLoadAppPlugins to prevent async state updates in tests +jest.mock('app/features/plugins/extensions/useLoadAppPlugins', () => ({ + useLoadAppPlugins: jest.fn().mockReturnValue({ isLoading: false }), +})); + +// Mock usePluginComponents to return empty components for all tests by default +// Tests that need to test plugin components can override this mock +jest.mock('app/features/plugins/extensions/usePluginComponents', () => ({ + ...jest.requireActual('app/features/plugins/extensions/usePluginComponents'), + usePluginComponents: jest.fn().mockReturnValue({ components: [], isLoading: false }), +})); + // our tests are heavy in CI due to parallelisation and monaco and kusto // so we increase the default timeout to 2secs to avoid flakiness configure({ asyncUtilTimeout: 2000 }); diff --git a/yarn.lock b/yarn.lock index 778173eec14..131a46fe1fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2454,7 +2454,7 @@ __metadata: "@types/react-dom": "npm:18.3.5" fast-deep-equal: "npm:^3.1.3" i18next: "npm:^25.0.0" - i18next-cli: "npm:1.11.12" + i18next-cli: "npm:^1.24.22" immer: "npm:10.2.0" jest: "npm:29.7.0" lodash: "npm:4.17.21" @@ -2739,7 +2739,7 @@ __metadata: "@types/lodash": "npm:4.17.20" "@types/node": "npm:24.10.1" "@types/react": "npm:18.3.18" - i18next-cli: "npm:1.11.12" + i18next-cli: "npm:^1.24.22" lodash: "npm:4.17.21" react: "npm:18.3.1" rxjs: "npm:7.8.2" @@ -3019,7 +3019,7 @@ __metadata: "@types/tinycolor2": "npm:^1" fishery: "npm:^2.3.1" i18next: "npm:^25.5.2" - i18next-cli: "npm:1.11.12" + i18next-cli: "npm:^1.24.22" lodash: "npm:^4.17.21" react: "npm:18.3.1" react-dom: "npm:18.3.1" @@ -3521,7 +3521,7 @@ __metadata: "@types/uuid": "npm:10.0.0" debounce-promise: "npm:3.1.2" esbuild: "npm:0.25.8" - i18next-cli: "npm:1.11.12" + i18next-cli: "npm:^1.24.22" jest: "npm:29.7.0" jest-environment-jsdom: "npm:29.7.0" lodash: "npm:4.17.21" @@ -3709,7 +3709,7 @@ __metadata: "@types/react-virtualized-auto-sizer": "npm:1.0.8" "@types/systemjs": "npm:6.15.3" "@types/uuid": "npm:10.0.0" - i18next-cli: "npm:1.11.12" + i18next-cli: "npm:^1.24.22" immutable: "npm:5.1.4" jest: "npm:^29.6.4" lodash: "npm:4.17.21" @@ -4010,257 +4010,250 @@ __metadata: languageName: node linkType: hard -"@inquirer/ansi@npm:^1.0.0, @inquirer/ansi@npm:^1.0.1": - version: 1.0.1 - resolution: "@inquirer/ansi@npm:1.0.1" - checksum: 10/0dda65720736f3e730715f3778e0e90f039ebd1382c277495a4d1cdbd2b2863095aa7291cd8ea7d3c0618bdee04a375db6e10a7bae5fb904df0b632a1c7774f9 +"@inquirer/ansi@npm:^1.0.1, @inquirer/ansi@npm:^1.0.2": + version: 1.0.2 + resolution: "@inquirer/ansi@npm:1.0.2" + checksum: 10/d1496e573a63ee6752bcf3fc93375cdabc55b0d60f0588fe7902282c710b223252ad318ff600ee904e48555634663b53fda517f5b29ce9fbda90bfae18592fbc languageName: node linkType: hard -"@inquirer/checkbox@npm:^4.3.0": - version: 4.3.0 - resolution: "@inquirer/checkbox@npm:4.3.0" +"@inquirer/checkbox@npm:^4.3.2": + version: 4.3.2 + resolution: "@inquirer/checkbox@npm:4.3.2" dependencies: - "@inquirer/ansi": "npm:^1.0.1" - "@inquirer/core": "npm:^10.3.0" - "@inquirer/figures": "npm:^1.0.14" - "@inquirer/type": "npm:^3.0.9" - yoctocolors-cjs: "npm:^2.1.2" + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/aa7ddf0816bc1718afdc38d7ed1e16207bbf944164a5a3ac0d87072a5a1b51cf3417617c72128a78d66b2e40a45f4a5658bdfc4bb546e92a208e30c8006674e6 + checksum: 10/4ac5dd2679981e23f066c51c605cb1c63ccda9ea6e1ad895e675eb26702aaf6cf961bf5ca3acd832efba5edcf9883b6742002c801673d2b35c123a7fa7db7b23 languageName: node linkType: hard -"@inquirer/confirm@npm:^5.0.0, @inquirer/confirm@npm:^5.1.19": - version: 5.1.19 - resolution: "@inquirer/confirm@npm:5.1.19" +"@inquirer/confirm@npm:^5.0.0, @inquirer/confirm@npm:^5.1.21": + version: 5.1.21 + resolution: "@inquirer/confirm@npm:5.1.21" dependencies: - "@inquirer/core": "npm:^10.3.0" - "@inquirer/type": "npm:^3.0.9" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/d65e0addf80c146d71a74057d77048bd78a4a80d74a9e0d774b759ff1adf38a33cde6c06a6d6ef802bb61ef9158770315dec3931f89b3624c0e63c595c0473c1 + checksum: 10/a107aa0073965ea510affb9e5b55baf40333503d600970c458c07770cd4e0eee01efc4caba66f0409b0fadc9550d127329622efb543cffcabff3ad0e7f865372 languageName: node linkType: hard -"@inquirer/core@npm:^10.2.2, @inquirer/core@npm:^10.3.0": - version: 10.3.0 - resolution: "@inquirer/core@npm:10.3.0" +"@inquirer/core@npm:^10.3.0, @inquirer/core@npm:^10.3.2": + version: 10.3.2 + resolution: "@inquirer/core@npm:10.3.2" dependencies: - "@inquirer/ansi": "npm:^1.0.1" - "@inquirer/figures": "npm:^1.0.14" - "@inquirer/type": "npm:^3.0.9" + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" cli-width: "npm:^4.1.0" mute-stream: "npm:^2.0.0" signal-exit: "npm:^4.1.0" wrap-ansi: "npm:^6.2.0" - yoctocolors-cjs: "npm:^2.1.2" + yoctocolors-cjs: "npm:^2.1.3" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/41392e38dc3253b3dbebda9cca344ca5cfd72c05e1c5b8460cb00e0ce7fa665a1970a2c4af89dcb951d75fedfdf775cdd7ac3be0748dbe3dec47db5d899d6963 + checksum: 10/eb434bdf0ae7d904367003c772bcd80cbf679f79c087c99a4949fd7288e9a2f713ec3ea63381b9a001f52389ab56a77fcd88d64d81a03b1195193410ce8971c2 languageName: node linkType: hard -"@inquirer/editor@npm:^4.2.21": - version: 4.2.21 - resolution: "@inquirer/editor@npm:4.2.21" +"@inquirer/editor@npm:^4.2.23": + version: 4.2.23 + resolution: "@inquirer/editor@npm:4.2.23" dependencies: - "@inquirer/core": "npm:^10.3.0" - "@inquirer/external-editor": "npm:^1.0.2" - "@inquirer/type": "npm:^3.0.9" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/external-editor": "npm:^1.0.3" + "@inquirer/type": "npm:^3.0.10" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/cf2d4237dc86abb2143ba6e99a4a60368ee992ba56b0072457e4dd472e7ac99ebffcbd0895bf36d5f694f3e327ff0dbb70265952b03ec8a0dbf4abd1db306991 + checksum: 10/f91b9aadba6ea28a0f4ea5f075af421e076262aebbd737e1b9779f086fa9d559d064e9942a581544645d1dcf56d6b685e8063fe46677880fbca73f6de4e4e7c5 languageName: node linkType: hard -"@inquirer/expand@npm:^4.0.21": - version: 4.0.21 - resolution: "@inquirer/expand@npm:4.0.21" +"@inquirer/expand@npm:^4.0.23": + version: 4.0.23 + resolution: "@inquirer/expand@npm:4.0.23" dependencies: - "@inquirer/core": "npm:^10.3.0" - "@inquirer/type": "npm:^3.0.9" - yoctocolors-cjs: "npm:^2.1.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/eb1900c443895377c03652c3e2b6ca29c572fe6ee2682e264572957b9b4a596d3d55c9ea271934846fb05d5cc5195cca0dffde1386e41358ac5c308698320e93 + checksum: 10/73ad1d6376e5efe2a452c33494d6d16ee2670c638ae470a795fdff4acb59a8e032e38e141f87b603b6e96320977519b375dac6471d86d5e3087a9c1db40e3111 languageName: node linkType: hard -"@inquirer/external-editor@npm:^1.0.2": - version: 1.0.2 - resolution: "@inquirer/external-editor@npm:1.0.2" +"@inquirer/external-editor@npm:^1.0.3": + version: 1.0.3 + resolution: "@inquirer/external-editor@npm:1.0.3" dependencies: - chardet: "npm:^2.1.0" + chardet: "npm:^2.1.1" iconv-lite: "npm:^0.7.0" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/d0c5c73249b8153f4cf872c4fba01c57a7653142a4cad496f17ed03ef3769330a4b3c519b68d70af69d4bb33003d2599b66b2242be85411c0b027ff383619666 + checksum: 10/c95d7237a885b32031715089f92820525731d4d3c2bd7afdb826307dc296cc2b39e7a644b0bb265441963348cca42e7785feb29c3aaf18fd2b63131769bf6587 languageName: node linkType: hard -"@inquirer/figures@npm:^1.0.14": - version: 1.0.14 - resolution: "@inquirer/figures@npm:1.0.14" - checksum: 10/39df361eb607cea5a020d457e25f9c6aee3a1de8975c6295a4b3bfe86ba7e7f7bfbefa6a52b145b1790f2690e5c8f10eb822e5bc764aff7ba00a6cd24eec5a25 +"@inquirer/figures@npm:^1.0.15, @inquirer/figures@npm:^1.0.3": + version: 1.0.15 + resolution: "@inquirer/figures@npm:1.0.15" + checksum: 10/3f858807f361ca29f41ec1076bbece4098cc140d86a06159d42c6e3f6e4d9bec9e10871ccfcbbaa367d6a8462b01dff89f2b1b157d9de6e8726bec85533f525c languageName: node linkType: hard -"@inquirer/figures@npm:^1.0.3": - version: 1.0.11 - resolution: "@inquirer/figures@npm:1.0.11" - checksum: 10/357ddd2e83718bc3c9189d518b93fd69099af9c860354df9a5ac0ec024cb5df1228ae4608d2de7625624d2adcd047db813f29426a610eaae7b9e449f8c753c6b - languageName: node - linkType: hard - -"@inquirer/input@npm:^4.2.5": - version: 4.2.5 - resolution: "@inquirer/input@npm:4.2.5" +"@inquirer/input@npm:^4.3.1": + version: 4.3.1 + resolution: "@inquirer/input@npm:4.3.1" dependencies: - "@inquirer/core": "npm:^10.3.0" - "@inquirer/type": "npm:^3.0.9" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/150dee6d6094663439a9941e5df5f1dadb819cd5ac68a7b5be0e0aceb4b74db0c8fa9a5fa0d21879928df6a53c84c9214b699f3fddbb6c617dd9c253a9d95155 + checksum: 10/713aaa4c94263299fbd7adfd65378f788cac1b5047f2b7e1ea349ca669db6c7c91b69ab6e2f6660cdbc28c7f7888c5c77ab4433bd149931597e43976d1ba5f34 languageName: node linkType: hard -"@inquirer/number@npm:^3.0.21": - version: 3.0.21 - resolution: "@inquirer/number@npm:3.0.21" +"@inquirer/number@npm:^3.0.23": + version: 3.0.23 + resolution: "@inquirer/number@npm:3.0.23" dependencies: - "@inquirer/core": "npm:^10.3.0" - "@inquirer/type": "npm:^3.0.9" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/7b254cb3947c78a83593d82347bf93929b0af714cffa20f9f4503ec338004b3c0d6813945e3e3d7062b5d50006412b181ed833ac88c7da4df538df8bb15775d0 + checksum: 10/50694807b71746e15ed69d100aae3c8014d83c90aa660e8a179fe0db1046f26d727947542f64e24cc8b969a61659cb89fe36208cc2b59c1816382b598e686dd2 languageName: node linkType: hard -"@inquirer/password@npm:^4.0.21": - version: 4.0.21 - resolution: "@inquirer/password@npm:4.0.21" +"@inquirer/password@npm:^4.0.23": + version: 4.0.23 + resolution: "@inquirer/password@npm:4.0.23" dependencies: - "@inquirer/ansi": "npm:^1.0.1" - "@inquirer/core": "npm:^10.3.0" - "@inquirer/type": "npm:^3.0.9" + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/07fb1527ea2d44a81b79d9263f59713e66977e21fbf44efedb6bf08d27d617900ef481c49c91b0a749caf1d282f2b5e19fe6b7474acc98db3edd174eb5d45416 + checksum: 10/97364970b01c85946a4a50ad876c53ef0c1857a9144e24fad65e5dfa4b4e5dd42564fbcdfa2b49bb049a25d127efbe0882cb18afcdd47b166ebd01c6c4b5e825 languageName: node linkType: hard -"@inquirer/prompts@npm:^7.8.6": - version: 7.9.0 - resolution: "@inquirer/prompts@npm:7.9.0" +"@inquirer/prompts@npm:^7.9.0": + version: 7.10.1 + resolution: "@inquirer/prompts@npm:7.10.1" dependencies: - "@inquirer/checkbox": "npm:^4.3.0" - "@inquirer/confirm": "npm:^5.1.19" - "@inquirer/editor": "npm:^4.2.21" - "@inquirer/expand": "npm:^4.0.21" - "@inquirer/input": "npm:^4.2.5" - "@inquirer/number": "npm:^3.0.21" - "@inquirer/password": "npm:^4.0.21" - "@inquirer/rawlist": "npm:^4.1.9" - "@inquirer/search": "npm:^3.2.0" - "@inquirer/select": "npm:^4.4.0" + "@inquirer/checkbox": "npm:^4.3.2" + "@inquirer/confirm": "npm:^5.1.21" + "@inquirer/editor": "npm:^4.2.23" + "@inquirer/expand": "npm:^4.0.23" + "@inquirer/input": "npm:^4.3.1" + "@inquirer/number": "npm:^3.0.23" + "@inquirer/password": "npm:^4.0.23" + "@inquirer/rawlist": "npm:^4.1.11" + "@inquirer/search": "npm:^3.2.2" + "@inquirer/select": "npm:^4.4.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/ca58889261018be39a58e93c03c542d47d25dd7f5bb93e98bde7e3bf63eb70f8d5243d32a3fd53797bb474471682490513d99ec5179323862e5a15cdeb35f4b5 + checksum: 10/b3e3386edd255e4e91c7908050674f8a2e69b043883c00feec2f87d697be37bc6e8cd4a360e7e3233a9825ae7ea044a2ac63d5700926d27f9959013d8566f890 languageName: node linkType: hard -"@inquirer/rawlist@npm:^4.1.9": - version: 4.1.9 - resolution: "@inquirer/rawlist@npm:4.1.9" +"@inquirer/rawlist@npm:^4.1.11": + version: 4.1.11 + resolution: "@inquirer/rawlist@npm:4.1.11" dependencies: - "@inquirer/core": "npm:^10.3.0" - "@inquirer/type": "npm:^3.0.9" - yoctocolors-cjs: "npm:^2.1.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/ec23e087bfa9497b36d51b53e8da18c837e4a0c5c091bce7d1a6b52d9664035d7e22c3753993dd3c7c9ebfd5e9b71f1738873f2c25422668733ddb28d74bf26b + checksum: 10/0d8f6484cfc20749190e95eecfb2d034bafb3644ec4907b84b1673646f5dd71730e38e35565ea98dfd240d8851e3cff653edafcc4e0af617054b127b407e3229 languageName: node linkType: hard -"@inquirer/search@npm:^3.2.0": - version: 3.2.0 - resolution: "@inquirer/search@npm:3.2.0" +"@inquirer/search@npm:^3.2.2": + version: 3.2.2 + resolution: "@inquirer/search@npm:3.2.2" dependencies: - "@inquirer/core": "npm:^10.3.0" - "@inquirer/figures": "npm:^1.0.14" - "@inquirer/type": "npm:^3.0.9" - yoctocolors-cjs: "npm:^2.1.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/b01a53d6f72090f45cda39b75dd2e613e2d7fa0f454c0781f846e543b833eed4da831fec8d9e5325ebd4383684a69a74c5a38520b7ee0734b1090f71cf5dd372 + checksum: 10/abaed2df7763633ff4414b58d1c87233b69ed3cd2ac77629f0d54b72b8b585dc4806c7a2a8261daba58af5b0a2147e586d079fdc82060b6bcf56b75d3d03f3a7 languageName: node linkType: hard -"@inquirer/select@npm:^4.4.0": - version: 4.4.0 - resolution: "@inquirer/select@npm:4.4.0" +"@inquirer/select@npm:^4.4.2": + version: 4.4.2 + resolution: "@inquirer/select@npm:4.4.2" dependencies: - "@inquirer/ansi": "npm:^1.0.1" - "@inquirer/core": "npm:^10.3.0" - "@inquirer/figures": "npm:^1.0.14" - "@inquirer/type": "npm:^3.0.9" - yoctocolors-cjs: "npm:^2.1.2" + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/97f1f167d640c668ea5f137fca76fd9096215546f0890732ab8ed0c58ac3d9f01db5d1877e877e74ef6877a139ab8970ee683c6c3f2c08f2e11522f5e0bfd61e + checksum: 10/795ec0ac77d575f20bd6a12fb1c040093e62217ac0c80194829a8d3c3d1e09f70ad738e9a9dd6095cc8358fff4e13882209c09bdf8eb0864a86dcabef5b0a6a6 languageName: node linkType: hard -"@inquirer/type@npm:^3.0.8, @inquirer/type@npm:^3.0.9": - version: 3.0.9 - resolution: "@inquirer/type@npm:3.0.9" +"@inquirer/type@npm:^3.0.10, @inquirer/type@npm:^3.0.9": + version: 3.0.10 + resolution: "@inquirer/type@npm:3.0.10" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/960ba4737405f70bac17e7cdc4696c60064b06c8dd13a4b3d0783763ba1714bdadbd598b88d537ab9415b7d5d61e011ac042cfbd1438b2a35298e2868724b853 + checksum: 10/57d113a9db7abc73326491e29bedc88ef362e53779f9f58a1b61225e0be068ce0c54e33cd65f4a13ca46131676fb72c3ef488463c4c9af0aa89680684c55d74c languageName: node linkType: hard @@ -7114,7 +7107,7 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:2.10.1": +"@reduxjs/toolkit@npm:2.10.1, @reduxjs/toolkit@npm:^2.9.0": version: 2.10.1 resolution: "@reduxjs/toolkit@npm:2.10.1" dependencies: @@ -7136,28 +7129,6 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^2.9.0": - version: 2.9.2 - resolution: "@reduxjs/toolkit@npm:2.9.2" - dependencies: - "@standard-schema/spec": "npm:^1.0.0" - "@standard-schema/utils": "npm:^0.3.0" - immer: "npm:^10.0.3" - redux: "npm:^5.0.1" - redux-thunk: "npm:^3.1.0" - reselect: "npm:^5.1.0" - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - checksum: 10/7a8fa57086bc44dafe2f04428d6557edad46ee6eb46b2fe80468ebf934a441a7d4d750b50a6d41bc360d9ab61baadb1dddb5a8f556cfac68977f56e832f509a2 - languageName: node - linkType: hard - "@remix-run/router@npm:1.19.1": version: 1.19.1 resolution: "@remix-run/router@npm:1.19.1" @@ -8896,13 +8867,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-darwin-arm64@npm:1.13.19" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@swc/core-darwin-arm64@npm:1.13.3": version: 1.13.3 resolution: "@swc/core-darwin-arm64@npm:1.13.3" @@ -8910,10 +8874,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-darwin-x64@npm:1.13.19" - conditions: os=darwin & cpu=x64 +"@swc/core-darwin-arm64@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-darwin-arm64@npm:1.15.2" + conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -8924,10 +8888,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.13.19" - conditions: os=linux & cpu=arm +"@swc/core-darwin-x64@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-darwin-x64@npm:1.15.2" + conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -8938,10 +8902,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-linux-arm64-gnu@npm:1.13.19" - conditions: os=linux & cpu=arm64 & libc=glibc +"@swc/core-linux-arm-gnueabihf@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.2" + conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -8952,10 +8916,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-linux-arm64-musl@npm:1.13.19" - conditions: os=linux & cpu=arm64 & libc=musl +"@swc/core-linux-arm64-gnu@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.2" + conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard @@ -8966,10 +8930,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-linux-x64-gnu@npm:1.13.19" - conditions: os=linux & cpu=x64 & libc=glibc +"@swc/core-linux-arm64-musl@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.2" + conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard @@ -8980,10 +8944,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-linux-x64-musl@npm:1.13.19" - conditions: os=linux & cpu=x64 & libc=musl +"@swc/core-linux-x64-gnu@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.2" + conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard @@ -8994,10 +8958,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-win32-arm64-msvc@npm:1.13.19" - conditions: os=win32 & cpu=arm64 +"@swc/core-linux-x64-musl@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-linux-x64-musl@npm:1.15.2" + conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard @@ -9008,10 +8972,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-win32-ia32-msvc@npm:1.13.19" - conditions: os=win32 & cpu=ia32 +"@swc/core-win32-arm64-msvc@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.2" + conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -9022,10 +8986,10 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core-win32-x64-msvc@npm:1.13.19" - conditions: os=win32 & cpu=x64 +"@swc/core-win32-ia32-msvc@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.2" + conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -9036,53 +9000,14 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:1.13.19": - version: 1.13.19 - resolution: "@swc/core@npm:1.13.19" - dependencies: - "@swc/core-darwin-arm64": "npm:1.13.19" - "@swc/core-darwin-x64": "npm:1.13.19" - "@swc/core-linux-arm-gnueabihf": "npm:1.13.19" - "@swc/core-linux-arm64-gnu": "npm:1.13.19" - "@swc/core-linux-arm64-musl": "npm:1.13.19" - "@swc/core-linux-x64-gnu": "npm:1.13.19" - "@swc/core-linux-x64-musl": "npm:1.13.19" - "@swc/core-win32-arm64-msvc": "npm:1.13.19" - "@swc/core-win32-ia32-msvc": "npm:1.13.19" - "@swc/core-win32-x64-msvc": "npm:1.13.19" - "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.25" - peerDependencies: - "@swc/helpers": ">=0.5.17" - dependenciesMeta: - "@swc/core-darwin-arm64": - optional: true - "@swc/core-darwin-x64": - optional: true - "@swc/core-linux-arm-gnueabihf": - optional: true - "@swc/core-linux-arm64-gnu": - optional: true - "@swc/core-linux-arm64-musl": - optional: true - "@swc/core-linux-x64-gnu": - optional: true - "@swc/core-linux-x64-musl": - optional: true - "@swc/core-win32-arm64-msvc": - optional: true - "@swc/core-win32-ia32-msvc": - optional: true - "@swc/core-win32-x64-msvc": - optional: true - peerDependenciesMeta: - "@swc/helpers": - optional: true - checksum: 10/07d0f72d8c82070a1ec2b439964780f0ba7aec61b17fa6518b1c372373bbe208ea9ce68f70f606d42df71a33149e4f704eabe07c6197e15d133e375546915f57 +"@swc/core-win32-x64-msvc@npm:1.15.2": + version: 1.15.2 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.2" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:1.13.3, @swc/core@npm:^1.10.8, @swc/core@npm:^1.5.22": +"@swc/core@npm:1.13.3": version: 1.13.3 resolution: "@swc/core@npm:1.13.3" dependencies: @@ -9128,6 +9053,52 @@ __metadata: languageName: node linkType: hard +"@swc/core@npm:1.15.2, @swc/core@npm:^1.10.8, @swc/core@npm:^1.5.22": + version: 1.15.2 + resolution: "@swc/core@npm:1.15.2" + dependencies: + "@swc/core-darwin-arm64": "npm:1.15.2" + "@swc/core-darwin-x64": "npm:1.15.2" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.2" + "@swc/core-linux-arm64-gnu": "npm:1.15.2" + "@swc/core-linux-arm64-musl": "npm:1.15.2" + "@swc/core-linux-x64-gnu": "npm:1.15.2" + "@swc/core-linux-x64-musl": "npm:1.15.2" + "@swc/core-win32-arm64-msvc": "npm:1.15.2" + "@swc/core-win32-ia32-msvc": "npm:1.15.2" + "@swc/core-win32-x64-msvc": "npm:1.15.2" + "@swc/counter": "npm:^0.1.3" + "@swc/types": "npm:^0.1.25" + peerDependencies: + "@swc/helpers": ">=0.5.17" + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm-gnueabihf": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10/55c112b2d3ff098efd1d2d941ec8fc8ddbcd81c53ede67bcaa28d176a711fb39a250a63a627104f0c2f6f764e41f78eeb72edb5300c6d98cbbaf97cce84927a9 + languageName: node + linkType: hard + "@swc/counter@npm:^0.1.3": version: 0.1.3 resolution: "@swc/counter@npm:0.1.3" @@ -12316,18 +12287,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1, axios@npm:^1.10.0, axios@npm:^1.6.1, axios@npm:^1.8.3": - version: 1.12.2 - resolution: "axios@npm:1.12.2" - dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.4" - proxy-from-env: "npm:^1.1.0" - checksum: 10/886a79770594eaad76493fecf90344b567bd956240609b5dcd09bd0afe8d3e6f1ad6d3257a93a483b6192b409d4b673d9515a34619e3e3ed1b2c0ec2a83b20ba - languageName: node - linkType: hard - -"axios@npm:^1.12.2": +"axios@npm:^1, axios@npm:^1.10.0, axios@npm:^1.12.2, axios@npm:^1.6.1, axios@npm:^1.8.3": version: 1.13.2 resolution: "axios@npm:1.13.2" dependencies: @@ -13251,7 +13211,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:5.6.2, chalk@npm:^5.6.2": +"chalk@npm:5.6.2, chalk@npm:^5.2.0, chalk@npm:^5.3.0, chalk@npm:^5.4.1, chalk@npm:^5.6.2": version: 5.6.2 resolution: "chalk@npm:5.6.2" checksum: 10/1b2f48f6fba1370670d5610f9cd54c391d6ede28f4b7062dd38244ea5768777af72e5be6b74fb6c6d54cb84c4a2dff3f3afa9b7cb5948f7f022cfd3d087989e0 @@ -13289,13 +13249,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.2.0, chalk@npm:^5.3.0, chalk@npm:^5.4.1": - version: 5.4.1 - resolution: "chalk@npm:5.4.1" - checksum: 10/29df3ffcdf25656fed6e95962e2ef86d14dfe03cd50e7074b06bad9ffbbf6089adbb40f75c00744d843685c8d008adaf3aed31476780312553caf07fa86e5bc7 - languageName: node - linkType: hard - "chance@npm:^1.1.13": version: 1.1.13 resolution: "chance@npm:1.1.13" @@ -13365,10 +13318,10 @@ __metadata: languageName: node linkType: hard -"chardet@npm:^2.1.0": - version: 2.1.0 - resolution: "chardet@npm:2.1.0" - checksum: 10/8085fd8e5b1234fafacb279b4dab84dc127f512f953441daf09fc71ade70106af0dff28e86bfda00bab0de61fb475fa9003c87f82cbad3da02a4f299bfd427da +"chardet@npm:^2.1.1": + version: 2.1.1 + resolution: "chardet@npm:2.1.1" + checksum: 10/d56913b65e45c5c86f331988e2ef6264c131bfeadaae098ee719bf6610546c77740e37221ffec802dde56b5e4466613a4c754786f4da6b5f6c5477243454d324 languageName: node linkType: hard @@ -13433,7 +13386,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:4.0.3, chokidar@npm:^4.0.0, chokidar@npm:^4.0.1": +"chokidar@npm:4.0.3, chokidar@npm:^4.0.0, chokidar@npm:^4.0.1, chokidar@npm:^4.0.3": version: 4.0.3 resolution: "chokidar@npm:4.0.3" dependencies: @@ -13899,10 +13852,10 @@ __metadata: languageName: node linkType: hard -"commander@npm:14.0.1": - version: 14.0.1 - resolution: "commander@npm:14.0.1" - checksum: 10/783115e9403caeca29c0fcbd4e0358f70c67760e4e4933f3453fcdd5ddba2ec44173c8da5213d7ce5e404f51c7e71203a42c548164dbe27b668b32a8981577f1 +"commander@npm:14.0.2, commander@npm:~14.0.0": + version: 14.0.2 + resolution: "commander@npm:14.0.2" + checksum: 10/2d202db5e5f9bb770112a3c1579b893d17ac6f6d932183077308bdd96d0f87f0bbe6a68b5b9ed2cf3b2514be6bb7de637480703c0e2db9741ee1b383237deb26 languageName: node linkType: hard @@ -13976,13 +13929,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:~14.0.0": - version: 14.0.0 - resolution: "commander@npm:14.0.0" - checksum: 10/c05418bfc35a3e8b5c67bd9f75f5b773f386f9b85f83e70e7c926047f270929cb06cf13cd68f387dd6e7e23c6157de8171b28ba606abd3e6256028f1f789becf - languageName: node - linkType: hard - "comment-parser@npm:1.4.1": version: 1.4.1 resolution: "comment-parser@npm:1.4.1" @@ -16230,16 +16176,7 @@ __metadata: languageName: node linkType: hard -"enquirer@npm:^2.3.6, enquirer@npm:~2.3.6": - version: 2.3.6 - resolution: "enquirer@npm:2.3.6" - dependencies: - ansi-colors: "npm:^4.1.1" - checksum: 10/751d14f037eb7683997e696fb8d5fe2675e0b0cde91182c128cf598acf3f5bd9005f35f7c2a9109e291140af496ebec237b6dac86067d59a9b44f3688107f426 - languageName: node - linkType: hard - -"enquirer@npm:^2.4.1": +"enquirer@npm:^2.3.6, enquirer@npm:^2.4.1": version: 2.4.1 resolution: "enquirer@npm:2.4.1" dependencies: @@ -16249,6 +16186,15 @@ __metadata: languageName: node linkType: hard +"enquirer@npm:~2.3.6": + version: 2.3.6 + resolution: "enquirer@npm:2.3.6" + dependencies: + ansi-colors: "npm:^4.1.1" + checksum: 10/751d14f037eb7683997e696fb8d5fe2675e0b0cde91182c128cf598acf3f5bd9005f35f7c2a9109e291140af496ebec237b6dac86067d59a9b44f3688107f426 + languageName: node + linkType: hard + "ensure-posix-path@npm:^1.1.0": version: 1.1.1 resolution: "ensure-posix-path@npm:1.1.1" @@ -18658,19 +18604,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:*, glob@npm:11.0.3": - version: 11.0.3 - resolution: "glob@npm:11.0.3" +"glob@npm:*, glob@npm:12.0.0": + version: 12.0.0 + resolution: "glob@npm:12.0.0" dependencies: foreground-child: "npm:^3.3.1" jackspeak: "npm:^4.1.1" - minimatch: "npm:^10.0.3" + minimatch: "npm:^10.1.1" minipass: "npm:^7.1.2" package-json-from-dist: "npm:^1.0.0" path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10/2ae536c1360c0266b523b2bfa6aadc10144a8b7e08869b088e37ac3c27cd30774f82e4bfb291cde796776e878f9e13200c7ff44010eb7054e00f46f649397893 + checksum: 10/6e21b3f1f1fa635836d45e54bbe50704884cc3e310e0cc011cfb5429db65a030e12936d99b07e66236370efe45dc8c8b26fa5334dbf555d6f8709e0315c77c30 languageName: node linkType: hard @@ -19137,7 +19083,7 @@ __metadata: http-server: "npm:14.1.1" i18next: "npm:^25.0.0" i18next-browser-languagedetector: "npm:^8.0.0" - i18next-cli: "npm:1.11.12" + i18next-cli: "npm:^1.24.22" i18next-pseudo: "npm:^2.2.1" immer: "npm:10.2.0" immutable: "npm:5.1.4" @@ -20000,25 +19946,26 @@ __metadata: languageName: node linkType: hard -"i18next-cli@npm:1.11.12": - version: 1.11.12 - resolution: "i18next-cli@npm:1.11.12" +"i18next-cli@npm:^1.24.22": + version: 1.24.22 + resolution: "i18next-cli@npm:1.24.22" dependencies: - "@swc/core": "npm:1.13.19" + "@swc/core": "npm:1.15.2" chalk: "npm:5.6.2" chokidar: "npm:4.0.3" - commander: "npm:14.0.1" + commander: "npm:14.0.2" execa: "npm:9.6.0" - glob: "npm:11.0.3" - i18next-resources-for-ts: "npm:1.7.4" - inquirer: "npm:12.9.6" + glob: "npm:12.0.0" + i18next-resources-for-ts: "npm:1.8.0" + inquirer: "npm:12.10.0" jiti: "npm:2.6.1" jsonc-parser: "npm:3.3.1" + minimatch: "npm:10.1.1" ora: "npm:9.0.0" - swc-walk: "npm:1.0.0" + swc-walk: "npm:1.0.1" bin: i18next-cli: dist/esm/cli.js - checksum: 10/b714cddb1fc5a611541a83f54320a84f4df5bd8308cd5b0c7abf6222d6d8e8cdac1e41fd9ecbb4d2930cf994e98d60235201f22e4f15baff0defb1759b2f3541 + checksum: 10/09dc8c551155c0a59fe793ba685d75a4a7810eb047113df3b43234ec5abdd26ffacb9e4f3c78b637f906518053b4aa8ea3e8af9e52fa0bab43e6379a81d44693 languageName: node linkType: hard @@ -20058,15 +20005,16 @@ __metadata: languageName: node linkType: hard -"i18next-resources-for-ts@npm:1.7.4": - version: 1.7.4 - resolution: "i18next-resources-for-ts@npm:1.7.4" +"i18next-resources-for-ts@npm:1.8.0": + version: 1.8.0 + resolution: "i18next-resources-for-ts@npm:1.8.0" dependencies: - "@babel/runtime": "npm:^7.27.0" - yaml: "npm:^2.7.1" + "@babel/runtime": "npm:^7.28.4" + chokidar: "npm:^4.0.3" + yaml: "npm:^2.8.1" bin: i18next-resources-for-ts: bin/i18next-resources-for-ts.js - checksum: 10/02ce71939dc6f38672d3b33281367ad7f2c52623371fd88fc1d0033eac5a6385ef45fed49c7a49e5bac0f9a8a97cdc95ec3ee1c68f26cc75dcbdcb5b525ca6cc + checksum: 10/217a6f4d978c36b83ed5e3bc9c550f0845e4e06378257be4a88f34a4f246278c677384d5481ae7936e8dd762dd68f0a86e136364f15ae2d887cef3704d4e3958 languageName: node linkType: hard @@ -20207,7 +20155,7 @@ __metadata: languageName: node linkType: hard -"immer@npm:10.2.0, immer@npm:^10.0.3, immer@npm:^10.2.0": +"immer@npm:10.2.0, immer@npm:^10.2.0": version: 10.2.0 resolution: "immer@npm:10.2.0" checksum: 10/d73e218c8f8ffbb39f9290dfafa478b94af73403dcf26b5672eef35233bb30f09ffe231f8a78a6c9cb442968510edd89e851776ec90a5ddfa82cee6db6b35137 @@ -20385,14 +20333,14 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:12.9.6": - version: 12.9.6 - resolution: "inquirer@npm:12.9.6" +"inquirer@npm:12.10.0": + version: 12.10.0 + resolution: "inquirer@npm:12.10.0" dependencies: - "@inquirer/ansi": "npm:^1.0.0" - "@inquirer/core": "npm:^10.2.2" - "@inquirer/prompts": "npm:^7.8.6" - "@inquirer/type": "npm:^3.0.8" + "@inquirer/ansi": "npm:^1.0.1" + "@inquirer/core": "npm:^10.3.0" + "@inquirer/prompts": "npm:^7.9.0" + "@inquirer/type": "npm:^3.0.9" mute-stream: "npm:^2.0.0" run-async: "npm:^4.0.5" rxjs: "npm:^7.8.2" @@ -20401,7 +20349,7 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true - checksum: 10/bcac231b3eba055aa16dbdb60ba6d7bfe66109be654bfb19f92095f703af07fc01528f716e86ec62f7bf7bd17b4e21ad4bb32b677cf42075dee04568afe9686b + checksum: 10/ed24f3fa202cd8b8fa05c56c8f42c202be7c24d621d968937ea2e298e1d06192979568706587fd243144e1ed55d248ba5c6a27b2c7ee6926864faad532d8572f languageName: node linkType: hard @@ -23807,6 +23755,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:10.1.1, minimatch@npm:^10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10/110f38921ea527022e90f7a5f43721838ac740d0a0c26881c03b57c261354fb9a0430e40b2c56dfcea2ef3c773768f27210d1106f1f2be19cde3eea93f26f45e + languageName: node + linkType: hard + "minimatch@npm:3.0.5": version: 3.0.5 resolution: "minimatch@npm:3.0.5" @@ -23825,15 +23782,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.3, minimatch@npm:^10.1.1": - version: 10.1.1 - resolution: "minimatch@npm:10.1.1" - dependencies: - "@isaacs/brace-expansion": "npm:^5.0.0" - checksum: 10/110f38921ea527022e90f7a5f43721838ac740d0a0c26881c03b57c261354fb9a0430e40b2c56dfcea2ef3c773768f27210d1106f1f2be19cde3eea93f26f45e - languageName: node - linkType: hard - "minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -26498,15 +26446,6 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.54.1": - version: 1.54.1 - resolution: "playwright-core@npm:1.54.1" - bin: - playwright-core: cli.js - checksum: 10/c0acfb7ecb48e9fb6c22f71298b966244bd4f8659093a798cc7f9a509deee22109560e44f2d507124e7718779640e0b4cb05a05c068b034405418d8740ec31ff - languageName: node - linkType: hard - "playwright-core@npm:1.56.1, playwright-core@npm:>=1.2.0": version: 1.56.1 resolution: "playwright-core@npm:1.56.1" @@ -26516,7 +26455,7 @@ __metadata: languageName: node linkType: hard -"playwright@npm:1.56.1": +"playwright@npm:1.56.1, playwright@npm:^1.14.0": version: 1.56.1 resolution: "playwright@npm:1.56.1" dependencies: @@ -26531,21 +26470,6 @@ __metadata: languageName: node linkType: hard -"playwright@npm:^1.14.0": - version: 1.54.1 - resolution: "playwright@npm:1.54.1" - dependencies: - fsevents: "npm:2.3.2" - playwright-core: "npm:1.54.1" - dependenciesMeta: - fsevents: - optional: true - bin: - playwright: cli.js - checksum: 10/ebad2924b589a4cfedf48288e67b4afc81047222e607321c9452a37610025e6e249bdfeff010c1c70cf9a9d35c0b9e5cb7934a58986f67928dd469d9fde1a035 - languageName: node - linkType: hard - "plop@npm:^4.0.1": version: 4.0.1 resolution: "plop@npm:4.0.1" @@ -31588,12 +31512,12 @@ __metadata: languageName: node linkType: hard -"swc-walk@npm:1.0.0": - version: 1.0.0 - resolution: "swc-walk@npm:1.0.0" +"swc-walk@npm:1.0.1": + version: 1.0.1 + resolution: "swc-walk@npm:1.0.1" dependencies: acorn-walk: "npm:^8.3.4" - checksum: 10/f6dc27eef421033956acbe12061e5f43a18b28747fd914734589f25d4144b0ef7de53bb1bf24fcf3a2355f305a3eb0593d0eaa006a43316113c79672b0c070bd + checksum: 10/7e1c727a094e8de7692280f4cfa16e9ee42b8f2b9a32b8c39fdfe47401edc392187c3cbb1e20a2e840d214096791019653392c0d6b469caf7b471fab1d268a64 languageName: node linkType: hard @@ -34276,7 +34200,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0, yaml@npm:^2.3.4, yaml@npm:^2.6.0, yaml@npm:^2.7.1": +"yaml@npm:^2.0.0, yaml@npm:^2.3.4, yaml@npm:^2.6.0, yaml@npm:^2.8.1": version: 2.8.1 resolution: "yaml@npm:2.8.1" bin: @@ -34403,10 +34327,10 @@ __metadata: languageName: node linkType: hard -"yoctocolors-cjs@npm:^2.1.2": - version: 2.1.2 - resolution: "yoctocolors-cjs@npm:2.1.2" - checksum: 10/d731e3ba776a0ee19021d909787942933a6c2eafb2bbe85541f0c59aa5c7d475ce86fcb860d5803105e32244c3dd5ba875b87c4c6bf2d6f297da416aa54e556f +"yoctocolors-cjs@npm:^2.1.2, yoctocolors-cjs@npm:^2.1.3": + version: 2.1.3 + resolution: "yoctocolors-cjs@npm:2.1.3" + checksum: 10/b2144b38807673a4254dae06fe1a212729550609e606289c305e45c585b36fab1dbba44fe6cde90db9b28be465ec63f4c2a50867aeec6672f6bc36b6c9a361a0 languageName: node linkType: hard