diff --git a/.betterer.results b/.betterer.results index 3d25ebbffdc..03734dce277 100644 --- a/.betterer.results +++ b/.betterer.results @@ -44,6 +44,12 @@ exports[`no enzyme tests`] = { "packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js:1734982398": [ [14, 26, 13, "RegExp match", "2409514259"] ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js:2025513694": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js:3813002651": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js:700147304": [ [16, 19, 13, "RegExp match", "2409514259"] ], @@ -80,7 +86,7 @@ exports[`no enzyme tests`] = { "public/app/features/alerting/TestRuleResult.test.tsx:2358420489": [ [0, 19, 13, "RegExp match", "2409514259"] ], - "public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:3716733755": [ + "public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:2357087833": [ [0, 35, 13, "RegExp match", "2409514259"] ], "public/app/features/dashboard/dashgrid/DashboardGrid.test.tsx:2723773538": [ @@ -134,7 +140,7 @@ exports[`better eslint`] = { [12, 17, 10, "Do not use any type assertions.", "1579919174"], [12, 24, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "e2e/dashboards-suite/dashboard-templating.spec.ts:1854610528": [ + "e2e/dashboards-suite/dashboard-templating.spec.ts:3846708644": [ [12, 17, 3, "Unexpected any. Specify a different type.", "193409811"] ], "e2e/dashboards-suite/textbox-variables.spec.ts:2500589821": [ @@ -316,7 +322,7 @@ exports[`better eslint`] = { [91, 13, 28, "Do not use any type assertions.", "2187961375"], [93, 13, 24, "Do not use any type assertions.", "1019297299"] ], - "packages/grafana-data/src/datetime/rangeutil.ts:2234129242": [ + "packages/grafana-data/src/datetime/rangeutil.ts:2182917545": [ [88, 18, 3, "Unexpected any. Specify a different type.", "193409811"], [89, 27, 3, "Unexpected any. Specify a different type.", "193409811"], [92, 33, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -625,6 +631,9 @@ exports[`better eslint`] = { [315, 14, 3, "Unexpected any. Specify a different type.", "193409811"], [315, 22, 3, "Unexpected any. Specify a different type.", "193409811"] ], + "packages/grafana-data/src/transformations/transformers/joinDataFrames.ts:1554585514": [ + [340, 13, 3, "Unexpected any. Specify a different type.", "193409811"] + ], "packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts:1352052528": [ [393, 13, 3, "Unexpected any. Specify a different type.", "193409811"] ], @@ -678,9 +687,9 @@ exports[`better eslint`] = { "packages/grafana-data/src/types/app.ts:2148970488": [ [75, 48, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-data/src/types/config.ts:21897200": [ - [185, 11, 3, "Unexpected any. Specify a different type.", "193409811"], - [199, 16, 3, "Unexpected any. Specify a different type.", "193409811"] + "packages/grafana-data/src/types/config.ts:1574035243": [ + [171, 11, 3, "Unexpected any. Specify a different type.", "193409811"], + [184, 16, 3, "Unexpected any. Specify a different type.", "193409811"] ], "packages/grafana-data/src/types/dashboard.ts:1867834569": [ [12, 39, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -813,10 +822,10 @@ exports[`better eslint`] = { [192, 52, 3, "Unexpected any. Specify a different type.", "193409811"], [192, 72, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-data/src/types/plugin.ts:2556367579": [ - [174, 22, 3, "Unexpected any. Specify a different type.", "193409811"], - [191, 29, 3, "Unexpected any. Specify a different type.", "193409811"], - [197, 16, 7, "Do not use any type assertions.", "3399135973"] + "packages/grafana-data/src/types/plugin.ts:695757440": [ + [173, 22, 3, "Unexpected any. Specify a different type.", "193409811"], + [190, 29, 3, "Unexpected any. Specify a different type.", "193409811"], + [196, 16, 7, "Do not use any type assertions.", "3399135973"] ], "packages/grafana-data/src/types/query.ts:4277928644": [ [100, 14, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -1205,19 +1214,19 @@ exports[`better eslint`] = { [31, 49, 3, "Unexpected any. Specify a different type.", "193409811"], [31, 73, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-runtime/src/config.ts:2639113063": [ + "packages/grafana-runtime/src/config.ts:2061755826": [ [66, 11, 3, "Unexpected any. Specify a different type.", "193409811"], [75, 29, 17, "Do not use any type assertions.", "4278379396"], - [107, 16, 3, "Unexpected any. Specify a different type.", "193409811"], - [192, 18, 13, "Do not use any type assertions.", "538937261"], - [192, 28, 3, "Unexpected any. Specify a different type.", "193409811"] + [98, 16, 3, "Unexpected any. Specify a different type.", "193409811"], + [183, 18, 13, "Do not use any type assertions.", "538937261"], + [183, 28, 3, "Unexpected any. Specify a different type.", "193409811"] ], "packages/grafana-runtime/src/services/AngularLoader.ts:3455177907": [ [45, 14, 3, "Unexpected any. Specify a different type.", "193409811"], [61, 13, 3, "Unexpected any. Specify a different type.", "193409811"], [61, 30, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-runtime/src/services/EchoSrv.ts:347668574": [ + "packages/grafana-runtime/src/services/EchoSrv.ts:2163840677": [ [51, 51, 3, "Unexpected any. Specify a different type.", "193409811"], [51, 60, 3, "Unexpected any. Specify a different type.", "193409811"], [63, 53, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -1709,7 +1718,7 @@ exports[`better eslint`] = { "packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx:3875310700": [ [22, 53, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-ui/src/components/Graph/utils.ts:3619837376": [ + "packages/grafana-ui/src/components/Graph/utils.ts:3717868215": [ [104, 56, 3, "Unexpected any. Specify a different type.", "193409811"] ], "packages/grafana-ui/src/components/GraphNG/GraphNG.tsx:94669281": [ @@ -1740,7 +1749,7 @@ exports[`better eslint`] = { [2, 101, 3, "Unexpected any. Specify a different type.", "193409811"], [15, 28, 17, "Do not use any type assertions.", "2923536692"] ], - "packages/grafana-ui/src/components/GraphNG/utils.ts:460323924": [ + "packages/grafana-ui/src/components/GraphNG/utils.ts:3480871323": [ [23, 18, 35, "Do not use any type assertions.", "180126096"], [23, 48, 3, "Unexpected any. Specify a different type.", "193409811"] ], @@ -2083,7 +2092,7 @@ exports[`better eslint`] = { "packages/grafana-ui/src/components/Switch/Switch.story.tsx:3337756944": [ [11, 15, 258, "Do not use any type assertions.", "1871090638"] ], - "packages/grafana-ui/src/components/Table/BarGaugeCell.tsx:1481110243": [ + "packages/grafana-ui/src/components/Table/BarGaugeCell.tsx:2469721145": [ [46, 13, 17, "Do not use any type assertions.", "3640560184"] ], "packages/grafana-ui/src/components/Table/CellActions.tsx:3266589396": [ @@ -2091,8 +2100,8 @@ exports[`better eslint`] = { [22, 10, 16, "Do not use any type assertions.", "737436615"], [23, 22, 25, "Do not use any type assertions.", "1478996352"] ], - "packages/grafana-ui/src/components/Table/DefaultCell.tsx:3618905143": [ - [16, 34, 40, "Do not use any type assertions.", "91092480"] + "packages/grafana-ui/src/components/Table/DefaultCell.tsx:1844923424": [ + [14, 34, 40, "Do not use any type assertions.", "91092480"] ], "packages/grafana-ui/src/components/Table/Filter.tsx:1102571026": [ [13, 10, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -2112,8 +2121,8 @@ exports[`better eslint`] = { [53, 38, 13, "Do not use any type assertions.", "947160887"], [53, 48, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-ui/src/components/Table/JSONViewCell.tsx:2181157412": [ - [12, 34, 40, "Do not use any type assertions.", "91092480"] + "packages/grafana-ui/src/components/Table/JSONViewCell.tsx:4022130242": [ + [11, 34, 40, "Do not use any type assertions.", "91092480"] ], "packages/grafana-ui/src/components/Table/Table.story.tsx:4272567078": [ [23, 15, 364, "Do not use any type assertions.", "4121186137"] @@ -2210,11 +2219,9 @@ exports[`better eslint`] = { [56, 22, 24, "Do not use any type assertions.", "4121028738"], [56, 43, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-ui/src/components/TimeSeries/utils.ts:1896295166": [ - [34, 29, 3, "Unexpected any. Specify a different type.", "193409811"], - [136, 19, 146, "Do not use any type assertions.", "3723122882"], - [308, 12, 73, "Do not use any type assertions.", "587500217"], - [311, 17, 3, "Unexpected any. Specify a different type.", "193409811"] + "packages/grafana-ui/src/components/TimeSeries/utils.ts:3806893813": [ + [33, 29, 3, "Unexpected any. Specify a different type.", "193409811"], + [135, 19, 146, "Do not use any type assertions.", "3723122882"] ], "packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.story.tsx:1133352709": [ [10, 15, 780, "Do not use any type assertions.", "3533394757"] @@ -2293,7 +2300,7 @@ exports[`better eslint`] = { [59, 12, 198, "Do not use any type assertions.", "2203342350"], [65, 26, 341, "Do not use any type assertions.", "3792878509"] ], - "packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts:3245297597": [ + "packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts:895023121": [ [23, 20, 3, "Unexpected any. Specify a different type.", "193409811"], [170, 39, 3, "Unexpected any. Specify a different type.", "193409811"], [174, 5, 13, "Do not use any type assertions.", "268797995"], @@ -2322,13 +2329,13 @@ exports[`better eslint`] = { "packages/grafana-ui/src/components/uPlot/types.ts:1918909040": [ [16, 26, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-ui/src/components/uPlot/utils.ts:1283022966": [ + "packages/grafana-ui/src/components/uPlot/utils.ts:20167453": [ [70, 13, 20, "Do not use any type assertions.", "1571376654"], [131, 20, 34, "Do not use any type assertions.", "1148863494"], [134, 11, 35, "Do not use any type assertions.", "2376372234"], [136, 11, 45, "Do not use any type assertions.", "1627514634"], [164, 13, 41, "Do not use any type assertions.", "1747339107"], - [264, 14, 32, "Do not use any type assertions.", "319021204"] + [261, 14, 32, "Do not use any type assertions.", "319021204"] ], "packages/grafana-ui/src/options/builder/axis.tsx:1832382450": [ [91, 14, 30, "Do not use any type assertions.", "3478399522"], @@ -2479,6 +2486,9 @@ exports[`better eslint`] = { "packages/grafana-ui/src/utils/colors.ts:349987544": [ [110, 25, 3, "Unexpected any. Specify a different type.", "193409811"] ], + "packages/grafana-ui/src/utils/dataLinks.ts:2916992126": [ + [16, 12, 71, "Do not use any type assertions.", "1504235577"] + ], "packages/grafana-ui/src/utils/debug.ts:2900904491": [ [6, 56, 3, "Unexpected any. Specify a different type.", "193409811"] ], @@ -2506,9 +2516,9 @@ exports[`better eslint`] = { [38, 72, 3, "Unexpected any. Specify a different type.", "193409811"], [38, 85, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/grafana-ui/src/utils/table.ts:2798596296": [ - [7, 52, 3, "Unexpected any. Specify a different type.", "193409811"], - [8, 29, 3, "Unexpected any. Specify a different type.", "193409811"] + "packages/grafana-ui/src/utils/table.ts:3564715667": [ + [8, 52, 3, "Unexpected any. Specify a different type.", "193409811"], + [9, 22, 3, "Unexpected any. Specify a different type.", "193409811"] ], "packages/grafana-ui/src/utils/useAsyncDependency.ts:4089662838": [ [3, 60, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -2549,7 +2559,7 @@ exports[`better eslint`] = { "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx:1954428690": [ [97, 35, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx:3587041872": [ + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx:1594863792": [ [77, 35, 3, "Unexpected any. Specify a different type.", "193409811"] ], "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx:3379895243": [ @@ -3176,7 +3186,7 @@ exports[`better eslint`] = { [73, 37, 3, "Unexpected any. Specify a different type.", "193409811"], [89, 39, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/core/components/ColorScale/ColorScale.tsx:12349772": [ + "public/app/core/components/ColorScale/ColorScale.tsx:661627953": [ [38, 24, 43, "Do not use any type assertions.", "1727107527"], [38, 25, 19, "Do not use any type assertions.", "1979047218"], [38, 41, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -3226,9 +3236,9 @@ exports[`better eslint`] = { [120, 15, 41, "Do not use any type assertions.", "1904113237"], [137, 15, 41, "Do not use any type assertions.", "1904113237"] ], - "public/app/core/components/NavBar/NavBarMenu.tsx:2778426907": [ - [245, 54, 26, "Do not use any type assertions.", "1541386804"], - [463, 23, 21, "Do not use any type assertions.", "1121249950"] + "public/app/core/components/NavBar/NavBarMenu.tsx:2095049018": [ + [241, 54, 26, "Do not use any type assertions.", "1541386804"], + [445, 23, 21, "Do not use any type assertions.", "1121249950"] ], "public/app/core/components/OptionsUI/DashboardPicker.tsx:3166151962": [ [11, 85, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -3398,14 +3408,14 @@ exports[`better eslint`] = { [8, 12, 3, "Unexpected any. Specify a different type.", "193409811"], [11, 53, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/core/components/TagFilter/TagFilter.tsx:2477083789": [ + "public/app/core/components/TagFilter/TagFilter.tsx:3569632478": [ [35, 30, 3, "Unexpected any. Specify a different type.", "193409811"], - [109, 32, 3, "Unexpected any. Specify a different type.", "193409811"], - [132, 24, 3, "Unexpected any. Specify a different type.", "193409811"], - [133, 24, 3, "Unexpected any. Specify a different type.", "193409811"], - [144, 27, 3, "Unexpected any. Specify a different type.", "193409811"], - [147, 30, 3, "Unexpected any. Specify a different type.", "193409811"], - [156, 44, 3, "Unexpected any. Specify a different type.", "193409811"] + [103, 32, 3, "Unexpected any. Specify a different type.", "193409811"], + [121, 24, 3, "Unexpected any. Specify a different type.", "193409811"], + [122, 24, 3, "Unexpected any. Specify a different type.", "193409811"], + [133, 27, 3, "Unexpected any. Specify a different type.", "193409811"], + [136, 30, 3, "Unexpected any. Specify a different type.", "193409811"], + [145, 44, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/core/components/TagFilter/TagOption.tsx:108355790": [ [10, 50, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -3440,43 +3450,6 @@ exports[`better eslint`] = { "public/app/core/history/richHistoryLocalStorageUtils.ts:708464706": [ [59, 99, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/core/logsModel.test.ts:342304192": [ - [34, 32, 132, "Do not use any type assertions.", "3240026221"], - [41, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [46, 32, 250, "Do not use any type assertions.", "1056401323"], - [59, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [77, 32, 255, "Do not use any type assertions.", "3910157567"], - [90, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [108, 32, 255, "Do not use any type assertions.", "3910157567"], - [121, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [131, 32, 146, "Do not use any type assertions.", "4042945873"], - [141, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [159, 32, 245, "Do not use any type assertions.", "1219087628"], - [172, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [181, 32, 399, "Do not use any type assertions.", "951740236"], - [202, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [214, 22, 3, "Unexpected any. Specify a different type.", "193409811"], - [223, 32, 17, "Do not use any type assertions.", "3976469210"], - [1085, 32, 96, "Do not use any type assertions.", "2446954905"], - [1088, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [1096, 32, 96, "Do not use any type assertions.", "2446954905"], - [1099, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [1146, 14, 152, "Do not use any type assertions.", "1058688753"], - [1146, 14, 117, "Do not use any type assertions.", "2244099206"] - ], - "public/app/core/logsModel.ts:1903332011": [ - [113, 23, 3, "Unexpected any. Specify a different type.", "193409811"], - [114, 20, 3, "Unexpected any. Specify a different type.", "193409811"], - [461, 21, 196, "Do not use any type assertions.", "842163811"], - [462, 24, 3, "Unexpected any. Specify a different type.", "193409811"], - [462, 35, 3, "Unexpected any. Specify a different type.", "193409811"], - [466, 31, 3, "Unexpected any. Specify a different type.", "193409811"], - [610, 27, 17, "Do not use any type assertions.", "1830153619"], - [611, 30, 17, "Do not use any type assertions.", "1830153619"], - [664, 31, 3, "Unexpected any. Specify a different type.", "193409811"], - [664, 36, 3, "Unexpected any. Specify a different type.", "193409811"], - [685, 26, 68, "Do not use any type assertions.", "1746242431"] - ], "public/app/core/mod_defs.d.ts:305963360": [ [1, 12, 3, "Unexpected any. Specify a different type.", "193409811"], [6, 12, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -3538,7 +3511,7 @@ exports[`better eslint`] = { [11, 6, 169, "Do not use any type assertions.", "2248693214"], [17, 11, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/core/navigation/types.ts:2395305220": [ + "public/app/core/navigation/types.ts:2017971154": [ [10, 38, 3, "Unexpected any. Specify a different type.", "193409811"], [14, 35, 3, "Unexpected any. Specify a different type.", "193409811"] ], @@ -3617,24 +3590,24 @@ exports[`better eslint`] = { [0, 34, 3, "Unexpected any. Specify a different type.", "193409811"], [0, 39, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/core/services/backend_srv.ts:360059123": [ - [83, 20, 3, "Unexpected any. Specify a different type.", "193409811"], - [153, 63, 3, "Unexpected any. Specify a different type.", "193409811"], - [231, 40, 3, "Unexpected any. Specify a different type.", "193409811"], - [285, 38, 20, "Do not use any type assertions.", "443888156"], - [285, 55, 3, "Unexpected any. Specify a different type.", "193409811"], - [356, 103, 3, "Unexpected any. Specify a different type.", "193409811"], - [392, 49, 3, "Unexpected any. Specify a different type.", "193409811"], - [396, 16, 3, "Unexpected any. Specify a different type.", "193409811"], - [396, 43, 3, "Unexpected any. Specify a different type.", "193409811"], - [400, 35, 3, "Unexpected any. Specify a different type.", "193409811"], + "public/app/core/services/backend_srv.ts:3750224237": [ + [79, 20, 3, "Unexpected any. Specify a different type.", "193409811"], + [149, 63, 3, "Unexpected any. Specify a different type.", "193409811"], + [227, 40, 3, "Unexpected any. Specify a different type.", "193409811"], + [281, 38, 20, "Do not use any type assertions.", "443888156"], + [281, 55, 3, "Unexpected any. Specify a different type.", "193409811"], + [352, 103, 3, "Unexpected any. Specify a different type.", "193409811"], + [388, 49, 3, "Unexpected any. Specify a different type.", "193409811"], + [392, 16, 3, "Unexpected any. Specify a different type.", "193409811"], + [392, 43, 3, "Unexpected any. Specify a different type.", "193409811"], + [396, 35, 3, "Unexpected any. Specify a different type.", "193409811"], + [400, 33, 3, "Unexpected any. Specify a different type.", "193409811"], [404, 33, 3, "Unexpected any. Specify a different type.", "193409811"], - [408, 33, 3, "Unexpected any. Specify a different type.", "193409811"], + [408, 31, 3, "Unexpected any. Specify a different type.", "193409811"], [412, 31, 3, "Unexpected any. Specify a different type.", "193409811"], - [416, 31, 3, "Unexpected any. Specify a different type.", "193409811"], - [427, 16, 3, "Unexpected any. Specify a different type.", "193409811"] + [423, 16, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/core/services/context_srv.ts:698805616": [ + "public/app/core/services/context_srv.ts:1157446284": [ [59, 10, 3, "Unexpected any. Specify a different type.", "193409811"], [60, 11, 3, "Unexpected any. Specify a different type.", "193409811"], [62, 14, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -4176,17 +4149,29 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/AmRoutes.tsx:746213791": [ [54, 20, 124, "Do not use any type assertions.", "1859971542"] ], - "public/app/features/alerting/unified/PanelAlertTabContent.test.tsx:2750168837": [ + "public/app/features/alerting/unified/PanelAlertTabContent.test.tsx:2885585711": [ [148, 18, 168, "Do not use any type assertions.", "3635996173"], + [161, 14, 241, "Do not use any type assertions.", "583862789"], + [161, 14, 227, "Do not use any type assertions.", "578877230"], + [174, 5, 3, "Unexpected any. Specify a different type.", "193409811"], [186, 56, 87, "Do not use any type assertions.", "3591252716"], [188, 23, 3, "Unexpected any. Specify a different type.", "193409811"], [188, 28, 3, "Unexpected any. Specify a different type.", "193409811"], [189, 53, 90, "Do not use any type assertions.", "3360422103"], [190, 6, 3, "Unexpected any. Specify a different type.", "193409811"], [191, 6, 3, "Unexpected any. Specify a different type.", "193409811"], - [257, 5, 46, "Do not use any type assertions.", "3849301614"], - [257, 5, 25, "Do not use any type assertions.", "2832141036"], - [257, 27, 3, "Unexpected any. Specify a different type.", "193409811"] + [198, 43, 93, "Do not use any type assertions.", "491507647"], + [198, 43, 79, "Do not use any type assertions.", "2198135124"], + [202, 9, 3, "Unexpected any. Specify a different type.", "193409811"], + [224, 43, 122, "Do not use any type assertions.", "1400972852"], + [224, 43, 108, "Do not use any type assertions.", "2513195871"], + [229, 9, 3, "Unexpected any. Specify a different type.", "193409811"], + [251, 5, 46, "Do not use any type assertions.", "3849301614"], + [251, 5, 25, "Do not use any type assertions.", "2832141036"], + [251, 27, 3, "Unexpected any. Specify a different type.", "193409811"], + [253, 43, 70, "Do not use any type assertions.", "4292164046"], + [253, 43, 56, "Do not use any type assertions.", "2628506213"], + [256, 9, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/alerting/unified/Receivers.test.tsx:3742116867": [ [143, 34, 29, "Do not use any type assertions.", "2249960884"] @@ -4201,11 +4186,14 @@ exports[`better eslint`] = { [127, 14, 16, "Do not use any type assertions.", "1747412709"], [128, 18, 24, "Do not use any type assertions.", "3254438164"] ], - "public/app/features/alerting/unified/RuleEditor.test.tsx:2974108144": [ - [233, 42, 149, "Do not use any type assertions.", "2017267554"], - [454, 42, 32, "Do not use any type assertions.", "2259972478"], - [507, 65, 3, "Unexpected any. Specify a different type.", "193409811"], - [642, 42, 20, "Do not use any type assertions.", "1750699644"] + "public/app/features/alerting/unified/RuleEditor.test.tsx:3247797279": [ + [231, 42, 149, "Do not use any type assertions.", "2017267554"], + [418, 23, 50, "Do not use any type assertions.", "772738025"], + [418, 23, 36, "Do not use any type assertions.", "4069427080"], + [420, 9, 3, "Unexpected any. Specify a different type.", "193409811"], + [452, 42, 32, "Do not use any type assertions.", "2259972478"], + [505, 65, 3, "Unexpected any. Specify a different type.", "193409811"], + [640, 42, 20, "Do not use any type assertions.", "1750699644"] ], "public/app/features/alerting/unified/RuleList.test.tsx:629893124": [ [125, 21, 16, "Do not use any type assertions.", "2939667099"], @@ -4383,8 +4371,8 @@ exports[`better eslint`] = { [43, 13, 12, "Do not use any type assertions.", "96869412"], [64, 7, 25, "Do not use any type assertions.", "4119455957"] ], - "public/app/features/alerting/unified/hooks/useIsRuleEditable.test.tsx:2319947585": [ - [163, 64, 29, "Do not use any type assertions.", "2249960884"] + "public/app/features/alerting/unified/hooks/useIsRuleEditable.test.tsx:4024141754": [ + [150, 64, 29, "Do not use any type assertions.", "2249960884"] ], "public/app/features/alerting/unified/mocks.ts:2719319751": [ [48, 14, 7, "Do not use any type assertions.", "3399135973"], @@ -4408,9 +4396,11 @@ exports[`better eslint`] = { [234, 6, 172, "Do not use any type assertions.", "1154786841"], [242, 6, 177, "Do not use any type assertions.", "3855113404"] ], - "public/app/features/alerting/unified/state/actions.ts:175963059": [ - [74, 23, 24, "Do not use any type assertions.", "3178482079"], - [254, 70, 24, "Do not use any type assertions.", "3178482079"] + "public/app/features/alerting/unified/state/actions.ts:2828725785": [ + [73, 23, 24, "Do not use any type assertions.", "3178482079"], + [253, 70, 24, "Do not use any type assertions.", "3178482079"], + [570, 60, 22, "Do not use any type assertions.", "3527197253"], + [570, 79, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/alerting/unified/types/receiver-form.ts:4067603235": [ [10, 27, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -4420,8 +4410,8 @@ exports[`better eslint`] = { [28, 4, 25, "Do not use any type assertions.", "2565039386"], [106, 22, 82, "Do not use any type assertions.", "713417051"] ], - "public/app/features/alerting/unified/utils/datasource.ts:3850518020": [ - [137, 5, 69, "Do not use any type assertions.", "2540015575"] + "public/app/features/alerting/unified/utils/datasource.ts:2520335938": [ + [136, 5, 69, "Do not use any type assertions.", "2540015575"] ], "public/app/features/alerting/unified/utils/misc.test.ts:962758579": [ [19, 29, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -4479,8 +4469,9 @@ exports[`better eslint`] = { "public/app/features/annotations/components/AnnotationResultMapper.tsx:1382385589": [ [131, 41, 15, "Do not use any type assertions.", "3610795007"] ], - "public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx:1738271764": [ - [28, 10, 11, "Do not use any type assertions.", "750480870"] + "public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx:46885179": [ + [28, 10, 11, "Do not use any type assertions.", "750480870"], + [78, 17, 16, "Do not use any type assertions.", "388222280"] ], "public/app/features/annotations/events_processing.ts:311785085": [ [2, 46, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -4560,14 +4551,16 @@ exports[`better eslint`] = { "public/app/features/canvas/runtime/root.tsx:2845174121": [ [24, 19, 36, "Do not use any type assertions.", "1413431594"] ], - "public/app/features/canvas/runtime/scene.tsx:4221384430": [ + "public/app/features/canvas/runtime/scene.tsx:2928385813": [ [155, 61, 25, "Do not use any type assertions.", "588553285"], [160, 42, 25, "Do not use any type assertions.", "588553285"] ], "public/app/features/comments/CommentView.tsx:3338885322": [ [31, 20, 32, "Do not use any type assertions.", "3501587761"] ], - "public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx:3529318871": [ + "public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx:1976108855": [ + [9, 15, 20, "Do not use any type assertions.", "2923490522"], + [10, 11, 16, "Do not use any type assertions.", "388222280"], [11, 14, 16, "Do not use any type assertions.", "2607709806"], [11, 27, 3, "Unexpected any. Specify a different type.", "193409811"] ], @@ -4580,7 +4573,7 @@ exports[`better eslint`] = { [90, 20, 3, "Unexpected any. Specify a different type.", "193409811"], [126, 18, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:3288513450": [ + "public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:2274633003": [ [22, 6, 59, "Do not use any type assertions.", "3685154675"], [22, 6, 49, "Do not use any type assertions.", "1184085652"], [25, 15, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -4605,28 +4598,30 @@ exports[`better eslint`] = { [318, 17, 3, "Unexpected any. Specify a different type.", "193409811"], [325, 20, 3, "Unexpected any. Specify a different type.", "193409811"], [334, 19, 3, "Unexpected any. Specify a different type.", "193409811"], - [364, 13, 18, "Do not use any type assertions.", "3420875325"], - [365, 9, 55, "Do not use any type assertions.", "1888720338"], - [365, 17, 10, "Do not use any type assertions.", "1501063862"], - [365, 24, 3, "Unexpected any. Specify a different type.", "193409811"], - [365, 61, 3, "Unexpected any. Specify a different type.", "193409811"] + [370, 13, 18, "Do not use any type assertions.", "3420875325"], + [371, 9, 55, "Do not use any type assertions.", "1888720338"], + [371, 17, 10, "Do not use any type assertions.", "1501063862"], + [371, 24, 3, "Unexpected any. Specify a different type.", "193409811"], + [371, 61, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts:565128344": [ - [18, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [47, 37, 3, "Unexpected any. Specify a different type.", "193409811"], - [65, 9, 3, "Unexpected any. Specify a different type.", "193409811"], - [86, 43, 3, "Unexpected any. Specify a different type.", "193409811"], - [93, 45, 3, "Unexpected any. Specify a different type.", "193409811"], - [100, 30, 3, "Unexpected any. Specify a different type.", "193409811"], - [102, 37, 17, "Do not use any type assertions.", "1733699692"], - [102, 51, 3, "Unexpected any. Specify a different type.", "193409811"], - [169, 41, 3, "Unexpected any. Specify a different type.", "193409811"], - [198, 29, 31, "Do not use any type assertions.", "1451241646"], - [198, 29, 13, "Do not use any type assertions.", "2146830713"], - [217, 32, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts:568589687": [ + [17, 9, 3, "Unexpected any. Specify a different type.", "193409811"], + [44, 9, 3, "Unexpected any. Specify a different type.", "193409811"], + [65, 43, 3, "Unexpected any. Specify a different type.", "193409811"], + [72, 45, 3, "Unexpected any. Specify a different type.", "193409811"], + [79, 30, 3, "Unexpected any. Specify a different type.", "193409811"], + [82, 25, 17, "Do not use any type assertions.", "1733699692"], + [82, 39, 3, "Unexpected any. Specify a different type.", "193409811"], + [83, 20, 33, "Do not use any type assertions.", "1729190460"], + [83, 21, 17, "Do not use any type assertions.", "1733699692"], + [83, 35, 3, "Unexpected any. Specify a different type.", "193409811"], + [148, 41, 3, "Unexpected any. Specify a different type.", "193409811"], + [176, 29, 31, "Do not use any type assertions.", "1451241646"], + [176, 29, 13, "Do not use any type assertions.", "2146830713"], + [195, 32, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/DashNav/DashNav.tsx:1533394562": [ - [67, 87, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/components/DashNav/DashNav.tsx:574528540": [ + [66, 87, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/components/DashNav/DashNavTimeControls.test.tsx:3825334541": [ [49, 17, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -4689,9 +4684,11 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx:1752755114": [ [40, 53, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx:428611755": [ + "public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx:1246666905": [ [13, 14, 50, "Do not use any type assertions.", "3302172944"], - [15, 5, 3, "Unexpected any. Specify a different type.", "193409811"] + [15, 5, 3, "Unexpected any. Specify a different type.", "193409811"], + [19, 15, 428, "Do not use any type assertions.", "4070674009"], + [19, 15, 410, "Do not use any type assertions.", "815903200"] ], "public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx:2117359978": [ [38, 14, 51, "Do not use any type assertions.", "4093646718"] @@ -4855,46 +4852,48 @@ exports[`better eslint`] = { [16, 40, 3, "Unexpected any. Specify a different type.", "193409811"], [30, 18, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx:1798881678": [ - [41, 3, 13, "Do not use any type assertions.", "538937261"], - [41, 13, 3, "Unexpected any. Specify a different type.", "193409811"], - [50, 24, 3, "Unexpected any. Specify a different type.", "193409811"], - [57, 22, 55, "Do not use any type assertions.", "2080143574"], - [61, 9, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx:2703340283": [ + [39, 3, 13, "Do not use any type assertions.", "538937261"], + [39, 13, 3, "Unexpected any. Specify a different type.", "193409811"], + [48, 24, 3, "Unexpected any. Specify a different type.", "193409811"], + [54, 22, 55, "Do not use any type assertions.", "2080143574"], + [58, 9, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/ShareModal/ShareExport.tsx:1231464422": [ - [55, 67, 3, "Unexpected any. Specify a different type.", "193409811"], - [59, 25, 3, "Unexpected any. Specify a different type.", "193409811"], - [70, 23, 3, "Unexpected any. Specify a different type.", "193409811"], - [85, 67, 3, "Unexpected any. Specify a different type.", "193409811"], - [89, 25, 3, "Unexpected any. Specify a different type.", "193409811"], - [100, 23, 3, "Unexpected any. Specify a different type.", "193409811"], - [109, 28, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/components/ShareModal/ShareExport.tsx:3901008754": [ + [51, 67, 3, "Unexpected any. Specify a different type.", "193409811"], + [55, 25, 3, "Unexpected any. Specify a different type.", "193409811"], + [66, 23, 3, "Unexpected any. Specify a different type.", "193409811"], + [81, 67, 3, "Unexpected any. Specify a different type.", "193409811"], + [85, 25, 3, "Unexpected any. Specify a different type.", "193409811"], + [96, 23, 3, "Unexpected any. Specify a different type.", "193409811"], + [105, 28, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:3716733755": [ - [43, 3, 13, "Do not use any type assertions.", "538937261"], - [43, 13, 3, "Unexpected any. Specify a different type.", "193409811"], - [77, 15, 3, "Unexpected any. Specify a different type.", "193409811"], - [78, 18, 3, "Unexpected any. Specify a different type.", "193409811"], - [81, 30, 3, "Unexpected any. Specify a different type.", "193409811"], - [82, 21, 3, "Unexpected any. Specify a different type.", "193409811"], - [113, 24, 63, "Do not use any type assertions.", "3674793430"], - [117, 11, 3, "Unexpected any. Specify a different type.", "193409811"], - [199, 24, 3, "Unexpected any. Specify a different type.", "193409811"], - [205, 22, 55, "Do not use any type assertions.", "2080143574"], - [209, 9, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:2357087833": [ + [33, 3, 13, "Do not use any type assertions.", "538937261"], + [33, 13, 3, "Unexpected any. Specify a different type.", "193409811"], + [42, 3, 13, "Do not use any type assertions.", "538937261"], + [42, 13, 3, "Unexpected any. Specify a different type.", "193409811"], + [76, 15, 3, "Unexpected any. Specify a different type.", "193409811"], + [77, 18, 3, "Unexpected any. Specify a different type.", "193409811"], + [80, 30, 3, "Unexpected any. Specify a different type.", "193409811"], + [81, 21, 3, "Unexpected any. Specify a different type.", "193409811"], + [111, 24, 63, "Do not use any type assertions.", "3674793430"], + [115, 11, 3, "Unexpected any. Specify a different type.", "193409811"], + [197, 24, 3, "Unexpected any. Specify a different type.", "193409811"], + [203, 22, 55, "Do not use any type assertions.", "2080143574"], + [207, 9, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/ShareModal/ShareModal.tsx:2780210084": [ + "public/app/features/dashboard/components/ShareModal/ShareModal.tsx:3869940488": [ [86, 20, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/components/ShareModal/SharePublicDashboard.test.tsx:3176925826": [ - [20, 19, 68, "Do not use any type assertions.", "630749985"], - [20, 19, 54, "Do not use any type assertions.", "4291334336"], - [25, 6, 59, "Do not use any type assertions.", "3685154675"], - [25, 6, 49, "Do not use any type assertions.", "1184085652"], - [46, 24, 3, "Unexpected any. Specify a different type.", "193409811"], - [53, 22, 55, "Do not use any type assertions.", "2080143574"], - [57, 9, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/components/ShareModal/SharePublicDashboard.test.tsx:3005912975": [ + [18, 19, 68, "Do not use any type assertions.", "630749985"], + [18, 19, 54, "Do not use any type assertions.", "4291334336"], + [23, 6, 59, "Do not use any type assertions.", "3685154675"], + [23, 6, 49, "Do not use any type assertions.", "1184085652"], + [44, 24, 3, "Unexpected any. Specify a different type.", "193409811"], + [50, 22, 55, "Do not use any type assertions.", "2080143574"], + [54, 9, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/components/ShareModal/SharePublicDashboardUtils.test.tsx:3549968113": [ [24, 18, 46, "Do not use any type assertions.", "2293567025"], @@ -4902,8 +4901,8 @@ exports[`better eslint`] = { [36, 18, 30, "Do not use any type assertions.", "387012726"], [38, 14, 21, "Do not use any type assertions.", "3316176532"] ], - "public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx:2987761063": [ - [149, 43, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx:305910085": [ + [148, 43, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/components/ShareModal/utils.test.ts:3192556439": [ [34, 31, 115, "Do not use any type assertions.", "3780428905"], @@ -4992,11 +4991,11 @@ exports[`better eslint`] = { [264, 15, 56, "Do not use any type assertions.", "2674630592"], [266, 13, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/containers/DashboardPage.tsx:3173260528": [ - [108, 36, 40, "Do not use any type assertions.", "2547843745"], - [108, 73, 3, "Unexpected any. Specify a different type.", "193409811"], - [144, 32, 40, "Do not use any type assertions.", "2547843745"], - [144, 69, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/containers/DashboardPage.tsx:707193595": [ + [107, 36, 40, "Do not use any type assertions.", "2547843745"], + [107, 73, 3, "Unexpected any. Specify a different type.", "193409811"], + [142, 32, 40, "Do not use any type assertions.", "2547843745"], + [142, 69, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/containers/SoloPanelPage.test.tsx:1655234814": [ [26, 29, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5019,7 +5018,7 @@ exports[`better eslint`] = { [21, 38, 3, "Unexpected any. Specify a different type.", "193409811"], [21, 59, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/dashgrid/DashboardGrid.tsx:25656274": [ + "public/app/features/dashboard/dashgrid/DashboardGrid.tsx:3771579139": [ [79, 22, 3, "Unexpected any. Specify a different type.", "193409811"], [189, 40, 3, "Unexpected any. Specify a different type.", "193409811"], [189, 53, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5032,11 +5031,13 @@ exports[`better eslint`] = { "public/app/features/dashboard/dashgrid/LazyLoader.tsx:2512448803": [ [52, 23, 60, "Do not use any type assertions.", "1332568031"] ], - "public/app/features/dashboard/dashgrid/PanelChrome.test.tsx:3660061275": [ + "public/app/features/dashboard/dashgrid/PanelChrome.test.tsx:514058263": [ [24, 27, 184, "Do not use any type assertions.", "2137031202"], [24, 27, 164, "Do not use any type assertions.", "4126965436"], [30, 18, 53, "Do not use any type assertions.", "3326921050"], [30, 18, 42, "Do not use any type assertions.", "1020693066"], + [36, 11, 258, "Do not use any type assertions.", "3677324054"], + [36, 11, 244, "Do not use any type assertions.", "2585800637"], [45, 15, 188, "Do not use any type assertions.", "3092730874"], [45, 15, 170, "Do not use any type assertions.", "2122268803"], [53, 12, 104, "Do not use any type assertions.", "4098692416"], @@ -5203,7 +5204,7 @@ exports[`better eslint`] = { [1031, 24, 100, "Do not use any type assertions.", "1490128200"], [1031, 24, 89, "Do not use any type assertions.", "1592095128"] ], - "public/app/features/dashboard/state/DashboardModel.ts:2406615335": [ + "public/app/features/dashboard/state/DashboardModel.ts:2545728044": [ [61, 8, 3, "Unexpected any. Specify a different type.", "193409811"], [62, 15, 3, "Unexpected any. Specify a different type.", "193409811"], [69, 6, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5229,6 +5230,7 @@ exports[`better eslint`] = { [312, 10, 3, "Unexpected any. Specify a different type.", "193409811"], [327, 27, 3, "Unexpected any. Specify a different type.", "193409811"], [423, 32, 3, "Unexpected any. Specify a different type.", "193409811"], + [446, 14, 22, "Do not use any type assertions.", "1197383730"], [473, 22, 3, "Unexpected any. Specify a different type.", "193409811"], [689, 61, 3, "Unexpected any. Specify a different type.", "193409811"], [693, 62, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5243,7 +5245,7 @@ exports[`better eslint`] = { [1137, 40, 3, "Unexpected any. Specify a different type.", "193409811"], [1137, 48, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/state/PanelModel.test.ts:4035235685": [ + "public/app/features/dashboard/state/PanelModel.test.ts:1787146930": [ [29, 11, 92, "Do not use any type assertions.", "2976372744"], [34, 5, 3, "Unexpected any. Specify a different type.", "193409811"], [52, 15, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5256,12 +5258,12 @@ exports[`better eslint`] = { [373, 28, 3, "Unexpected any. Specify a different type.", "193409811"], [376, 80, 25, "Do not use any type assertions.", "2825799852"], [376, 102, 3, "Unexpected any. Specify a different type.", "193409811"], - [459, 49, 81, "Do not use any type assertions.", "1932069967"], - [460, 32, 15, "Do not use any type assertions.", "1703404951"], - [496, 25, 293, "Do not use any type assertions.", "3989849883"], - [498, 21, 213, "Do not use any type assertions.", "2695721884"] + [453, 49, 81, "Do not use any type assertions.", "1932069967"], + [454, 32, 15, "Do not use any type assertions.", "1703404951"], + [490, 25, 293, "Do not use any type assertions.", "3989849883"], + [492, 21, 213, "Do not use any type assertions.", "2695721884"] ], - "public/app/features/dashboard/state/PanelModel.ts:2979822970": [ + "public/app/features/dashboard/state/PanelModel.ts:3953810869": [ [112, 16, 3, "Unexpected any. Specify a different type.", "193409811"], [131, 10, 3, "Unexpected any. Specify a different type.", "193409811"], [145, 15, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5280,17 +5282,17 @@ exports[`better eslint`] = { [223, 7, 11, "Do not use any type assertions.", "3816020039"], [223, 15, 3, "Unexpected any. Specify a different type.", "193409811"], [285, 17, 3, "Unexpected any. Specify a different type.", "193409811"], - [369, 21, 11, "Do not use any type assertions.", "3816020039"], - [369, 29, 3, "Unexpected any. Specify a different type.", "193409811"], - [382, 7, 11, "Do not use any type assertions.", "3816020039"], - [382, 15, 3, "Unexpected any. Specify a different type.", "193409811"], - [433, 14, 11, "Do not use any type assertions.", "3816020039"], - [433, 22, 3, "Unexpected any. Specify a different type.", "193409811"], - [453, 16, 3, "Unexpected any. Specify a different type.", "193409811"], - [464, 22, 3, "Unexpected any. Specify a different type.", "193409811"], - [512, 22, 18, "Do not use any type assertions.", "1060162663"], - [595, 38, 3, "Unexpected any. Specify a different type.", "193409811"], - [654, 14, 3, "Unexpected any. Specify a different type.", "193409811"] + [367, 21, 11, "Do not use any type assertions.", "3816020039"], + [367, 29, 3, "Unexpected any. Specify a different type.", "193409811"], + [380, 7, 11, "Do not use any type assertions.", "3816020039"], + [380, 15, 3, "Unexpected any. Specify a different type.", "193409811"], + [431, 14, 11, "Do not use any type assertions.", "3816020039"], + [431, 22, 3, "Unexpected any. Specify a different type.", "193409811"], + [451, 16, 3, "Unexpected any. Specify a different type.", "193409811"], + [462, 22, 3, "Unexpected any. Specify a different type.", "193409811"], + [510, 22, 18, "Do not use any type assertions.", "1060162663"], + [593, 38, 3, "Unexpected any. Specify a different type.", "193409811"], + [652, 14, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/state/TimeModel.ts:2763994651": [ [3, 8, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5330,12 +5332,26 @@ exports[`better eslint`] = { [143, 17, 41, "Do not use any type assertions.", "1363816804"], [145, 11, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/dashboard/state/initDashboard.ts:1143357497": [ - [216, 84, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/dashboard/state/initDashboard.ts:4291397377": [ + [215, 71, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/dashboard/state/reducers.ts:2272523560": [ [64, 9, 3, "Unexpected any. Specify a different type.", "193409811"] ], + "public/app/features/dashboard/state/utils.test.ts:1279742222": [ + [8, 38, 64, "Do not use any type assertions.", "3380969"], + [8, 38, 50, "Do not use any type assertions.", "3221043074"], + [9, 37, 64, "Do not use any type assertions.", "524183405"], + [9, 37, 50, "Do not use any type assertions.", "420885766"], + [17, 38, 64, "Do not use any type assertions.", "3380969"], + [17, 38, 50, "Do not use any type assertions.", "3221043074"], + [18, 37, 64, "Do not use any type assertions.", "1764689129"], + [18, 37, 50, "Do not use any type assertions.", "560997762"], + [26, 38, 127, "Do not use any type assertions.", "281200413"], + [26, 38, 113, "Do not use any type assertions.", "3150415926"], + [30, 37, 64, "Do not use any type assertions.", "524183405"], + [30, 37, 50, "Do not use any type assertions.", "420885766"] + ], "public/app/features/dashboard/utils/getPanelMenu.test.ts:2133911514": [ [98, 18, 3, "Unexpected any. Specify a different type.", "193409811"], [99, 19, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -5402,12 +5418,8 @@ exports[`better eslint`] = { "public/app/features/datasources/DataSourceDashboards.tsx:3932611556": [ [49, 16, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/datasources/DataSourcesListPage.test.tsx:368182366": [ - [19, 17, 26, "Do not use any type assertions.", "3994646623"], - [22, 14, 126, "Do not use any type assertions.", "1409469672"] - ], - "public/app/features/datasources/DataSourcesListPage.tsx:247845505": [ - [44, 14, 22, "Do not use any type assertions.", "2167463294"] + "public/app/features/datasources/DataSourcesListPage.test.tsx:3726518198": [ + [32, 19, 26, "Do not use any type assertions.", "3994646623"] ], "public/app/features/datasources/__mocks__/dataSourcesMocks.ts:1217473343": [ [23, 9, 35, "Do not use any type assertions.", "2095437639"] @@ -5608,7 +5620,7 @@ exports[`better eslint`] = { "public/app/features/explore/ExploreQueryInspector.tsx:1760146357": [ [33, 62, 44, "Do not use any type assertions.", "290789231"] ], - "public/app/features/explore/Logs.tsx:3417011349": [ + "public/app/features/explore/Logs.tsx:4028397642": [ [78, 77, 3, "Unexpected any. Specify a different type.", "193409811"], [198, 68, 17, "Do not use any type assertions.", "1830153619"] ], @@ -5657,7 +5669,7 @@ exports[`better eslint`] = { [56, 15, 3, "Unexpected any. Specify a different type.", "193409811"], [75, 24, 86, "Do not use any type assertions.", "2242309894"] ], - "public/app/features/explore/TraceView/TraceView.test.tsx:598956578": [ + "public/app/features/explore/TraceView/TraceView.test.tsx:4208667279": [ [61, 21, 79, "Do not use any type assertions.", "4192888253"], [65, 9, 3, "Unexpected any. Specify a different type.", "193409811"], [159, 18, 9, "Do not use any type assertions.", "3815122951"], @@ -5989,10 +6001,8 @@ exports[`better eslint`] = { [9, 6, 59, "Do not use any type assertions.", "3685154675"], [9, 6, 49, "Do not use any type assertions.", "1184085652"] ], - "public/app/features/geo/utils/frameVectorSource.ts:119928285": [ - [28, 20, 29, "Do not use any type assertions.", "1337699239"], - [47, 21, 81, "Do not use any type assertions.", "1774144811"], - [52, 18, 13, "Do not use any type assertions.", "18139833"] + "public/app/features/geo/utils/frameVectorSource.ts:3630880852": [ + [28, 20, 29, "Do not use any type assertions.", "1337699239"] ], "public/app/features/geo/utils/location.test.ts:3751297173": [ [30, 61, 10, "Do not use any type assertions.", "525921067"], @@ -6013,7 +6023,7 @@ exports[`better eslint`] = { [150, 23, 339, "Do not use any type assertions.", "1654092041"], [150, 23, 324, "Do not use any type assertions.", "1057375888"] ], - "public/app/features/inspector/InspectDataTab.tsx:269212810": [ + "public/app/features/inspector/InspectDataTab.tsx:3622788309": [ [172, 25, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/inspector/InspectErrorTab.tsx:1005725224": [ @@ -6146,7 +6156,7 @@ exports[`better eslint`] = { [81, 19, 3, "Unexpected any. Specify a different type.", "193409811"], [82, 25, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/manage-dashboards/components/ImportDashboardForm.tsx:123008784": [ + "public/app/features/manage-dashboards/components/ImportDashboardForm.tsx:72231267": [ [62, 28, 9, "Do not use any type assertions.", "3692209159"], [62, 34, 3, "Unexpected any. Specify a different type.", "193409811"], [125, 22, 23, "Do not use any type assertions.", "2412780303"], @@ -6174,7 +6184,7 @@ exports[`better eslint`] = { [38, 9, 3, "Unexpected any. Specify a different type.", "193409811"], [44, 23, 174, "Do not use any type assertions.", "1993531937"] ], - "public/app/features/manage-dashboards/state/actions.ts:658353090": [ + "public/app/features/manage-dashboards/state/actions.ts:2109642147": [ [43, 47, 3, "Unexpected any. Specify a different type.", "193409811"], [51, 38, 3, "Unexpected any. Specify a different type.", "193409811"], [54, 20, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -6197,7 +6207,7 @@ exports[`better eslint`] = { [295, 9, 3, "Unexpected any. Specify a different type.", "193409811"], [319, 31, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/manage-dashboards/state/reducers.test.ts:354708423": [ + "public/app/features/manage-dashboards/state/reducers.test.ts:4097685863": [ [94, 25, 53, "Do not use any type assertions.", "3616064645"], [95, 23, 51, "Do not use any type assertions.", "2715348726"], [108, 25, 53, "Do not use any type assertions.", "3616064645"], @@ -6208,7 +6218,7 @@ exports[`better eslint`] = { [125, 23, 51, "Do not use any type assertions.", "2715348726"], [128, 23, 71, "Do not use any type assertions.", "1935232832"] ], - "public/app/features/manage-dashboards/state/reducers.ts:2004505684": [ + "public/app/features/manage-dashboards/state/reducers.ts:3417985415": [ [58, 13, 3, "Unexpected any. Specify a different type.", "193409811"], [68, 10, 21, "Do not use any type assertions.", "2133660528"], [76, 81, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -6370,8 +6380,8 @@ exports[`better eslint`] = { [32, 25, 3, "Unexpected any. Specify a different type.", "193409811"], [61, 22, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/plugins/admin/components/PluginDetailsBody.tsx:2575001007": [ - [46, 35, 25, "Do not use any type assertions.", "3298329852"] + "public/app/features/plugins/admin/components/PluginDetailsBody.tsx:3199506957": [ + [45, 35, 25, "Do not use any type assertions.", "3298329852"] ], "public/app/features/plugins/admin/components/PluginDetailsHeader.tsx:4233259": [ [64, 48, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -6392,8 +6402,8 @@ exports[`better eslint`] = { [135, 28, 52, "Do not use any type assertions.", "963107955"], [177, 42, 56, "Do not use any type assertions.", "2128061450"] ], - "public/app/features/plugins/admin/helpers.ts:1783021315": [ - [216, 5, 45, "Do not use any type assertions.", "2083447632"] + "public/app/features/plugins/admin/helpers.ts:2721255382": [ + [215, 5, 45, "Do not use any type assertions.", "2083447632"] ], "public/app/features/plugins/admin/hooks/useHistory.tsx:3882862818": [ [4, 22, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -6412,7 +6422,7 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/pages/PluginDetails.test.tsx:186575126": [ [85, 15, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/plugins/admin/pages/PluginDetails.tsx:2558474094": [ + "public/app/features/plugins/admin/pages/PluginDetails.tsx:4160094062": [ [43, 18, 32, "Do not use any type assertions.", "4000149916"], [87, 24, 20, "Do not use any type assertions.", "2850977225"] ], @@ -6428,8 +6438,8 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/state/selectors.ts:345773501": [ [62, 23, 27, "Do not use any type assertions.", "1285719276"] ], - "public/app/features/plugins/admin/types.ts:804526334": [ - [239, 10, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/plugins/admin/types.ts:593250396": [ + [236, 10, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/plugins/built_in_plugins.ts:2973583336": [ [93, 22, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -6533,16 +6543,16 @@ exports[`better eslint`] = { "public/app/features/profile/state/reducers.test.ts:1105044753": [ [18, 15, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/query/components/QueryEditorRow.test.ts:4201471442": [ - [7, 9, 60, "Do not use any type assertions.", "155833034"], - [50, 22, 101, "Do not use any type assertions.", "3734235969"], - [50, 22, 88, "Do not use any type assertions.", "3757401717"] + "public/app/features/query/components/QueryEditorRow.test.ts:589354782": [ + [5, 9, 60, "Do not use any type assertions.", "155833034"], + [48, 22, 101, "Do not use any type assertions.", "3734235969"], + [48, 22, 88, "Do not use any type assertions.", "3757401717"] ], - "public/app/features/query/components/QueryEditorRow.tsx:209136238": [ - [97, 22, 20, "Do not use any type assertions.", "2923490522"], - [144, 18, 46, "Do not use any type assertions.", "1673417097"], - [144, 18, 21, "Do not use any type assertions.", "1354497810"], - [356, 22, 40, "Do not use any type assertions.", "1350130209"] + "public/app/features/query/components/QueryEditorRow.tsx:3534885470": [ + [95, 22, 20, "Do not use any type assertions.", "2923490522"], + [142, 18, 46, "Do not use any type assertions.", "1673417097"], + [142, 18, 21, "Do not use any type assertions.", "1354497810"], + [317, 22, 40, "Do not use any type assertions.", "1350130209"] ], "public/app/features/query/components/QueryEditorRowHeader.test.tsx:2607197828": [ [99, 16, 32, "Do not use any type assertions.", "2255106576"] @@ -6727,17 +6737,6 @@ exports[`better eslint`] = { "public/app/features/sandbox/TestStuffPage.tsx:3698683147": [ [134, 30, 29, "Do not use any type assertions.", "3195381622"] ], - "public/app/features/search/components/DashboardSearch.test.tsx:3245889886": [ - [18, 23, 33, "Do not use any type assertions.", "2540133228"], - [30, 15, 3, "Unexpected any. Specify a different type.", "193409811"], - [72, 14, 130, "Do not use any type assertions.", "3429995880"], - [75, 10, 3, "Unexpected any. Specify a different type.", "193409811"] - ], - "public/app/features/search/components/MoveToFolderModal.tsx:3435881653": [ - [31, 21, 48, "Do not use any type assertions.", "646093117"], - [31, 93, 3, "Unexpected any. Specify a different type.", "193409811"], - [64, 51, 15, "Do not use any type assertions.", "3135191849"] - ], "public/app/features/search/components/SearchCard.tsx:417509806": [ [20, 36, 3, "Unexpected any. Specify a different type.", "193409811"], [116, 37, 32, "Do not use any type assertions.", "1966145380"], @@ -6746,18 +6745,6 @@ exports[`better eslint`] = { "public/app/features/search/components/SearchItem.tsx:1278234167": [ [15, 35, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/search/components/SearchResults.tsx:4264592788": [ - [19, 35, 3, "Unexpected any. Specify a different type.", "193409811"] - ], - "public/app/features/search/hooks/useDashboardSearch.ts:2034499138": [ - [61, 28, 32, "Do not use any type assertions.", "327142118"] - ], - "public/app/features/search/hooks/useManageDashboards.test.ts:3293235715": [ - [31, 45, 41, "Do not use any type assertions.", "3856094708"], - [31, 45, 15, "Do not use any type assertions.", "363922340"], - [33, 43, 31, "Do not use any type assertions.", "2087413285"], - [33, 43, 13, "Do not use any type assertions.", "2146830713"] - ], "public/app/features/search/hooks/useSearchKeyboardSelection.ts:877924082": [ [87, 24, 42, "Do not use any type assertions.", "3335657801"] ], @@ -6789,32 +6776,13 @@ exports[`better eslint`] = { [62, 12, 21, "Do not use any type assertions.", "976337522"], [63, 14, 26, "Do not use any type assertions.", "2909521803"] ], - "public/app/features/search/page/components/columns.tsx:2440349722": [ - [34, 20, 70, "Do not use any type assertions.", "512413936"], - [34, 21, 13, "Do not use any type assertions.", "3933930693"], - [34, 31, 3, "Unexpected any. Specify a different type.", "193409811"], - [49, 32, 21, "Do not use any type assertions.", "3454251755"], - [49, 50, 3, "Unexpected any. Specify a different type.", "193409811"], - [145, 15, 56, "Do not use any type assertions.", "1375039711"] - ], - "public/app/features/search/reducers/dashboardSearch.test.ts:1813679195": [ - [5, 66, 17, "Do not use any type assertions.", "3518965757"], - [5, 78, 3, "Unexpected any. Specify a different type.", "193409811"], - [8, 42, 9, "Do not use any type assertions.", "3692209159"], - [8, 48, 3, "Unexpected any. Specify a different type.", "193409811"] - ], - "public/app/features/search/reducers/manageDashboards.test.ts:2083873033": [ - [11, 26, 9, "Do not use any type assertions.", "3692209159"], - [11, 32, 3, "Unexpected any. Specify a different type.", "193409811"], - [16, 43, 3, "Unexpected any. Specify a different type.", "193409811"], - [17, 43, 3, "Unexpected any. Specify a different type.", "193409811"], - [17, 77, 3, "Unexpected any. Specify a different type.", "193409811"], - [23, 44, 3, "Unexpected any. Specify a different type.", "193409811"], - [24, 44, 3, "Unexpected any. Specify a different type.", "193409811"], - [24, 78, 3, "Unexpected any. Specify a different type.", "193409811"] - ], - "public/app/features/search/reducers/manageDashboards.ts:4114284001": [ - [81, 11, 24, "Do not use any type assertions.", "1870559034"] + "public/app/features/search/page/components/columns.tsx:282017932": [ + [33, 20, 70, "Do not use any type assertions.", "512413936"], + [33, 21, 13, "Do not use any type assertions.", "3933930693"], + [33, 31, 3, "Unexpected any. Specify a different type.", "193409811"], + [48, 32, 21, "Do not use any type assertions.", "3454251755"], + [48, 50, 3, "Unexpected any. Specify a different type.", "193409811"], + [127, 15, 56, "Do not use any type assertions.", "1375039711"] ], "public/app/features/search/service/bluge.ts:59496993": [ [21, 15, 68, "Do not use any type assertions.", "2766474629"], @@ -7024,10 +6992,10 @@ exports[`better eslint`] = { "public/app/features/transformers/calculateHeatmap/editor/helper.ts:2898656130": [ [10, 37, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/transformers/calculateHeatmap/heatmap.ts:3354697941": [ + "public/app/features/transformers/calculateHeatmap/heatmap.ts:2895010613": [ [65, 9, 52, "Do not use any type assertions.", "3480700880"], - [326, 63, 29, "Do not use any type assertions.", "255738422"], - [326, 89, 3, "Unexpected any. Specify a different type.", "193409811"] + [306, 63, 29, "Do not use any type assertions.", "255738422"], + [306, 89, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.test.tsx:1060905279": [ [44, 29, 21, "Do not use any type assertions.", "1548027068"] @@ -7356,10 +7324,18 @@ exports[`better eslint`] = { [87, 60, 3, "Unexpected any. Specify a different type.", "193409811"], [88, 24, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/variables/inspect/utils.test.ts:1490985138": [ - [346, 30, 3, "Unexpected any. Specify a different type.", "193409811"], - [1208, 45, 3, "Unexpected any. Specify a different type.", "193409811"], - [1707, 28, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/variables/inspect/utils.test.ts:554142034": [ + [224, 11, 103, "Do not use any type assertions.", "2980842252"], + [224, 11, 89, "Do not use any type assertions.", "4144868007"], + [268, 11, 103, "Do not use any type assertions.", "2980842252"], + [268, 11, 89, "Do not use any type assertions.", "4144868007"], + [286, 11, 103, "Do not use any type assertions.", "2980842252"], + [286, 11, 89, "Do not use any type assertions.", "4144868007"], + [304, 11, 103, "Do not use any type assertions.", "2980842252"], + [304, 11, 89, "Do not use any type assertions.", "4144868007"], + [358, 30, 3, "Unexpected any. Specify a different type.", "193409811"], + [1220, 45, 3, "Unexpected any. Specify a different type.", "193409811"], + [1719, 28, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/features/variables/inspect/utils.ts:3863933569": [ [62, 77, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -7402,11 +7378,11 @@ exports[`better eslint`] = { "public/app/features/variables/pickers/OptionsPicker/OptionPicker.test.tsx:2807633419": [ [51, 15, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/variables/pickers/OptionsPicker/actions.test.ts:1831073775": [ - [400, 15, 14, "Do not use any type assertions.", "241960896"], - [400, 24, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/features/variables/pickers/OptionsPicker/actions.test.ts:436404309": [ + [365, 15, 14, "Do not use any type assertions.", "241960896"], + [365, 24, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/variables/pickers/OptionsPicker/actions.ts:4177332433": [ + "public/app/features/variables/pickers/OptionsPicker/actions.ts:1878014439": [ [79, 74, 3, "Unexpected any. Specify a different type.", "193409811"], [105, 61, 3, "Unexpected any. Specify a different type.", "193409811"] ], @@ -7726,18 +7702,18 @@ exports[`better eslint`] = { [168, 77, 3, "Unexpected any. Specify a different type.", "193409811"], [168, 100, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/features/variables/utils.ts:724230159": [ + "public/app/features/variables/utils.ts:3099442303": [ [58, 42, 3, "Unexpected any. Specify a different type.", "193409811"], [74, 40, 3, "Unexpected any. Specify a different type.", "193409811"], [108, 41, 3, "Unexpected any. Specify a different type.", "193409811"], - [161, 22, 3, "Unexpected any. Specify a different type.", "193409811"], - [174, 24, 30, "Do not use any type assertions.", "4095267826"], - [197, 41, 3, "Unexpected any. Specify a different type.", "193409811"], - [197, 47, 3, "Unexpected any. Specify a different type.", "193409811"], - [254, 42, 3, "Unexpected any. Specify a different type.", "193409811"], - [254, 48, 3, "Unexpected any. Specify a different type.", "193409811"], - [294, 44, 3, "Unexpected any. Specify a different type.", "193409811"], - [305, 45, 9, "Do not use any type assertions.", "744351187"] + [145, 22, 3, "Unexpected any. Specify a different type.", "193409811"], + [158, 24, 30, "Do not use any type assertions.", "4095267826"], + [181, 41, 3, "Unexpected any. Specify a different type.", "193409811"], + [181, 47, 3, "Unexpected any. Specify a different type.", "193409811"], + [238, 42, 3, "Unexpected any. Specify a different type.", "193409811"], + [238, 48, 3, "Unexpected any. Specify a different type.", "193409811"], + [278, 44, 3, "Unexpected any. Specify a different type.", "193409811"], + [289, 45, 9, "Do not use any type assertions.", "744351187"] ], "public/app/plugins/datasource/alertmanager/ConfigEditor.tsx:2064418308": [ [50, 36, 41, "Do not use any type assertions.", "3906591734"] @@ -8284,7 +8260,7 @@ exports[`better eslint`] = { [1034, 11, 93, "Do not use any type assertions.", "2047151891"], [1037, 9, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/datasource/elasticsearch/datasource.ts:2846413304": [ + "public/app/plugins/datasource/elasticsearch/datasource.ts:1546021543": [ [104, 55, 26, "Do not use any type assertions.", "3809377898"], [139, 16, 3, "Unexpected any. Specify a different type.", "193409811"], [171, 13, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -9259,7 +9235,7 @@ exports[`better eslint`] = { [868, 15, 27, "Do not use any type assertions.", "2478144973"], [870, 9, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/datasource/loki/datasource.ts:1935613834": [ + "public/app/plugins/datasource/loki/datasource.ts:979697429": [ [219, 23, 3, "Unexpected any. Specify a different type.", "193409811"], [355, 30, 3, "Unexpected any. Specify a different type.", "193409811"], [359, 30, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -9407,10 +9383,10 @@ exports[`better eslint`] = { [51, 27, 3, "Unexpected any. Specify a different type.", "193409811"], [56, 19, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/datasource/mssql/response_parser.ts:3188817026": [ - [7, 19, 44, "Do not use any type assertions.", "1721934533"], - [36, 45, 3, "Unexpected any. Specify a different type.", "193409811"], - [37, 19, 55, "Do not use any type assertions.", "52331741"] + "public/app/plugins/datasource/mssql/response_parser.ts:4040977456": [ + [5, 19, 44, "Do not use any type assertions.", "1721934533"], + [37, 45, 3, "Unexpected any. Specify a different type.", "193409811"], + [38, 19, 55, "Do not use any type assertions.", "52331741"] ], "public/app/plugins/datasource/mssql/specs/datasource.test.ts:1031027185": [ [11, 6, 59, "Do not use any type assertions.", "3685154675"], @@ -9531,10 +9507,10 @@ exports[`better eslint`] = { [642, 24, 3, "Unexpected any. Specify a different type.", "193409811"], [642, 30, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/datasource/mysql/response_parser.ts:2094461587": [ - [7, 19, 44, "Do not use any type assertions.", "1721934533"], - [36, 45, 3, "Unexpected any. Specify a different type.", "193409811"], - [37, 19, 55, "Do not use any type assertions.", "52331741"] + "public/app/plugins/datasource/mysql/response_parser.ts:2781493345": [ + [5, 19, 44, "Do not use any type assertions.", "1721934533"], + [37, 45, 3, "Unexpected any. Specify a different type.", "193409811"], + [38, 19, 55, "Do not use any type assertions.", "52331741"] ], "public/app/plugins/datasource/mysql/specs/datasource.test.ts:3991124757": [ [19, 38, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -9791,10 +9767,10 @@ exports[`better eslint`] = { [688, 24, 3, "Unexpected any. Specify a different type.", "193409811"], [688, 30, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/datasource/postgres/response_parser.ts:2167297341": [ - [7, 19, 44, "Do not use any type assertions.", "1721934533"], - [36, 45, 3, "Unexpected any. Specify a different type.", "193409811"], - [37, 19, 55, "Do not use any type assertions.", "52331741"] + "public/app/plugins/datasource/postgres/response_parser.ts:2077296207": [ + [5, 19, 44, "Do not use any type assertions.", "1721934533"], + [37, 45, 3, "Unexpected any. Specify a different type.", "193409811"], + [38, 19, 55, "Do not use any type assertions.", "52331741"] ], "public/app/plugins/datasource/postgres/specs/datasource.test.ts:2366687477": [ [19, 6, 59, "Do not use any type assertions.", "3685154675"], @@ -10263,7 +10239,7 @@ exports[`better eslint`] = { [327, 27, 45, "Do not use any type assertions.", "881765776"], [330, 29, 45, "Do not use any type assertions.", "881765776"] ], - "public/app/plugins/datasource/prometheus/metric_find_query.test.ts:1350978765": [ + "public/app/plugins/datasource/prometheus/metric_find_query.test.ts:142842024": [ [12, 6, 59, "Do not use any type assertions.", "3685154675"], [12, 6, 49, "Do not use any type assertions.", "1184085652"], [18, 25, 178, "Do not use any type assertions.", "2743844758"], @@ -10275,15 +10251,18 @@ exports[`better eslint`] = { [47, 5, 3, "Unexpected any. Specify a different type.", "193409811"], [59, 38, 3, "Unexpected any. Specify a different type.", "193409811"], [60, 42, 70, "Do not use any type assertions.", "164910658"], - [60, 42, 53, "Do not use any type assertions.", "1065771343"] + [60, 42, 53, "Do not use any type assertions.", "1065771343"], + [165, 21, 3, "Unexpected any. Specify a different type.", "193409811"], + [216, 21, 3, "Unexpected any. Specify a different type.", "193409811"], + [240, 21, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/datasource/prometheus/metric_find_query.ts:1417569751": [ + "public/app/plugins/datasource/prometheus/metric_find_query.ts:3246701167": [ [62, 70, 3, "Unexpected any. Specify a different type.", "193409811"], [83, 72, 3, "Unexpected any. Specify a different type.", "193409811"], [96, 72, 3, "Unexpected any. Specify a different type.", "193409811"], [122, 70, 3, "Unexpected any. Specify a different type.", "193409811"], [140, 43, 35, "Do not use any type assertions.", "2712117061"], - [188, 70, 3, "Unexpected any. Specify a different type.", "193409811"] + [175, 70, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/plugins/datasource/prometheus/query_hints.test.ts:3821515673": [ [45, 23, 28, "Do not use any type assertions.", "252196522"] @@ -10348,20 +10327,20 @@ exports[`better eslint`] = { [163, 25, 27, "Do not use any type assertions.", "3051431932"], [163, 49, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx:2056401604": [ - [67, 23, 155, "Do not use any type assertions.", "2419762696"], - [67, 23, 128, "Do not use any type assertions.", "1950878359"], - [71, 19, 3, "Unexpected any. Specify a different type.", "193409811"], - [83, 23, 63, "Do not use any type assertions.", "168255657"], - [83, 23, 36, "Do not use any type assertions.", "381764118"], - [83, 56, 3, "Unexpected any. Specify a different type.", "193409811"], - [112, 23, 92, "Do not use any type assertions.", "511072900"], - [112, 23, 65, "Do not use any type assertions.", "791801755"], - [112, 85, 3, "Unexpected any. Specify a different type.", "193409811"], - [115, 30, 3, "Unexpected any. Specify a different type.", "193409811"], - [119, 23, 87, "Do not use any type assertions.", "1044305842"], - [119, 23, 60, "Do not use any type assertions.", "4026468781"], - [119, 80, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilterItem.tsx:3990888924": [ + [65, 23, 155, "Do not use any type assertions.", "2419762696"], + [65, 23, 128, "Do not use any type assertions.", "1950878359"], + [69, 19, 3, "Unexpected any. Specify a different type.", "193409811"], + [80, 23, 63, "Do not use any type assertions.", "168255657"], + [80, 23, 36, "Do not use any type assertions.", "381764118"], + [80, 56, 3, "Unexpected any. Specify a different type.", "193409811"], + [108, 23, 92, "Do not use any type assertions.", "511072900"], + [108, 23, 65, "Do not use any type assertions.", "791801755"], + [108, 85, 3, "Unexpected any. Specify a different type.", "193409811"], + [111, 30, 3, "Unexpected any. Specify a different type.", "193409811"], + [115, 23, 87, "Do not use any type assertions.", "1044305842"], + [115, 23, 60, "Do not use any type assertions.", "4026468781"], + [115, 80, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters.tsx:1456752614": [ [36, 15, 38, "Do not use any type assertions.", "1544016021"] @@ -10817,15 +10796,15 @@ exports[`better eslint`] = { "public/app/plugins/panel/barchart/TickSpacingEditor.tsx:335508426": [ [28, 69, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/panel/barchart/bars.ts:3843272290": [ + "public/app/plugins/panel/barchart/bars.ts:4025264055": [ [51, 58, 3, "Unexpected any. Specify a different type.", "193409811"], [53, 42, 3, "Unexpected any. Specify a different type.", "193409811"], - [414, 7, 17, "Do not use any type assertions.", "3632921021"], - [469, 23, 11, "Do not use any type assertions.", "319541530"], - [469, 31, 3, "Unexpected any. Specify a different type.", "193409811"], - [471, 23, 11, "Do not use any type assertions.", "319541530"], - [471, 31, 3, "Unexpected any. Specify a different type.", "193409811"], - [479, 22, 33, "Do not use any type assertions.", "3146060940"] + [409, 7, 17, "Do not use any type assertions.", "3632921021"], + [464, 23, 11, "Do not use any type assertions.", "319541530"], + [464, 31, 3, "Unexpected any. Specify a different type.", "193409811"], + [466, 23, 11, "Do not use any type assertions.", "319541530"], + [466, 31, 3, "Unexpected any. Specify a different type.", "193409811"], + [474, 22, 33, "Do not use any type assertions.", "3146060940"] ], "public/app/plugins/panel/barchart/module.tsx:780565076": [ [70, 95, 9, "Do not use any type assertions.", "3692209159"], @@ -10868,8 +10847,8 @@ exports[`better eslint`] = { [102, 16, 9, "Do not use any type assertions.", "3692209159"], [102, 22, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/panel/bargauge/BarGaugePanel.tsx:2584207123": [ - [112, 75, 3, "Unexpected any. Specify a different type.", "193409811"] + "public/app/plugins/panel/bargauge/BarGaugePanel.tsx:3184205061": [ + [114, 75, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/plugins/panel/candlestick/CandlestickPanel.tsx:3928129934": [ [116, 26, 33, "Do not use any type assertions.", "1940217301"], @@ -10930,9 +10909,10 @@ exports[`better eslint`] = { [41, 64, 3, "Unexpected any. Specify a different type.", "193409811"], [41, 69, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx:2123096129": [ + "public/app/plugins/panel/canvas/editor/LayerElementListEditor.tsx:2866351070": [ [21, 33, 3, "Unexpected any. Specify a different type.", "193409811"], - [40, 30, 44, "Do not use any type assertions.", "712744497"] + [40, 30, 44, "Do not use any type assertions.", "712744497"], + [50, 20, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/plugins/panel/canvas/editor/PlacementEditor.tsx:1494111960": [ [33, 53, 3, "Unexpected any. Specify a different type.", "193409811"] @@ -11654,15 +11634,14 @@ exports[`better eslint`] = { "public/app/plugins/panel/heatmap/fields.test.ts:2095719388": [ [7, 32, 18, "Do not use any type assertions.", "739464119"] ], - "public/app/plugins/panel/heatmap/migrations.test.ts:22150861": [ - [15, 18, 16, "Do not use any type assertions.", "388222280"], - [96, 8, 16, "Do not use any type assertions.", "388222280"], - [108, 8, 16, "Do not use any type assertions.", "388222280"] + "public/app/plugins/panel/heatmap/migrations.test.ts:1017455994": [ + [15, 15, 3, "Unexpected any. Specify a different type.", "193409811"], + [18, 18, 16, "Do not use any type assertions.", "388222280"] ], - "public/app/plugins/panel/heatmap/migrations.ts:1677424344": [ + "public/app/plugins/panel/heatmap/migrations.ts:2547355944": [ [43, 47, 3, "Unexpected any. Specify a different type.", "193409811"], - [129, 22, 27, "Do not use any type assertions.", "3349635193"], - [162, 21, 3, "Unexpected any. Specify a different type.", "193409811"] + [128, 22, 27, "Do not use any type assertions.", "3349635193"], + [161, 21, 3, "Unexpected any. Specify a different type.", "193409811"] ], "public/app/plugins/panel/heatmap/module.tsx:3365492927": [ [27, 16, 30, "Do not use any type assertions.", "3478399522"], @@ -11675,29 +11654,29 @@ exports[`better eslint`] = { [87, 39, 23, "Do not use any type assertions.", "3906212682"], [87, 59, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/panel/heatmap/utils.ts:2227722730": [ + "public/app/plugins/panel/heatmap/utils.ts:215809412": [ [260, 23, 53, "Do not use any type assertions.", "4087268626"], [287, 49, 51, "Do not use any type assertions.", "4216242190"], [287, 49, 28, "Do not use any type assertions.", "1631991143"], - [488, 14, 190, "Do not use any type assertions.", "4120382085"], - [499, 14, 55, "Do not use any type assertions.", "2123957774"], - [499, 14, 43, "Do not use any type assertions.", "3195961945"], - [642, 19, 27, "Do not use any type assertions.", "3603045978"], - [642, 19, 15, "Do not use any type assertions.", "3099372941"], - [643, 19, 27, "Do not use any type assertions.", "3664280731"], - [643, 19, 15, "Do not use any type assertions.", "2963010956"], - [644, 23, 27, "Do not use any type assertions.", "1493467224"], - [644, 23, 15, "Do not use any type assertions.", "2572578447"], - [750, 25, 30, "Do not use any type assertions.", "1186575399"], - [750, 25, 16, "Do not use any type assertions.", "1728826198"], - [822, 22, 27, "Do not use any type assertions.", "3603045978"], - [822, 22, 15, "Do not use any type assertions.", "3099372941"], - [823, 22, 27, "Do not use any type assertions.", "3664280731"], - [823, 22, 15, "Do not use any type assertions.", "2963010956"], - [824, 22, 27, "Do not use any type assertions.", "1493467224"], - [824, 22, 15, "Do not use any type assertions.", "2572578447"], - [825, 23, 27, "Do not use any type assertions.", "1554701977"], - [825, 23, 15, "Do not use any type assertions.", "2436216462"] + [476, 14, 190, "Do not use any type assertions.", "4120382085"], + [487, 14, 55, "Do not use any type assertions.", "2123957774"], + [487, 14, 43, "Do not use any type assertions.", "3195961945"], + [630, 19, 27, "Do not use any type assertions.", "3603045978"], + [630, 19, 15, "Do not use any type assertions.", "3099372941"], + [631, 19, 27, "Do not use any type assertions.", "3664280731"], + [631, 19, 15, "Do not use any type assertions.", "2963010956"], + [632, 23, 27, "Do not use any type assertions.", "1493467224"], + [632, 23, 15, "Do not use any type assertions.", "2572578447"], + [738, 25, 30, "Do not use any type assertions.", "1186575399"], + [738, 25, 16, "Do not use any type assertions.", "1728826198"], + [810, 22, 27, "Do not use any type assertions.", "3603045978"], + [810, 22, 15, "Do not use any type assertions.", "3099372941"], + [811, 22, 27, "Do not use any type assertions.", "3664280731"], + [811, 22, 15, "Do not use any type assertions.", "2963010956"], + [812, 22, 27, "Do not use any type assertions.", "1493467224"], + [812, 22, 15, "Do not use any type assertions.", "2572578447"], + [813, 23, 27, "Do not use any type assertions.", "1554701977"], + [813, 23, 15, "Do not use any type assertions.", "2436216462"] ], "public/app/plugins/panel/histogram/Histogram.tsx:3993177092": [ [130, 31, 3, "Unexpected any. Specify a different type.", "193409811"], @@ -11742,8 +11721,8 @@ exports[`better eslint`] = { [100, 16, 495, "Do not use any type assertions.", "2521048876"], [100, 16, 477, "Do not use any type assertions.", "1400793553"] ], - "public/app/plugins/panel/logs/LogsPanel.tsx:1594220909": [ - [93, 41, 28, "Do not use any type assertions.", "3428864287"] + "public/app/plugins/panel/logs/LogsPanel.tsx:4080293044": [ + [95, 41, 28, "Do not use any type assertions.", "3428864287"] ], "public/app/plugins/panel/news/utils.test.ts:1075350837": [ [25, 24, 2416, "Do not use any type assertions.", "1190328332"] @@ -11815,7 +11794,7 @@ exports[`better eslint`] = { [194, 16, 3, "Unexpected any. Specify a different type.", "193409811"], [256, 16, 3, "Unexpected any. Specify a different type.", "193409811"] ], - "public/app/plugins/panel/piechart/PieChart.tsx:3082793434": [ + "public/app/plugins/panel/piechart/PieChart.tsx:1361550264": [ [201, 12, 3, "Unexpected any. Specify a different type.", "193409811"], [217, 12, 3, "Unexpected any. Specify a different type.", "193409811"] ], @@ -12511,18 +12490,12 @@ exports[`no undocumented stories`] = { "packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx:5381": [ [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] ], - "packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx:5381": [ - [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] - ], "packages/grafana-ui/src/components/QueryField/QueryField.story.tsx:5381": [ [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] ], "packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.story.tsx:5381": [ [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] ], - "packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx:5381": [ - [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] - ], "packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.story.tsx:5381": [ [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] ], diff --git a/.betterer.ts b/.betterer.ts index 9553bd4621f..236d57b7286 100644 --- a/.betterer.ts +++ b/.betterer.ts @@ -1,28 +1,18 @@ import { regexp } from '@betterer/regexp'; -import { eslint } from '@betterer/eslint'; import { BettererFileTest } from '@betterer/betterer'; +import { ESLint, Linter } from 'eslint'; +import { existsSync } from 'fs'; export default { 'no enzyme tests': () => regexp(/from 'enzyme'/g).include('**/*.test.*'), - 'better eslint': () => - eslint({ - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/consistent-type-assertions': [ - 'error', - { - assertionStyle: 'never', - }, - ], - }).include('**/*.{ts,tsx}'), - 'no undocumented stories': () => countUndocumentedStories().include('**/*.{story.tsx,mdx}'), + 'better eslint': () => countEslintErrors().include('**/*.{ts,tsx}'), + 'no undocumented stories': () => countUndocumentedStories().include('**/*.story.tsx'), }; function countUndocumentedStories() { return new BettererFileTest(async (filePaths, fileTestResult) => { - const storyFilePaths = filePaths.filter((filePath) => filePath.endsWith('story.tsx')); - const mdxFilePaths = filePaths.filter((filePath) => filePath.endsWith('mdx')); - storyFilePaths.forEach((filePath) => { - if (!mdxFilePaths.includes(filePath.replace(/\.story.tsx$/, '.mdx'))) { + filePaths.forEach((filePath) => { + if (!existsSync(filePath.replace(/\.story.tsx$/, '.mdx'))) { // In this case the file contents don't matter: const file = fileTestResult.addFile(filePath, ''); // Add the issue to the first character of the file: @@ -31,3 +21,49 @@ function countUndocumentedStories() { }); }); } + +function countEslintErrors() { + return new BettererFileTest(async (filePaths, fileTestResult, resolver) => { + const { baseDirectory } = resolver; + const cli = new ESLint({ cwd: baseDirectory }); + + await Promise.all( + filePaths.map(async (filePath) => { + const linterOptions = (await cli.calculateConfigForFile(filePath)) as Linter.Config; + + const rules: Partial = { + '@typescript-eslint/no-explicit-any': 'error', + }; + + if (!filePath.endsWith('.test.tsx') && !filePath.endsWith('.test.ts')) { + rules['@typescript-eslint/consistent-type-assertions'] = [ + 'error', + { + assertionStyle: 'never', + }, + ]; + } + + const runner = new ESLint({ + baseConfig: { + ...linterOptions, + rules, + }, + useEslintrc: false, + cwd: baseDirectory, + }); + + const lintResults = await runner.lintFiles([filePath]); + lintResults + .filter((lintResult) => lintResult.source) + .forEach((lintResult) => { + const { messages } = lintResult; + const file = fileTestResult.addFile(filePath, ''); + messages.forEach((message, index) => { + file.addIssue(0, 0, message.message, `${index}`); + }); + }); + }) + ); + }); +} diff --git a/.drone.yml b/.drone.yml index 9dac0630448..41e6ed13ffc 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -91,7 +91,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -197,7 +197,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -243,7 +243,6 @@ steps: from_secret: drone_token - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} - --variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64 depends_on: - gen-version - wire-install @@ -455,7 +454,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -542,7 +541,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -632,7 +631,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -721,7 +720,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -789,7 +788,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -884,7 +883,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -979,7 +978,7 @@ steps: image: grafana/build-container:1.5.7 name: build-frontend-packages - commands: - - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin + - ./bin/grabpl build-plugins --jobs 8 --edition oss depends_on: - gen-version - yarn-install @@ -1230,7 +1229,7 @@ steps: repo: - grafana/grafana - commands: - - ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads + - ./bin/grabpl upload-packages --edition oss depends_on: - end-to-end-tests-dashboards-suite - end-to-end-tests-panels-suite @@ -1247,7 +1246,7 @@ steps: repo: - grafana/grafana - commands: - - ./bin/grabpl upload-cdn --edition oss --src-bucket "grafana-static-assets" + - ./bin/grabpl upload-cdn --edition oss depends_on: - grafana-server environment: @@ -1308,7 +1307,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1398,7 +1397,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -1486,10 +1485,16 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl +- commands: + - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} + depends_on: + - grabpl + image: grafana/build-container:1.5.7 + name: gen-version - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 @@ -1571,7 +1576,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1628,7 +1633,7 @@ steps: image: grafana/build-container:1.5.7 name: build-frontend-packages - commands: - - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin + - ./bin/grabpl build-plugins --jobs 8 --edition oss depends_on: - gen-version - yarn-install @@ -1778,8 +1783,7 @@ steps: image: grafana/build-container:1.5.7 name: build-storybook - commands: - - ./bin/grabpl upload-cdn --edition oss --src-bucket "$${PRERELEASE_BUCKET}" --src-dir - artifacts/static-assets + - ./bin/grabpl upload-cdn --edition oss depends_on: - grafana-server environment: @@ -1790,7 +1794,7 @@ steps: image: grafana/grafana-ci-deploy:1.3.1 name: upload-cdn-assets - commands: - - ./bin/grabpl upload-packages --edition oss --packages-bucket $${PRERELEASE_BUCKET}/artifacts/downloads + - ./bin/grabpl upload-packages --edition oss depends_on: - end-to-end-tests-dashboards-suite - end-to-end-tests-panels-suite @@ -1869,7 +1873,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2004,7 +2008,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2098,7 +2102,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -2157,7 +2161,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2244,7 +2248,7 @@ steps: image: grafana/build-container:1.5.7 name: build-frontend-packages - commands: - - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --sign --signing-admin + - ./bin/grabpl build-plugins --jobs 8 --edition enterprise depends_on: - gen-version - yarn-install @@ -2393,8 +2397,7 @@ steps: - success - failure - commands: - - ./bin/grabpl upload-cdn --edition enterprise --src-bucket "$${PRERELEASE_BUCKET}" - --src-dir artifacts/static-assets + - ./bin/grabpl upload-cdn --edition enterprise depends_on: - package environment: @@ -2405,7 +2408,7 @@ steps: image: grafana/grafana-ci-deploy:1.3.1 name: upload-cdn-assets - commands: - - ./bin/grabpl upload-packages --edition enterprise --packages-bucket $${PRERELEASE_BUCKET}/artifacts/downloads + - ./bin/grabpl upload-packages --edition enterprise depends_on: - package environment: @@ -2446,8 +2449,7 @@ steps: image: grafana/build-container:1.5.7 name: package-enterprise2 - commands: - - ./bin/grabpl upload-cdn --edition enterprise2 --src-bucket "$${PRERELEASE_BUCKET}" - --src-dir artifacts/static-assets + - ./bin/grabpl upload-cdn --edition enterprise2 depends_on: - package-enterprise2 environment: @@ -2458,7 +2460,7 @@ steps: image: grafana/grafana-ci-deploy:1.3.1 name: upload-cdn-assets-enterprise2 - commands: - - ./bin/grabpl upload-packages --edition enterprise2 --packages-bucket $${PRERELEASE_BUCKET}/artifacts/downloads-enterprise2 + - ./bin/grabpl upload-packages --edition enterprise2 depends_on: - package-enterprise2 environment: @@ -2505,7 +2507,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2688,7 +2690,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2830,7 +2832,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2905,7 +2907,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2983,7 +2985,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3044,7 +3046,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3123,7 +3125,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3185,7 +3187,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3223,7 +3225,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3261,7 +3263,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3317,7 +3319,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3365,7 +3367,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3414,7 +3416,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3471,7 +3473,7 @@ steps: image: grafana/build-container:1.5.7 name: build-frontend-packages - commands: - - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin + - ./bin/grabpl build-plugins --jobs 8 --edition oss depends_on: - gen-version - yarn-install @@ -3621,7 +3623,7 @@ steps: image: grafana/build-container:1.5.7 name: build-storybook - commands: - - ./bin/grabpl upload-cdn --edition oss --src-bucket "grafana-static-assets" + - ./bin/grabpl upload-cdn --edition oss depends_on: - grafana-server environment: @@ -3635,7 +3637,7 @@ steps: repo: - grafana/grafana - commands: - - ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads + - ./bin/grabpl upload-packages --edition oss depends_on: - end-to-end-tests-dashboards-suite - end-to-end-tests-panels-suite @@ -3682,7 +3684,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3811,7 +3813,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3899,7 +3901,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -3947,7 +3949,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4032,7 +4034,7 @@ steps: image: grafana/build-container:1.5.7 name: build-frontend-packages - commands: - - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --sign --signing-admin + - ./bin/grabpl build-plugins --jobs 8 --edition enterprise depends_on: - gen-version - yarn-install @@ -4193,7 +4195,7 @@ steps: image: grafana/build-container:1.5.7 name: build-storybook - commands: - - ./bin/grabpl upload-cdn --edition enterprise --src-bucket "grafana-static-assets" + - ./bin/grabpl upload-cdn --edition enterprise depends_on: - package environment: @@ -4207,7 +4209,7 @@ steps: repo: - grafana/grafana - commands: - - ./bin/grabpl upload-packages --edition enterprise --packages-bucket grafana-downloads + - ./bin/grabpl upload-packages --edition enterprise depends_on: - package environment: @@ -4241,7 +4243,7 @@ steps: image: grafana/build-container:1.5.7 name: package-enterprise2 - commands: - - ./bin/grabpl upload-cdn --edition enterprise2 --src-bucket "grafana-static-assets" + - ./bin/grabpl upload-cdn --edition enterprise2 depends_on: - package-enterprise2 environment: @@ -4252,7 +4254,7 @@ steps: image: grafana/grafana-ci-deploy:1.3.1 name: upload-cdn-assets-enterprise2 - commands: - - ./bin/grabpl upload-packages --edition enterprise2 --packages-bucket grafana-downloads-enterprise2 + - ./bin/grabpl upload-packages --edition enterprise2 depends_on: - package-enterprise2 environment: @@ -4293,7 +4295,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4467,7 +4469,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4600,7 +4602,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.50/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.52/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -4797,6 +4799,6 @@ kind: secret name: gcp_upload_artifacts_key --- kind: signature -hmac: 12fbd61337ed1ae006e8c0ff71d1290e27b5910211369cdd47e1d604afd9befa +hmac: b885bb4e5374691a66b4fa8c6f53cdcebeef573895ea629d4e8b0af715573cea ... diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 058656c2d25..ad9fff7905b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -31,9 +31,9 @@ go.sum @grafana/backend-platform /pkg/build/ @grafana/grafana-release-eng # Cloud Datasources backend code -/pkg/tsdb/cloudwatch @grafana/cloud-datasources -/pkg/tsdb/azuremonitor @grafana/cloud-datasources -/pkg/tsdb/cloudmonitoring @grafana/cloud-datasources +/pkg/tsdb/cloudwatch @grafana/aws-plugins +/pkg/tsdb/azuremonitor @grafana/cloud-provider-plugins +/pkg/tsdb/cloudmonitoring @grafana/cloud-provider-plugins # Observability backend code /pkg/tsdb/prometheus @grafana/observability-metrics @@ -108,6 +108,7 @@ pkg/tsdb/testdatasource/sims/ @grafana/grafana-edge-squad /public/app/features/comments/ @grafana/grafana-edge-squad /public/app/features/dimensions/ @grafana/grafana-edge-squad /public/app/features/geo/ @grafana/grafana-edge-squad +/public/app/features/storage/ @grafana/grafana-edge-squad /public/app/features/live/ @grafana/grafana-edge-squad /public/app/features/explore/ @grafana/observability-experience-squad /public/app/features/plugins @grafana/plugins-platform-frontend @@ -148,9 +149,9 @@ lerna.json @grafana/frontend-ops *.mdx @marcusolsson @jessover9000 @grafana/plugins-platform-frontend # Core datasources -/public/app/plugins/datasource/cloudwatch @grafana/cloud-datasources +/public/app/plugins/datasource/cloudwatch @grafana/aws-plugins /public/app/plugins/datasource/elasticsearch @grafana/observability-logs-and-traces -/public/app/plugins/datasource/grafana-azure-monitor-datasource @grafana/cloud-datasources +/public/app/plugins/datasource/grafana-azure-monitor-datasource @grafana/cloud-provider-plugins /public/app/plugins/datasource/graphite @grafana/observability-metrics /public/app/plugins/datasource/influxdb @grafana/observability-metrics /public/app/plugins/datasource/jaeger @grafana/observability-logs-and-traces @@ -160,7 +161,7 @@ lerna.json @grafana/frontend-ops /public/app/plugins/datasource/opentsdb @grafana/backend-platform /public/app/plugins/datasource/postgres @grafana/grafana-bi-squad /public/app/plugins/datasource/prometheus @grafana/observability-metrics -/public/app/plugins/datasource/cloud-monitoring @grafana/cloud-datasources +/public/app/plugins/datasource/cloud-monitoring @grafana/cloud-provider-plugins /public/app/plugins/datasource/zipkin @grafana/observability-logs-and-traces /public/app/plugins/datasource/tempo @grafana/observability-logs-and-traces /public/app/plugins/datasource/alertmanager @grafana/alerting-squad diff --git a/.github/workflows/doc-validator.yml b/.github/workflows/doc-validator.yml new file mode 100644 index 00000000000..63a2ef54dcb --- /dev/null +++ b/.github/workflows/doc-validator.yml @@ -0,0 +1,16 @@ +name: "doc-validator" +on: + pull_request: + paths: ["docs/sources/**"] + workflow_dispatch: +jobs: + doc-validator: + runs-on: "ubuntu-latest" + container: + image: "grafana/doc-validator:latest" + steps: + - name: "Checkout code" + uses: "actions/checkout@v3" + - name: "Run doc-validator tool" + # Ensure that the CI always passes until all errors are resolved. + run: "doc-validator ./docs/sources || true" diff --git a/contribute/architecture/backend/communication.md b/contribute/architecture/backend/communication.md index f59f598cdf5..151da5978fc 100644 --- a/contribute/architecture/backend/communication.md +++ b/contribute/architecture/backend/communication.md @@ -2,9 +2,95 @@ Grafana uses a _bus_ to pass messages between different parts of the application. All communication over the bus happens synchronously. -> **Deprecated:** The bus has officially been deprecated, however, we're still using the command/query objects paradigms. +## Commands and queries -There are three types of messages: _events_, _commands_, and _queries_. +Grafana structures arguments to [services](services.md) using a command/query +separation where commands are instructions for a mutation and queries retrieve +records from a service. + +Services should define their methods as `func[T, U any](ctx context.Context, args T) (U, error)`. + +Each function should take two arguments. First, a `context.Context` that +carries information about the tracing span, cancellation, and similar +runtime information that might be relevant to the call. Secondly, `T` is +a `struct` defined in the service's root package (see the instructions +for [package hierarchy](package-hierarchy.md)) that contains zero or +more arguments that can be passed to the method. + +The return values is more flexible, and may consist of none, one, or two +values. If there are two values returned, the second value should be +either an `bool` or `error` indicating the success or failure of the +call. The first value `U` carries a value of any exported type that +makes sense for the service. + +Following is an example of an interface providing method signatures for +some calls adhering to these guidelines: + +``` +type Alphabetical interface { + // GetLetter returns either an error or letter. + GetLetter(context.Context, GetLetterQuery) (Letter, error) + // ListCachedLetters cannot fail, and doesn't return an error. + ListCachedLetters(context.Context, ListCachedLettersQuery) Letters + // DeleteLetter doesn't have any return values other than errors, so it + // returns only an error. + DeleteLetter(context.Contxt, DeleteLetterCommand) error +} +``` + +> Because we request an operation to be performed, command are written in imperative mood, such as `CreateFolderCommand`, `GetDashboardQuery` and `DeletePlaylistCommand`. + +The use of complex types for arguments in Go means a few different +things for us, it provides us with the equivalent of named parameters +from other languages, and it reduces the headache of figuring out which +argument is which that often occurs with three or more arguments. + +On the flip-side, it means that all input parameters are optional and +that it is up to the programmer to make sure that the zero value is +useful or at least safe for all fields and that while it's easy to add +another field, if that field must be set for the correct function of the +service that is not detectable at compile time. + +### Queries with Result fields + +Some queries have a Result field that is mutated and populated by the +method being called. This is a remainder from when the _bus_ was used +for sending commands and queries as well as for events. + +All bus commands and queries had to implement the Go type +`func(ctx context.Context, msg interface{}) error` +and mutation of the `msg` variable or returning structured information in +`error` were the two most convenient ways to communicate with the caller. + +All `Result` fields should be refactored so that they are returned from +the query method: + +``` +type GetQuery struct { + Something int + + Result ResultType +} + +func (s *Service) Get(ctx context.Context, cmd *GetQuery) error { + // ...do something + cmd.Result = result + return nil +} +``` + +should become + +``` +type GetQuery struct { + Something int +} + +func (s *Service) Get(ctx context.Context, cmd GetQuery) (ResultType, error) { + // ...do something + return result, nil +} +``` ## Events @@ -44,92 +130,3 @@ if err := s.bus.Publish(event); err != nil { return err } ``` - -## Commands - -A command is a request for an action to be taken. Unlike an event's fire-and-forget approach, a command can fail as it is handled. The handler will then return an error. - -> Because we request an operation to be performed, command are written in imperative mood, such as `CreateFolderCommand`, and `DeletePlaylistCommand`. - -### Dispatch a command - -To dispatch a command, pass the `context.Context` and object to the `DispatchCtx` method: - -```go -// context.Context from caller -ctx := req.Request.Context() -cmd := &models.SendStickersCommand { - UserID: "taylor", - Count: 1, -} -if err := s.bus.DispatchCtx(ctx, cmd); err != nil { - if err == bus.ErrHandlerNotFound { - return nil - } - return err -} -``` - -> **Note:** `DispatchCtx` will return an error if no handler is registered for that command. - -> **Note:** `Dispatch` currently exists and requires no `context.Context` to be provided, but it's strongly suggested to not use this since there's an ongoing refactoring to remove usage of non-context-aware functions/methods and use context.Context everywhere. - -**Tip:** Browse the available commands in the `models` package. - -### Handle commands - -Let other parts of the application dispatch commands to a service, by registering a _command handler_: - -To handle a command, register a command handler in the `Init` function. - -```go -func (s *MyService) Init() error { - s.bus.AddHandlerCtx(s.SendStickers) - return nil -} - -func (s *MyService) SendStickers(ctx context.Context, cmd *models.SendStickersCommand) error { - // ... -} -``` - -> **Note:** The handler method may return an error if unable to complete the command. - -> **Note:** `AddHandler` currently exists and requires no `context.Context` to be provided, but it's strongly suggested to not use this since there's an ongoing refactoring to remove usage of non-context-aware functions/methods and use context.Context everywhere. - -## Queries - -A command handler can optionally populate the command sent to it. This pattern is commonly used to implement _queries_. - -### Making a query - -To make a query, dispatch the query instance just like you would a command. When the `DispatchCtx` method returns, the `Results` field contains the result of the query. - -```go -// context.Context from caller -ctx := req.Request.Context() -query := &models.FindDashboardQuery{ - ID: "foo", -} -if err := bus.Dispatch(ctx, query); err != nil { - return err -} -// The query now contains a result. -for _, item := range query.Results { - // ... -} -``` - -> **Note:** `Dispatch` currently exists and requires no `context.Context` to be provided, but it's strongly suggested to not use this since there's an ongoing refactoring to remove usage of non-context-aware functions/methods and use context.Context everywhere. - -### Return query results - -To return results for a query, set any of the fields on the query argument before returning: - -```go -func (s *MyService) FindDashboard(ctx context.Context, query *models.FindDashboardQuery) error { - // ... - query.Result = dashboard - return nil -} -``` diff --git a/contribute/architecture/backend/errors.md b/contribute/architecture/backend/errors.md new file mode 100644 index 00000000000..fa02090b6d8 --- /dev/null +++ b/contribute/architecture/backend/errors.md @@ -0,0 +1,81 @@ +# Errors + +Grafana introduced its own error type `github.com/grafana/grafana/pkg/util/errutil.Error` +in June 2022. It's built on top of the Go `error` interface extended to +contain all the information necessary by Grafana to handle errors in an +informative and safe way. + +Previously, Grafana has passed around regular Go errors and have had to +rely on bespoke solutions in API handlers to communicate informative +messages to the end-user. With the new `errutil.Error`, the API handlers +can be slimmed as information about public messaging, structured data +related to the error, localization metadata, log level, HTTP status +code, and so forth are carried by the error. + +## Basic use + +### Declaring errors + +For a service, declare the different categories of errors that may occur +from your service (this corresponds to what you might want to have +specific public error messages or their templates for) by globally +constructing variables using the `errutil.NewBase(status, messageID, opts...)` +function. + +The status code loosely corresponds to HTTP status codes and provides a +default log level for errors to ensure that the request logging is +properly informing administrators about various errors occurring in +Grafana (e.g. `StatusBadRequest` is generally speaking not as relevant +as `StatusInternal`). All available status codes live in the `errutil` +package and have names starting with `Status`. + +The messageID is constructed as `.` where +the `` corresponds to the root service directory per +[the package hierarchy](package-hierarchy.md) and `` +is a short identifier using dashes for word separation that identifies +the specific category of errors within the service. + +To set a static message sent to the client when the error occurs, the +`errutil.WithPublicMessage(message string)` option may be appended to +the NewBase function call. For dynamic messages or more options, refer +to the `errutil` package's GoDocs. + +Errors are then constructed using the `Base.Errorf` method, which +functions like the [fmt.Errorf](https://pkg.go.dev/fmt#Errorf) method +except that it creates an `errutil.Error`. + +```go +package main + +import ( + "errors" + "github.com/grafana/grafana/pkg/util/errutil" + "example.org/thing" +) + +var ErrBaseNotFound = errutil.NewBase(errutil.StatusNotFound, "main.not-found", errutil.WithPublicMessage("Thing not found")) + +func Look(id int) (*Thing, error) { + t, err := thing.GetByID(id) + if errors.Is(err, thing.ErrNotFound) { + return nil, ErrBaseNotFound.Errorf("did not find thing with ID %d: %w", id, err) + } + + return t, nil +} +``` + +Check out [errutil's GoDocs](https://pkg.go.dev/github.com/grafana/grafana@v0.0.0-20220621133844-0f4fc1290421/pkg/util/errutil) +for details on how to construct and use Grafana style errors. + +### Handling errors in the API + +API handlers use the `github.com/grafana/grafana/pkg/api/response.Err` +function to create responses based on `errutil.Error`s. + +> **Note:** (@sakjur 2022-06) `response.Err` requires all errors to be +> `errutil.Error` or it'll be considered an internal server error. +> This is something that should be fixed in the near future to allow +> fallback behavior to make it possible to correctly handle Grafana +> style errors if they're present but allow fallback to a reasonable +> default otherwise. diff --git a/contribute/architecture/backend/package-hierarchy.md b/contribute/architecture/backend/package-hierarchy.md index d940910cdd0..7f52da3682e 100644 --- a/contribute/architecture/backend/package-hierarchy.md +++ b/contribute/architecture/backend/package-hierarchy.md @@ -1,16 +1,217 @@ # Package hierarchy -The Go package hierarchy in Grafana should be organized logically (Ben Johnson's -[article](https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1) served as inspiration), according to the -following principles: +The Go packages in Grafana should be packaged by feature, keeping +packages as small as reasonable while retaining a clear sole ownership +of a single domain. -- Domain types and interfaces should be in "root" packages (not necessarily at the very top, of the hierarchy, but - logical roots) -- Sub-packages should depend on roots - sub-packages here typically contain implementations, for example of services +[Ben Johnson's standard package layout](https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1) serves as +inspiration for the way we organize packages. + +## Principles of how to structure a service in Grafana + +[](services.md) + +### Domain types and interfaces should be in local "root" packages + +Let's say you're creating a _tea pot_ service, place everything another +service needs to interact with the tea pot service in +_pkg/services/teapot_, choosing a name according to +[Go's package naming conventions](https://go.dev/blog/package-names). + +Typically, you'd have one or more interfaces that your service provides +in the root package along with any types, errors, and other constants +that makes sense for another service interacting with this service to +use. + +Avoid depending on other services when structuring the root package to +reduce the risk of running into circular dependencies. + +### Sub-packages should depend on roots, not the other way around + +Small-to-medium sized packages should be able to have only a single +sub-package containing the implementation of the service. By moving the +implementation into a separate package we reduce the risk of triggering +circular dependencies (in Go, circular dependencies are evaluated per +package and this structure logically moves it to be per type or function +declaration). + +Large packages may need utilize multiple sub-packages at the discretion +of the implementor. Keep interfaces and domain types to the root +package. + +### Try to name sub-packages for project wide uniqueness + +Prefix sub-packages with the service name or an abbreviation of the +service name (whichever is more appropriate) to provide an ideally +unique package name. This allows `teaimpl` to be distinguished from +`coffeeimpl` without the need for package aliases, and encourages the +use of the same name to reference your package throughout the codebase. + +### A well-behaving service provides test doubles for itself + +Other services may depend on your service, and it's good practice to +provide means for those services to set up a test instance of the +dependency as needed. Refer to +[Google Testing's Testing on the Toilet: Know Your Test Doubles](https://testing.googleblog.com/2013/07/testing-on-toilet-know-your-test-doubles.html) for a brief +explanation of how we semantically aim to differentiate fakes, mocks, +and stubs within our codebase. + +Place test doubles in a sub-package to your root package named +`test` or `test`, such that the `teapot` service may have the +`teapottest` or `teatest` + +A stub or mock may be sufficient if the service is not a dependency of a +lot of services or if it's called primarily for side effects so that a +no-op default behavior makes sense. + +Services which serve many other services and where it's feasible should +provide an in-memory backed test fake that can be used like the +regular service without the need of complicated setup. + +### Separate store and logic + +When building a new service, data validation, manipulation, scheduled +events and so forth should be collected in a service implementation that +is built to be agnostic about its store. + +The storage should be an interface that is not directly called from +outside the service and should be kept to a minimum complexity to +provide the functionality necessary for the service. + +A litmus test to reduce the complexity of the storage interface is +whether an in-memory implementation is a feasible test double to build +to test the service. + +### Outside the service root + +Some parts of the service definition remains outside the +service directory and reflects the legacy package hierarchy. +As of June 2022, the parts that remain outside the service are: + +#### Migrations + +`pkg/services/sqlstore/migrations` contains all migrations for SQL +databases, for all services (not including Grafana Enterprise). +Migrations are written per the [database.md](database.md#migrations) document. + +#### API endpoints + +`pkg/api/api.go` contains the endpoint definitions for the most of +Grafana HTTP API (not including Grafana Enterprise). ## Practical example -The `pkg/plugins` package contains plugin domain types, for example `DataPlugin`, and also interfaces -such as `RequestHandler`. Then you have the `pkg/plugins/managers` subpackage, which contains concrete implementations -such as the service `PluginManager`. The subpackage `pkg/plugins/backendplugin/coreplugin` contains `plugins.DataPlugin` -implementations. +The following is a simplified example of the package structure for a +service that doesn't do anything in particular. + +None of the methods or functions are populated and in practice most +packages will consist of multiple files. There isn't a Grafana-wide +convention for which files should exist and contain what. + +`pkg/services/alphabetical` + +``` +package alphabetical + +type Alphabetical interface { + // GetLetter returns either an error or letter. + GetLetter(context.Context, GetLetterQuery) (Letter, error) + // ListCachedLetters cannot fail, and doesn't return an error. + ListCachedLetters(context.Context, ListCachedLettersQuery) Letters + // DeleteLetter doesn't have any return values other than errors, so it + // returns only an error. + DeleteLetter(context.Contxt, DeltaCommand) error +} + +type Letter byte + +type Letters []Letter + +type GetLetterQuery struct { + ID int +} + +// Create queries/commands for methods even if they are empty. +type ListCachedLettersQuery struct {} + +type DeleteLetterCommand struct { + ID int +} + +``` + +`pkg/services/alphabetical/alphabeticalimpl` + +``` +package alphabeticalimpl + +// this name can be whatever, it's not supposed to be used from outside +// the service except for in Wire. +type Svc struct { … } + +func ProviceSvc(numbers numerical.Numerical, db db.DB) Svc { … } + +func (s *Svc) GetLetter(ctx context.Context, q root.GetLetterQuery) (root.Letter, error) { … } +func (s *Svc) ListCachedLetters(ctx context.Context, q root.ListCachedLettersQuery) root.Letters { … } +func (s *Svc) DeleteLetter(ctx context.Context, q root.DeleteLetterCommand) error { … } + +type letterStore interface { + Get(ctx.Context, id int) (root.Letter, error) + Delete(ctx.Context, root.DeleteLetterCommand) error +} + +type sqlLetterStore struct { + db.DB +} + +func (s *sqlStore) Get(ctx.Context, id int) (root.Letter, error) { … } +func (s *sqlStore) Delete(ctx.Context, root.DeleteLetterCommand) error { … } +``` + +## Legacy package hierarchy + +> **Note:** A lot of services still adhere to the legacy model as outlined below. While it is ok to +> extend existing services based on the legacy model, you are _strongly_ encouraged to structure any +> new services or major refactorings using the new package layout. + +Grafana has long used a package-by-layer layout where domain types +are placed in **pkg/models**, all SQL logic in **pkg/services/sqlstore**, +and so forth. + +This is an example of how the _tea pot_ service could be structured +throughout the codebase in the legacy model. + +- _pkg/_ + - _api/_ + - _api.go_ contains the endpoints for the + - _tea_pot.go_ contains methods on the _pkg/api.HTTPServer_ type + that interacts with the service based on queries coming in via the HTTP + API. + - _dtos/tea_pot.go_ extends the _pkg/models_ file with types + that are meant for translation to and from the API. It's not as commonly + present as _pkg/models_. + - _models/tea_pot.go_ contains the models for the service, this + includes the _command_ and _query_ structs that are used when calling + the service or SQL store methods related to the service and also any + models representing an abstraction provided by the service. + - _services/_ + - _sqlstore_ + - _tea_pot.go_ contains SQL queries for + interacting with stored objects related to the tea pot service. + - _migrations/tea_pot.go_ contains the migrations necessary to + build the + - _teapot/\*_ contains functions or a service for doing + logical operations beyond those done in _pkg/api_ or _pkg/services/sqlstore_ + for the service. + +The implementation of legacy services varies widely from service to +service, some or more of these files may be missing and there may be +more files related to a service than those listed here. + +Some legacy services providing infrastructure will also take care of the +integration with several domains. The cleanup service both +provides the infrastructure to occasionally run cleanup scripts and +defines the cleanup scripts. Ideally, this would be migrated +to only handle the scheduling and synchronization of clean up jobs. +The logic for the individual jobs would be placed with a service that is +related to whatever is being cleaned up. diff --git a/devenv/docker/blocks/influxdb/config.yaml b/devenv/docker/blocks/influxdb/config.yaml deleted file mode 100644 index 15dd892ce27..00000000000 --- a/devenv/docker/blocks/influxdb/config.yaml +++ /dev/null @@ -1 +0,0 @@ -http-bind-address: :8086 \ No newline at end of file diff --git a/devenv/docker/blocks/influxdb/docker-compose.yaml b/devenv/docker/blocks/influxdb/docker-compose.yaml index 803eb4be511..244bb472107 100644 --- a/devenv/docker/blocks/influxdb/docker-compose.yaml +++ b/devenv/docker/blocks/influxdb/docker-compose.yaml @@ -5,6 +5,7 @@ - '8086:8086' environment: INFLUXD_REPORTING_DISABLED: 'true' + INFLUXD_HTTP_BIND_ADDRESS: ':8086' DOCKER_INFLUXDB_INIT_MODE: 'setup' DOCKER_INFLUXDB_INIT_USERNAME: 'grafana' DOCKER_INFLUXDB_INIT_PASSWORD: 'grafana12345' @@ -12,7 +13,6 @@ DOCKER_INFLUXDB_INIT_BUCKET: 'mybucket' DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: 'mytoken' volumes: - - ./docker/blocks/influxdb/config.yaml:/etc/influxdb2/config.yaml - ./docker/blocks/influxdb/setup_influxql.sh:/docker-entrypoint-initdb.d/setup_influxql.sh telegraf: diff --git a/devenv/docker/blocks/oauth/cloak.sql b/devenv/docker/blocks/oauth/cloak.sql new file mode 100644 index 00000000000..b8f933d4723 --- /dev/null +++ b/devenv/docker/blocks/oauth/cloak.sql @@ -0,0 +1,5487 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 12.2 (Debian 12.2-2.pgdg100+1) +-- Dumped by pg_dump version 12.2 (Debian 12.2-2.pgdg100+1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: admin_event_entity; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.admin_event_entity ( + id character varying(36) NOT NULL, + admin_event_time bigint, + realm_id character varying(255), + operation_type character varying(255), + auth_realm_id character varying(255), + auth_client_id character varying(255), + auth_user_id character varying(255), + ip_address character varying(255), + resource_path character varying(2550), + representation text, + error character varying(255), + resource_type character varying(64) +); + + +ALTER TABLE public.admin_event_entity OWNER TO keycloak; + +-- +-- Name: associated_policy; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.associated_policy ( + policy_id character varying(36) NOT NULL, + associated_policy_id character varying(36) NOT NULL +); + + +ALTER TABLE public.associated_policy OWNER TO keycloak; + +-- +-- Name: authentication_execution; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.authentication_execution ( + id character varying(36) NOT NULL, + alias character varying(255), + authenticator character varying(36), + realm_id character varying(36), + flow_id character varying(36), + requirement integer, + priority integer, + authenticator_flow boolean DEFAULT false NOT NULL, + auth_flow_id character varying(36), + auth_config character varying(36) +); + + +ALTER TABLE public.authentication_execution OWNER TO keycloak; + +-- +-- Name: authentication_flow; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.authentication_flow ( + id character varying(36) NOT NULL, + alias character varying(255), + description character varying(255), + realm_id character varying(36), + provider_id character varying(36) DEFAULT 'basic-flow'::character varying NOT NULL, + top_level boolean DEFAULT false NOT NULL, + built_in boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.authentication_flow OWNER TO keycloak; + +-- +-- Name: authenticator_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.authenticator_config ( + id character varying(36) NOT NULL, + alias character varying(255), + realm_id character varying(36) +); + + +ALTER TABLE public.authenticator_config OWNER TO keycloak; + +-- +-- Name: authenticator_config_entry; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.authenticator_config_entry ( + authenticator_id character varying(36) NOT NULL, + value text, + name character varying(255) NOT NULL +); + + +ALTER TABLE public.authenticator_config_entry OWNER TO keycloak; + +-- +-- Name: broker_link; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.broker_link ( + identity_provider character varying(255) NOT NULL, + storage_provider_id character varying(255), + realm_id character varying(36) NOT NULL, + broker_user_id character varying(255), + broker_username character varying(255), + token text, + user_id character varying(255) NOT NULL +); + + +ALTER TABLE public.broker_link OWNER TO keycloak; + +-- +-- Name: client; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client ( + id character varying(36) NOT NULL, + enabled boolean DEFAULT false NOT NULL, + full_scope_allowed boolean DEFAULT false NOT NULL, + client_id character varying(255), + not_before integer, + public_client boolean DEFAULT false NOT NULL, + secret character varying(255), + base_url character varying(255), + bearer_only boolean DEFAULT false NOT NULL, + management_url character varying(255), + surrogate_auth_required boolean DEFAULT false NOT NULL, + realm_id character varying(36), + protocol character varying(255), + node_rereg_timeout integer DEFAULT 0, + frontchannel_logout boolean DEFAULT false NOT NULL, + consent_required boolean DEFAULT false NOT NULL, + name character varying(255), + service_accounts_enabled boolean DEFAULT false NOT NULL, + client_authenticator_type character varying(255), + root_url character varying(255), + description character varying(255), + registration_token character varying(255), + standard_flow_enabled boolean DEFAULT true NOT NULL, + implicit_flow_enabled boolean DEFAULT false NOT NULL, + direct_access_grants_enabled boolean DEFAULT false NOT NULL, + always_display_in_console boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.client OWNER TO keycloak; + +-- +-- Name: client_attributes; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_attributes ( + client_id character varying(36) NOT NULL, + value character varying(4000), + name character varying(255) NOT NULL +); + + +ALTER TABLE public.client_attributes OWNER TO keycloak; + +-- +-- Name: client_auth_flow_bindings; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_auth_flow_bindings ( + client_id character varying(36) NOT NULL, + flow_id character varying(36), + binding_name character varying(255) NOT NULL +); + + +ALTER TABLE public.client_auth_flow_bindings OWNER TO keycloak; + +-- +-- Name: client_default_roles; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_default_roles ( + client_id character varying(36) NOT NULL, + role_id character varying(36) NOT NULL +); + + +ALTER TABLE public.client_default_roles OWNER TO keycloak; + +-- +-- Name: client_initial_access; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_initial_access ( + id character varying(36) NOT NULL, + realm_id character varying(36) NOT NULL, + "timestamp" integer, + expiration integer, + count integer, + remaining_count integer +); + + +ALTER TABLE public.client_initial_access OWNER TO keycloak; + +-- +-- Name: client_node_registrations; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_node_registrations ( + client_id character varying(36) NOT NULL, + value integer, + name character varying(255) NOT NULL +); + + +ALTER TABLE public.client_node_registrations OWNER TO keycloak; + +-- +-- Name: client_scope; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_scope ( + id character varying(36) NOT NULL, + name character varying(255), + realm_id character varying(36), + description character varying(255), + protocol character varying(255) +); + + +ALTER TABLE public.client_scope OWNER TO keycloak; + +-- +-- Name: client_scope_attributes; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_scope_attributes ( + scope_id character varying(36) NOT NULL, + value character varying(2048), + name character varying(255) NOT NULL +); + + +ALTER TABLE public.client_scope_attributes OWNER TO keycloak; + +-- +-- Name: client_scope_client; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_scope_client ( + client_id character varying(36) NOT NULL, + scope_id character varying(36) NOT NULL, + default_scope boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.client_scope_client OWNER TO keycloak; + +-- +-- Name: client_scope_role_mapping; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_scope_role_mapping ( + scope_id character varying(36) NOT NULL, + role_id character varying(36) NOT NULL +); + + +ALTER TABLE public.client_scope_role_mapping OWNER TO keycloak; + +-- +-- Name: client_session; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_session ( + id character varying(36) NOT NULL, + client_id character varying(36), + redirect_uri character varying(255), + state character varying(255), + "timestamp" integer, + session_id character varying(36), + auth_method character varying(255), + realm_id character varying(255), + auth_user_id character varying(36), + current_action character varying(36) +); + + +ALTER TABLE public.client_session OWNER TO keycloak; + +-- +-- Name: client_session_auth_status; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_session_auth_status ( + authenticator character varying(36) NOT NULL, + status integer, + client_session character varying(36) NOT NULL +); + + +ALTER TABLE public.client_session_auth_status OWNER TO keycloak; + +-- +-- Name: client_session_note; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_session_note ( + name character varying(255) NOT NULL, + value character varying(255), + client_session character varying(36) NOT NULL +); + + +ALTER TABLE public.client_session_note OWNER TO keycloak; + +-- +-- Name: client_session_prot_mapper; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_session_prot_mapper ( + protocol_mapper_id character varying(36) NOT NULL, + client_session character varying(36) NOT NULL +); + + +ALTER TABLE public.client_session_prot_mapper OWNER TO keycloak; + +-- +-- Name: client_session_role; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_session_role ( + role_id character varying(255) NOT NULL, + client_session character varying(36) NOT NULL +); + + +ALTER TABLE public.client_session_role OWNER TO keycloak; + +-- +-- Name: client_user_session_note; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.client_user_session_note ( + name character varying(255) NOT NULL, + value character varying(2048), + client_session character varying(36) NOT NULL +); + + +ALTER TABLE public.client_user_session_note OWNER TO keycloak; + +-- +-- Name: component; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.component ( + id character varying(36) NOT NULL, + name character varying(255), + parent_id character varying(36), + provider_id character varying(36), + provider_type character varying(255), + realm_id character varying(36), + sub_type character varying(255) +); + + +ALTER TABLE public.component OWNER TO keycloak; + +-- +-- Name: component_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.component_config ( + id character varying(36) NOT NULL, + component_id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + value character varying(4000) +); + + +ALTER TABLE public.component_config OWNER TO keycloak; + +-- +-- Name: composite_role; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.composite_role ( + composite character varying(36) NOT NULL, + child_role character varying(36) NOT NULL +); + + +ALTER TABLE public.composite_role OWNER TO keycloak; + +-- +-- Name: credential; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.credential ( + id character varying(36) NOT NULL, + salt bytea, + type character varying(255), + user_id character varying(36), + created_date bigint, + user_label character varying(255), + secret_data text, + credential_data text, + priority integer +); + + +ALTER TABLE public.credential OWNER TO keycloak; + +-- +-- Name: databasechangelog; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.databasechangelog ( + id character varying(255) NOT NULL, + author character varying(255) NOT NULL, + filename character varying(255) NOT NULL, + dateexecuted timestamp without time zone NOT NULL, + orderexecuted integer NOT NULL, + exectype character varying(10) NOT NULL, + md5sum character varying(35), + description character varying(255), + comments character varying(255), + tag character varying(255), + liquibase character varying(20), + contexts character varying(255), + labels character varying(255), + deployment_id character varying(10) +); + + +ALTER TABLE public.databasechangelog OWNER TO keycloak; + +-- +-- Name: databasechangeloglock; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.databasechangeloglock ( + id integer NOT NULL, + locked boolean NOT NULL, + lockgranted timestamp without time zone, + lockedby character varying(255) +); + + +ALTER TABLE public.databasechangeloglock OWNER TO keycloak; + +-- +-- Name: default_client_scope; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.default_client_scope ( + realm_id character varying(36) NOT NULL, + scope_id character varying(36) NOT NULL, + default_scope boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.default_client_scope OWNER TO keycloak; + +-- +-- Name: event_entity; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.event_entity ( + id character varying(36) NOT NULL, + client_id character varying(255), + details_json character varying(2550), + error character varying(255), + ip_address character varying(255), + realm_id character varying(255), + session_id character varying(255), + event_time bigint, + type character varying(255), + user_id character varying(255) +); + + +ALTER TABLE public.event_entity OWNER TO keycloak; + +-- +-- Name: fed_user_attribute; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.fed_user_attribute ( + id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + user_id character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL, + storage_provider_id character varying(36), + value character varying(2024) +); + + +ALTER TABLE public.fed_user_attribute OWNER TO keycloak; + +-- +-- Name: fed_user_consent; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.fed_user_consent ( + id character varying(36) NOT NULL, + client_id character varying(255), + user_id character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL, + storage_provider_id character varying(36), + created_date bigint, + last_updated_date bigint, + client_storage_provider character varying(36), + external_client_id character varying(255) +); + + +ALTER TABLE public.fed_user_consent OWNER TO keycloak; + +-- +-- Name: fed_user_consent_cl_scope; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.fed_user_consent_cl_scope ( + user_consent_id character varying(36) NOT NULL, + scope_id character varying(36) NOT NULL +); + + +ALTER TABLE public.fed_user_consent_cl_scope OWNER TO keycloak; + +-- +-- Name: fed_user_credential; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.fed_user_credential ( + id character varying(36) NOT NULL, + salt bytea, + type character varying(255), + created_date bigint, + user_id character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL, + storage_provider_id character varying(36), + user_label character varying(255), + secret_data text, + credential_data text, + priority integer +); + + +ALTER TABLE public.fed_user_credential OWNER TO keycloak; + +-- +-- Name: fed_user_group_membership; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.fed_user_group_membership ( + group_id character varying(36) NOT NULL, + user_id character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL, + storage_provider_id character varying(36) +); + + +ALTER TABLE public.fed_user_group_membership OWNER TO keycloak; + +-- +-- Name: fed_user_required_action; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.fed_user_required_action ( + required_action character varying(255) DEFAULT ' '::character varying NOT NULL, + user_id character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL, + storage_provider_id character varying(36) +); + + +ALTER TABLE public.fed_user_required_action OWNER TO keycloak; + +-- +-- Name: fed_user_role_mapping; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.fed_user_role_mapping ( + role_id character varying(36) NOT NULL, + user_id character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL, + storage_provider_id character varying(36) +); + + +ALTER TABLE public.fed_user_role_mapping OWNER TO keycloak; + +-- +-- Name: federated_identity; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.federated_identity ( + identity_provider character varying(255) NOT NULL, + realm_id character varying(36), + federated_user_id character varying(255), + federated_username character varying(255), + token text, + user_id character varying(36) NOT NULL +); + + +ALTER TABLE public.federated_identity OWNER TO keycloak; + +-- +-- Name: federated_user; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.federated_user ( + id character varying(255) NOT NULL, + storage_provider_id character varying(255), + realm_id character varying(36) NOT NULL +); + + +ALTER TABLE public.federated_user OWNER TO keycloak; + +-- +-- Name: group_attribute; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.group_attribute ( + id character varying(36) DEFAULT 'sybase-needs-something-here'::character varying NOT NULL, + name character varying(255) NOT NULL, + value character varying(255), + group_id character varying(36) NOT NULL +); + + +ALTER TABLE public.group_attribute OWNER TO keycloak; + +-- +-- Name: group_role_mapping; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.group_role_mapping ( + role_id character varying(36) NOT NULL, + group_id character varying(36) NOT NULL +); + + +ALTER TABLE public.group_role_mapping OWNER TO keycloak; + +-- +-- Name: identity_provider; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.identity_provider ( + internal_id character varying(36) NOT NULL, + enabled boolean DEFAULT false NOT NULL, + provider_alias character varying(255), + provider_id character varying(255), + store_token boolean DEFAULT false NOT NULL, + authenticate_by_default boolean DEFAULT false NOT NULL, + realm_id character varying(36), + add_token_role boolean DEFAULT true NOT NULL, + trust_email boolean DEFAULT false NOT NULL, + first_broker_login_flow_id character varying(36), + post_broker_login_flow_id character varying(36), + provider_display_name character varying(255), + link_only boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.identity_provider OWNER TO keycloak; + +-- +-- Name: identity_provider_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.identity_provider_config ( + identity_provider_id character varying(36) NOT NULL, + value text, + name character varying(255) NOT NULL +); + + +ALTER TABLE public.identity_provider_config OWNER TO keycloak; + +-- +-- Name: identity_provider_mapper; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.identity_provider_mapper ( + id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + idp_alias character varying(255) NOT NULL, + idp_mapper_name character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL +); + + +ALTER TABLE public.identity_provider_mapper OWNER TO keycloak; + +-- +-- Name: idp_mapper_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.idp_mapper_config ( + idp_mapper_id character varying(36) NOT NULL, + value text, + name character varying(255) NOT NULL +); + + +ALTER TABLE public.idp_mapper_config OWNER TO keycloak; + +-- +-- Name: keycloak_group; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.keycloak_group ( + id character varying(36) NOT NULL, + name character varying(255), + parent_group character varying(36) NOT NULL, + realm_id character varying(36) +); + + +ALTER TABLE public.keycloak_group OWNER TO keycloak; + +-- +-- Name: keycloak_role; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.keycloak_role ( + id character varying(36) NOT NULL, + client_realm_constraint character varying(255), + client_role boolean DEFAULT false NOT NULL, + description character varying(255), + name character varying(255), + realm_id character varying(255), + client character varying(36), + realm character varying(36) +); + + +ALTER TABLE public.keycloak_role OWNER TO keycloak; + +-- +-- Name: migration_model; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.migration_model ( + id character varying(36) NOT NULL, + version character varying(36), + update_time bigint DEFAULT 0 NOT NULL +); + + +ALTER TABLE public.migration_model OWNER TO keycloak; + +-- +-- Name: offline_client_session; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.offline_client_session ( + user_session_id character varying(36) NOT NULL, + client_id character varying(255) NOT NULL, + offline_flag character varying(4) NOT NULL, + "timestamp" integer, + data text, + client_storage_provider character varying(36) DEFAULT 'local'::character varying NOT NULL, + external_client_id character varying(255) DEFAULT 'local'::character varying NOT NULL +); + + +ALTER TABLE public.offline_client_session OWNER TO keycloak; + +-- +-- Name: offline_user_session; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.offline_user_session ( + user_session_id character varying(36) NOT NULL, + user_id character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL, + created_on integer NOT NULL, + offline_flag character varying(4) NOT NULL, + data text, + last_session_refresh integer DEFAULT 0 NOT NULL +); + + +ALTER TABLE public.offline_user_session OWNER TO keycloak; + +-- +-- Name: policy_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.policy_config ( + policy_id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + value text +); + + +ALTER TABLE public.policy_config OWNER TO keycloak; + +-- +-- Name: protocol_mapper; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.protocol_mapper ( + id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + protocol character varying(255) NOT NULL, + protocol_mapper_name character varying(255) NOT NULL, + client_id character varying(36), + client_scope_id character varying(36) +); + + +ALTER TABLE public.protocol_mapper OWNER TO keycloak; + +-- +-- Name: protocol_mapper_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.protocol_mapper_config ( + protocol_mapper_id character varying(36) NOT NULL, + value text, + name character varying(255) NOT NULL +); + + +ALTER TABLE public.protocol_mapper_config OWNER TO keycloak; + +-- +-- Name: realm; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm ( + id character varying(36) NOT NULL, + access_code_lifespan integer, + user_action_lifespan integer, + access_token_lifespan integer, + account_theme character varying(255), + admin_theme character varying(255), + email_theme character varying(255), + enabled boolean DEFAULT false NOT NULL, + events_enabled boolean DEFAULT false NOT NULL, + events_expiration bigint, + login_theme character varying(255), + name character varying(255), + not_before integer, + password_policy character varying(2550), + registration_allowed boolean DEFAULT false NOT NULL, + remember_me boolean DEFAULT false NOT NULL, + reset_password_allowed boolean DEFAULT false NOT NULL, + social boolean DEFAULT false NOT NULL, + ssl_required character varying(255), + sso_idle_timeout integer, + sso_max_lifespan integer, + update_profile_on_soc_login boolean DEFAULT false NOT NULL, + verify_email boolean DEFAULT false NOT NULL, + master_admin_client character varying(36), + login_lifespan integer, + internationalization_enabled boolean DEFAULT false NOT NULL, + default_locale character varying(255), + reg_email_as_username boolean DEFAULT false NOT NULL, + admin_events_enabled boolean DEFAULT false NOT NULL, + admin_events_details_enabled boolean DEFAULT false NOT NULL, + edit_username_allowed boolean DEFAULT false NOT NULL, + otp_policy_counter integer DEFAULT 0, + otp_policy_window integer DEFAULT 1, + otp_policy_period integer DEFAULT 30, + otp_policy_digits integer DEFAULT 6, + otp_policy_alg character varying(36) DEFAULT 'HmacSHA1'::character varying, + otp_policy_type character varying(36) DEFAULT 'totp'::character varying, + browser_flow character varying(36), + registration_flow character varying(36), + direct_grant_flow character varying(36), + reset_credentials_flow character varying(36), + client_auth_flow character varying(36), + offline_session_idle_timeout integer DEFAULT 0, + revoke_refresh_token boolean DEFAULT false NOT NULL, + access_token_life_implicit integer DEFAULT 0, + login_with_email_allowed boolean DEFAULT true NOT NULL, + duplicate_emails_allowed boolean DEFAULT false NOT NULL, + docker_auth_flow character varying(36), + refresh_token_max_reuse integer DEFAULT 0, + allow_user_managed_access boolean DEFAULT false NOT NULL, + sso_max_lifespan_remember_me integer DEFAULT 0 NOT NULL, + sso_idle_timeout_remember_me integer DEFAULT 0 NOT NULL +); + + +ALTER TABLE public.realm OWNER TO keycloak; + +-- +-- Name: realm_attribute; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_attribute ( + name character varying(255) NOT NULL, + value character varying(255), + realm_id character varying(36) NOT NULL +); + + +ALTER TABLE public.realm_attribute OWNER TO keycloak; + +-- +-- Name: realm_default_groups; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_default_groups ( + realm_id character varying(36) NOT NULL, + group_id character varying(36) NOT NULL +); + + +ALTER TABLE public.realm_default_groups OWNER TO keycloak; + +-- +-- Name: realm_default_roles; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_default_roles ( + realm_id character varying(36) NOT NULL, + role_id character varying(36) NOT NULL +); + + +ALTER TABLE public.realm_default_roles OWNER TO keycloak; + +-- +-- Name: realm_enabled_event_types; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_enabled_event_types ( + realm_id character varying(36) NOT NULL, + value character varying(255) NOT NULL +); + + +ALTER TABLE public.realm_enabled_event_types OWNER TO keycloak; + +-- +-- Name: realm_events_listeners; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_events_listeners ( + realm_id character varying(36) NOT NULL, + value character varying(255) NOT NULL +); + + +ALTER TABLE public.realm_events_listeners OWNER TO keycloak; + +-- +-- Name: realm_localizations; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_localizations ( + realm_id character varying(255) NOT NULL, + locale character varying(255) NOT NULL, + texts text NOT NULL +); + + +ALTER TABLE public.realm_localizations OWNER TO keycloak; + +-- +-- Name: realm_required_credential; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_required_credential ( + type character varying(255) NOT NULL, + form_label character varying(255), + input boolean DEFAULT false NOT NULL, + secret boolean DEFAULT false NOT NULL, + realm_id character varying(36) NOT NULL +); + + +ALTER TABLE public.realm_required_credential OWNER TO keycloak; + +-- +-- Name: realm_smtp_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_smtp_config ( + realm_id character varying(36) NOT NULL, + value character varying(255), + name character varying(255) NOT NULL +); + + +ALTER TABLE public.realm_smtp_config OWNER TO keycloak; + +-- +-- Name: realm_supported_locales; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.realm_supported_locales ( + realm_id character varying(36) NOT NULL, + value character varying(255) NOT NULL +); + + +ALTER TABLE public.realm_supported_locales OWNER TO keycloak; + +-- +-- Name: redirect_uris; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.redirect_uris ( + client_id character varying(36) NOT NULL, + value character varying(255) NOT NULL +); + + +ALTER TABLE public.redirect_uris OWNER TO keycloak; + +-- +-- Name: required_action_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.required_action_config ( + required_action_id character varying(36) NOT NULL, + value text, + name character varying(255) NOT NULL +); + + +ALTER TABLE public.required_action_config OWNER TO keycloak; + +-- +-- Name: required_action_provider; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.required_action_provider ( + id character varying(36) NOT NULL, + alias character varying(255), + name character varying(255), + realm_id character varying(36), + enabled boolean DEFAULT false NOT NULL, + default_action boolean DEFAULT false NOT NULL, + provider_id character varying(255), + priority integer +); + + +ALTER TABLE public.required_action_provider OWNER TO keycloak; + +-- +-- Name: resource_attribute; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_attribute ( + id character varying(36) DEFAULT 'sybase-needs-something-here'::character varying NOT NULL, + name character varying(255) NOT NULL, + value character varying(255), + resource_id character varying(36) NOT NULL +); + + +ALTER TABLE public.resource_attribute OWNER TO keycloak; + +-- +-- Name: resource_policy; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_policy ( + resource_id character varying(36) NOT NULL, + policy_id character varying(36) NOT NULL +); + + +ALTER TABLE public.resource_policy OWNER TO keycloak; + +-- +-- Name: resource_scope; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_scope ( + resource_id character varying(36) NOT NULL, + scope_id character varying(36) NOT NULL +); + + +ALTER TABLE public.resource_scope OWNER TO keycloak; + +-- +-- Name: resource_server; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_server ( + id character varying(36) NOT NULL, + allow_rs_remote_mgmt boolean DEFAULT false NOT NULL, + policy_enforce_mode character varying(15) NOT NULL, + decision_strategy smallint DEFAULT 1 NOT NULL +); + + +ALTER TABLE public.resource_server OWNER TO keycloak; + +-- +-- Name: resource_server_perm_ticket; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_server_perm_ticket ( + id character varying(36) NOT NULL, + owner character varying(255) NOT NULL, + requester character varying(255) NOT NULL, + created_timestamp bigint NOT NULL, + granted_timestamp bigint, + resource_id character varying(36) NOT NULL, + scope_id character varying(36), + resource_server_id character varying(36) NOT NULL, + policy_id character varying(36) +); + + +ALTER TABLE public.resource_server_perm_ticket OWNER TO keycloak; + +-- +-- Name: resource_server_policy; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_server_policy ( + id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + description character varying(255), + type character varying(255) NOT NULL, + decision_strategy character varying(20), + logic character varying(20), + resource_server_id character varying(36) NOT NULL, + owner character varying(255) +); + + +ALTER TABLE public.resource_server_policy OWNER TO keycloak; + +-- +-- Name: resource_server_resource; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_server_resource ( + id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + type character varying(255), + icon_uri character varying(255), + owner character varying(255) NOT NULL, + resource_server_id character varying(36) NOT NULL, + owner_managed_access boolean DEFAULT false NOT NULL, + display_name character varying(255) +); + + +ALTER TABLE public.resource_server_resource OWNER TO keycloak; + +-- +-- Name: resource_server_scope; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_server_scope ( + id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + icon_uri character varying(255), + resource_server_id character varying(36) NOT NULL, + display_name character varying(255) +); + + +ALTER TABLE public.resource_server_scope OWNER TO keycloak; + +-- +-- Name: resource_uris; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.resource_uris ( + resource_id character varying(36) NOT NULL, + value character varying(255) NOT NULL +); + + +ALTER TABLE public.resource_uris OWNER TO keycloak; + +-- +-- Name: role_attribute; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.role_attribute ( + id character varying(36) NOT NULL, + role_id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + value character varying(255) +); + + +ALTER TABLE public.role_attribute OWNER TO keycloak; + +-- +-- Name: scope_mapping; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.scope_mapping ( + client_id character varying(36) NOT NULL, + role_id character varying(36) NOT NULL +); + + +ALTER TABLE public.scope_mapping OWNER TO keycloak; + +-- +-- Name: scope_policy; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.scope_policy ( + scope_id character varying(36) NOT NULL, + policy_id character varying(36) NOT NULL +); + + +ALTER TABLE public.scope_policy OWNER TO keycloak; + +-- +-- Name: user_attribute; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_attribute ( + name character varying(255) NOT NULL, + value character varying(255), + user_id character varying(36) NOT NULL, + id character varying(36) DEFAULT 'sybase-needs-something-here'::character varying NOT NULL +); + + +ALTER TABLE public.user_attribute OWNER TO keycloak; + +-- +-- Name: user_consent; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_consent ( + id character varying(36) NOT NULL, + client_id character varying(255), + user_id character varying(36) NOT NULL, + created_date bigint, + last_updated_date bigint, + client_storage_provider character varying(36), + external_client_id character varying(255) +); + + +ALTER TABLE public.user_consent OWNER TO keycloak; + +-- +-- Name: user_consent_client_scope; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_consent_client_scope ( + user_consent_id character varying(36) NOT NULL, + scope_id character varying(36) NOT NULL +); + + +ALTER TABLE public.user_consent_client_scope OWNER TO keycloak; + +-- +-- Name: user_entity; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_entity ( + id character varying(36) NOT NULL, + email character varying(255), + email_constraint character varying(255), + email_verified boolean DEFAULT false NOT NULL, + enabled boolean DEFAULT false NOT NULL, + federation_link character varying(255), + first_name character varying(255), + last_name character varying(255), + realm_id character varying(255), + username character varying(255), + created_timestamp bigint, + service_account_client_link character varying(255), + not_before integer DEFAULT 0 NOT NULL +); + + +ALTER TABLE public.user_entity OWNER TO keycloak; + +-- +-- Name: user_federation_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_federation_config ( + user_federation_provider_id character varying(36) NOT NULL, + value character varying(255), + name character varying(255) NOT NULL +); + + +ALTER TABLE public.user_federation_config OWNER TO keycloak; + +-- +-- Name: user_federation_mapper; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_federation_mapper ( + id character varying(36) NOT NULL, + name character varying(255) NOT NULL, + federation_provider_id character varying(36) NOT NULL, + federation_mapper_type character varying(255) NOT NULL, + realm_id character varying(36) NOT NULL +); + + +ALTER TABLE public.user_federation_mapper OWNER TO keycloak; + +-- +-- Name: user_federation_mapper_config; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_federation_mapper_config ( + user_federation_mapper_id character varying(36) NOT NULL, + value character varying(255), + name character varying(255) NOT NULL +); + + +ALTER TABLE public.user_federation_mapper_config OWNER TO keycloak; + +-- +-- Name: user_federation_provider; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_federation_provider ( + id character varying(36) NOT NULL, + changed_sync_period integer, + display_name character varying(255), + full_sync_period integer, + last_sync integer, + priority integer, + provider_name character varying(255), + realm_id character varying(36) +); + + +ALTER TABLE public.user_federation_provider OWNER TO keycloak; + +-- +-- Name: user_group_membership; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_group_membership ( + group_id character varying(36) NOT NULL, + user_id character varying(36) NOT NULL +); + + +ALTER TABLE public.user_group_membership OWNER TO keycloak; + +-- +-- Name: user_required_action; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_required_action ( + user_id character varying(36) NOT NULL, + required_action character varying(255) DEFAULT ' '::character varying NOT NULL +); + + +ALTER TABLE public.user_required_action OWNER TO keycloak; + +-- +-- Name: user_role_mapping; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_role_mapping ( + role_id character varying(255) NOT NULL, + user_id character varying(36) NOT NULL +); + + +ALTER TABLE public.user_role_mapping OWNER TO keycloak; + +-- +-- Name: user_session; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_session ( + id character varying(36) NOT NULL, + auth_method character varying(255), + ip_address character varying(255), + last_session_refresh integer, + login_username character varying(255), + realm_id character varying(255), + remember_me boolean DEFAULT false NOT NULL, + started integer, + user_id character varying(255), + user_session_state integer, + broker_session_id character varying(255), + broker_user_id character varying(255) +); + + +ALTER TABLE public.user_session OWNER TO keycloak; + +-- +-- Name: user_session_note; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.user_session_note ( + user_session character varying(36) NOT NULL, + name character varying(255) NOT NULL, + value character varying(2048) +); + + +ALTER TABLE public.user_session_note OWNER TO keycloak; + +-- +-- Name: username_login_failure; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.username_login_failure ( + realm_id character varying(36) NOT NULL, + username character varying(255) NOT NULL, + failed_login_not_before integer, + last_failure bigint, + last_ip_failure character varying(255), + num_failures integer +); + + +ALTER TABLE public.username_login_failure OWNER TO keycloak; + +-- +-- Name: web_origins; Type: TABLE; Schema: public; Owner: keycloak +-- + +CREATE TABLE public.web_origins ( + client_id character varying(36) NOT NULL, + value character varying(255) NOT NULL +); + + +ALTER TABLE public.web_origins OWNER TO keycloak; + +-- +-- Data for Name: admin_event_entity; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.admin_event_entity (id, admin_event_time, realm_id, operation_type, auth_realm_id, auth_client_id, auth_user_id, ip_address, resource_path, representation, error, resource_type) FROM stdin; +\. + + +-- +-- Data for Name: associated_policy; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.associated_policy (policy_id, associated_policy_id) FROM stdin; +\. + + +-- +-- Data for Name: authentication_execution; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.authentication_execution (id, alias, authenticator, realm_id, flow_id, requirement, priority, authenticator_flow, auth_flow_id, auth_config) FROM stdin; +a3eef0c8-a14f-4d33-b4ee-d9eba1e14350 \N auth-cookie master ef998ef5-ca12-45db-a252-2e71b1419039 2 10 f \N \N +c4489997-ee7b-4649-845e-70b79d3cd49f \N auth-spnego master ef998ef5-ca12-45db-a252-2e71b1419039 3 20 f \N \N +6ae8f57d-d882-4e46-ad47-1e634302f979 \N identity-provider-redirector master ef998ef5-ca12-45db-a252-2e71b1419039 2 25 f \N \N +8478e3a6-1659-47a9-b7eb-503148adec2d \N \N master ef998ef5-ca12-45db-a252-2e71b1419039 2 30 t 4e407b0a-c011-4aef-bcf5-e8c5e649493e \N +da80fc4b-ebb0-4ef8-8594-8ba7a03506b9 \N auth-username-password-form master 4e407b0a-c011-4aef-bcf5-e8c5e649493e 0 10 f \N \N +c54d81b7-e944-4b9d-9657-b01ee5bff6a4 \N \N master 4e407b0a-c011-4aef-bcf5-e8c5e649493e 1 20 t 8561a6a9-da18-4977-a92d-2c85763d042a \N +e57dc48f-3217-4401-a8dc-4d386396525a \N conditional-user-configured master 8561a6a9-da18-4977-a92d-2c85763d042a 0 10 f \N \N +69a60f10-cc10-4604-890a-59fe4eb255b7 \N auth-otp-form master 8561a6a9-da18-4977-a92d-2c85763d042a 0 20 f \N \N +96dbd0ee-fcc4-4e27-85ac-a89bee432892 \N direct-grant-validate-username master 5f6f801e-0588-4a6e-860a-35483f5c1ec7 0 10 f \N \N +bfbcc1e9-f129-4336-a0cd-b6960a811bd9 \N direct-grant-validate-password master 5f6f801e-0588-4a6e-860a-35483f5c1ec7 0 20 f \N \N +079621e7-6daf-4df0-b3d3-97a3b53cdec1 \N \N master 5f6f801e-0588-4a6e-860a-35483f5c1ec7 1 30 t 99865746-4232-46f0-84b5-20952fe9eb51 \N +511afd6e-e447-4877-8fe9-c54c938e70a6 \N conditional-user-configured master 99865746-4232-46f0-84b5-20952fe9eb51 0 10 f \N \N +5a2470c4-3136-4cf0-8383-e74a413ccd48 \N direct-grant-validate-otp master 99865746-4232-46f0-84b5-20952fe9eb51 0 20 f \N \N +47c96943-ad68-4d93-afff-ff54fc86eb0b \N registration-page-form master 1695e7d2-ad80-4502-8479-8121a6e2a2f0 0 10 t 8fb96669-d28d-4173-a8f4-dc24d41c7d27 \N +a6678624-1bd4-4793-bfac-68551cf0ac7c \N registration-user-creation master 8fb96669-d28d-4173-a8f4-dc24d41c7d27 0 20 f \N \N +5b45827d-5dfd-4152-a99e-373cb975ef87 \N registration-profile-action master 8fb96669-d28d-4173-a8f4-dc24d41c7d27 0 40 f \N \N +5a2fb70d-63ae-4604-b37c-ae043d6a900d \N registration-password-action master 8fb96669-d28d-4173-a8f4-dc24d41c7d27 0 50 f \N \N +0b06a30e-daa7-498a-9fdb-899abbf36450 \N registration-recaptcha-action master 8fb96669-d28d-4173-a8f4-dc24d41c7d27 3 60 f \N \N +b6ab0b5d-8184-4609-bb81-da8413dfb858 \N reset-credentials-choose-user master 954b046d-2b24-405e-84ee-c44ffe603df2 0 10 f \N \N +300fb529-aee1-416d-803c-ca24e01af5a0 \N reset-credential-email master 954b046d-2b24-405e-84ee-c44ffe603df2 0 20 f \N \N +de127e3d-11fa-4ddb-bd33-86ed8006be63 \N reset-password master 954b046d-2b24-405e-84ee-c44ffe603df2 0 30 f \N \N +9d819c31-4238-4b6f-9318-95e1826d4a4c \N \N master 954b046d-2b24-405e-84ee-c44ffe603df2 1 40 t b379b44c-beef-4065-882c-d04cf6d4ffc8 \N +65875994-0342-452e-b7ad-547a9092e302 \N conditional-user-configured master b379b44c-beef-4065-882c-d04cf6d4ffc8 0 10 f \N \N +0f202e0b-da55-43a5-95a6-a38cfeb97529 \N reset-otp master b379b44c-beef-4065-882c-d04cf6d4ffc8 0 20 f \N \N +988c39a0-4c44-4090-bdfd-4b5a8060e822 \N client-secret master 023dc515-c259-42bb-88a8-2e8d84abca92 2 10 f \N \N +cd4e6875-71cf-436c-bf3f-5f5ac4402627 \N client-jwt master 023dc515-c259-42bb-88a8-2e8d84abca92 2 20 f \N \N +fac88bab-1ea5-4617-bbda-d187cba68a45 \N client-secret-jwt master 023dc515-c259-42bb-88a8-2e8d84abca92 2 30 f \N \N +d19571b1-5eeb-44d4-8866-273da4b34850 \N client-x509 master 023dc515-c259-42bb-88a8-2e8d84abca92 2 40 f \N \N +4198a01d-d3cd-49b2-8e8a-f506f2c46fc1 \N idp-review-profile master 242efff2-c3b7-42c0-a48a-77bb1b54502a 0 10 f \N 8ab33625-af83-4fcd-aa77-6bd365100d7b +d9b78c97-27b0-4eef-8d54-6143ca48cffd \N \N master 242efff2-c3b7-42c0-a48a-77bb1b54502a 0 20 t d46ab605-5f1e-4649-88bf-6c2dc79d636d \N +2a3faef7-dc89-4cf2-b299-d754a15af259 \N idp-create-user-if-unique master d46ab605-5f1e-4649-88bf-6c2dc79d636d 2 10 f \N c7d1ba52-6053-4219-8118-a64cebfab1e1 +12c7ff48-2eec-4091-920f-6a1ad3d2d3ad \N \N master d46ab605-5f1e-4649-88bf-6c2dc79d636d 2 20 t c39b0bc2-aba3-414d-ad3e-b648708e24d1 \N +015a4d49-de8a-4cb0-b5c5-1868c30085d3 \N idp-confirm-link master c39b0bc2-aba3-414d-ad3e-b648708e24d1 0 10 f \N \N +0bfffcda-f282-4370-b55f-1b44519be4da \N \N master c39b0bc2-aba3-414d-ad3e-b648708e24d1 0 20 t a7ca6b5a-fa8a-4f4a-bafa-ae178db785a3 \N +8e0d10d1-47ff-4998-a26b-882ed2b71ab4 \N idp-email-verification master a7ca6b5a-fa8a-4f4a-bafa-ae178db785a3 2 10 f \N \N +6a396600-1b18-472c-b755-ad36857abf68 \N \N master a7ca6b5a-fa8a-4f4a-bafa-ae178db785a3 2 20 t ca3a3600-552c-4849-9a9d-826c8aa3e646 \N +d600bb67-e258-44be-8f69-f1bae9c35a0f \N idp-username-password-form master ca3a3600-552c-4849-9a9d-826c8aa3e646 0 10 f \N \N +638e46e8-cf88-4dfa-911d-5659854dd390 \N \N master ca3a3600-552c-4849-9a9d-826c8aa3e646 1 20 t a7d23655-efbb-4950-8ab6-50dbc85681a0 \N +61fc6720-91ff-4ba3-880b-9d0a22deb7dc \N conditional-user-configured master a7d23655-efbb-4950-8ab6-50dbc85681a0 0 10 f \N \N +90cc39a9-cddb-49bd-b9f5-d64d03341333 \N auth-otp-form master a7d23655-efbb-4950-8ab6-50dbc85681a0 0 20 f \N \N +b0634301-594e-42db-9736-6c90ebbeb8b2 \N http-basic-authenticator master 57c56583-d91c-4399-bd15-05a1a17d48c1 0 10 f \N \N +34fa4d44-716b-4b2a-b98e-aa9748154292 \N docker-http-basic-authenticator master 032b05cf-0007-44da-a370-b42039f6b762 0 10 f \N \N +4838277a-46ea-4d95-bd86-d8dc6fdce352 \N no-cookie-redirect master 1c7af06b-3085-46c3-849c-34c67f581b9e 0 10 f \N \N +59a349ee-20ce-42d8-b20b-8f902c09742d \N \N master 1c7af06b-3085-46c3-849c-34c67f581b9e 0 20 t 85c00992-77dd-4262-8744-a9dd8521e98e \N +d9b5fa46-6595-4406-9841-2c0720dbf644 \N basic-auth master 85c00992-77dd-4262-8744-a9dd8521e98e 0 10 f \N \N +3a4ee6f1-1528-47c7-aeda-f317248b3b93 \N basic-auth-otp master 85c00992-77dd-4262-8744-a9dd8521e98e 3 20 f \N \N +014847fc-06df-4ddf-a8f2-deeb0f1eb59a \N auth-spnego master 85c00992-77dd-4262-8744-a9dd8521e98e 3 30 f \N \N +b46bc4f6-2fe5-44d5-b47f-36880742cf50 \N auth-cookie grafana a38aeb47-f27e-4e68-82ff-7cc7371a47a7 2 10 f \N \N +6cec48cc-066a-4e3e-8158-85351bfa4c27 \N auth-spnego grafana a38aeb47-f27e-4e68-82ff-7cc7371a47a7 3 20 f \N \N +63c55c5a-ad11-4f83-9d6e-d8ca2efcaf66 \N identity-provider-redirector grafana a38aeb47-f27e-4e68-82ff-7cc7371a47a7 2 25 f \N \N +9a986c59-e016-45e2-8eb6-77ccdd0fd0f5 \N \N grafana a38aeb47-f27e-4e68-82ff-7cc7371a47a7 2 30 t c53e357f-e276-43aa-b36c-46366a7ffd35 \N +85672b45-ebc9-40e8-a579-fbf5c4e2de9f \N auth-username-password-form grafana c53e357f-e276-43aa-b36c-46366a7ffd35 0 10 f \N \N +09025e52-b379-4457-8ab4-74a2426a7139 \N \N grafana c53e357f-e276-43aa-b36c-46366a7ffd35 1 20 t cf4831e9-3e1d-452e-984e-e6d4d9eeafb5 \N +64d5c6d6-1dde-4c42-b502-1abdf939e55b \N conditional-user-configured grafana cf4831e9-3e1d-452e-984e-e6d4d9eeafb5 0 10 f \N \N +4b782423-ec3d-4e88-8fcf-fa12b4a34fc3 \N auth-otp-form grafana cf4831e9-3e1d-452e-984e-e6d4d9eeafb5 0 20 f \N \N +07052e96-64b2-41b5-95fc-e3ac6abcc577 \N direct-grant-validate-username grafana b478ecfb-db7e-4797-a245-8fc3b4dec884 0 10 f \N \N +10c22bdd-d243-44be-810f-d2fedbb973e1 \N direct-grant-validate-password grafana b478ecfb-db7e-4797-a245-8fc3b4dec884 0 20 f \N \N +6a6273e9-146c-4b4e-b7ce-42ed72cbc03f \N conditional-user-configured grafana b3491338-0630-4232-97e7-a518c254b248 0 10 f \N \N +d87abeef-9f1d-46f5-9f36-acd7eaf21a72 \N direct-grant-validate-otp grafana b3491338-0630-4232-97e7-a518c254b248 0 20 f \N \N +4f204bab-0311-44b4-80b6-37d23fd0fd5a \N registration-page-form grafana 9d02badd-cb1c-4655-bf5e-f888861433ff 0 10 t c3ed2ad1-cfb4-49fa-8c75-cf5047527c68 \N +2d4ee446-623c-42a0-8d4a-9f6c4f7f28ec \N registration-user-creation grafana c3ed2ad1-cfb4-49fa-8c75-cf5047527c68 0 20 f \N \N +d806effc-dd17-4468-9a98-4e1c2f9e799d \N registration-profile-action grafana c3ed2ad1-cfb4-49fa-8c75-cf5047527c68 0 40 f \N \N +306fa749-c191-43c6-bf04-0eb6d3d02732 \N registration-password-action grafana c3ed2ad1-cfb4-49fa-8c75-cf5047527c68 0 50 f \N \N +7de9bbee-eb3d-4f3e-a134-e7e8d4a6df25 \N registration-recaptcha-action grafana c3ed2ad1-cfb4-49fa-8c75-cf5047527c68 3 60 f \N \N +8a31d18e-1622-4eac-8eff-9434fa9cade3 \N reset-credentials-choose-user grafana 3085fb68-fc1f-4e1c-a8be-33fb45194b04 0 10 f \N \N +6006359c-b678-4526-90de-3dcfb3200868 \N reset-credential-email grafana 3085fb68-fc1f-4e1c-a8be-33fb45194b04 0 20 f \N \N +e74add33-2692-4c20-8605-cc98c1901b98 \N reset-password grafana 3085fb68-fc1f-4e1c-a8be-33fb45194b04 0 30 f \N \N +52942b96-3bf2-47e7-9863-a917f6df716c \N \N grafana 3085fb68-fc1f-4e1c-a8be-33fb45194b04 1 40 t 079166ff-6d61-4bb2-a26d-374b8558f628 \N +bc646947-4121-4d38-96b1-a8ed4b534cc7 \N conditional-user-configured grafana 079166ff-6d61-4bb2-a26d-374b8558f628 0 10 f \N \N +efd9bb77-2d97-4ec0-9653-cd8fef30b307 \N reset-otp grafana 079166ff-6d61-4bb2-a26d-374b8558f628 0 20 f \N \N +0bdc0916-5d84-4ac8-8cc9-0235cfb18262 \N client-secret grafana cbb4b3ca-ced6-4046-8b59-f1c3959c7948 2 10 f \N \N +919f02c4-745b-43f6-a50f-5bdc9792f017 \N client-jwt grafana cbb4b3ca-ced6-4046-8b59-f1c3959c7948 2 20 f \N \N +84e716ef-c5b6-4c8e-b4ca-acaa35b6e2a0 \N client-secret-jwt grafana cbb4b3ca-ced6-4046-8b59-f1c3959c7948 2 30 f \N \N +1da09fa3-1b81-4e97-bbd5-e5b5baccb73b \N client-x509 grafana cbb4b3ca-ced6-4046-8b59-f1c3959c7948 2 40 f \N \N +f707b2f6-05b1-4cc8-8eaf-ed7006975583 \N idp-review-profile grafana 0af1201c-a206-4393-9528-cb6083b9caa0 0 10 f \N e159a12b-cd0b-4241-a41c-e13f84e92052 +9762f956-a74f-40c7-b0a3-52a699892652 \N \N grafana 0af1201c-a206-4393-9528-cb6083b9caa0 0 20 t df86516c-dcb1-41a8-877e-eb8805bcac8c \N +186e5f38-1a9f-4fa2-bc61-749118e4f76b \N idp-create-user-if-unique grafana df86516c-dcb1-41a8-877e-eb8805bcac8c 2 10 f \N f159d9c3-3ea7-460d-a719-b9c88bcbf650 +1b08ecd3-fc9f-4f17-bfa1-5bf0cc482d47 \N \N grafana df86516c-dcb1-41a8-877e-eb8805bcac8c 2 20 t 9947a1b3-c26c-423f-b380-deadb5dce1ad \N +3fa35527-e92c-4159-a80d-98412291f023 \N idp-confirm-link grafana 9947a1b3-c26c-423f-b380-deadb5dce1ad 0 10 f \N \N +ce94d52f-caf6-4388-a9e0-ac00870a8c6b \N \N grafana 9947a1b3-c26c-423f-b380-deadb5dce1ad 0 20 t 3c15ac69-f452-49ab-94d6-92e6bf809ebc \N +4a901e36-7af7-406a-a274-ac39a7bc5c8f \N idp-email-verification grafana 3c15ac69-f452-49ab-94d6-92e6bf809ebc 2 10 f \N \N +5ff95eb9-2f72-42ea-92da-7bd11a7bc4f8 \N \N grafana 3c15ac69-f452-49ab-94d6-92e6bf809ebc 2 20 t 1075a862-7836-4d28-a191-8be19a6574cf \N +72dbe3c2-0648-493f-a1e4-4b8fd6fc73ea \N idp-username-password-form grafana 1075a862-7836-4d28-a191-8be19a6574cf 0 10 f \N \N +e42d20ac-5167-4048-8658-3ebe3e7b9a70 \N \N grafana 1075a862-7836-4d28-a191-8be19a6574cf 1 20 t 21fbd70a-286f-431a-abc4-fbf6590fcdc3 \N +b8ce6905-73eb-493b-9ce1-408ec55e3c46 \N conditional-user-configured grafana 21fbd70a-286f-431a-abc4-fbf6590fcdc3 0 10 f \N \N +54d7692d-c0e3-40ef-9ef9-d9e8227d618d \N auth-otp-form grafana 21fbd70a-286f-431a-abc4-fbf6590fcdc3 0 20 f \N \N +3722f24d-6ffb-4b20-a481-1fd8a17afdf6 \N http-basic-authenticator grafana ba53abf5-9a64-4371-810b-67378eb3d781 0 10 f \N \N +a700b05f-a61d-4eeb-ad75-1a3df05ed429 \N docker-http-basic-authenticator grafana 95e02703-f5bc-4e04-8bef-f6adc2d8173f 0 10 f \N \N +035c4f94-03a6-4101-a729-f3c01ee4c490 \N no-cookie-redirect grafana f397495e-d073-4ef1-babf-569a338db596 0 10 f \N \N +29f310db-b302-44b2-9182-4b91648cbabf \N \N grafana f397495e-d073-4ef1-babf-569a338db596 0 20 t 56c40f89-4d69-46fd-bb18-d6c01808d2af \N +4e7d257c-e013-4597-a44d-b186a85606af \N basic-auth grafana 56c40f89-4d69-46fd-bb18-d6c01808d2af 0 10 f \N \N +2ba05817-a59f-4e72-a565-f3b4591390dc \N basic-auth-otp grafana 56c40f89-4d69-46fd-bb18-d6c01808d2af 3 20 f \N \N +5db9c781-6718-4674-a833-9a4ac3e8212e \N auth-spnego grafana 56c40f89-4d69-46fd-bb18-d6c01808d2af 3 30 f \N \N +5f032dbb-bd37-425b-af1e-ba555c7a8245 \N \N grafana b478ecfb-db7e-4797-a245-8fc3b4dec884 1 30 t b3491338-0630-4232-97e7-a518c254b248 \N +\. + + +-- +-- Data for Name: authentication_flow; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.authentication_flow (id, alias, description, realm_id, provider_id, top_level, built_in) FROM stdin; +ef998ef5-ca12-45db-a252-2e71b1419039 browser browser based authentication master basic-flow t t +4e407b0a-c011-4aef-bcf5-e8c5e649493e forms Username, password, otp and other auth forms. master basic-flow f t +8561a6a9-da18-4977-a92d-2c85763d042a Browser - Conditional OTP Flow to determine if the OTP is required for the authentication master basic-flow f t +5f6f801e-0588-4a6e-860a-35483f5c1ec7 direct grant OpenID Connect Resource Owner Grant master basic-flow t t +99865746-4232-46f0-84b5-20952fe9eb51 Direct Grant - Conditional OTP Flow to determine if the OTP is required for the authentication master basic-flow f t +1695e7d2-ad80-4502-8479-8121a6e2a2f0 registration registration flow master basic-flow t t +8fb96669-d28d-4173-a8f4-dc24d41c7d27 registration form registration form master form-flow f t +954b046d-2b24-405e-84ee-c44ffe603df2 reset credentials Reset credentials for a user if they forgot their password or something master basic-flow t t +b379b44c-beef-4065-882c-d04cf6d4ffc8 Reset - Conditional OTP Flow to determine if the OTP should be reset or not. Set to REQUIRED to force. master basic-flow f t +023dc515-c259-42bb-88a8-2e8d84abca92 clients Base authentication for clients master client-flow t t +242efff2-c3b7-42c0-a48a-77bb1b54502a first broker login Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account master basic-flow t t +d46ab605-5f1e-4649-88bf-6c2dc79d636d User creation or linking Flow for the existing/non-existing user alternatives master basic-flow f t +c39b0bc2-aba3-414d-ad3e-b648708e24d1 Handle Existing Account Handle what to do if there is existing account with same email/username like authenticated identity provider master basic-flow f t +a7ca6b5a-fa8a-4f4a-bafa-ae178db785a3 Account verification options Method with which to verity the existing account master basic-flow f t +ca3a3600-552c-4849-9a9d-826c8aa3e646 Verify Existing Account by Re-authentication Reauthentication of existing account master basic-flow f t +a7d23655-efbb-4950-8ab6-50dbc85681a0 First broker login - Conditional OTP Flow to determine if the OTP is required for the authentication master basic-flow f t +57c56583-d91c-4399-bd15-05a1a17d48c1 saml ecp SAML ECP Profile Authentication Flow master basic-flow t t +032b05cf-0007-44da-a370-b42039f6b762 docker auth Used by Docker clients to authenticate against the IDP master basic-flow t t +1c7af06b-3085-46c3-849c-34c67f581b9e http challenge An authentication flow based on challenge-response HTTP Authentication Schemes master basic-flow t t +85c00992-77dd-4262-8744-a9dd8521e98e Authentication Options Authentication options. master basic-flow f t +a38aeb47-f27e-4e68-82ff-7cc7371a47a7 browser browser based authentication grafana basic-flow t t +c53e357f-e276-43aa-b36c-46366a7ffd35 forms Username, password, otp and other auth forms. grafana basic-flow f t +cf4831e9-3e1d-452e-984e-e6d4d9eeafb5 Browser - Conditional OTP Flow to determine if the OTP is required for the authentication grafana basic-flow f t +b478ecfb-db7e-4797-a245-8fc3b4dec884 direct grant OpenID Connect Resource Owner Grant grafana basic-flow t t +b3491338-0630-4232-97e7-a518c254b248 Direct Grant - Conditional OTP Flow to determine if the OTP is required for the authentication grafana basic-flow f t +9d02badd-cb1c-4655-bf5e-f888861433ff registration registration flow grafana basic-flow t t +c3ed2ad1-cfb4-49fa-8c75-cf5047527c68 registration form registration form grafana form-flow f t +3085fb68-fc1f-4e1c-a8be-33fb45194b04 reset credentials Reset credentials for a user if they forgot their password or something grafana basic-flow t t +079166ff-6d61-4bb2-a26d-374b8558f628 Reset - Conditional OTP Flow to determine if the OTP should be reset or not. Set to REQUIRED to force. grafana basic-flow f t +cbb4b3ca-ced6-4046-8b59-f1c3959c7948 clients Base authentication for clients grafana client-flow t t +0af1201c-a206-4393-9528-cb6083b9caa0 first broker login Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account grafana basic-flow t t +df86516c-dcb1-41a8-877e-eb8805bcac8c User creation or linking Flow for the existing/non-existing user alternatives grafana basic-flow f t +9947a1b3-c26c-423f-b380-deadb5dce1ad Handle Existing Account Handle what to do if there is existing account with same email/username like authenticated identity provider grafana basic-flow f t +3c15ac69-f452-49ab-94d6-92e6bf809ebc Account verification options Method with which to verity the existing account grafana basic-flow f t +1075a862-7836-4d28-a191-8be19a6574cf Verify Existing Account by Re-authentication Reauthentication of existing account grafana basic-flow f t +21fbd70a-286f-431a-abc4-fbf6590fcdc3 First broker login - Conditional OTP Flow to determine if the OTP is required for the authentication grafana basic-flow f t +ba53abf5-9a64-4371-810b-67378eb3d781 saml ecp SAML ECP Profile Authentication Flow grafana basic-flow t t +95e02703-f5bc-4e04-8bef-f6adc2d8173f docker auth Used by Docker clients to authenticate against the IDP grafana basic-flow t t +f397495e-d073-4ef1-babf-569a338db596 http challenge An authentication flow based on challenge-response HTTP Authentication Schemes grafana basic-flow t t +56c40f89-4d69-46fd-bb18-d6c01808d2af Authentication Options Authentication options. grafana basic-flow f t +\. + + +-- +-- Data for Name: authenticator_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.authenticator_config (id, alias, realm_id) FROM stdin; +8ab33625-af83-4fcd-aa77-6bd365100d7b review profile config master +c7d1ba52-6053-4219-8118-a64cebfab1e1 create unique user config master +e159a12b-cd0b-4241-a41c-e13f84e92052 review profile config grafana +f159d9c3-3ea7-460d-a719-b9c88bcbf650 create unique user config grafana +\. + + +-- +-- Data for Name: authenticator_config_entry; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.authenticator_config_entry (authenticator_id, value, name) FROM stdin; +8ab33625-af83-4fcd-aa77-6bd365100d7b missing update.profile.on.first.login +c7d1ba52-6053-4219-8118-a64cebfab1e1 false require.password.update.after.registration +e159a12b-cd0b-4241-a41c-e13f84e92052 missing update.profile.on.first.login +f159d9c3-3ea7-460d-a719-b9c88bcbf650 false require.password.update.after.registration +\. + + +-- +-- Data for Name: broker_link; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.broker_link (identity_provider, storage_provider_id, realm_id, broker_user_id, broker_username, token, user_id) FROM stdin; +\. + + +-- +-- Data for Name: client; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client (id, enabled, full_scope_allowed, client_id, not_before, public_client, secret, base_url, bearer_only, management_url, surrogate_auth_required, realm_id, protocol, node_rereg_timeout, frontchannel_logout, consent_required, name, service_accounts_enabled, client_authenticator_type, root_url, description, registration_token, standard_flow_enabled, implicit_flow_enabled, direct_access_grants_enabled, always_display_in_console) FROM stdin; +3cd285ea-0f6e-43b6-ab5c-d021c33a551b t t master-realm 0 f e223073e-1025-4f3a-90d3-e79e3e4e8ffe \N t \N f master \N 0 f f master Realm f client-secret \N \N \N t f f f +eed689c6-49da-4d91-98eb-cd495bcc07a3 t f account 0 f edbe696c-b249-49c5-af33-b7e36f28a259 /realms/master/account/ f \N f master openid-connect 0 f f ${client_account} f client-secret ${authBaseUrl} \N \N t f f f +11c67f5b-dde7-4680-b05b-c9c59d78bda4 t f account-console 0 t 3c802dbd-ab38-4f29-a7cd-799000d7fa6b /realms/master/account/ f \N f master openid-connect 0 f f ${client_account-console} f client-secret ${authBaseUrl} \N \N t f f f +1e30397c-eac2-41fb-87bc-d90484992e65 t f broker 0 f 44f53260-bed3-434f-b44f-bc4a8a546243 \N f \N f master openid-connect 0 f f ${client_broker} f client-secret \N \N \N t f f f +2f521d09-7304-4b5e-a94b-7cc7300b8b50 t f security-admin-console 0 t 0abe5b86-38bd-458c-aee5-c88495207eef /admin/master/console/ f \N f master openid-connect 0 f f ${client_security-admin-console} f client-secret ${authAdminUrl} \N \N t f f f +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 t f admin-cli 0 t 1cf461d4-8b50-45d9-b69a-7703c4d99f54 \N f \N f master openid-connect 0 f f ${client_admin-cli} f client-secret \N \N \N f f t f +ef7f6eac-9fff-44aa-a86c-5125d52acc82 t t grafana-realm 0 f 969c7bb6-18d9-47d9-bd3a-b4440be4afe6 \N t \N f master \N 0 f f grafana Realm f client-secret \N \N \N t f f f +a8698f4f-5fa1-4baa-be05-87d03052af49 t f realm-management 0 f a313dae0-428d-4b35-b5cd-724201173481 \N t \N f grafana openid-connect 0 f f ${client_realm-management} f client-secret \N \N \N t f f f +a5a8fed6-0bca-4646-9946-2fe84175353b t f account 0 f d0b8b6b6-2a02-412c-84d1-716418c4f591 /realms/grafana/account/ f \N f grafana openid-connect 0 f f ${client_account} f client-secret ${authBaseUrl} \N \N t f f f +230081b5-9161-45c3-9e08-9eda5412f7f7 t f account-console 0 t 5cf0655c-c137-438c-9c3c-bea9541f41f1 /realms/grafana/account/ f \N f grafana openid-connect 0 f f ${client_account-console} f client-secret ${authBaseUrl} \N \N t f f f +77ff47f8-f578-477d-8c06-e70a846332f5 t f broker 0 f 589951e9-e77f-4d1d-90cd-796848190eff \N f \N f grafana openid-connect 0 f f ${client_broker} f client-secret \N \N \N t f f f +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 t f security-admin-console 0 t 27d2217e-9934-4971-93b8-77969e47ecf7 /admin/grafana/console/ f \N f grafana openid-connect 0 f f ${client_security-admin-console} f client-secret ${authAdminUrl} \N \N t f f f +6bd2d943-9800-4839-9ddc-03c04930cd9f t f admin-cli 0 t da0811c3-5031-4f35-9dc5-441050461a37 \N f \N f grafana openid-connect 0 f f ${client_admin-cli} f client-secret \N \N \N f f t f +09b79548-8426-4c0e-8e0b-7488467532c7 t t grafana-oauth 0 f d17b9ea9-bcb1-43d2-b132-d339e55872a8 http://localhost:3000 f http://localhost:3000 f grafana openid-connect -1 f f \N f client-secret http://localhost:3000 \N \N t f t f +\. + + +-- +-- Data for Name: client_attributes; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_attributes (client_id, value, name) FROM stdin; +11c67f5b-dde7-4680-b05b-c9c59d78bda4 S256 pkce.code.challenge.method +2f521d09-7304-4b5e-a94b-7cc7300b8b50 S256 pkce.code.challenge.method +230081b5-9161-45c3-9e08-9eda5412f7f7 S256 pkce.code.challenge.method +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 S256 pkce.code.challenge.method +09b79548-8426-4c0e-8e0b-7488467532c7 true backchannel.logout.session.required +09b79548-8426-4c0e-8e0b-7488467532c7 false backchannel.logout.revoke.offline.tokens +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.server.signature +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.server.signature.keyinfo.ext +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.assertion.signature +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.client.signature +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.encrypt +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.authnstatement +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.onetimeuse.condition +09b79548-8426-4c0e-8e0b-7488467532c7 false saml_force_name_id_format +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.multivalued.roles +09b79548-8426-4c0e-8e0b-7488467532c7 false saml.force.post.binding +09b79548-8426-4c0e-8e0b-7488467532c7 false exclude.session.state.from.auth.response +09b79548-8426-4c0e-8e0b-7488467532c7 false tls.client.certificate.bound.access.tokens +09b79548-8426-4c0e-8e0b-7488467532c7 false client_credentials.use_refresh_token +09b79548-8426-4c0e-8e0b-7488467532c7 false display.on.consent.screen +09b79548-8426-4c0e-8e0b-7488467532c7 backchannel.logout.url +\. + + +-- +-- Data for Name: client_auth_flow_bindings; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_auth_flow_bindings (client_id, flow_id, binding_name) FROM stdin; +\. + + +-- +-- Data for Name: client_default_roles; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_default_roles (client_id, role_id) FROM stdin; +eed689c6-49da-4d91-98eb-cd495bcc07a3 86a4b6a9-93db-4177-a72f-95fd937a2c8d +eed689c6-49da-4d91-98eb-cd495bcc07a3 619ba870-921e-4f28-b26c-89b11f39dddf +a5a8fed6-0bca-4646-9946-2fe84175353b f1311ecb-6a6a-49d6-bb16-5132daf93a64 +a5a8fed6-0bca-4646-9946-2fe84175353b 18a7066b-fe71-410e-9581-69f78347ec29 +\. + + +-- +-- Data for Name: client_initial_access; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_initial_access (id, realm_id, "timestamp", expiration, count, remaining_count) FROM stdin; +\. + + +-- +-- Data for Name: client_node_registrations; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_node_registrations (client_id, value, name) FROM stdin; +\. + + +-- +-- Data for Name: client_scope; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_scope (id, name, realm_id, description, protocol) FROM stdin; +0cc71c8c-fb37-41f2-b4d8-13210d3cf8be offline_access master OpenID Connect built-in scope: offline_access openid-connect +47f35d4b-35c7-4c6d-8bae-eff0a5046861 role_list master SAML role list saml +66deef47-2158-4d5b-a75f-0bf42f642e7b profile master OpenID Connect built-in scope: profile openid-connect +94ef659c-4c4a-4a33-98e8-bfcf443e9268 email master OpenID Connect built-in scope: email openid-connect +96a960d2-c203-4ef0-a53c-c3edd01f2305 address master OpenID Connect built-in scope: address openid-connect +3f705379-3361-486d-b75a-f7b4e4be492c phone master OpenID Connect built-in scope: phone openid-connect +b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 roles master OpenID Connect scope for add user roles to the access token openid-connect +619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 web-origins master OpenID Connect scope for add allowed web origins to the access token openid-connect +42bfb506-bf0d-424e-8649-53a9a93d252d microprofile-jwt master Microprofile - JWT built-in scope openid-connect +0e98d5f9-d3f7-4b1d-9791-d442524fc2ab offline_access grafana OpenID Connect built-in scope: offline_access openid-connect +a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 role_list grafana SAML role list saml +74daf2cd-40d4-4304-87a8-92cdca808512 profile grafana OpenID Connect built-in scope: profile openid-connect +96d521d3-facc-4b5a-a8b4-a879bae6be07 email grafana OpenID Connect built-in scope: email openid-connect +a5bb3a5f-fd26-4be6-9557-26e20a03d33d address grafana OpenID Connect built-in scope: address openid-connect +d6ffe9fc-a03c-4496-85dc-dbb5e7754587 phone grafana OpenID Connect built-in scope: phone openid-connect +d6077ed7-b265-4f82-9336-24614967bd5d roles grafana OpenID Connect scope for add user roles to the access token openid-connect +699671ab-e7c1-4fcf-beb8-ea54f1471fc1 web-origins grafana OpenID Connect scope for add allowed web origins to the access token openid-connect +c61f5b19-c17e-49a1-91b8-a0296411b928 microprofile-jwt grafana Microprofile - JWT built-in scope openid-connect +f619a55a-d565-4cc0-8bf4-4dbaab5382fe username grafana openid-connect +0a7c7dde-23d7-4a93-bdee-4a8963aee9a4 login grafana login openid-connect +d4723cd4-f717-44b7-a9b0-6c32c5ecd23f name grafana user name openid-connect +\. + + +-- +-- Data for Name: client_scope_attributes; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_scope_attributes (scope_id, value, name) FROM stdin; +0cc71c8c-fb37-41f2-b4d8-13210d3cf8be true display.on.consent.screen +0cc71c8c-fb37-41f2-b4d8-13210d3cf8be ${offlineAccessScopeConsentText} consent.screen.text +47f35d4b-35c7-4c6d-8bae-eff0a5046861 true display.on.consent.screen +47f35d4b-35c7-4c6d-8bae-eff0a5046861 ${samlRoleListScopeConsentText} consent.screen.text +66deef47-2158-4d5b-a75f-0bf42f642e7b true display.on.consent.screen +66deef47-2158-4d5b-a75f-0bf42f642e7b ${profileScopeConsentText} consent.screen.text +66deef47-2158-4d5b-a75f-0bf42f642e7b true include.in.token.scope +94ef659c-4c4a-4a33-98e8-bfcf443e9268 true display.on.consent.screen +94ef659c-4c4a-4a33-98e8-bfcf443e9268 ${emailScopeConsentText} consent.screen.text +94ef659c-4c4a-4a33-98e8-bfcf443e9268 true include.in.token.scope +96a960d2-c203-4ef0-a53c-c3edd01f2305 true display.on.consent.screen +96a960d2-c203-4ef0-a53c-c3edd01f2305 ${addressScopeConsentText} consent.screen.text +96a960d2-c203-4ef0-a53c-c3edd01f2305 true include.in.token.scope +3f705379-3361-486d-b75a-f7b4e4be492c true display.on.consent.screen +3f705379-3361-486d-b75a-f7b4e4be492c ${phoneScopeConsentText} consent.screen.text +3f705379-3361-486d-b75a-f7b4e4be492c true include.in.token.scope +b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 true display.on.consent.screen +b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 ${rolesScopeConsentText} consent.screen.text +b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 false include.in.token.scope +619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 false display.on.consent.screen +619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 consent.screen.text +619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 false include.in.token.scope +42bfb506-bf0d-424e-8649-53a9a93d252d false display.on.consent.screen +42bfb506-bf0d-424e-8649-53a9a93d252d true include.in.token.scope +0e98d5f9-d3f7-4b1d-9791-d442524fc2ab true display.on.consent.screen +0e98d5f9-d3f7-4b1d-9791-d442524fc2ab ${offlineAccessScopeConsentText} consent.screen.text +a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 true display.on.consent.screen +a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 ${samlRoleListScopeConsentText} consent.screen.text +74daf2cd-40d4-4304-87a8-92cdca808512 true display.on.consent.screen +74daf2cd-40d4-4304-87a8-92cdca808512 ${profileScopeConsentText} consent.screen.text +74daf2cd-40d4-4304-87a8-92cdca808512 true include.in.token.scope +96d521d3-facc-4b5a-a8b4-a879bae6be07 true display.on.consent.screen +96d521d3-facc-4b5a-a8b4-a879bae6be07 ${emailScopeConsentText} consent.screen.text +96d521d3-facc-4b5a-a8b4-a879bae6be07 true include.in.token.scope +a5bb3a5f-fd26-4be6-9557-26e20a03d33d true display.on.consent.screen +a5bb3a5f-fd26-4be6-9557-26e20a03d33d ${addressScopeConsentText} consent.screen.text +a5bb3a5f-fd26-4be6-9557-26e20a03d33d true include.in.token.scope +d6ffe9fc-a03c-4496-85dc-dbb5e7754587 true display.on.consent.screen +d6ffe9fc-a03c-4496-85dc-dbb5e7754587 ${phoneScopeConsentText} consent.screen.text +d6ffe9fc-a03c-4496-85dc-dbb5e7754587 true include.in.token.scope +d6077ed7-b265-4f82-9336-24614967bd5d true display.on.consent.screen +d6077ed7-b265-4f82-9336-24614967bd5d ${rolesScopeConsentText} consent.screen.text +d6077ed7-b265-4f82-9336-24614967bd5d false include.in.token.scope +699671ab-e7c1-4fcf-beb8-ea54f1471fc1 false display.on.consent.screen +699671ab-e7c1-4fcf-beb8-ea54f1471fc1 consent.screen.text +699671ab-e7c1-4fcf-beb8-ea54f1471fc1 false include.in.token.scope +c61f5b19-c17e-49a1-91b8-a0296411b928 false display.on.consent.screen +c61f5b19-c17e-49a1-91b8-a0296411b928 true include.in.token.scope +f619a55a-d565-4cc0-8bf4-4dbaab5382fe true display.on.consent.screen +f619a55a-d565-4cc0-8bf4-4dbaab5382fe true include.in.token.scope +0a7c7dde-23d7-4a93-bdee-4a8963aee9a4 true display.on.consent.screen +0a7c7dde-23d7-4a93-bdee-4a8963aee9a4 true include.in.token.scope +d4723cd4-f717-44b7-a9b0-6c32c5ecd23f true display.on.consent.screen +d4723cd4-f717-44b7-a9b0-6c32c5ecd23f true include.in.token.scope +\. + + +-- +-- Data for Name: client_scope_client; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_scope_client (client_id, scope_id, default_scope) FROM stdin; +eed689c6-49da-4d91-98eb-cd495bcc07a3 47f35d4b-35c7-4c6d-8bae-eff0a5046861 t +11c67f5b-dde7-4680-b05b-c9c59d78bda4 47f35d4b-35c7-4c6d-8bae-eff0a5046861 t +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 47f35d4b-35c7-4c6d-8bae-eff0a5046861 t +1e30397c-eac2-41fb-87bc-d90484992e65 47f35d4b-35c7-4c6d-8bae-eff0a5046861 t +3cd285ea-0f6e-43b6-ab5c-d021c33a551b 47f35d4b-35c7-4c6d-8bae-eff0a5046861 t +2f521d09-7304-4b5e-a94b-7cc7300b8b50 47f35d4b-35c7-4c6d-8bae-eff0a5046861 t +eed689c6-49da-4d91-98eb-cd495bcc07a3 66deef47-2158-4d5b-a75f-0bf42f642e7b t +eed689c6-49da-4d91-98eb-cd495bcc07a3 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 t +eed689c6-49da-4d91-98eb-cd495bcc07a3 94ef659c-4c4a-4a33-98e8-bfcf443e9268 t +eed689c6-49da-4d91-98eb-cd495bcc07a3 b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 t +eed689c6-49da-4d91-98eb-cd495bcc07a3 3f705379-3361-486d-b75a-f7b4e4be492c f +eed689c6-49da-4d91-98eb-cd495bcc07a3 0cc71c8c-fb37-41f2-b4d8-13210d3cf8be f +eed689c6-49da-4d91-98eb-cd495bcc07a3 42bfb506-bf0d-424e-8649-53a9a93d252d f +eed689c6-49da-4d91-98eb-cd495bcc07a3 96a960d2-c203-4ef0-a53c-c3edd01f2305 f +11c67f5b-dde7-4680-b05b-c9c59d78bda4 66deef47-2158-4d5b-a75f-0bf42f642e7b t +11c67f5b-dde7-4680-b05b-c9c59d78bda4 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 t +11c67f5b-dde7-4680-b05b-c9c59d78bda4 94ef659c-4c4a-4a33-98e8-bfcf443e9268 t +11c67f5b-dde7-4680-b05b-c9c59d78bda4 b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 t +11c67f5b-dde7-4680-b05b-c9c59d78bda4 3f705379-3361-486d-b75a-f7b4e4be492c f +11c67f5b-dde7-4680-b05b-c9c59d78bda4 0cc71c8c-fb37-41f2-b4d8-13210d3cf8be f +11c67f5b-dde7-4680-b05b-c9c59d78bda4 42bfb506-bf0d-424e-8649-53a9a93d252d f +11c67f5b-dde7-4680-b05b-c9c59d78bda4 96a960d2-c203-4ef0-a53c-c3edd01f2305 f +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 66deef47-2158-4d5b-a75f-0bf42f642e7b t +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 t +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 94ef659c-4c4a-4a33-98e8-bfcf443e9268 t +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 t +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 3f705379-3361-486d-b75a-f7b4e4be492c f +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 0cc71c8c-fb37-41f2-b4d8-13210d3cf8be f +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 42bfb506-bf0d-424e-8649-53a9a93d252d f +63d16a7e-aa65-486e-a0e1-81f928d3e3b8 96a960d2-c203-4ef0-a53c-c3edd01f2305 f +1e30397c-eac2-41fb-87bc-d90484992e65 66deef47-2158-4d5b-a75f-0bf42f642e7b t +1e30397c-eac2-41fb-87bc-d90484992e65 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 t +1e30397c-eac2-41fb-87bc-d90484992e65 94ef659c-4c4a-4a33-98e8-bfcf443e9268 t +1e30397c-eac2-41fb-87bc-d90484992e65 b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 t +1e30397c-eac2-41fb-87bc-d90484992e65 3f705379-3361-486d-b75a-f7b4e4be492c f +1e30397c-eac2-41fb-87bc-d90484992e65 0cc71c8c-fb37-41f2-b4d8-13210d3cf8be f +1e30397c-eac2-41fb-87bc-d90484992e65 42bfb506-bf0d-424e-8649-53a9a93d252d f +1e30397c-eac2-41fb-87bc-d90484992e65 96a960d2-c203-4ef0-a53c-c3edd01f2305 f +3cd285ea-0f6e-43b6-ab5c-d021c33a551b 66deef47-2158-4d5b-a75f-0bf42f642e7b t +3cd285ea-0f6e-43b6-ab5c-d021c33a551b 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 t +3cd285ea-0f6e-43b6-ab5c-d021c33a551b 94ef659c-4c4a-4a33-98e8-bfcf443e9268 t +3cd285ea-0f6e-43b6-ab5c-d021c33a551b b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 t +3cd285ea-0f6e-43b6-ab5c-d021c33a551b 3f705379-3361-486d-b75a-f7b4e4be492c f +3cd285ea-0f6e-43b6-ab5c-d021c33a551b 0cc71c8c-fb37-41f2-b4d8-13210d3cf8be f +3cd285ea-0f6e-43b6-ab5c-d021c33a551b 42bfb506-bf0d-424e-8649-53a9a93d252d f +3cd285ea-0f6e-43b6-ab5c-d021c33a551b 96a960d2-c203-4ef0-a53c-c3edd01f2305 f +2f521d09-7304-4b5e-a94b-7cc7300b8b50 66deef47-2158-4d5b-a75f-0bf42f642e7b t +2f521d09-7304-4b5e-a94b-7cc7300b8b50 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 t +2f521d09-7304-4b5e-a94b-7cc7300b8b50 94ef659c-4c4a-4a33-98e8-bfcf443e9268 t +2f521d09-7304-4b5e-a94b-7cc7300b8b50 b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 t +2f521d09-7304-4b5e-a94b-7cc7300b8b50 3f705379-3361-486d-b75a-f7b4e4be492c f +2f521d09-7304-4b5e-a94b-7cc7300b8b50 0cc71c8c-fb37-41f2-b4d8-13210d3cf8be f +2f521d09-7304-4b5e-a94b-7cc7300b8b50 42bfb506-bf0d-424e-8649-53a9a93d252d f +2f521d09-7304-4b5e-a94b-7cc7300b8b50 96a960d2-c203-4ef0-a53c-c3edd01f2305 f +ef7f6eac-9fff-44aa-a86c-5125d52acc82 47f35d4b-35c7-4c6d-8bae-eff0a5046861 t +ef7f6eac-9fff-44aa-a86c-5125d52acc82 66deef47-2158-4d5b-a75f-0bf42f642e7b t +ef7f6eac-9fff-44aa-a86c-5125d52acc82 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 t +ef7f6eac-9fff-44aa-a86c-5125d52acc82 94ef659c-4c4a-4a33-98e8-bfcf443e9268 t +ef7f6eac-9fff-44aa-a86c-5125d52acc82 b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 t +ef7f6eac-9fff-44aa-a86c-5125d52acc82 3f705379-3361-486d-b75a-f7b4e4be492c f +ef7f6eac-9fff-44aa-a86c-5125d52acc82 0cc71c8c-fb37-41f2-b4d8-13210d3cf8be f +ef7f6eac-9fff-44aa-a86c-5125d52acc82 42bfb506-bf0d-424e-8649-53a9a93d252d f +ef7f6eac-9fff-44aa-a86c-5125d52acc82 96a960d2-c203-4ef0-a53c-c3edd01f2305 f +a5a8fed6-0bca-4646-9946-2fe84175353b a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 t +230081b5-9161-45c3-9e08-9eda5412f7f7 a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 t +6bd2d943-9800-4839-9ddc-03c04930cd9f a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 t +77ff47f8-f578-477d-8c06-e70a846332f5 a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 t +a8698f4f-5fa1-4baa-be05-87d03052af49 a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 t +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 t +a5a8fed6-0bca-4646-9946-2fe84175353b d6077ed7-b265-4f82-9336-24614967bd5d t +a5a8fed6-0bca-4646-9946-2fe84175353b 74daf2cd-40d4-4304-87a8-92cdca808512 t +a5a8fed6-0bca-4646-9946-2fe84175353b 96d521d3-facc-4b5a-a8b4-a879bae6be07 t +a5a8fed6-0bca-4646-9946-2fe84175353b 699671ab-e7c1-4fcf-beb8-ea54f1471fc1 t +a5a8fed6-0bca-4646-9946-2fe84175353b 0e98d5f9-d3f7-4b1d-9791-d442524fc2ab f +a5a8fed6-0bca-4646-9946-2fe84175353b a5bb3a5f-fd26-4be6-9557-26e20a03d33d f +a5a8fed6-0bca-4646-9946-2fe84175353b d6ffe9fc-a03c-4496-85dc-dbb5e7754587 f +a5a8fed6-0bca-4646-9946-2fe84175353b c61f5b19-c17e-49a1-91b8-a0296411b928 f +230081b5-9161-45c3-9e08-9eda5412f7f7 d6077ed7-b265-4f82-9336-24614967bd5d t +230081b5-9161-45c3-9e08-9eda5412f7f7 74daf2cd-40d4-4304-87a8-92cdca808512 t +230081b5-9161-45c3-9e08-9eda5412f7f7 96d521d3-facc-4b5a-a8b4-a879bae6be07 t +230081b5-9161-45c3-9e08-9eda5412f7f7 699671ab-e7c1-4fcf-beb8-ea54f1471fc1 t +230081b5-9161-45c3-9e08-9eda5412f7f7 0e98d5f9-d3f7-4b1d-9791-d442524fc2ab f +230081b5-9161-45c3-9e08-9eda5412f7f7 a5bb3a5f-fd26-4be6-9557-26e20a03d33d f +230081b5-9161-45c3-9e08-9eda5412f7f7 d6ffe9fc-a03c-4496-85dc-dbb5e7754587 f +230081b5-9161-45c3-9e08-9eda5412f7f7 c61f5b19-c17e-49a1-91b8-a0296411b928 f +6bd2d943-9800-4839-9ddc-03c04930cd9f d6077ed7-b265-4f82-9336-24614967bd5d t +6bd2d943-9800-4839-9ddc-03c04930cd9f 74daf2cd-40d4-4304-87a8-92cdca808512 t +6bd2d943-9800-4839-9ddc-03c04930cd9f 96d521d3-facc-4b5a-a8b4-a879bae6be07 t +6bd2d943-9800-4839-9ddc-03c04930cd9f 699671ab-e7c1-4fcf-beb8-ea54f1471fc1 t +6bd2d943-9800-4839-9ddc-03c04930cd9f 0e98d5f9-d3f7-4b1d-9791-d442524fc2ab f +6bd2d943-9800-4839-9ddc-03c04930cd9f a5bb3a5f-fd26-4be6-9557-26e20a03d33d f +6bd2d943-9800-4839-9ddc-03c04930cd9f d6ffe9fc-a03c-4496-85dc-dbb5e7754587 f +6bd2d943-9800-4839-9ddc-03c04930cd9f c61f5b19-c17e-49a1-91b8-a0296411b928 f +77ff47f8-f578-477d-8c06-e70a846332f5 d6077ed7-b265-4f82-9336-24614967bd5d t +77ff47f8-f578-477d-8c06-e70a846332f5 74daf2cd-40d4-4304-87a8-92cdca808512 t +77ff47f8-f578-477d-8c06-e70a846332f5 96d521d3-facc-4b5a-a8b4-a879bae6be07 t +77ff47f8-f578-477d-8c06-e70a846332f5 699671ab-e7c1-4fcf-beb8-ea54f1471fc1 t +77ff47f8-f578-477d-8c06-e70a846332f5 0e98d5f9-d3f7-4b1d-9791-d442524fc2ab f +77ff47f8-f578-477d-8c06-e70a846332f5 a5bb3a5f-fd26-4be6-9557-26e20a03d33d f +77ff47f8-f578-477d-8c06-e70a846332f5 d6ffe9fc-a03c-4496-85dc-dbb5e7754587 f +77ff47f8-f578-477d-8c06-e70a846332f5 c61f5b19-c17e-49a1-91b8-a0296411b928 f +a8698f4f-5fa1-4baa-be05-87d03052af49 d6077ed7-b265-4f82-9336-24614967bd5d t +a8698f4f-5fa1-4baa-be05-87d03052af49 74daf2cd-40d4-4304-87a8-92cdca808512 t +a8698f4f-5fa1-4baa-be05-87d03052af49 96d521d3-facc-4b5a-a8b4-a879bae6be07 t +a8698f4f-5fa1-4baa-be05-87d03052af49 699671ab-e7c1-4fcf-beb8-ea54f1471fc1 t +a8698f4f-5fa1-4baa-be05-87d03052af49 0e98d5f9-d3f7-4b1d-9791-d442524fc2ab f +a8698f4f-5fa1-4baa-be05-87d03052af49 a5bb3a5f-fd26-4be6-9557-26e20a03d33d f +a8698f4f-5fa1-4baa-be05-87d03052af49 d6ffe9fc-a03c-4496-85dc-dbb5e7754587 f +a8698f4f-5fa1-4baa-be05-87d03052af49 c61f5b19-c17e-49a1-91b8-a0296411b928 f +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 d6077ed7-b265-4f82-9336-24614967bd5d t +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 74daf2cd-40d4-4304-87a8-92cdca808512 t +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 96d521d3-facc-4b5a-a8b4-a879bae6be07 t +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 699671ab-e7c1-4fcf-beb8-ea54f1471fc1 t +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 0e98d5f9-d3f7-4b1d-9791-d442524fc2ab f +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 a5bb3a5f-fd26-4be6-9557-26e20a03d33d f +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 d6ffe9fc-a03c-4496-85dc-dbb5e7754587 f +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 c61f5b19-c17e-49a1-91b8-a0296411b928 f +09b79548-8426-4c0e-8e0b-7488467532c7 a1d5ab0b-6c06-4dc5-bdca-3fefe915f4f3 t +09b79548-8426-4c0e-8e0b-7488467532c7 96d521d3-facc-4b5a-a8b4-a879bae6be07 t +09b79548-8426-4c0e-8e0b-7488467532c7 d6077ed7-b265-4f82-9336-24614967bd5d t +09b79548-8426-4c0e-8e0b-7488467532c7 d4723cd4-f717-44b7-a9b0-6c32c5ecd23f t +09b79548-8426-4c0e-8e0b-7488467532c7 0a7c7dde-23d7-4a93-bdee-4a8963aee9a4 t +09b79548-8426-4c0e-8e0b-7488467532c7 74daf2cd-40d4-4304-87a8-92cdca808512 t +\. + + +-- +-- Data for Name: client_scope_role_mapping; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_scope_role_mapping (scope_id, role_id) FROM stdin; +0cc71c8c-fb37-41f2-b4d8-13210d3cf8be 16d5987b-dcbb-4650-8f52-3469f3974846 +0e98d5f9-d3f7-4b1d-9791-d442524fc2ab c49bddc6-ec92-4caa-bc04-57ba80a92eb9 +\. + + +-- +-- Data for Name: client_session; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_session (id, client_id, redirect_uri, state, "timestamp", session_id, auth_method, realm_id, auth_user_id, current_action) FROM stdin; +\. + + +-- +-- Data for Name: client_session_auth_status; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_session_auth_status (authenticator, status, client_session) FROM stdin; +\. + + +-- +-- Data for Name: client_session_note; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_session_note (name, value, client_session) FROM stdin; +\. + + +-- +-- Data for Name: client_session_prot_mapper; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_session_prot_mapper (protocol_mapper_id, client_session) FROM stdin; +\. + + +-- +-- Data for Name: client_session_role; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_session_role (role_id, client_session) FROM stdin; +\. + + +-- +-- Data for Name: client_user_session_note; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.client_user_session_note (name, value, client_session) FROM stdin; +\. + + +-- +-- Data for Name: component; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.component (id, name, parent_id, provider_id, provider_type, realm_id, sub_type) FROM stdin; +bf743b0a-d8f9-4635-bcbe-e1c8b92075e2 Trusted Hosts master trusted-hosts org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy master anonymous +d6e91e34-9d10-46e6-a343-c767cd9817ab Consent Required master consent-required org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy master anonymous +b914dfd7-6556-40b2-8055-bf0a131d9b6a Full Scope Disabled master scope org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy master anonymous +2ab822d8-3278-42f3-a27a-e9d7104ce361 Max Clients Limit master max-clients org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy master anonymous +f95566ed-b955-4668-88fc-e7413fd98615 Allowed Protocol Mapper Types master allowed-protocol-mappers org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy master anonymous +588cf9d4-1fb1-43d5-b454-9f9239d1dda7 Allowed Client Scopes master allowed-client-templates org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy master anonymous +28d2466c-5af6-4786-a8a2-c25d6cb4833f Allowed Protocol Mapper Types master allowed-protocol-mappers org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy master authenticated +1dc5700c-668d-4988-8920-f1b21f22aaa2 Allowed Client Scopes master allowed-client-templates org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy master authenticated +ec24e563-c82e-4a0f-89db-6c2b75e5383a fallback-HS256 master hmac-generated org.keycloak.keys.KeyProvider master \N +281b3291-097c-42af-8d52-46606d3b669f fallback-RS256 master rsa-generated org.keycloak.keys.KeyProvider master \N +80af2f23-4a51-498a-a011-732cf9cfa8f8 rsa-generated grafana rsa-generated org.keycloak.keys.KeyProvider grafana \N +a5b75d44-0bf1-400e-9e87-4293efeb3051 hmac-generated grafana hmac-generated org.keycloak.keys.KeyProvider grafana \N +9877acf2-e1cc-4038-a3c2-75db29b432e0 aes-generated grafana aes-generated org.keycloak.keys.KeyProvider grafana \N +5a1232c0-4243-454f-a26e-5d771efd3585 Trusted Hosts grafana trusted-hosts org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy grafana anonymous +5382514f-29bc-4cfd-b1b9-e1cf85dc1ed3 Consent Required grafana consent-required org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy grafana anonymous +1e55b1d2-5402-4b33-8c4a-59d0d5ddba32 Full Scope Disabled grafana scope org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy grafana anonymous +3021f045-3220-4e56-872c-d3491f6601f6 Max Clients Limit grafana max-clients org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy grafana anonymous +cb7cf482-8ac6-4999-ab67-1d48fef549f5 Allowed Protocol Mapper Types grafana allowed-protocol-mappers org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy grafana anonymous +7488860e-5bba-4f89-bde2-c63b0290cc0f Allowed Client Scopes grafana allowed-client-templates org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy grafana anonymous +261e38de-3e1f-40a3-9200-f5aac1975701 Allowed Protocol Mapper Types grafana allowed-protocol-mappers org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy grafana authenticated +4223e0de-8a82-464f-b466-048d3682d8df Allowed Client Scopes grafana allowed-client-templates org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy grafana authenticated +\. + + +-- +-- Data for Name: component_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.component_config (id, component_id, name, value) FROM stdin; +9a09b41f-3340-49d7-b65c-fb02300ac5a0 1dc5700c-668d-4988-8920-f1b21f22aaa2 allow-default-scopes true +81cf1edb-60cb-4336-8e61-3676e82a8496 f95566ed-b955-4668-88fc-e7413fd98615 allowed-protocol-mapper-types saml-role-list-mapper +ebd9fd67-3468-4640-a130-dadf8bd0d3dd f95566ed-b955-4668-88fc-e7413fd98615 allowed-protocol-mapper-types oidc-address-mapper +5a2075e1-ff50-4221-ba0f-13c221b745f1 f95566ed-b955-4668-88fc-e7413fd98615 allowed-protocol-mapper-types oidc-sha256-pairwise-sub-mapper +d47557df-07ff-4cae-bedd-b584c0697852 f95566ed-b955-4668-88fc-e7413fd98615 allowed-protocol-mapper-types saml-user-attribute-mapper +91a7d433-6520-4710-a95a-b1d6ed1932f7 f95566ed-b955-4668-88fc-e7413fd98615 allowed-protocol-mapper-types oidc-usermodel-attribute-mapper +35c3fff3-1779-4ea1-b8c2-b93b746ad4df f95566ed-b955-4668-88fc-e7413fd98615 allowed-protocol-mapper-types oidc-usermodel-property-mapper +64312a90-809f-455e-b89e-52b9f0a34229 f95566ed-b955-4668-88fc-e7413fd98615 allowed-protocol-mapper-types oidc-full-name-mapper +5f0a8c49-4279-41bc-9b1f-cdf3acf14bc9 f95566ed-b955-4668-88fc-e7413fd98615 allowed-protocol-mapper-types saml-user-property-mapper +2ae4acc4-c6d6-4d2f-92c6-3a222a7d078a 28d2466c-5af6-4786-a8a2-c25d6cb4833f allowed-protocol-mapper-types oidc-sha256-pairwise-sub-mapper +880a35e4-65a1-4697-836d-fbc46641d676 28d2466c-5af6-4786-a8a2-c25d6cb4833f allowed-protocol-mapper-types oidc-full-name-mapper +b087e631-754f-4c03-8cfc-354c7e7456fe 28d2466c-5af6-4786-a8a2-c25d6cb4833f allowed-protocol-mapper-types oidc-usermodel-property-mapper +079e63d4-0862-4e63-a62f-1a168cbbc25c 28d2466c-5af6-4786-a8a2-c25d6cb4833f allowed-protocol-mapper-types saml-user-property-mapper +da61fbc2-7533-4bc1-b0c4-357db9f108e4 28d2466c-5af6-4786-a8a2-c25d6cb4833f allowed-protocol-mapper-types oidc-address-mapper +37a4be58-26b0-4d4a-9c41-a89b27fc25f6 28d2466c-5af6-4786-a8a2-c25d6cb4833f allowed-protocol-mapper-types saml-user-attribute-mapper +0eb7fcb8-1afb-4d73-bbf7-9827e7669990 28d2466c-5af6-4786-a8a2-c25d6cb4833f allowed-protocol-mapper-types oidc-usermodel-attribute-mapper +1e0e9459-5116-46b7-a247-0212c2e8d719 28d2466c-5af6-4786-a8a2-c25d6cb4833f allowed-protocol-mapper-types saml-role-list-mapper +6bd5567c-e438-4884-a7ad-4f0450f2c75b 2ab822d8-3278-42f3-a27a-e9d7104ce361 max-clients 200 +13ec6e6a-b4a9-4ca6-bee5-fe6d7df07bd4 588cf9d4-1fb1-43d5-b454-9f9239d1dda7 allow-default-scopes true +9e4f8b41-d2e6-4b67-9784-f319b804b66b bf743b0a-d8f9-4635-bcbe-e1c8b92075e2 host-sending-registration-request-must-match true +4ca659f4-e9cf-45ff-a891-42b973fee220 bf743b0a-d8f9-4635-bcbe-e1c8b92075e2 client-uris-must-match true +69a74c94-26ec-40d1-8859-c3077a84c374 ec24e563-c82e-4a0f-89db-6c2b75e5383a secret R0msuv6OjhKyzluLnbkkgkM1s0Mi5aK0Ck-3o-kbGMwsE2TPdzsoH-9Z_P2OEmJ6dqppkp9H8eZE9pdC8uDJHA +19b2af74-0667-4ade-993e-d734eb465a16 ec24e563-c82e-4a0f-89db-6c2b75e5383a algorithm HS256 +b2a0d967-1111-4c79-9273-e05450fe26c5 ec24e563-c82e-4a0f-89db-6c2b75e5383a priority -100 +63fba7e3-ad5f-475b-a367-8c0af5302b0b ec24e563-c82e-4a0f-89db-6c2b75e5383a kid e19299b5-a2bc-45d4-bd71-2beeaab7dfbf +c2e6f8d9-fa94-4cda-8cab-558695b50ae4 281b3291-097c-42af-8d52-46606d3b669f privateKey MIIEowIBAAKCAQEAjuRpHyJ/1ki93NW9sMrppl7MC4DfzODtvirvslRfYvhVMm0rMWPw2q1bqrrzQMR+VdKQbp+M7jt9PYryNrzsGy+iiD7mi+KTrLnmQ+c+r867flwbivBB0LR63LidcpOExVUuREwilOcywfFXeFiBQ9Tbxcwztu6J+b9kjlSYNSg+JCzn2Wn2kMQXSTd2ddxBHs8bmxyibg18UMovJLt42E8/C8+3iP7++5gSy9FeVCjf1eIh7EF3LwPNJxeyJhd3fNfmB8YnWGoz2Jw1RZE7Hm/BW5uSsM7rQpkvEACHbsMCSlzxvJjzSGHYG+PksMgfiDLiezxBQhgD+5Z5DhImywIDAQABAoIBAFAAKaq40gHS8Bm3wWA9+tqesHawTJyUQgb6WwDopA7xIiH9ZPVeEvcbn/rSeGaGnITIQvzsbybiP5g5NqrW0wnVfZXyQXmH/U3zNqxFx57+i5KPVwxOv0puAWuaIOyJEwi4TBMI3UOovY4/5M0IIDct8W2oijudCbq+ITpeumjnrJxwV0aSlD6hYKiL4OWtcFJiAu+ZeSOlQOTY2jielAvlnOmFygx2KWE7I/JUhu2w2hO2Q1aQHPgbbPR8VxRZLID411VztuW3T4w9ur057YRIqB16ZwI6knMETGxHc1rYo6mMh+t45rWHzJ+hKJC/NKQ+IRI1U4YfUXDkdAg2b+ECgYEAyHdHS9p9lcQQFqkZVL2FLeEAbhkosAZjIe7gblj2Kt57QKJ6NJangV61hqHAr93uU3DJu5bertsCZjqN7SHelusyUXEBc4SrFhftjZNFsnRB/yj3jU3ag11Ro40cBW7fosUgrieUI3CZEB0SPdXoc7LgwheEViKu9zyPl+PcyD8CgYEAtnoZBJMaEl6mjkXjLhKM5U5eT50pDPct9p/pl5fwpBxPkJJXBYMuybjff1D+VlAboMOImLOijjjgS7bcr/DvnOCIhqy9FMT+57KxAaa1ZcrQ4MUj3Tj4Vg0kDMbu5GMZ40lmOjR6tTrIy0riOk64rJAPztcLoV2gFl98Dwp83nUCgYA6UpGYnQGqn/c6UIpBID5uAac5YPJ4e/M9fR0onZNJF59uR5ccU7R6LA7OE6NWx0++UPMwM42n+6nwChseoZr794OVNDaC4FdSPzXq2a0OZUqKLOYQ41SuoWjOF5DOd9pypb2DTZqI0QqHKJ4VBXXyq1k+vs7OrJqQ7bqtKyshywKBgQCw/1PfBRTH9rlVzWJkISg7kD2YudfEtMoHq+s32PBZLwDaOahhN3Kdxk47v4NEk6WI1cFcZPnrPC4MIw6DNpAlOgITp+AsEj0y3zgkYuEXIJhlPbPg9E6loU9zeU7lh17oAR1AngDcY227CyLO7ebhs0cyGZM1bYxHx0ydhk3CtQKBgHIT08PBgZYhHPJZ09H76gKTKcoL2XS4uEJb6/xjp4ACa1wlFhWZmUOq/DhWeAx313Mf5sRAGQGieiaPamBwt9cOai6V+Agk1YMS97Fg4eV/aYThor8qi1O3dEGSFw4GaGGwvL7Trvcpogk2N6+Pm2LJ5aPy048FPnR+YZ9/yIzr +c35d99d1-ca8d-4f82-bbb8-0a7b663e9d09 281b3291-097c-42af-8d52-46606d3b669f algorithm RS256 +3f4c9dd6-37cc-444d-b9d0-caba781e20cd 281b3291-097c-42af-8d52-46606d3b669f priority -100 +4bc9ffd3-9f15-442f-bdbf-aa5a73adfa65 281b3291-097c-42af-8d52-46606d3b669f certificate MIICmzCCAYMCBgF+u1WYqDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjIwMjAyMTY0NTU2WhcNMzIwMjAyMTY0NzM2WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCO5GkfIn/WSL3c1b2wyummXswLgN/M4O2+Ku+yVF9i+FUybSsxY/DarVuquvNAxH5V0pBun4zuO309ivI2vOwbL6KIPuaL4pOsueZD5z6vzrt+XBuK8EHQtHrcuJ1yk4TFVS5ETCKU5zLB8Vd4WIFD1NvFzDO27on5v2SOVJg1KD4kLOfZafaQxBdJN3Z13EEezxubHKJuDXxQyi8ku3jYTz8Lz7eI/v77mBLL0V5UKN/V4iHsQXcvA80nF7ImF3d81+YHxidYajPYnDVFkTseb8Fbm5KwzutCmS8QAIduwwJKXPG8mPNIYdgb4+SwyB+IMuJ7PEFCGAP7lnkOEibLAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIXG0C7YyWpCrpIUfq3dKFSISjUJ3yfJsm7UX43YeFhSk0XKaeNaP0IfDa1zu4x2BbmsKZn8QI6DHENRPxcVFutkjuVbdLUWz8JxnpKzvKHiJX+CnL1PZ1RKM6coQi/hX1uI3dNM/kQk/FzkhspD2l3Q/7F2ix5sFk6G0UrlvJ7NgR2hy4DujhvsO+tF5hq2ZvLn4WCbiMgLPzDIcvDSm1ytWPSpSKGJs+zANotZnVRTkYF5bXKQH8EEdbgcU9sIHy/UJ08bSQ/9H/j2X8W+62eOchQjzNK5DslwoN2jakK0JJeNvRfIM8ETucLC4e+jCAB2QaeKnjpX7CrA8hiN8AM= +a3fdfa84-e986-426d-ba8e-49f2bdb91cd8 a5b75d44-0bf1-400e-9e87-4293efeb3051 priority 100 +0240dece-6d4d-41e0-a64f-5e863a44354a a5b75d44-0bf1-400e-9e87-4293efeb3051 algorithm HS256 +93341052-b55f-43fd-8f15-72b5812024d3 a5b75d44-0bf1-400e-9e87-4293efeb3051 secret YrYYWSiul7DxTUeKd5ZeFlyrDLvJj0aOY5UMijdFx6qaDkwjMSl74kMAso4cID-qX582X_n-_vcbWFkwpdJDyA +ea21db3f-c474-445c-926d-e7d161e5721f a5b75d44-0bf1-400e-9e87-4293efeb3051 kid bb678665-e694-435a-b5bb-8c11e4727c1c +bb8c28ca-bb74-4a07-82c3-8293354517be 9877acf2-e1cc-4038-a3c2-75db29b432e0 secret mjCx83NwCZkLHZ5sRvZ7lw +08300dda-1f12-45b7-98b5-23805cb1ed84 9877acf2-e1cc-4038-a3c2-75db29b432e0 priority 100 +64ea89c8-a2ce-4d2d-896d-aa49e7ca9fcc 9877acf2-e1cc-4038-a3c2-75db29b432e0 kid 7d80efc5-222b-4b6d-9b99-c3b516a59733 +bec1483f-75e7-46e8-916c-102db4cbefb5 80af2f23-4a51-498a-a011-732cf9cfa8f8 certificate MIICnTCCAYUCBgF+u1ir8jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdncmFmYW5hMB4XDTIyMDIwMjE2NDkxN1oXDTMyMDIwMjE2NTA1N1owEjEQMA4GA1UEAwwHZ3JhZmFuYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKg5kB303DkDs5jSW1b7b7kvKfxIJorHD+7wPz2TcisfTu7rchrqAJiR/HtsPICyAw1h5ef8fGgCJf/k0z00osl/COvK8iHUdvGUnubuKUXaVwlbyaTnnyjSMUAkx+67OCrkY9B2drtZrtVc+fwnggqCsCkpoXg97tcUyfPlcUJnanxsYbirZ5KH+/e+x1jlsuBiwxascmB4IoT/zJknk5l1IVXmSOiDgqhzKRfHhVlRijOlfKyCn/EDtiv7wyQTP9wvd97zGPJqkkF2yNxueMftJsgGkF6+CZMY71BioOWAt2V8OwI32b/1v30DhtBmKdoUNGpEeCjSk91zzZqTFZUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEABlW64QxuREB81VMGsyhj4Q5RykFaVuD5O8YlwUpmVfAVLzb0Drf54Kn4bnpnckKyYV+T+HsN4QXt81UE41xH0Aai2H3vrGH+PJf6aLPCDE+jpMqtN3n6IgImJXJPL8upMfhhWDv4nkM4uynEwWupzmrKi4oJuTETSMktJby4o6//XWnCzCVMoAGFJU4gtjBUzOMLW26zD+yc+BuUtfR3HzItVHSZKQSNSFO0kVS68RgrER8qJw07z3BOJ2bPpPM0PYyEngGMaowz/T6lI32ymGMWYMAnslthS1KAW9xcTBwnrW1nMhe5a0LPxIktys/wJtxIHZLc5sOddGT4xYklLg== +48e8b904-1393-43a4-aaa1-30e2d9634b36 80af2f23-4a51-498a-a011-732cf9cfa8f8 privateKey MIIEowIBAAKCAQEAqDmQHfTcOQOzmNJbVvtvuS8p/EgmiscP7vA/PZNyKx9O7utyGuoAmJH8e2w8gLIDDWHl5/x8aAIl/+TTPTSiyX8I68ryIdR28ZSe5u4pRdpXCVvJpOefKNIxQCTH7rs4KuRj0HZ2u1mu1Vz5/CeCCoKwKSmheD3u1xTJ8+VxQmdqfGxhuKtnkof7977HWOWy4GLDFqxyYHgihP/MmSeTmXUhVeZI6IOCqHMpF8eFWVGKM6V8rIKf8QO2K/vDJBM/3C933vMY8mqSQXbI3G54x+0myAaQXr4JkxjvUGKg5YC3ZXw7AjfZv/W/fQOG0GYp2hQ0akR4KNKT3XPNmpMVlQIDAQABAoIBAF3kEt3FZoyj1j97WOOJXmf7PPHDy081n1z61jEl9FjBFqse2gbPiBmfkU3JsVMbB70WYN1D/KOIX3EdZBELKbhQoMgJ826SSPi4vJ+jWYHVRTLB+h+B70E3X6mvXa+O6uB1rIgTNl2Gxp/rTtM/scLwAiZXR/n2hzGgNr9b1gT7D7kyCUIKDiJsQed2pA6ZbSDNTQQDE2qeN/Rr/+VV4XRuIIdBXn+Brap8ihjx4Gnn/SDBKM3MZoacwgS+9CZNhLjs8Ou4xD+KyitbbGGY4ZK/ZW2eSAH4ra8vVGTDK6bJrMTAE/73A23Evp/sKtRkFqcNumJ3rCykcAJorDUK48ECgYEA0TpkTwK6f2a72ncmw7Xzy4zdu+FdkI9PzWmS4IrbYOYRwsRMQLowsUPIdtU0EMrs7PRl1dSQa3/kAnw5J8wogkqVf4dJ3/lb/N5qtsjHP87N35QMXnMdE/BC6o7t5e+iRoHMloAuSBonGC9uBn1UeHWrNeLCB8+upJoSEBKbEQ0CgYEAzdSoKL4CJo31gVEqoJFPzPemsYGmV6Zl1Vj8mqMbQd8wcJoSJWbNV/3sfpIqdvOJOXQrzm+LYIbhhUnS3ZlGBmFsqUtC7dw3AqLg53msCyJLxmCIib0m/ONCY9qczV4OkUM6HVAdgzmMqqhk5ZB70IdUyStn2meqXKXLGFlLJKkCgYEAuizHTTcUVIFJ7x/PMp8ZjKqQM7pZ02Rykkm7FGr6wsJ2U2TwpTgIU/QI0RTt+3NWV5MxepBm4gEvFrcK9MrJ0QYk+RGdPttYay5Ors8B3Vlb//Jw/ypXWYKVSLpeHhiZwTuGnPT6OdZrqy2pLcUgAQBTlONt3B2FPZqLMBoeOZECgYBuCwm0bpF7x13AO4LMwaOmc6jdMfGa3s2G2MKEcjt6ZjbhnJ2i/Wk/Z/RuXvrxCZcN7nwVLDGZ88LSnftsmiuD8cZEZIZt4NRQRoBzgOtoMHfOoYGeElCr11yBQjme2nBzXTvOvCxrIfOAsfLvgOWRQSklPF2TuOSuD72bUPIJsQKBgBbeb8rIXKRRFC3A0IkKm8C51gG8mMgPdxZMKKuVhpb6D8B5aPh+WW44yNOpn7LInYI4jzf3Kv+kcj0PNH3nFaplnyCGFfmUIg27SAsRcrJcTlXtIKooZw6oPU+dQfAo11suArVAVD6OQc/7z99VoIpLN0cUHfS/g0H1dx/r/32o +258b8704-0587-4208-996f-96627992e370 80af2f23-4a51-498a-a011-732cf9cfa8f8 priority 100 +12cd94c5-bd7e-4a5c-95e2-256b0bcf14bd 261e38de-3e1f-40a3-9200-f5aac1975701 allowed-protocol-mapper-types oidc-address-mapper +f33b34f1-3793-4786-a281-b286fce52f45 261e38de-3e1f-40a3-9200-f5aac1975701 allowed-protocol-mapper-types saml-role-list-mapper +d056c1de-9aa1-46a0-a644-fafb33088967 261e38de-3e1f-40a3-9200-f5aac1975701 allowed-protocol-mapper-types saml-user-property-mapper +de895b7b-16e0-4f8e-95c4-055b4fd70c91 261e38de-3e1f-40a3-9200-f5aac1975701 allowed-protocol-mapper-types saml-user-attribute-mapper +aec970eb-6722-4ed6-b8d3-4bbbfb2e3324 261e38de-3e1f-40a3-9200-f5aac1975701 allowed-protocol-mapper-types oidc-usermodel-attribute-mapper +43020e0a-dac7-4255-9e30-b838279905d4 261e38de-3e1f-40a3-9200-f5aac1975701 allowed-protocol-mapper-types oidc-usermodel-property-mapper +b8ec71f0-39a3-476d-baae-ee3632cadd2a 261e38de-3e1f-40a3-9200-f5aac1975701 allowed-protocol-mapper-types oidc-full-name-mapper +7bbfd5ae-854e-42b6-ac26-017656bafa61 261e38de-3e1f-40a3-9200-f5aac1975701 allowed-protocol-mapper-types oidc-sha256-pairwise-sub-mapper +07031bc1-b7b6-44b1-bdf7-8b9f5a31db40 3021f045-3220-4e56-872c-d3491f6601f6 max-clients 200 +c9bba7d6-e8f7-46bd-9c58-15cf58860eae cb7cf482-8ac6-4999-ab67-1d48fef549f5 allowed-protocol-mapper-types saml-role-list-mapper +5c047fce-366d-4c39-8846-14a975a4dc07 cb7cf482-8ac6-4999-ab67-1d48fef549f5 allowed-protocol-mapper-types saml-user-attribute-mapper +8961856f-6ae2-4e98-b372-0e9c18ee8e17 cb7cf482-8ac6-4999-ab67-1d48fef549f5 allowed-protocol-mapper-types oidc-usermodel-attribute-mapper +79445172-82cc-46fd-97ef-059b9f75ea39 cb7cf482-8ac6-4999-ab67-1d48fef549f5 allowed-protocol-mapper-types oidc-full-name-mapper +db4114c8-392d-427a-9e23-c430401cd93f cb7cf482-8ac6-4999-ab67-1d48fef549f5 allowed-protocol-mapper-types oidc-sha256-pairwise-sub-mapper +b52190d4-ee85-492b-9589-dbfee4afa60d cb7cf482-8ac6-4999-ab67-1d48fef549f5 allowed-protocol-mapper-types oidc-address-mapper +a0173a1b-dd1a-450c-8a6e-f2e1d7a5d3d8 cb7cf482-8ac6-4999-ab67-1d48fef549f5 allowed-protocol-mapper-types saml-user-property-mapper +0e4535b0-2979-40f7-ad71-b57275cd0fdc cb7cf482-8ac6-4999-ab67-1d48fef549f5 allowed-protocol-mapper-types oidc-usermodel-property-mapper +aa3d34ee-58f9-4017-83ff-69f252d2b54b 4223e0de-8a82-464f-b466-048d3682d8df allow-default-scopes true +e77f7d11-31d8-468e-8232-cd5045556d23 5a1232c0-4243-454f-a26e-5d771efd3585 host-sending-registration-request-must-match true +230d9d99-1b5c-49c5-853a-75967443a767 5a1232c0-4243-454f-a26e-5d771efd3585 client-uris-must-match true +53a1ee77-7350-40cb-be63-6ac417f14e6f 7488860e-5bba-4f89-bde2-c63b0290cc0f allow-default-scopes true +\. + + +-- +-- Data for Name: composite_role; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.composite_role (composite, child_role) FROM stdin; +4a3204aa-320e-4584-b8ee-ea2989b3f330 847ebc80-6849-4c47-9f9e-5bba0c0d754d +4a3204aa-320e-4584-b8ee-ea2989b3f330 103dc6a6-5e7a-4c27-b4f0-9dbb1fdcf214 +4a3204aa-320e-4584-b8ee-ea2989b3f330 13c94e3b-b22f-4503-bc56-75e1bd2a927f +4a3204aa-320e-4584-b8ee-ea2989b3f330 4364a376-4ed0-4051-aeab-609f62420e5d +4a3204aa-320e-4584-b8ee-ea2989b3f330 f12af4b7-8828-47a5-abbc-dbb09b9d409e +4a3204aa-320e-4584-b8ee-ea2989b3f330 2606a5b9-699b-488a-a819-d6f368e66697 +4a3204aa-320e-4584-b8ee-ea2989b3f330 2cf34980-2606-4faf-bc40-b9a47c69ef1c +4a3204aa-320e-4584-b8ee-ea2989b3f330 13e61c6b-aff6-4ef8-ab56-ad4abefcb101 +4a3204aa-320e-4584-b8ee-ea2989b3f330 632bad74-a33f-4fd5-9393-ec0a07898b1a +4a3204aa-320e-4584-b8ee-ea2989b3f330 4607a008-f45c-45f5-b506-6de020b7e366 +4a3204aa-320e-4584-b8ee-ea2989b3f330 edd471cc-81d5-43e4-bb43-41fe88ff537d +4a3204aa-320e-4584-b8ee-ea2989b3f330 4c2b4e2a-e792-4ffd-969d-e33ecdf7158f +4a3204aa-320e-4584-b8ee-ea2989b3f330 38282bc7-ea21-46db-a36e-ca621d3275b4 +4a3204aa-320e-4584-b8ee-ea2989b3f330 12111f4a-16ee-4ee7-8576-7956b9440dc5 +4a3204aa-320e-4584-b8ee-ea2989b3f330 f417ae21-5fb4-40fb-bda8-54c61ce7461d +4a3204aa-320e-4584-b8ee-ea2989b3f330 7adeaf33-05d3-4a81-a7bf-f99c721b5d9c +4a3204aa-320e-4584-b8ee-ea2989b3f330 60870d03-d96a-4371-bdad-e3fac925a8df +4a3204aa-320e-4584-b8ee-ea2989b3f330 94363dbd-a6b8-4678-8231-50208c32c22c +f12af4b7-8828-47a5-abbc-dbb09b9d409e 7adeaf33-05d3-4a81-a7bf-f99c721b5d9c +4364a376-4ed0-4051-aeab-609f62420e5d f417ae21-5fb4-40fb-bda8-54c61ce7461d +4364a376-4ed0-4051-aeab-609f62420e5d 94363dbd-a6b8-4678-8231-50208c32c22c +619ba870-921e-4f28-b26c-89b11f39dddf a42d235d-2864-4a99-9592-211d89d0407d +828c3ba8-a13d-49f5-8975-8eb00afbf7de a1a08dbc-4553-4be7-85f5-88c417bdcd45 +4a3204aa-320e-4584-b8ee-ea2989b3f330 b44e0fe0-0fb7-4e12-a6f0-b352431a0f57 +4a3204aa-320e-4584-b8ee-ea2989b3f330 95dfed9c-47fe-489b-aa28-52f0d7aa7c49 +4a3204aa-320e-4584-b8ee-ea2989b3f330 07e1586d-a943-46d9-9c3d-1f3544c8c27f +4a3204aa-320e-4584-b8ee-ea2989b3f330 293d0c06-6dce-4303-9cd3-dfdd6d1275b8 +4a3204aa-320e-4584-b8ee-ea2989b3f330 cfdeeb7b-c70e-496b-9605-70377168a6cb +4a3204aa-320e-4584-b8ee-ea2989b3f330 74252705-a339-4513-97ca-d5617977d5ff +4a3204aa-320e-4584-b8ee-ea2989b3f330 77c3f67e-21d7-4c18-9971-4baf4c20eeaa +4a3204aa-320e-4584-b8ee-ea2989b3f330 5de01bf1-bfac-4ea2-8fb1-ed95594fe1da +4a3204aa-320e-4584-b8ee-ea2989b3f330 a72adc0b-5220-48e4-a66a-9e15dca5f574 +4a3204aa-320e-4584-b8ee-ea2989b3f330 f29b8efa-3c08-410a-a5c0-15b52253d2e2 +4a3204aa-320e-4584-b8ee-ea2989b3f330 dd3ecc72-aaee-43d5-8f7e-f6dcdfb5a608 +4a3204aa-320e-4584-b8ee-ea2989b3f330 9d5a8bab-e112-4e1c-8196-604f3d0143ea +4a3204aa-320e-4584-b8ee-ea2989b3f330 ffff4251-e0a4-4f9c-8bf6-5461b2f52766 +4a3204aa-320e-4584-b8ee-ea2989b3f330 5fafdde9-71f7-4f67-9c1d-f3f4bc7f5128 +4a3204aa-320e-4584-b8ee-ea2989b3f330 6cfc2ac6-bdd7-4b90-ac16-27a75f2eb00a +4a3204aa-320e-4584-b8ee-ea2989b3f330 c3ded8eb-c970-4e43-bea9-5e07795d20ef +4a3204aa-320e-4584-b8ee-ea2989b3f330 811c2a39-6614-46fb-acf5-889d52248171 +4a3204aa-320e-4584-b8ee-ea2989b3f330 2a90f228-2ca4-413f-bc4b-7939af8abcbf +cfdeeb7b-c70e-496b-9605-70377168a6cb c3ded8eb-c970-4e43-bea9-5e07795d20ef +293d0c06-6dce-4303-9cd3-dfdd6d1275b8 2a90f228-2ca4-413f-bc4b-7939af8abcbf +293d0c06-6dce-4303-9cd3-dfdd6d1275b8 6cfc2ac6-bdd7-4b90-ac16-27a75f2eb00a +85afffb5-2069-4873-b6c8-08159c1e4bdd d0e4028d-a604-427a-9262-a1a9513dafc8 +85afffb5-2069-4873-b6c8-08159c1e4bdd 2b8b60c5-d388-4925-b735-858df38dae6e +85afffb5-2069-4873-b6c8-08159c1e4bdd e9c997c8-ad6b-4a99-81e1-c248e94fbeac +85afffb5-2069-4873-b6c8-08159c1e4bdd 8c4449b9-5add-40ba-a19f-cf5d80425e68 +85afffb5-2069-4873-b6c8-08159c1e4bdd a5f31b90-986b-46d5-a385-a639b4e19e37 +85afffb5-2069-4873-b6c8-08159c1e4bdd 99bd546f-a5ed-47f8-862c-9a5e8345bf3b +85afffb5-2069-4873-b6c8-08159c1e4bdd 9096d8df-9d5b-4fb5-b93e-49acc6df0be5 +85afffb5-2069-4873-b6c8-08159c1e4bdd 03230264-ed7a-46b2-939d-53ebe9a59812 +85afffb5-2069-4873-b6c8-08159c1e4bdd 2240d1de-5ac4-44ac-91be-cee70e1dd22b +85afffb5-2069-4873-b6c8-08159c1e4bdd 6d2fd708-445b-44a8-b950-f1350a15dd14 +85afffb5-2069-4873-b6c8-08159c1e4bdd 82266aa3-67ea-485a-a078-4671eb141853 +85afffb5-2069-4873-b6c8-08159c1e4bdd d6dad388-8c69-4bba-940e-371afc98042e +85afffb5-2069-4873-b6c8-08159c1e4bdd 5d7868e1-0c4a-46cc-8bac-bd19c0ea1bde +85afffb5-2069-4873-b6c8-08159c1e4bdd 85e6229e-e246-4e9a-8b39-7bae49754f7d +85afffb5-2069-4873-b6c8-08159c1e4bdd bc618c28-98d1-477d-b4fc-c5ec7cd7f271 +85afffb5-2069-4873-b6c8-08159c1e4bdd 5059b239-0dce-4bb2-9c55-a6afc8dcbe3b +85afffb5-2069-4873-b6c8-08159c1e4bdd ac28461f-3416-4af4-be65-abc739dbeee5 +8c4449b9-5add-40ba-a19f-cf5d80425e68 bc618c28-98d1-477d-b4fc-c5ec7cd7f271 +e9c997c8-ad6b-4a99-81e1-c248e94fbeac ac28461f-3416-4af4-be65-abc739dbeee5 +e9c997c8-ad6b-4a99-81e1-c248e94fbeac 85e6229e-e246-4e9a-8b39-7bae49754f7d +18a7066b-fe71-410e-9581-69f78347ec29 68fdbd76-8688-47a6-b68d-3298a5401f05 +c7e799a5-1250-4bc8-b7c6-ffdc58361477 daaedcc6-e7a6-488e-921e-7022aa808da7 +4a3204aa-320e-4584-b8ee-ea2989b3f330 b8a4faaf-86d9-43eb-bb18-0eaa654b35a7 +85afffb5-2069-4873-b6c8-08159c1e4bdd 5e2301d7-2a9e-4f2d-a940-9bd442b15d8c +\. + + +-- +-- Data for Name: credential; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.credential (id, salt, type, user_id, created_date, user_label, secret_data, credential_data, priority) FROM stdin; +d4b2c483-1dd3-47f6-86bf-42548009918d \N password 74e29604-ff35-42bb-a26d-4d0b81ef0917 1643820449817 \N {"value":"Hou7HlbGvohOx6II0VSCP4BIGI4Cyzy+BcXbPUQe/kaMQzNU77kH2pOKZ236UPfkiCyOLe7A3oS0afExA+ymAQ==","salt":"urXvCw0KdWf9s74km4G+lA==","additionalParameters":{}} {"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}} 10 +b8c9b8b4-5943-43fe-9274-d63fd3e4a139 \N password c685749a-645e-4396-b9ee-6eedbfd89d5e 1656420634344 \N {"value":"IAOFzbDfWwzosZc+Z5nFm/i0B4foqmU4Q0EKG34RU3iwlIYUseEB3BoJqLEfM3Rj9oOSryEbCzblWRDS/5Padw==","salt":"7VR1+KwLVRZ6PenxaQoQTA==","additionalParameters":{}} {"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}} 10 +94aeafd3-71a5-4966-b2b6-34a083df6e92 \N password bdce2246-bb51-4f55-bb81-b7b8856225bc 1656425248776 \N {"value":"uD8KlRNocvZwYq1VZUShVp88zEtMUEeQnLYkW8ZvZXDdn1w1EahwnpNWYIc5QewEm3Nnf3DBYlUUrrbMC4XyfQ==","salt":"REwgUSsxRA/sqM5ujSrpcg==","additionalParameters":{}} {"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}} 10 +624725ce-9e36-4501-8bc8-ec39ee6b98d5 \N password 56eff2b3-e36a-4e3e-84a1-361ad312667b 1656428741229 \N {"value":"4UBzDNd3oPxP54/z7ez1Bd3xSfKJBpbE3rQppM3Xg+2bLaLNoU90TPEK+8SWbpMAFBKHz53qPWrZ50MbNgcGSA==","salt":"iTNvn3xr0acn9wqQxJ3d/A==","additionalParameters":{}} {"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}} 10 +\. + + +-- +-- Data for Name: databasechangelog; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.databasechangelog (id, author, filename, dateexecuted, orderexecuted, exectype, md5sum, description, comments, tag, liquibase, contexts, labels, deployment_id) FROM stdin; +1.0.0.Final-KEYCLOAK-5461 sthorger@redhat.com META-INF/jpa-changelog-1.0.0.Final.xml 2022-02-02 16:47:26.017844 1 EXECUTED 7:4e70412f24a3f382c82183742ec79317 createTable tableName=APPLICATION_DEFAULT_ROLES; createTable tableName=CLIENT; createTable tableName=CLIENT_SESSION; createTable tableName=CLIENT_SESSION_ROLE; createTable tableName=COMPOSITE_ROLE; createTable tableName=CREDENTIAL; createTable tab... \N 3.5.4 \N \N 3820445829 +1.0.0.Final-KEYCLOAK-5461 sthorger@redhat.com META-INF/db2-jpa-changelog-1.0.0.Final.xml 2022-02-02 16:47:26.03122 2 MARK_RAN 7:cb16724583e9675711801c6875114f28 createTable tableName=APPLICATION_DEFAULT_ROLES; createTable tableName=CLIENT; createTable tableName=CLIENT_SESSION; createTable tableName=CLIENT_SESSION_ROLE; createTable tableName=COMPOSITE_ROLE; createTable tableName=CREDENTIAL; createTable tab... \N 3.5.4 \N \N 3820445829 +1.1.0.Beta1 sthorger@redhat.com META-INF/jpa-changelog-1.1.0.Beta1.xml 2022-02-02 16:47:26.06085 3 EXECUTED 7:0310eb8ba07cec616460794d42ade0fa delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION; createTable tableName=CLIENT_ATTRIBUTES; createTable tableName=CLIENT_SESSION_NOTE; createTable tableName=APP_NODE_REGISTRATIONS; addColumn table... \N 3.5.4 \N \N 3820445829 +1.1.0.Final sthorger@redhat.com META-INF/jpa-changelog-1.1.0.Final.xml 2022-02-02 16:47:26.065284 4 EXECUTED 7:5d25857e708c3233ef4439df1f93f012 renameColumn newColumnName=EVENT_TIME, oldColumnName=TIME, tableName=EVENT_ENTITY \N 3.5.4 \N \N 3820445829 +1.2.0.Beta1 psilva@redhat.com META-INF/jpa-changelog-1.2.0.Beta1.xml 2022-02-02 16:47:26.130908 5 EXECUTED 7:c7a54a1041d58eb3817a4a883b4d4e84 delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION_NOTE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION; createTable tableName=PROTOCOL_MAPPER; createTable tableName=PROTOCOL_MAPPER_CONFIG; createTable tableName=... \N 3.5.4 \N \N 3820445829 +1.2.0.Beta1 psilva@redhat.com META-INF/db2-jpa-changelog-1.2.0.Beta1.xml 2022-02-02 16:47:26.133863 6 MARK_RAN 7:2e01012df20974c1c2a605ef8afe25b7 delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION_NOTE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION; createTable tableName=PROTOCOL_MAPPER; createTable tableName=PROTOCOL_MAPPER_CONFIG; createTable tableName=... \N 3.5.4 \N \N 3820445829 +1.2.0.RC1 bburke@redhat.com META-INF/jpa-changelog-1.2.0.CR1.xml 2022-02-02 16:47:26.183318 7 EXECUTED 7:0f08df48468428e0f30ee59a8ec01a41 delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION_NOTE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION_NOTE; delete tableName=USER_SESSION; createTable tableName=MIGRATION_MODEL; createTable tableName=IDENTITY_P... \N 3.5.4 \N \N 3820445829 +1.2.0.RC1 bburke@redhat.com META-INF/db2-jpa-changelog-1.2.0.CR1.xml 2022-02-02 16:47:26.186858 8 MARK_RAN 7:a77ea2ad226b345e7d689d366f185c8c delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION_NOTE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION_NOTE; delete tableName=USER_SESSION; createTable tableName=MIGRATION_MODEL; createTable tableName=IDENTITY_P... \N 3.5.4 \N \N 3820445829 +1.2.0.Final keycloak META-INF/jpa-changelog-1.2.0.Final.xml 2022-02-02 16:47:26.19172 9 EXECUTED 7:a3377a2059aefbf3b90ebb4c4cc8e2ab update tableName=CLIENT; update tableName=CLIENT; update tableName=CLIENT \N 3.5.4 \N \N 3820445829 +1.3.0 bburke@redhat.com META-INF/jpa-changelog-1.3.0.xml 2022-02-02 16:47:26.242162 10 EXECUTED 7:04c1dbedc2aa3e9756d1a1668e003451 delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION_PROT_MAPPER; delete tableName=CLIENT_SESSION_NOTE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION_NOTE; delete tableName=USER_SESSION; createTable tableName=ADMI... \N 3.5.4 \N \N 3820445829 +1.4.0 bburke@redhat.com META-INF/jpa-changelog-1.4.0.xml 2022-02-02 16:47:26.275929 11 EXECUTED 7:36ef39ed560ad07062d956db861042ba delete tableName=CLIENT_SESSION_AUTH_STATUS; delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION_PROT_MAPPER; delete tableName=CLIENT_SESSION_NOTE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION_NOTE; delete table... \N 3.5.4 \N \N 3820445829 +1.4.0 bburke@redhat.com META-INF/db2-jpa-changelog-1.4.0.xml 2022-02-02 16:47:26.278548 12 MARK_RAN 7:d909180b2530479a716d3f9c9eaea3d7 delete tableName=CLIENT_SESSION_AUTH_STATUS; delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION_PROT_MAPPER; delete tableName=CLIENT_SESSION_NOTE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION_NOTE; delete table... \N 3.5.4 \N \N 3820445829 +1.5.0 bburke@redhat.com META-INF/jpa-changelog-1.5.0.xml 2022-02-02 16:47:26.287616 13 EXECUTED 7:cf12b04b79bea5152f165eb41f3955f6 delete tableName=CLIENT_SESSION_AUTH_STATUS; delete tableName=CLIENT_SESSION_ROLE; delete tableName=CLIENT_SESSION_PROT_MAPPER; delete tableName=CLIENT_SESSION_NOTE; delete tableName=CLIENT_SESSION; delete tableName=USER_SESSION_NOTE; delete table... \N 3.5.4 \N \N 3820445829 +1.6.1_from15 mposolda@redhat.com META-INF/jpa-changelog-1.6.1.xml 2022-02-02 16:47:26.299798 14 EXECUTED 7:7e32c8f05c755e8675764e7d5f514509 addColumn tableName=REALM; addColumn tableName=KEYCLOAK_ROLE; addColumn tableName=CLIENT; createTable tableName=OFFLINE_USER_SESSION; createTable tableName=OFFLINE_CLIENT_SESSION; addPrimaryKey constraintName=CONSTRAINT_OFFL_US_SES_PK2, tableName=... \N 3.5.4 \N \N 3820445829 +1.6.1_from16-pre mposolda@redhat.com META-INF/jpa-changelog-1.6.1.xml 2022-02-02 16:47:26.302088 15 MARK_RAN 7:980ba23cc0ec39cab731ce903dd01291 delete tableName=OFFLINE_CLIENT_SESSION; delete tableName=OFFLINE_USER_SESSION \N 3.5.4 \N \N 3820445829 +1.6.1_from16 mposolda@redhat.com META-INF/jpa-changelog-1.6.1.xml 2022-02-02 16:47:26.303889 16 MARK_RAN 7:2fa220758991285312eb84f3b4ff5336 dropPrimaryKey constraintName=CONSTRAINT_OFFLINE_US_SES_PK, tableName=OFFLINE_USER_SESSION; dropPrimaryKey constraintName=CONSTRAINT_OFFLINE_CL_SES_PK, tableName=OFFLINE_CLIENT_SESSION; addColumn tableName=OFFLINE_USER_SESSION; update tableName=OF... \N 3.5.4 \N \N 3820445829 +1.6.1 mposolda@redhat.com META-INF/jpa-changelog-1.6.1.xml 2022-02-02 16:47:26.306641 17 EXECUTED 7:d41d8cd98f00b204e9800998ecf8427e empty \N 3.5.4 \N \N 3820445829 +1.7.0 bburke@redhat.com META-INF/jpa-changelog-1.7.0.xml 2022-02-02 16:47:26.338791 18 EXECUTED 7:91ace540896df890cc00a0490ee52bbc createTable tableName=KEYCLOAK_GROUP; createTable tableName=GROUP_ROLE_MAPPING; createTable tableName=GROUP_ATTRIBUTE; createTable tableName=USER_GROUP_MEMBERSHIP; createTable tableName=REALM_DEFAULT_GROUPS; addColumn tableName=IDENTITY_PROVIDER; ... \N 3.5.4 \N \N 3820445829 +1.8.0 mposolda@redhat.com META-INF/jpa-changelog-1.8.0.xml 2022-02-02 16:47:26.381463 19 EXECUTED 7:c31d1646dfa2618a9335c00e07f89f24 addColumn tableName=IDENTITY_PROVIDER; createTable tableName=CLIENT_TEMPLATE; createTable tableName=CLIENT_TEMPLATE_ATTRIBUTES; createTable tableName=TEMPLATE_SCOPE_MAPPING; dropNotNullConstraint columnName=CLIENT_ID, tableName=PROTOCOL_MAPPER; ad... \N 3.5.4 \N \N 3820445829 +1.8.0-2 keycloak META-INF/jpa-changelog-1.8.0.xml 2022-02-02 16:47:26.390165 20 EXECUTED 7:df8bc21027a4f7cbbb01f6344e89ce07 dropDefaultValue columnName=ALGORITHM, tableName=CREDENTIAL; update tableName=CREDENTIAL \N 3.5.4 \N \N 3820445829 +authz-3.4.0.CR1-resource-server-pk-change-part1 glavoie@gmail.com META-INF/jpa-changelog-authz-3.4.0.CR1.xml 2022-02-02 16:47:26.679075 45 EXECUTED 7:6a48ce645a3525488a90fbf76adf3bb3 addColumn tableName=RESOURCE_SERVER_POLICY; addColumn tableName=RESOURCE_SERVER_RESOURCE; addColumn tableName=RESOURCE_SERVER_SCOPE \N 3.5.4 \N \N 3820445829 +1.8.0 mposolda@redhat.com META-INF/db2-jpa-changelog-1.8.0.xml 2022-02-02 16:47:26.392862 21 MARK_RAN 7:f987971fe6b37d963bc95fee2b27f8df addColumn tableName=IDENTITY_PROVIDER; createTable tableName=CLIENT_TEMPLATE; createTable tableName=CLIENT_TEMPLATE_ATTRIBUTES; createTable tableName=TEMPLATE_SCOPE_MAPPING; dropNotNullConstraint columnName=CLIENT_ID, tableName=PROTOCOL_MAPPER; ad... \N 3.5.4 \N \N 3820445829 +1.8.0-2 keycloak META-INF/db2-jpa-changelog-1.8.0.xml 2022-02-02 16:47:26.395652 22 MARK_RAN 7:df8bc21027a4f7cbbb01f6344e89ce07 dropDefaultValue columnName=ALGORITHM, tableName=CREDENTIAL; update tableName=CREDENTIAL \N 3.5.4 \N \N 3820445829 +1.9.0 mposolda@redhat.com META-INF/jpa-changelog-1.9.0.xml 2022-02-02 16:47:26.40969 23 EXECUTED 7:ed2dc7f799d19ac452cbcda56c929e47 update tableName=REALM; update tableName=REALM; update tableName=REALM; update tableName=REALM; update tableName=CREDENTIAL; update tableName=CREDENTIAL; update tableName=CREDENTIAL; update tableName=REALM; update tableName=REALM; customChange; dr... \N 3.5.4 \N \N 3820445829 +1.9.1 keycloak META-INF/jpa-changelog-1.9.1.xml 2022-02-02 16:47:26.414344 24 EXECUTED 7:80b5db88a5dda36ece5f235be8757615 modifyDataType columnName=PRIVATE_KEY, tableName=REALM; modifyDataType columnName=PUBLIC_KEY, tableName=REALM; modifyDataType columnName=CERTIFICATE, tableName=REALM \N 3.5.4 \N \N 3820445829 +1.9.1 keycloak META-INF/db2-jpa-changelog-1.9.1.xml 2022-02-02 16:47:26.416193 25 MARK_RAN 7:1437310ed1305a9b93f8848f301726ce modifyDataType columnName=PRIVATE_KEY, tableName=REALM; modifyDataType columnName=CERTIFICATE, tableName=REALM \N 3.5.4 \N \N 3820445829 +1.9.2 keycloak META-INF/jpa-changelog-1.9.2.xml 2022-02-02 16:47:26.437367 26 EXECUTED 7:b82ffb34850fa0836be16deefc6a87c4 createIndex indexName=IDX_USER_EMAIL, tableName=USER_ENTITY; createIndex indexName=IDX_USER_ROLE_MAPPING, tableName=USER_ROLE_MAPPING; createIndex indexName=IDX_USER_GROUP_MAPPING, tableName=USER_GROUP_MEMBERSHIP; createIndex indexName=IDX_USER_CO... \N 3.5.4 \N \N 3820445829 +authz-2.0.0 psilva@redhat.com META-INF/jpa-changelog-authz-2.0.0.xml 2022-02-02 16:47:26.481647 27 EXECUTED 7:9cc98082921330d8d9266decdd4bd658 createTable tableName=RESOURCE_SERVER; addPrimaryKey constraintName=CONSTRAINT_FARS, tableName=RESOURCE_SERVER; addUniqueConstraint constraintName=UK_AU8TT6T700S9V50BU18WS5HA6, tableName=RESOURCE_SERVER; createTable tableName=RESOURCE_SERVER_RESOU... \N 3.5.4 \N \N 3820445829 +authz-2.5.1 psilva@redhat.com META-INF/jpa-changelog-authz-2.5.1.xml 2022-02-02 16:47:26.484459 28 EXECUTED 7:03d64aeed9cb52b969bd30a7ac0db57e update tableName=RESOURCE_SERVER_POLICY \N 3.5.4 \N \N 3820445829 +2.1.0-KEYCLOAK-5461 bburke@redhat.com META-INF/jpa-changelog-2.1.0.xml 2022-02-02 16:47:26.523006 29 EXECUTED 7:f1f9fd8710399d725b780f463c6b21cd createTable tableName=BROKER_LINK; createTable tableName=FED_USER_ATTRIBUTE; createTable tableName=FED_USER_CONSENT; createTable tableName=FED_USER_CONSENT_ROLE; createTable tableName=FED_USER_CONSENT_PROT_MAPPER; createTable tableName=FED_USER_CR... \N 3.5.4 \N \N 3820445829 +2.2.0 bburke@redhat.com META-INF/jpa-changelog-2.2.0.xml 2022-02-02 16:47:26.532066 30 EXECUTED 7:53188c3eb1107546e6f765835705b6c1 addColumn tableName=ADMIN_EVENT_ENTITY; createTable tableName=CREDENTIAL_ATTRIBUTE; createTable tableName=FED_CREDENTIAL_ATTRIBUTE; modifyDataType columnName=VALUE, tableName=CREDENTIAL; addForeignKeyConstraint baseTableName=FED_CREDENTIAL_ATTRIBU... \N 3.5.4 \N \N 3820445829 +2.3.0 bburke@redhat.com META-INF/jpa-changelog-2.3.0.xml 2022-02-02 16:47:26.541837 31 EXECUTED 7:d6e6f3bc57a0c5586737d1351725d4d4 createTable tableName=FEDERATED_USER; addPrimaryKey constraintName=CONSTR_FEDERATED_USER, tableName=FEDERATED_USER; dropDefaultValue columnName=TOTP, tableName=USER_ENTITY; dropColumn columnName=TOTP, tableName=USER_ENTITY; addColumn tableName=IDE... \N 3.5.4 \N \N 3820445829 +2.4.0 bburke@redhat.com META-INF/jpa-changelog-2.4.0.xml 2022-02-02 16:47:26.545809 32 EXECUTED 7:454d604fbd755d9df3fd9c6329043aa5 customChange \N 3.5.4 \N \N 3820445829 +2.5.0 bburke@redhat.com META-INF/jpa-changelog-2.5.0.xml 2022-02-02 16:47:26.549823 33 EXECUTED 7:57e98a3077e29caf562f7dbf80c72600 customChange; modifyDataType columnName=USER_ID, tableName=OFFLINE_USER_SESSION \N 3.5.4 \N \N 3820445829 +2.5.0-unicode-oracle hmlnarik@redhat.com META-INF/jpa-changelog-2.5.0.xml 2022-02-02 16:47:26.55176 34 MARK_RAN 7:e4c7e8f2256210aee71ddc42f538b57a modifyDataType columnName=DESCRIPTION, tableName=AUTHENTICATION_FLOW; modifyDataType columnName=DESCRIPTION, tableName=CLIENT_TEMPLATE; modifyDataType columnName=DESCRIPTION, tableName=RESOURCE_SERVER_POLICY; modifyDataType columnName=DESCRIPTION,... \N 3.5.4 \N \N 3820445829 +2.5.0-unicode-other-dbs hmlnarik@redhat.com META-INF/jpa-changelog-2.5.0.xml 2022-02-02 16:47:26.567305 35 EXECUTED 7:09a43c97e49bc626460480aa1379b522 modifyDataType columnName=DESCRIPTION, tableName=AUTHENTICATION_FLOW; modifyDataType columnName=DESCRIPTION, tableName=CLIENT_TEMPLATE; modifyDataType columnName=DESCRIPTION, tableName=RESOURCE_SERVER_POLICY; modifyDataType columnName=DESCRIPTION,... \N 3.5.4 \N \N 3820445829 +2.5.0-duplicate-email-support slawomir@dabek.name META-INF/jpa-changelog-2.5.0.xml 2022-02-02 16:47:26.570727 36 EXECUTED 7:26bfc7c74fefa9126f2ce702fb775553 addColumn tableName=REALM \N 3.5.4 \N \N 3820445829 +2.5.0-unique-group-names hmlnarik@redhat.com META-INF/jpa-changelog-2.5.0.xml 2022-02-02 16:47:26.578396 37 EXECUTED 7:a161e2ae671a9020fff61e996a207377 addUniqueConstraint constraintName=SIBLING_NAMES, tableName=KEYCLOAK_GROUP \N 3.5.4 \N \N 3820445829 +2.5.1 bburke@redhat.com META-INF/jpa-changelog-2.5.1.xml 2022-02-02 16:47:26.581391 38 EXECUTED 7:37fc1781855ac5388c494f1442b3f717 addColumn tableName=FED_USER_CONSENT \N 3.5.4 \N \N 3820445829 +3.0.0 bburke@redhat.com META-INF/jpa-changelog-3.0.0.xml 2022-02-02 16:47:26.584204 39 EXECUTED 7:13a27db0dae6049541136adad7261d27 addColumn tableName=IDENTITY_PROVIDER \N 3.5.4 \N \N 3820445829 +3.2.0-fix keycloak META-INF/jpa-changelog-3.2.0.xml 2022-02-02 16:47:26.585877 40 MARK_RAN 7:550300617e3b59e8af3a6294df8248a3 addNotNullConstraint columnName=REALM_ID, tableName=CLIENT_INITIAL_ACCESS \N 3.5.4 \N \N 3820445829 +3.2.0-fix-with-keycloak-5416 keycloak META-INF/jpa-changelog-3.2.0.xml 2022-02-02 16:47:26.587657 41 MARK_RAN 7:e3a9482b8931481dc2772a5c07c44f17 dropIndex indexName=IDX_CLIENT_INIT_ACC_REALM, tableName=CLIENT_INITIAL_ACCESS; addNotNullConstraint columnName=REALM_ID, tableName=CLIENT_INITIAL_ACCESS; createIndex indexName=IDX_CLIENT_INIT_ACC_REALM, tableName=CLIENT_INITIAL_ACCESS \N 3.5.4 \N \N 3820445829 +3.2.0-fix-offline-sessions hmlnarik META-INF/jpa-changelog-3.2.0.xml 2022-02-02 16:47:26.591561 42 EXECUTED 7:72b07d85a2677cb257edb02b408f332d customChange \N 3.5.4 \N \N 3820445829 +3.2.0-fixed keycloak META-INF/jpa-changelog-3.2.0.xml 2022-02-02 16:47:26.669981 43 EXECUTED 7:a72a7858967bd414835d19e04d880312 addColumn tableName=REALM; dropPrimaryKey constraintName=CONSTRAINT_OFFL_CL_SES_PK2, tableName=OFFLINE_CLIENT_SESSION; dropColumn columnName=CLIENT_SESSION_ID, tableName=OFFLINE_CLIENT_SESSION; addPrimaryKey constraintName=CONSTRAINT_OFFL_CL_SES_P... \N 3.5.4 \N \N 3820445829 +3.3.0 keycloak META-INF/jpa-changelog-3.3.0.xml 2022-02-02 16:47:26.673701 44 EXECUTED 7:94edff7cf9ce179e7e85f0cd78a3cf2c addColumn tableName=USER_ENTITY \N 3.5.4 \N \N 3820445829 +authz-3.4.0.CR1-resource-server-pk-change-part2-KEYCLOAK-6095 hmlnarik@redhat.com META-INF/jpa-changelog-authz-3.4.0.CR1.xml 2022-02-02 16:47:26.681987 46 EXECUTED 7:e64b5dcea7db06077c6e57d3b9e5ca14 customChange \N 3.5.4 \N \N 3820445829 +authz-3.4.0.CR1-resource-server-pk-change-part3-fixed glavoie@gmail.com META-INF/jpa-changelog-authz-3.4.0.CR1.xml 2022-02-02 16:47:26.683661 47 MARK_RAN 7:fd8cf02498f8b1e72496a20afc75178c dropIndex indexName=IDX_RES_SERV_POL_RES_SERV, tableName=RESOURCE_SERVER_POLICY; dropIndex indexName=IDX_RES_SRV_RES_RES_SRV, tableName=RESOURCE_SERVER_RESOURCE; dropIndex indexName=IDX_RES_SRV_SCOPE_RES_SRV, tableName=RESOURCE_SERVER_SCOPE \N 3.5.4 \N \N 3820445829 +authz-3.4.0.CR1-resource-server-pk-change-part3-fixed-nodropindex glavoie@gmail.com META-INF/jpa-changelog-authz-3.4.0.CR1.xml 2022-02-02 16:47:26.702743 48 EXECUTED 7:542794f25aa2b1fbabb7e577d6646319 addNotNullConstraint columnName=RESOURCE_SERVER_CLIENT_ID, tableName=RESOURCE_SERVER_POLICY; addNotNullConstraint columnName=RESOURCE_SERVER_CLIENT_ID, tableName=RESOURCE_SERVER_RESOURCE; addNotNullConstraint columnName=RESOURCE_SERVER_CLIENT_ID, ... \N 3.5.4 \N \N 3820445829 +authn-3.4.0.CR1-refresh-token-max-reuse glavoie@gmail.com META-INF/jpa-changelog-authz-3.4.0.CR1.xml 2022-02-02 16:47:26.706593 49 EXECUTED 7:edad604c882df12f74941dac3cc6d650 addColumn tableName=REALM \N 3.5.4 \N \N 3820445829 +3.4.0 keycloak META-INF/jpa-changelog-3.4.0.xml 2022-02-02 16:47:26.734467 50 EXECUTED 7:0f88b78b7b46480eb92690cbf5e44900 addPrimaryKey constraintName=CONSTRAINT_REALM_DEFAULT_ROLES, tableName=REALM_DEFAULT_ROLES; addPrimaryKey constraintName=CONSTRAINT_COMPOSITE_ROLE, tableName=COMPOSITE_ROLE; addPrimaryKey constraintName=CONSTR_REALM_DEFAULT_GROUPS, tableName=REALM... \N 3.5.4 \N \N 3820445829 +3.4.0-KEYCLOAK-5230 hmlnarik@redhat.com META-INF/jpa-changelog-3.4.0.xml 2022-02-02 16:47:26.78037 51 EXECUTED 7:d560e43982611d936457c327f872dd59 createIndex indexName=IDX_FU_ATTRIBUTE, tableName=FED_USER_ATTRIBUTE; createIndex indexName=IDX_FU_CONSENT, tableName=FED_USER_CONSENT; createIndex indexName=IDX_FU_CONSENT_RU, tableName=FED_USER_CONSENT; createIndex indexName=IDX_FU_CREDENTIAL, t... \N 3.5.4 \N \N 3820445829 +3.4.1 psilva@redhat.com META-INF/jpa-changelog-3.4.1.xml 2022-02-02 16:47:26.783989 52 EXECUTED 7:c155566c42b4d14ef07059ec3b3bbd8e modifyDataType columnName=VALUE, tableName=CLIENT_ATTRIBUTES \N 3.5.4 \N \N 3820445829 +3.4.2 keycloak META-INF/jpa-changelog-3.4.2.xml 2022-02-02 16:47:26.786619 53 EXECUTED 7:b40376581f12d70f3c89ba8ddf5b7dea update tableName=REALM \N 3.5.4 \N \N 3820445829 +3.4.2-KEYCLOAK-5172 mkanis@redhat.com META-INF/jpa-changelog-3.4.2.xml 2022-02-02 16:47:26.788788 54 EXECUTED 7:a1132cc395f7b95b3646146c2e38f168 update tableName=CLIENT \N 3.5.4 \N \N 3820445829 +4.0.0-KEYCLOAK-6335 bburke@redhat.com META-INF/jpa-changelog-4.0.0.xml 2022-02-02 16:47:26.794881 55 EXECUTED 7:d8dc5d89c789105cfa7ca0e82cba60af createTable tableName=CLIENT_AUTH_FLOW_BINDINGS; addPrimaryKey constraintName=C_CLI_FLOW_BIND, tableName=CLIENT_AUTH_FLOW_BINDINGS \N 3.5.4 \N \N 3820445829 +4.0.0-CLEANUP-UNUSED-TABLE bburke@redhat.com META-INF/jpa-changelog-4.0.0.xml 2022-02-02 16:47:26.799493 56 EXECUTED 7:7822e0165097182e8f653c35517656a3 dropTable tableName=CLIENT_IDENTITY_PROV_MAPPING \N 3.5.4 \N \N 3820445829 +4.0.0-KEYCLOAK-6228 bburke@redhat.com META-INF/jpa-changelog-4.0.0.xml 2022-02-02 16:47:26.810686 57 EXECUTED 7:c6538c29b9c9a08f9e9ea2de5c2b6375 dropUniqueConstraint constraintName=UK_JKUWUVD56ONTGSUHOGM8UEWRT, tableName=USER_CONSENT; dropNotNullConstraint columnName=CLIENT_ID, tableName=USER_CONSENT; addColumn tableName=USER_CONSENT; addUniqueConstraint constraintName=UK_JKUWUVD56ONTGSUHO... \N 3.5.4 \N \N 3820445829 +4.0.0-KEYCLOAK-5579-fixed mposolda@redhat.com META-INF/jpa-changelog-4.0.0.xml 2022-02-02 16:47:26.861332 58 EXECUTED 7:6d4893e36de22369cf73bcb051ded875 dropForeignKeyConstraint baseTableName=CLIENT_TEMPLATE_ATTRIBUTES, constraintName=FK_CL_TEMPL_ATTR_TEMPL; renameTable newTableName=CLIENT_SCOPE_ATTRIBUTES, oldTableName=CLIENT_TEMPLATE_ATTRIBUTES; renameColumn newColumnName=SCOPE_ID, oldColumnName... \N 3.5.4 \N \N 3820445829 +authz-4.0.0.CR1 psilva@redhat.com META-INF/jpa-changelog-authz-4.0.0.CR1.xml 2022-02-02 16:47:26.877018 59 EXECUTED 7:57960fc0b0f0dd0563ea6f8b2e4a1707 createTable tableName=RESOURCE_SERVER_PERM_TICKET; addPrimaryKey constraintName=CONSTRAINT_FAPMT, tableName=RESOURCE_SERVER_PERM_TICKET; addForeignKeyConstraint baseTableName=RESOURCE_SERVER_PERM_TICKET, constraintName=FK_FRSRHO213XCX4WNKOG82SSPMT... \N 3.5.4 \N \N 3820445829 +authz-4.0.0.Beta3 psilva@redhat.com META-INF/jpa-changelog-authz-4.0.0.Beta3.xml 2022-02-02 16:47:26.881203 60 EXECUTED 7:2b4b8bff39944c7097977cc18dbceb3b addColumn tableName=RESOURCE_SERVER_POLICY; addColumn tableName=RESOURCE_SERVER_PERM_TICKET; addForeignKeyConstraint baseTableName=RESOURCE_SERVER_PERM_TICKET, constraintName=FK_FRSRPO2128CX4WNKOG82SSRFY, referencedTableName=RESOURCE_SERVER_POLICY \N 3.5.4 \N \N 3820445829 +authz-4.2.0.Final mhajas@redhat.com META-INF/jpa-changelog-authz-4.2.0.Final.xml 2022-02-02 16:47:26.886177 61 EXECUTED 7:2aa42a964c59cd5b8ca9822340ba33a8 createTable tableName=RESOURCE_URIS; addForeignKeyConstraint baseTableName=RESOURCE_URIS, constraintName=FK_RESOURCE_SERVER_URIS, referencedTableName=RESOURCE_SERVER_RESOURCE; customChange; dropColumn columnName=URI, tableName=RESOURCE_SERVER_RESO... \N 3.5.4 \N \N 3820445829 +authz-4.2.0.Final-KEYCLOAK-9944 hmlnarik@redhat.com META-INF/jpa-changelog-authz-4.2.0.Final.xml 2022-02-02 16:47:26.890482 62 EXECUTED 7:9ac9e58545479929ba23f4a3087a0346 addPrimaryKey constraintName=CONSTRAINT_RESOUR_URIS_PK, tableName=RESOURCE_URIS \N 3.5.4 \N \N 3820445829 +4.2.0-KEYCLOAK-6313 wadahiro@gmail.com META-INF/jpa-changelog-4.2.0.xml 2022-02-02 16:47:26.893518 63 EXECUTED 7:14d407c35bc4fe1976867756bcea0c36 addColumn tableName=REQUIRED_ACTION_PROVIDER \N 3.5.4 \N \N 3820445829 +4.3.0-KEYCLOAK-7984 wadahiro@gmail.com META-INF/jpa-changelog-4.3.0.xml 2022-02-02 16:47:26.895621 64 EXECUTED 7:241a8030c748c8548e346adee548fa93 update tableName=REQUIRED_ACTION_PROVIDER \N 3.5.4 \N \N 3820445829 +4.6.0-KEYCLOAK-7950 psilva@redhat.com META-INF/jpa-changelog-4.6.0.xml 2022-02-02 16:47:26.89756 65 EXECUTED 7:7d3182f65a34fcc61e8d23def037dc3f update tableName=RESOURCE_SERVER_RESOURCE \N 3.5.4 \N \N 3820445829 +4.6.0-KEYCLOAK-8377 keycloak META-INF/jpa-changelog-4.6.0.xml 2022-02-02 16:47:26.908059 66 EXECUTED 7:b30039e00a0b9715d430d1b0636728fa createTable tableName=ROLE_ATTRIBUTE; addPrimaryKey constraintName=CONSTRAINT_ROLE_ATTRIBUTE_PK, tableName=ROLE_ATTRIBUTE; addForeignKeyConstraint baseTableName=ROLE_ATTRIBUTE, constraintName=FK_ROLE_ATTRIBUTE_ID, referencedTableName=KEYCLOAK_ROLE... \N 3.5.4 \N \N 3820445829 +4.6.0-KEYCLOAK-8555 gideonray@gmail.com META-INF/jpa-changelog-4.6.0.xml 2022-02-02 16:47:26.912693 67 EXECUTED 7:3797315ca61d531780f8e6f82f258159 createIndex indexName=IDX_COMPONENT_PROVIDER_TYPE, tableName=COMPONENT \N 3.5.4 \N \N 3820445829 +4.7.0-KEYCLOAK-1267 sguilhen@redhat.com META-INF/jpa-changelog-4.7.0.xml 2022-02-02 16:47:26.915771 68 EXECUTED 7:c7aa4c8d9573500c2d347c1941ff0301 addColumn tableName=REALM \N 3.5.4 \N \N 3820445829 +4.7.0-KEYCLOAK-7275 keycloak META-INF/jpa-changelog-4.7.0.xml 2022-02-02 16:47:26.924465 69 EXECUTED 7:b207faee394fc074a442ecd42185a5dd renameColumn newColumnName=CREATED_ON, oldColumnName=LAST_SESSION_REFRESH, tableName=OFFLINE_USER_SESSION; addNotNullConstraint columnName=CREATED_ON, tableName=OFFLINE_USER_SESSION; addColumn tableName=OFFLINE_USER_SESSION; customChange; createIn... \N 3.5.4 \N \N 3820445829 +4.8.0-KEYCLOAK-8835 sguilhen@redhat.com META-INF/jpa-changelog-4.8.0.xml 2022-02-02 16:47:26.928034 70 EXECUTED 7:ab9a9762faaba4ddfa35514b212c4922 addNotNullConstraint columnName=SSO_MAX_LIFESPAN_REMEMBER_ME, tableName=REALM; addNotNullConstraint columnName=SSO_IDLE_TIMEOUT_REMEMBER_ME, tableName=REALM \N 3.5.4 \N \N 3820445829 +authz-7.0.0-KEYCLOAK-10443 psilva@redhat.com META-INF/jpa-changelog-authz-7.0.0.xml 2022-02-02 16:47:26.93061 71 EXECUTED 7:b9710f74515a6ccb51b72dc0d19df8c4 addColumn tableName=RESOURCE_SERVER \N 3.5.4 \N \N 3820445829 +8.0.0-adding-credential-columns keycloak META-INF/jpa-changelog-8.0.0.xml 2022-02-02 16:47:26.933771 72 EXECUTED 7:ec9707ae4d4f0b7452fee20128083879 addColumn tableName=CREDENTIAL; addColumn tableName=FED_USER_CREDENTIAL \N 3.5.4 \N \N 3820445829 +8.0.0-updating-credential-data-not-oracle keycloak META-INF/jpa-changelog-8.0.0.xml 2022-02-02 16:47:26.937673 73 EXECUTED 7:03b3f4b264c3c68ba082250a80b74216 update tableName=CREDENTIAL; update tableName=CREDENTIAL; update tableName=CREDENTIAL; update tableName=FED_USER_CREDENTIAL; update tableName=FED_USER_CREDENTIAL; update tableName=FED_USER_CREDENTIAL \N 3.5.4 \N \N 3820445829 +8.0.0-updating-credential-data-oracle keycloak META-INF/jpa-changelog-8.0.0.xml 2022-02-02 16:47:26.939218 74 MARK_RAN 7:64c5728f5ca1f5aa4392217701c4fe23 update tableName=CREDENTIAL; update tableName=CREDENTIAL; update tableName=CREDENTIAL; update tableName=FED_USER_CREDENTIAL; update tableName=FED_USER_CREDENTIAL; update tableName=FED_USER_CREDENTIAL \N 3.5.4 \N \N 3820445829 +8.0.0-credential-cleanup-fixed keycloak META-INF/jpa-changelog-8.0.0.xml 2022-02-02 16:47:26.945819 75 EXECUTED 7:b48da8c11a3d83ddd6b7d0c8c2219345 dropDefaultValue columnName=COUNTER, tableName=CREDENTIAL; dropDefaultValue columnName=DIGITS, tableName=CREDENTIAL; dropDefaultValue columnName=PERIOD, tableName=CREDENTIAL; dropDefaultValue columnName=ALGORITHM, tableName=CREDENTIAL; dropColumn ... \N 3.5.4 \N \N 3820445829 +8.0.0-resource-tag-support keycloak META-INF/jpa-changelog-8.0.0.xml 2022-02-02 16:47:26.950255 76 EXECUTED 7:a73379915c23bfad3e8f5c6d5c0aa4bd addColumn tableName=MIGRATION_MODEL; createIndex indexName=IDX_UPDATE_TIME, tableName=MIGRATION_MODEL \N 3.5.4 \N \N 3820445829 +9.0.0-always-display-client keycloak META-INF/jpa-changelog-9.0.0.xml 2022-02-02 16:47:26.955505 77 EXECUTED 7:39e0073779aba192646291aa2332493d addColumn tableName=CLIENT \N 3.5.4 \N \N 3820445829 +9.0.0-drop-constraints-for-column-increase keycloak META-INF/jpa-changelog-9.0.0.xml 2022-02-02 16:47:26.957216 78 MARK_RAN 7:81f87368f00450799b4bf42ea0b3ec34 dropUniqueConstraint constraintName=UK_FRSR6T700S9V50BU18WS5PMT, tableName=RESOURCE_SERVER_PERM_TICKET; dropUniqueConstraint constraintName=UK_FRSR6T700S9V50BU18WS5HA6, tableName=RESOURCE_SERVER_RESOURCE; dropPrimaryKey constraintName=CONSTRAINT_O... \N 3.5.4 \N \N 3820445829 +9.0.0-increase-column-size-federated-fk keycloak META-INF/jpa-changelog-9.0.0.xml 2022-02-02 16:47:26.966746 79 EXECUTED 7:20b37422abb9fb6571c618148f013a15 modifyDataType columnName=CLIENT_ID, tableName=FED_USER_CONSENT; modifyDataType columnName=CLIENT_REALM_CONSTRAINT, tableName=KEYCLOAK_ROLE; modifyDataType columnName=OWNER, tableName=RESOURCE_SERVER_POLICY; modifyDataType columnName=CLIENT_ID, ta... \N 3.5.4 \N \N 3820445829 +9.0.0-recreate-constraints-after-column-increase keycloak META-INF/jpa-changelog-9.0.0.xml 2022-02-02 16:47:26.969643 80 MARK_RAN 7:1970bb6cfb5ee800736b95ad3fb3c78a addNotNullConstraint columnName=CLIENT_ID, tableName=OFFLINE_CLIENT_SESSION; addNotNullConstraint columnName=OWNER, tableName=RESOURCE_SERVER_PERM_TICKET; addNotNullConstraint columnName=REQUESTER, tableName=RESOURCE_SERVER_PERM_TICKET; addNotNull... \N 3.5.4 \N \N 3820445829 +9.0.1-add-index-to-client.client_id keycloak META-INF/jpa-changelog-9.0.1.xml 2022-02-02 16:47:26.975764 81 EXECUTED 7:45d9b25fc3b455d522d8dcc10a0f4c80 createIndex indexName=IDX_CLIENT_ID, tableName=CLIENT \N 3.5.4 \N \N 3820445829 +9.0.1-KEYCLOAK-12579-drop-constraints keycloak META-INF/jpa-changelog-9.0.1.xml 2022-02-02 16:47:26.977227 82 MARK_RAN 7:890ae73712bc187a66c2813a724d037f dropUniqueConstraint constraintName=SIBLING_NAMES, tableName=KEYCLOAK_GROUP \N 3.5.4 \N \N 3820445829 +9.0.1-KEYCLOAK-12579-add-not-null-constraint keycloak META-INF/jpa-changelog-9.0.1.xml 2022-02-02 16:47:26.980058 83 EXECUTED 7:0a211980d27fafe3ff50d19a3a29b538 addNotNullConstraint columnName=PARENT_GROUP, tableName=KEYCLOAK_GROUP \N 3.5.4 \N \N 3820445829 +9.0.1-KEYCLOAK-12579-recreate-constraints keycloak META-INF/jpa-changelog-9.0.1.xml 2022-02-02 16:47:26.981645 84 MARK_RAN 7:a161e2ae671a9020fff61e996a207377 addUniqueConstraint constraintName=SIBLING_NAMES, tableName=KEYCLOAK_GROUP \N 3.5.4 \N \N 3820445829 +9.0.1-add-index-to-events keycloak META-INF/jpa-changelog-9.0.1.xml 2022-02-02 16:47:26.985465 85 EXECUTED 7:01c49302201bdf815b0a18d1f98a55dc createIndex indexName=IDX_EVENT_TIME, tableName=EVENT_ENTITY \N 3.5.4 \N \N 3820445829 +map-remove-ri keycloak META-INF/jpa-changelog-11.0.0.xml 2022-02-02 16:47:26.98869 86 EXECUTED 7:3dace6b144c11f53f1ad2c0361279b86 dropForeignKeyConstraint baseTableName=REALM, constraintName=FK_TRAF444KK6QRKMS7N56AIWQ5Y; dropForeignKeyConstraint baseTableName=KEYCLOAK_ROLE, constraintName=FK_KJHO5LE2C0RAL09FL8CM9WFW9 \N 3.5.4 \N \N 3820445829 +map-remove-ri keycloak META-INF/jpa-changelog-12.0.0.xml 2022-02-02 16:47:26.992854 87 EXECUTED 7:578d0b92077eaf2ab95ad0ec087aa903 dropForeignKeyConstraint baseTableName=REALM_DEFAULT_GROUPS, constraintName=FK_DEF_GROUPS_GROUP; dropForeignKeyConstraint baseTableName=REALM_DEFAULT_ROLES, constraintName=FK_H4WPD7W4HSOOLNI3H0SW7BTJE; dropForeignKeyConstraint baseTableName=CLIENT... \N 3.5.4 \N \N 3820445829 +12.1.0-add-realm-localization-table keycloak META-INF/jpa-changelog-12.0.0.xml 2022-02-02 16:47:26.999694 88 EXECUTED 7:c95abe90d962c57a09ecaee57972835d createTable tableName=REALM_LOCALIZATIONS; addPrimaryKey tableName=REALM_LOCALIZATIONS \N 3.5.4 \N \N 3820445829 +\. + + +-- +-- Data for Name: databasechangeloglock; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.databasechangeloglock (id, locked, lockgranted, lockedby) FROM stdin; +1 f \N \N +1000 f \N \N +1001 f \N \N +\. + + +-- +-- Data for Name: default_client_scope; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.default_client_scope (realm_id, scope_id, default_scope) FROM stdin; +master 0cc71c8c-fb37-41f2-b4d8-13210d3cf8be f +master 66deef47-2158-4d5b-a75f-0bf42f642e7b t +master 94ef659c-4c4a-4a33-98e8-bfcf443e9268 t +master 96a960d2-c203-4ef0-a53c-c3edd01f2305 f +master 3f705379-3361-486d-b75a-f7b4e4be492c f +master b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 t +master 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 t +master 42bfb506-bf0d-424e-8649-53a9a93d252d f +grafana 0e98d5f9-d3f7-4b1d-9791-d442524fc2ab f +grafana 74daf2cd-40d4-4304-87a8-92cdca808512 t +grafana 96d521d3-facc-4b5a-a8b4-a879bae6be07 t +grafana a5bb3a5f-fd26-4be6-9557-26e20a03d33d f +grafana d6ffe9fc-a03c-4496-85dc-dbb5e7754587 f +grafana d6077ed7-b265-4f82-9336-24614967bd5d t +grafana 699671ab-e7c1-4fcf-beb8-ea54f1471fc1 t +grafana c61f5b19-c17e-49a1-91b8-a0296411b928 f +\. + + +-- +-- Data for Name: event_entity; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.event_entity (id, client_id, details_json, error, ip_address, realm_id, session_id, event_time, type, user_id) FROM stdin; +\. + + +-- +-- Data for Name: fed_user_attribute; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.fed_user_attribute (id, name, user_id, realm_id, storage_provider_id, value) FROM stdin; +\. + + +-- +-- Data for Name: fed_user_consent; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.fed_user_consent (id, client_id, user_id, realm_id, storage_provider_id, created_date, last_updated_date, client_storage_provider, external_client_id) FROM stdin; +\. + + +-- +-- Data for Name: fed_user_consent_cl_scope; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.fed_user_consent_cl_scope (user_consent_id, scope_id) FROM stdin; +\. + + +-- +-- Data for Name: fed_user_credential; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.fed_user_credential (id, salt, type, created_date, user_id, realm_id, storage_provider_id, user_label, secret_data, credential_data, priority) FROM stdin; +\. + + +-- +-- Data for Name: fed_user_group_membership; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.fed_user_group_membership (group_id, user_id, realm_id, storage_provider_id) FROM stdin; +\. + + +-- +-- Data for Name: fed_user_required_action; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.fed_user_required_action (required_action, user_id, realm_id, storage_provider_id) FROM stdin; +\. + + +-- +-- Data for Name: fed_user_role_mapping; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.fed_user_role_mapping (role_id, user_id, realm_id, storage_provider_id) FROM stdin; +\. + + +-- +-- Data for Name: federated_identity; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.federated_identity (identity_provider, realm_id, federated_user_id, federated_username, token, user_id) FROM stdin; +\. + + +-- +-- Data for Name: federated_user; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.federated_user (id, storage_provider_id, realm_id) FROM stdin; +\. + + +-- +-- Data for Name: group_attribute; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.group_attribute (id, name, value, group_id) FROM stdin; +\. + + +-- +-- Data for Name: group_role_mapping; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.group_role_mapping (role_id, group_id) FROM stdin; +\. + + +-- +-- Data for Name: identity_provider; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.identity_provider (internal_id, enabled, provider_alias, provider_id, store_token, authenticate_by_default, realm_id, add_token_role, trust_email, first_broker_login_flow_id, post_broker_login_flow_id, provider_display_name, link_only) FROM stdin; +\. + + +-- +-- Data for Name: identity_provider_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.identity_provider_config (identity_provider_id, value, name) FROM stdin; +\. + + +-- +-- Data for Name: identity_provider_mapper; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.identity_provider_mapper (id, name, idp_alias, idp_mapper_name, realm_id) FROM stdin; +\. + + +-- +-- Data for Name: idp_mapper_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.idp_mapper_config (idp_mapper_id, value, name) FROM stdin; +\. + + +-- +-- Data for Name: keycloak_group; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.keycloak_group (id, name, parent_group, realm_id) FROM stdin; +\. + + +-- +-- Data for Name: keycloak_role; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.keycloak_role (id, client_realm_constraint, client_role, description, name, realm_id, client, realm) FROM stdin; +4a3204aa-320e-4584-b8ee-ea2989b3f330 master f ${role_admin} admin master \N master +847ebc80-6849-4c47-9f9e-5bba0c0d754d master f ${role_create-realm} create-realm master \N master +103dc6a6-5e7a-4c27-b4f0-9dbb1fdcf214 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_create-client} create-client master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +13c94e3b-b22f-4503-bc56-75e1bd2a927f 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_view-realm} view-realm master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +4364a376-4ed0-4051-aeab-609f62420e5d 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_view-users} view-users master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +f12af4b7-8828-47a5-abbc-dbb09b9d409e 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_view-clients} view-clients master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +2606a5b9-699b-488a-a819-d6f368e66697 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_view-events} view-events master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +2cf34980-2606-4faf-bc40-b9a47c69ef1c 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_view-identity-providers} view-identity-providers master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +13e61c6b-aff6-4ef8-ab56-ad4abefcb101 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_view-authorization} view-authorization master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +632bad74-a33f-4fd5-9393-ec0a07898b1a 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_manage-realm} manage-realm master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +4607a008-f45c-45f5-b506-6de020b7e366 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_manage-users} manage-users master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +edd471cc-81d5-43e4-bb43-41fe88ff537d 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_manage-clients} manage-clients master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +4c2b4e2a-e792-4ffd-969d-e33ecdf7158f 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_manage-events} manage-events master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +38282bc7-ea21-46db-a36e-ca621d3275b4 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_manage-identity-providers} manage-identity-providers master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +12111f4a-16ee-4ee7-8576-7956b9440dc5 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_manage-authorization} manage-authorization master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +f417ae21-5fb4-40fb-bda8-54c61ce7461d 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_query-users} query-users master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +7adeaf33-05d3-4a81-a7bf-f99c721b5d9c 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_query-clients} query-clients master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +60870d03-d96a-4371-bdad-e3fac925a8df 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_query-realms} query-realms master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +94363dbd-a6b8-4678-8231-50208c32c22c 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_query-groups} query-groups master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +86a4b6a9-93db-4177-a72f-95fd937a2c8d eed689c6-49da-4d91-98eb-cd495bcc07a3 t ${role_view-profile} view-profile master eed689c6-49da-4d91-98eb-cd495bcc07a3 \N +619ba870-921e-4f28-b26c-89b11f39dddf eed689c6-49da-4d91-98eb-cd495bcc07a3 t ${role_manage-account} manage-account master eed689c6-49da-4d91-98eb-cd495bcc07a3 \N +a42d235d-2864-4a99-9592-211d89d0407d eed689c6-49da-4d91-98eb-cd495bcc07a3 t ${role_manage-account-links} manage-account-links master eed689c6-49da-4d91-98eb-cd495bcc07a3 \N +44798fae-3813-41e6-9352-6fb2d28a15a6 eed689c6-49da-4d91-98eb-cd495bcc07a3 t ${role_view-applications} view-applications master eed689c6-49da-4d91-98eb-cd495bcc07a3 \N +a1a08dbc-4553-4be7-85f5-88c417bdcd45 eed689c6-49da-4d91-98eb-cd495bcc07a3 t ${role_view-consent} view-consent master eed689c6-49da-4d91-98eb-cd495bcc07a3 \N +828c3ba8-a13d-49f5-8975-8eb00afbf7de eed689c6-49da-4d91-98eb-cd495bcc07a3 t ${role_manage-consent} manage-consent master eed689c6-49da-4d91-98eb-cd495bcc07a3 \N +f537ddeb-0973-445c-8f32-3beed99461ba eed689c6-49da-4d91-98eb-cd495bcc07a3 t ${role_delete-account} delete-account master eed689c6-49da-4d91-98eb-cd495bcc07a3 \N +102d3759-c50d-4325-932d-c7a02fc17cb8 1e30397c-eac2-41fb-87bc-d90484992e65 t ${role_read-token} read-token master 1e30397c-eac2-41fb-87bc-d90484992e65 \N +b44e0fe0-0fb7-4e12-a6f0-b352431a0f57 3cd285ea-0f6e-43b6-ab5c-d021c33a551b t ${role_impersonation} impersonation master 3cd285ea-0f6e-43b6-ab5c-d021c33a551b \N +16d5987b-dcbb-4650-8f52-3469f3974846 master f ${role_offline-access} offline_access master \N master +c014bfd1-a210-4e7a-8a26-35d1f5e8f1ed master f ${role_uma_authorization} uma_authorization master \N master +95dfed9c-47fe-489b-aa28-52f0d7aa7c49 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_create-client} create-client master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +07e1586d-a943-46d9-9c3d-1f3544c8c27f ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_view-realm} view-realm master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +293d0c06-6dce-4303-9cd3-dfdd6d1275b8 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_view-users} view-users master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +cfdeeb7b-c70e-496b-9605-70377168a6cb ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_view-clients} view-clients master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +74252705-a339-4513-97ca-d5617977d5ff ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_view-events} view-events master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +77c3f67e-21d7-4c18-9971-4baf4c20eeaa ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_view-identity-providers} view-identity-providers master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +5de01bf1-bfac-4ea2-8fb1-ed95594fe1da ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_view-authorization} view-authorization master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +a72adc0b-5220-48e4-a66a-9e15dca5f574 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_manage-realm} manage-realm master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +f29b8efa-3c08-410a-a5c0-15b52253d2e2 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_manage-users} manage-users master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +dd3ecc72-aaee-43d5-8f7e-f6dcdfb5a608 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_manage-clients} manage-clients master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +9d5a8bab-e112-4e1c-8196-604f3d0143ea ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_manage-events} manage-events master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +ffff4251-e0a4-4f9c-8bf6-5461b2f52766 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_manage-identity-providers} manage-identity-providers master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +5fafdde9-71f7-4f67-9c1d-f3f4bc7f5128 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_manage-authorization} manage-authorization master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +6cfc2ac6-bdd7-4b90-ac16-27a75f2eb00a ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_query-users} query-users master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +c3ded8eb-c970-4e43-bea9-5e07795d20ef ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_query-clients} query-clients master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +811c2a39-6614-46fb-acf5-889d52248171 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_query-realms} query-realms master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +2a90f228-2ca4-413f-bc4b-7939af8abcbf ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_query-groups} query-groups master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +85afffb5-2069-4873-b6c8-08159c1e4bdd a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_realm-admin} realm-admin grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +d0e4028d-a604-427a-9262-a1a9513dafc8 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_create-client} create-client grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +2b8b60c5-d388-4925-b735-858df38dae6e a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_view-realm} view-realm grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +e9c997c8-ad6b-4a99-81e1-c248e94fbeac a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_view-users} view-users grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +8c4449b9-5add-40ba-a19f-cf5d80425e68 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_view-clients} view-clients grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +a5f31b90-986b-46d5-a385-a639b4e19e37 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_view-events} view-events grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +99bd546f-a5ed-47f8-862c-9a5e8345bf3b a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_view-identity-providers} view-identity-providers grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +9096d8df-9d5b-4fb5-b93e-49acc6df0be5 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_view-authorization} view-authorization grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +03230264-ed7a-46b2-939d-53ebe9a59812 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_manage-realm} manage-realm grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +2240d1de-5ac4-44ac-91be-cee70e1dd22b a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_manage-users} manage-users grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +6d2fd708-445b-44a8-b950-f1350a15dd14 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_manage-clients} manage-clients grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +82266aa3-67ea-485a-a078-4671eb141853 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_manage-events} manage-events grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +d6dad388-8c69-4bba-940e-371afc98042e a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_manage-identity-providers} manage-identity-providers grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +5d7868e1-0c4a-46cc-8bac-bd19c0ea1bde a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_manage-authorization} manage-authorization grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +85e6229e-e246-4e9a-8b39-7bae49754f7d a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_query-users} query-users grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +bc618c28-98d1-477d-b4fc-c5ec7cd7f271 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_query-clients} query-clients grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +5059b239-0dce-4bb2-9c55-a6afc8dcbe3b a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_query-realms} query-realms grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +ac28461f-3416-4af4-be65-abc739dbeee5 a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_query-groups} query-groups grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +f1311ecb-6a6a-49d6-bb16-5132daf93a64 a5a8fed6-0bca-4646-9946-2fe84175353b t ${role_view-profile} view-profile grafana a5a8fed6-0bca-4646-9946-2fe84175353b \N +18a7066b-fe71-410e-9581-69f78347ec29 a5a8fed6-0bca-4646-9946-2fe84175353b t ${role_manage-account} manage-account grafana a5a8fed6-0bca-4646-9946-2fe84175353b \N +68fdbd76-8688-47a6-b68d-3298a5401f05 a5a8fed6-0bca-4646-9946-2fe84175353b t ${role_manage-account-links} manage-account-links grafana a5a8fed6-0bca-4646-9946-2fe84175353b \N +cb37c15a-5330-4e30-9421-e0b962a266de a5a8fed6-0bca-4646-9946-2fe84175353b t ${role_view-applications} view-applications grafana a5a8fed6-0bca-4646-9946-2fe84175353b \N +daaedcc6-e7a6-488e-921e-7022aa808da7 a5a8fed6-0bca-4646-9946-2fe84175353b t ${role_view-consent} view-consent grafana a5a8fed6-0bca-4646-9946-2fe84175353b \N +c7e799a5-1250-4bc8-b7c6-ffdc58361477 a5a8fed6-0bca-4646-9946-2fe84175353b t ${role_manage-consent} manage-consent grafana a5a8fed6-0bca-4646-9946-2fe84175353b \N +744bfdff-0e88-438a-b852-282a1b2aad3e a5a8fed6-0bca-4646-9946-2fe84175353b t ${role_delete-account} delete-account grafana a5a8fed6-0bca-4646-9946-2fe84175353b \N +b8a4faaf-86d9-43eb-bb18-0eaa654b35a7 ef7f6eac-9fff-44aa-a86c-5125d52acc82 t ${role_impersonation} impersonation master ef7f6eac-9fff-44aa-a86c-5125d52acc82 \N +5e2301d7-2a9e-4f2d-a940-9bd442b15d8c a8698f4f-5fa1-4baa-be05-87d03052af49 t ${role_impersonation} impersonation grafana a8698f4f-5fa1-4baa-be05-87d03052af49 \N +77ba7b40-e312-40d7-9da0-de41f0ed3b8c 77ff47f8-f578-477d-8c06-e70a846332f5 t ${role_read-token} read-token grafana 77ff47f8-f578-477d-8c06-e70a846332f5 \N +c49bddc6-ec92-4caa-bc04-57ba80a92eb9 grafana f ${role_offline-access} offline_access grafana \N grafana +0f3d47bb-002a-4cd0-a502-725f224308a7 grafana f ${role_uma_authorization} uma_authorization grafana \N grafana +60f1b1ea-9059-41ea-acef-573643b24709 grafana f Grafana Organization Administrator admin grafana \N grafana +c029a218-4519-4537-ae12-d8f3c27a0003 grafana f Grafana Server Admin serveradmin grafana \N grafana +c9a776f9-2740-435f-a725-4dbcc17a6c91 grafana f Grafana Viewer viewer grafana \N grafana +c4c74006-c346-48cf-8cf1-1617e3e1cde1 grafana f Grafana Editor editor grafana \N grafana +\. + + +-- +-- Data for Name: migration_model; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.migration_model (id, version, update_time) FROM stdin; +g5slr 12.0.1 1643820448 +\. + + +-- +-- Data for Name: offline_client_session; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.offline_client_session (user_session_id, client_id, offline_flag, "timestamp", data, client_storage_provider, external_client_id) FROM stdin; +\. + + +-- +-- Data for Name: offline_user_session; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.offline_user_session (user_session_id, user_id, realm_id, created_on, offline_flag, data, last_session_refresh) FROM stdin; +\. + + +-- +-- Data for Name: policy_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.policy_config (policy_id, name, value) FROM stdin; +\. + + +-- +-- Data for Name: protocol_mapper; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.protocol_mapper (id, name, protocol, protocol_mapper_name, client_id, client_scope_id) FROM stdin; +e4931993-ceb0-4048-8a37-ca4f438099f3 audience resolve openid-connect oidc-audience-resolve-mapper 11c67f5b-dde7-4680-b05b-c9c59d78bda4 \N +c1c53a76-92ee-42b8-8420-92a815267f71 locale openid-connect oidc-usermodel-attribute-mapper 2f521d09-7304-4b5e-a94b-7cc7300b8b50 \N +ddc7c8be-0753-417f-9d0e-ea22008f23f9 full name openid-connect oidc-full-name-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +fc77e0b8-b586-40dc-bc3d-7aa04a9c0f19 family name openid-connect oidc-usermodel-property-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +563b93e7-e66d-454f-9f34-4538cab6f260 given name openid-connect oidc-usermodel-property-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +30fa9092-eb2a-4a55-8d73-82e6e23334ec middle name openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +6aff4774-b4cf-4775-b4e5-0b20c549d181 nickname openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +5a603582-2511-483b-8e05-be891c7642b1 username openid-connect oidc-usermodel-property-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +9e111324-2508-4a4b-841a-19883a331f66 profile openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +5906ef3c-7b55-4b10-9ba1-0f3a25f3b005 picture openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +412ba9b5-f535-4263-9600-b23c2f682fc9 website openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +3741c094-0c4f-42fb-a178-89ceb85adeda gender openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +ae6e2dbc-b310-4443-acd2-894d4e9dcb79 birthdate openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +7f9b6774-17f5-417a-8fad-576fc862920c zoneinfo openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +7257c710-d01b-4c50-bb4f-060cfc8fe4b3 locale openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +1ab8f9c8-42cc-4604-8c04-43f8243acc9b updated at openid-connect oidc-usermodel-attribute-mapper \N 66deef47-2158-4d5b-a75f-0bf42f642e7b +b47f8f1c-0242-40c3-973a-d58a25022d6e email openid-connect oidc-usermodel-property-mapper \N 94ef659c-4c4a-4a33-98e8-bfcf443e9268 +a5fcd319-279d-4995-8896-4bf810343ad2 email verified openid-connect oidc-usermodel-property-mapper \N 94ef659c-4c4a-4a33-98e8-bfcf443e9268 +4d697f62-b924-4b0c-8202-0a82ee08684c address openid-connect oidc-address-mapper \N 96a960d2-c203-4ef0-a53c-c3edd01f2305 +d1eaf34e-6818-419c-b3c1-8f1b3627ca17 phone number openid-connect oidc-usermodel-attribute-mapper \N 3f705379-3361-486d-b75a-f7b4e4be492c +ee0ec8fa-c020-4cb9-991e-30180fe0c5dc phone number verified openid-connect oidc-usermodel-attribute-mapper \N 3f705379-3361-486d-b75a-f7b4e4be492c +bc41b27d-2e1b-48af-8184-e88e03f950e2 realm roles openid-connect oidc-usermodel-realm-role-mapper \N b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 +967cee35-09fd-400f-a634-db3fdbab2420 client roles openid-connect oidc-usermodel-client-role-mapper \N b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 +543542f4-71b1-4fef-8832-bb14b553ad9a audience resolve openid-connect oidc-audience-resolve-mapper \N b8a9cdd1-2f30-4e23-a721-78b01cfba1d7 +bf72f65b-9d9c-4c88-9cb9-478edbb721db allowed web origins openid-connect oidc-allowed-origins-mapper \N 619cf41a-5ff8-4a04-9f1e-50717e5f7ce8 +98402b93-9012-4e47-b008-99ffaf93043e upn openid-connect oidc-usermodel-property-mapper \N 42bfb506-bf0d-424e-8649-53a9a93d252d +39d571e6-0b8b-4b6d-aa2d-9cff126decd0 groups openid-connect oidc-usermodel-realm-role-mapper \N 42bfb506-bf0d-424e-8649-53a9a93d252d +7be7c4e2-7281-4226-acec-77f77b3072dc audience resolve openid-connect oidc-audience-resolve-mapper 230081b5-9161-45c3-9e08-9eda5412f7f7 \N +c5adae03-51f5-4acb-baeb-c0241a16757e full name openid-connect oidc-full-name-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +6d019964-a5e5-4737-a8bf-90c34ce33c0f family name openid-connect oidc-usermodel-property-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +e9cb431c-e1f1-4ce9-941e-a8a88bfce413 given name openid-connect oidc-usermodel-property-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +4cec49ad-50de-4fed-bf61-3928d88b9cfc middle name openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +21dd6189-62cb-4039-9590-9096ff6d14b2 nickname openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +bcb6bed8-ebfc-450b-b4a6-17f5bdfaa37c username openid-connect oidc-usermodel-property-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +c21b39cc-c761-4cf4-a4a4-6de3ff05476d profile openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +aeec7bd1-953e-4ba0-b146-c87f1e20f73f picture openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +02f83a6b-7a50-4541-9b12-968a23e2cf78 website openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +013a3f59-6a7f-42e4-9fce-4fc420a1b3ea gender openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +04b7ca11-80bd-44a1-87c3-835e7fb9e9f5 birthdate openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +49703eaa-a556-431d-b828-c64d8c791d00 zoneinfo openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +2b9ace9b-a654-4178-bb28-c8062569453c locale openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +60babdab-a8a4-41a4-98b0-08bd40182cdf updated at openid-connect oidc-usermodel-attribute-mapper \N 74daf2cd-40d4-4304-87a8-92cdca808512 +75ae2f8d-a382-47e7-978a-f51bf12b80ae email openid-connect oidc-usermodel-property-mapper \N 96d521d3-facc-4b5a-a8b4-a879bae6be07 +b75ba788-217a-47ad-bc81-2e8f4dcce913 email verified openid-connect oidc-usermodel-property-mapper \N 96d521d3-facc-4b5a-a8b4-a879bae6be07 +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 address openid-connect oidc-address-mapper \N a5bb3a5f-fd26-4be6-9557-26e20a03d33d +13c34a80-7711-4a0d-97b0-b29a501294fa phone number openid-connect oidc-usermodel-attribute-mapper \N d6ffe9fc-a03c-4496-85dc-dbb5e7754587 +b4854867-3bfb-409b-92a8-6ec37db17f99 phone number verified openid-connect oidc-usermodel-attribute-mapper \N d6ffe9fc-a03c-4496-85dc-dbb5e7754587 +1fc8999a-04d9-421b-8557-e417a3750358 realm roles openid-connect oidc-usermodel-realm-role-mapper \N d6077ed7-b265-4f82-9336-24614967bd5d +384e97dd-36ad-4b0e-af63-d0cb3a2153d4 allowed web origins openid-connect oidc-allowed-origins-mapper \N 699671ab-e7c1-4fcf-beb8-ea54f1471fc1 +f03cac68-3f0e-4068-9adf-ee64567689a7 upn openid-connect oidc-usermodel-property-mapper \N c61f5b19-c17e-49a1-91b8-a0296411b928 +04183ee1-b558-4f63-839f-922d30b34a9e groups openid-connect oidc-usermodel-realm-role-mapper \N c61f5b19-c17e-49a1-91b8-a0296411b928 +df78645e-c32b-4160-b79f-42e622d71982 locale openid-connect oidc-usermodel-attribute-mapper 805aebc8-9d01-42b6-bcce-6ce48ca63ef0 \N +0108b99f-2f31-4e73-9597-cb29e0e8c486 username openid-connect oidc-usermodel-property-mapper \N f619a55a-d565-4cc0-8bf4-4dbaab5382fe +70b0a264-a7c3-43ff-b24f-14ca4f5f118e login openid-connect oidc-usermodel-property-mapper \N 0a7c7dde-23d7-4a93-bdee-4a8963aee9a4 +2f8ee9af-b6dd-4790-9e7b-cce83a603566 name openid-connect oidc-full-name-mapper \N d4723cd4-f717-44b7-a9b0-6c32c5ecd23f +\. + + +-- +-- Data for Name: protocol_mapper_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.protocol_mapper_config (protocol_mapper_id, value, name) FROM stdin; +c1c53a76-92ee-42b8-8420-92a815267f71 true userinfo.token.claim +c1c53a76-92ee-42b8-8420-92a815267f71 locale user.attribute +c1c53a76-92ee-42b8-8420-92a815267f71 true id.token.claim +c1c53a76-92ee-42b8-8420-92a815267f71 true access.token.claim +c1c53a76-92ee-42b8-8420-92a815267f71 locale claim.name +c1c53a76-92ee-42b8-8420-92a815267f71 String jsonType.label +ddc7c8be-0753-417f-9d0e-ea22008f23f9 true userinfo.token.claim +ddc7c8be-0753-417f-9d0e-ea22008f23f9 true id.token.claim +ddc7c8be-0753-417f-9d0e-ea22008f23f9 true access.token.claim +fc77e0b8-b586-40dc-bc3d-7aa04a9c0f19 true userinfo.token.claim +fc77e0b8-b586-40dc-bc3d-7aa04a9c0f19 lastName user.attribute +fc77e0b8-b586-40dc-bc3d-7aa04a9c0f19 true id.token.claim +fc77e0b8-b586-40dc-bc3d-7aa04a9c0f19 true access.token.claim +fc77e0b8-b586-40dc-bc3d-7aa04a9c0f19 family_name claim.name +fc77e0b8-b586-40dc-bc3d-7aa04a9c0f19 String jsonType.label +563b93e7-e66d-454f-9f34-4538cab6f260 true userinfo.token.claim +563b93e7-e66d-454f-9f34-4538cab6f260 firstName user.attribute +563b93e7-e66d-454f-9f34-4538cab6f260 true id.token.claim +563b93e7-e66d-454f-9f34-4538cab6f260 true access.token.claim +563b93e7-e66d-454f-9f34-4538cab6f260 given_name claim.name +563b93e7-e66d-454f-9f34-4538cab6f260 String jsonType.label +30fa9092-eb2a-4a55-8d73-82e6e23334ec true userinfo.token.claim +30fa9092-eb2a-4a55-8d73-82e6e23334ec middleName user.attribute +30fa9092-eb2a-4a55-8d73-82e6e23334ec true id.token.claim +30fa9092-eb2a-4a55-8d73-82e6e23334ec true access.token.claim +30fa9092-eb2a-4a55-8d73-82e6e23334ec middle_name claim.name +30fa9092-eb2a-4a55-8d73-82e6e23334ec String jsonType.label +6aff4774-b4cf-4775-b4e5-0b20c549d181 true userinfo.token.claim +6aff4774-b4cf-4775-b4e5-0b20c549d181 nickname user.attribute +6aff4774-b4cf-4775-b4e5-0b20c549d181 true id.token.claim +6aff4774-b4cf-4775-b4e5-0b20c549d181 true access.token.claim +6aff4774-b4cf-4775-b4e5-0b20c549d181 nickname claim.name +6aff4774-b4cf-4775-b4e5-0b20c549d181 String jsonType.label +5a603582-2511-483b-8e05-be891c7642b1 true userinfo.token.claim +5a603582-2511-483b-8e05-be891c7642b1 username user.attribute +5a603582-2511-483b-8e05-be891c7642b1 true id.token.claim +5a603582-2511-483b-8e05-be891c7642b1 true access.token.claim +5a603582-2511-483b-8e05-be891c7642b1 preferred_username claim.name +5a603582-2511-483b-8e05-be891c7642b1 String jsonType.label +9e111324-2508-4a4b-841a-19883a331f66 true userinfo.token.claim +9e111324-2508-4a4b-841a-19883a331f66 profile user.attribute +9e111324-2508-4a4b-841a-19883a331f66 true id.token.claim +9e111324-2508-4a4b-841a-19883a331f66 true access.token.claim +9e111324-2508-4a4b-841a-19883a331f66 profile claim.name +9e111324-2508-4a4b-841a-19883a331f66 String jsonType.label +5906ef3c-7b55-4b10-9ba1-0f3a25f3b005 true userinfo.token.claim +5906ef3c-7b55-4b10-9ba1-0f3a25f3b005 picture user.attribute +5906ef3c-7b55-4b10-9ba1-0f3a25f3b005 true id.token.claim +5906ef3c-7b55-4b10-9ba1-0f3a25f3b005 true access.token.claim +5906ef3c-7b55-4b10-9ba1-0f3a25f3b005 picture claim.name +5906ef3c-7b55-4b10-9ba1-0f3a25f3b005 String jsonType.label +412ba9b5-f535-4263-9600-b23c2f682fc9 true userinfo.token.claim +412ba9b5-f535-4263-9600-b23c2f682fc9 website user.attribute +412ba9b5-f535-4263-9600-b23c2f682fc9 true id.token.claim +412ba9b5-f535-4263-9600-b23c2f682fc9 true access.token.claim +412ba9b5-f535-4263-9600-b23c2f682fc9 website claim.name +412ba9b5-f535-4263-9600-b23c2f682fc9 String jsonType.label +3741c094-0c4f-42fb-a178-89ceb85adeda true userinfo.token.claim +3741c094-0c4f-42fb-a178-89ceb85adeda gender user.attribute +3741c094-0c4f-42fb-a178-89ceb85adeda true id.token.claim +3741c094-0c4f-42fb-a178-89ceb85adeda true access.token.claim +3741c094-0c4f-42fb-a178-89ceb85adeda gender claim.name +3741c094-0c4f-42fb-a178-89ceb85adeda String jsonType.label +ae6e2dbc-b310-4443-acd2-894d4e9dcb79 true userinfo.token.claim +ae6e2dbc-b310-4443-acd2-894d4e9dcb79 birthdate user.attribute +ae6e2dbc-b310-4443-acd2-894d4e9dcb79 true id.token.claim +ae6e2dbc-b310-4443-acd2-894d4e9dcb79 true access.token.claim +ae6e2dbc-b310-4443-acd2-894d4e9dcb79 birthdate claim.name +ae6e2dbc-b310-4443-acd2-894d4e9dcb79 String jsonType.label +7f9b6774-17f5-417a-8fad-576fc862920c true userinfo.token.claim +7f9b6774-17f5-417a-8fad-576fc862920c zoneinfo user.attribute +7f9b6774-17f5-417a-8fad-576fc862920c true id.token.claim +7f9b6774-17f5-417a-8fad-576fc862920c true access.token.claim +7f9b6774-17f5-417a-8fad-576fc862920c zoneinfo claim.name +7f9b6774-17f5-417a-8fad-576fc862920c String jsonType.label +7257c710-d01b-4c50-bb4f-060cfc8fe4b3 true userinfo.token.claim +7257c710-d01b-4c50-bb4f-060cfc8fe4b3 locale user.attribute +7257c710-d01b-4c50-bb4f-060cfc8fe4b3 true id.token.claim +7257c710-d01b-4c50-bb4f-060cfc8fe4b3 true access.token.claim +7257c710-d01b-4c50-bb4f-060cfc8fe4b3 locale claim.name +7257c710-d01b-4c50-bb4f-060cfc8fe4b3 String jsonType.label +1ab8f9c8-42cc-4604-8c04-43f8243acc9b true userinfo.token.claim +1ab8f9c8-42cc-4604-8c04-43f8243acc9b updatedAt user.attribute +1ab8f9c8-42cc-4604-8c04-43f8243acc9b true id.token.claim +1ab8f9c8-42cc-4604-8c04-43f8243acc9b true access.token.claim +1ab8f9c8-42cc-4604-8c04-43f8243acc9b updated_at claim.name +1ab8f9c8-42cc-4604-8c04-43f8243acc9b String jsonType.label +b47f8f1c-0242-40c3-973a-d58a25022d6e true userinfo.token.claim +b47f8f1c-0242-40c3-973a-d58a25022d6e email user.attribute +b47f8f1c-0242-40c3-973a-d58a25022d6e true id.token.claim +b47f8f1c-0242-40c3-973a-d58a25022d6e true access.token.claim +b47f8f1c-0242-40c3-973a-d58a25022d6e email claim.name +b47f8f1c-0242-40c3-973a-d58a25022d6e String jsonType.label +a5fcd319-279d-4995-8896-4bf810343ad2 true userinfo.token.claim +a5fcd319-279d-4995-8896-4bf810343ad2 emailVerified user.attribute +a5fcd319-279d-4995-8896-4bf810343ad2 true id.token.claim +a5fcd319-279d-4995-8896-4bf810343ad2 true access.token.claim +a5fcd319-279d-4995-8896-4bf810343ad2 email_verified claim.name +a5fcd319-279d-4995-8896-4bf810343ad2 boolean jsonType.label +4d697f62-b924-4b0c-8202-0a82ee08684c formatted user.attribute.formatted +4d697f62-b924-4b0c-8202-0a82ee08684c country user.attribute.country +4d697f62-b924-4b0c-8202-0a82ee08684c postal_code user.attribute.postal_code +4d697f62-b924-4b0c-8202-0a82ee08684c true userinfo.token.claim +4d697f62-b924-4b0c-8202-0a82ee08684c street user.attribute.street +4d697f62-b924-4b0c-8202-0a82ee08684c true id.token.claim +4d697f62-b924-4b0c-8202-0a82ee08684c region user.attribute.region +4d697f62-b924-4b0c-8202-0a82ee08684c true access.token.claim +4d697f62-b924-4b0c-8202-0a82ee08684c locality user.attribute.locality +d1eaf34e-6818-419c-b3c1-8f1b3627ca17 true userinfo.token.claim +d1eaf34e-6818-419c-b3c1-8f1b3627ca17 phoneNumber user.attribute +d1eaf34e-6818-419c-b3c1-8f1b3627ca17 true id.token.claim +d1eaf34e-6818-419c-b3c1-8f1b3627ca17 true access.token.claim +d1eaf34e-6818-419c-b3c1-8f1b3627ca17 phone_number claim.name +d1eaf34e-6818-419c-b3c1-8f1b3627ca17 String jsonType.label +ee0ec8fa-c020-4cb9-991e-30180fe0c5dc true userinfo.token.claim +ee0ec8fa-c020-4cb9-991e-30180fe0c5dc phoneNumberVerified user.attribute +ee0ec8fa-c020-4cb9-991e-30180fe0c5dc true id.token.claim +ee0ec8fa-c020-4cb9-991e-30180fe0c5dc true access.token.claim +ee0ec8fa-c020-4cb9-991e-30180fe0c5dc phone_number_verified claim.name +ee0ec8fa-c020-4cb9-991e-30180fe0c5dc boolean jsonType.label +bc41b27d-2e1b-48af-8184-e88e03f950e2 true multivalued +bc41b27d-2e1b-48af-8184-e88e03f950e2 foo user.attribute +bc41b27d-2e1b-48af-8184-e88e03f950e2 true access.token.claim +bc41b27d-2e1b-48af-8184-e88e03f950e2 realm_access.roles claim.name +bc41b27d-2e1b-48af-8184-e88e03f950e2 String jsonType.label +967cee35-09fd-400f-a634-db3fdbab2420 true multivalued +967cee35-09fd-400f-a634-db3fdbab2420 foo user.attribute +967cee35-09fd-400f-a634-db3fdbab2420 true access.token.claim +967cee35-09fd-400f-a634-db3fdbab2420 resource_access.${client_id}.roles claim.name +967cee35-09fd-400f-a634-db3fdbab2420 String jsonType.label +98402b93-9012-4e47-b008-99ffaf93043e true userinfo.token.claim +98402b93-9012-4e47-b008-99ffaf93043e username user.attribute +98402b93-9012-4e47-b008-99ffaf93043e true id.token.claim +98402b93-9012-4e47-b008-99ffaf93043e true access.token.claim +98402b93-9012-4e47-b008-99ffaf93043e upn claim.name +98402b93-9012-4e47-b008-99ffaf93043e String jsonType.label +39d571e6-0b8b-4b6d-aa2d-9cff126decd0 true multivalued +39d571e6-0b8b-4b6d-aa2d-9cff126decd0 foo user.attribute +39d571e6-0b8b-4b6d-aa2d-9cff126decd0 true id.token.claim +39d571e6-0b8b-4b6d-aa2d-9cff126decd0 true access.token.claim +39d571e6-0b8b-4b6d-aa2d-9cff126decd0 groups claim.name +39d571e6-0b8b-4b6d-aa2d-9cff126decd0 String jsonType.label +c5adae03-51f5-4acb-baeb-c0241a16757e true userinfo.token.claim +c5adae03-51f5-4acb-baeb-c0241a16757e true id.token.claim +c5adae03-51f5-4acb-baeb-c0241a16757e true access.token.claim +6d019964-a5e5-4737-a8bf-90c34ce33c0f true userinfo.token.claim +6d019964-a5e5-4737-a8bf-90c34ce33c0f lastName user.attribute +6d019964-a5e5-4737-a8bf-90c34ce33c0f true id.token.claim +6d019964-a5e5-4737-a8bf-90c34ce33c0f true access.token.claim +6d019964-a5e5-4737-a8bf-90c34ce33c0f family_name claim.name +6d019964-a5e5-4737-a8bf-90c34ce33c0f String jsonType.label +e9cb431c-e1f1-4ce9-941e-a8a88bfce413 true userinfo.token.claim +e9cb431c-e1f1-4ce9-941e-a8a88bfce413 firstName user.attribute +e9cb431c-e1f1-4ce9-941e-a8a88bfce413 true id.token.claim +e9cb431c-e1f1-4ce9-941e-a8a88bfce413 true access.token.claim +e9cb431c-e1f1-4ce9-941e-a8a88bfce413 given_name claim.name +e9cb431c-e1f1-4ce9-941e-a8a88bfce413 String jsonType.label +4cec49ad-50de-4fed-bf61-3928d88b9cfc true userinfo.token.claim +4cec49ad-50de-4fed-bf61-3928d88b9cfc middleName user.attribute +4cec49ad-50de-4fed-bf61-3928d88b9cfc true id.token.claim +4cec49ad-50de-4fed-bf61-3928d88b9cfc true access.token.claim +4cec49ad-50de-4fed-bf61-3928d88b9cfc middle_name claim.name +4cec49ad-50de-4fed-bf61-3928d88b9cfc String jsonType.label +21dd6189-62cb-4039-9590-9096ff6d14b2 true userinfo.token.claim +21dd6189-62cb-4039-9590-9096ff6d14b2 nickname user.attribute +21dd6189-62cb-4039-9590-9096ff6d14b2 true id.token.claim +21dd6189-62cb-4039-9590-9096ff6d14b2 true access.token.claim +21dd6189-62cb-4039-9590-9096ff6d14b2 nickname claim.name +21dd6189-62cb-4039-9590-9096ff6d14b2 String jsonType.label +bcb6bed8-ebfc-450b-b4a6-17f5bdfaa37c true userinfo.token.claim +bcb6bed8-ebfc-450b-b4a6-17f5bdfaa37c username user.attribute +bcb6bed8-ebfc-450b-b4a6-17f5bdfaa37c true id.token.claim +bcb6bed8-ebfc-450b-b4a6-17f5bdfaa37c true access.token.claim +bcb6bed8-ebfc-450b-b4a6-17f5bdfaa37c preferred_username claim.name +bcb6bed8-ebfc-450b-b4a6-17f5bdfaa37c String jsonType.label +c21b39cc-c761-4cf4-a4a4-6de3ff05476d true userinfo.token.claim +c21b39cc-c761-4cf4-a4a4-6de3ff05476d profile user.attribute +c21b39cc-c761-4cf4-a4a4-6de3ff05476d true id.token.claim +c21b39cc-c761-4cf4-a4a4-6de3ff05476d true access.token.claim +c21b39cc-c761-4cf4-a4a4-6de3ff05476d profile claim.name +c21b39cc-c761-4cf4-a4a4-6de3ff05476d String jsonType.label +aeec7bd1-953e-4ba0-b146-c87f1e20f73f true userinfo.token.claim +aeec7bd1-953e-4ba0-b146-c87f1e20f73f picture user.attribute +aeec7bd1-953e-4ba0-b146-c87f1e20f73f true id.token.claim +aeec7bd1-953e-4ba0-b146-c87f1e20f73f true access.token.claim +aeec7bd1-953e-4ba0-b146-c87f1e20f73f picture claim.name +aeec7bd1-953e-4ba0-b146-c87f1e20f73f String jsonType.label +02f83a6b-7a50-4541-9b12-968a23e2cf78 true userinfo.token.claim +02f83a6b-7a50-4541-9b12-968a23e2cf78 website user.attribute +02f83a6b-7a50-4541-9b12-968a23e2cf78 true id.token.claim +02f83a6b-7a50-4541-9b12-968a23e2cf78 true access.token.claim +02f83a6b-7a50-4541-9b12-968a23e2cf78 website claim.name +02f83a6b-7a50-4541-9b12-968a23e2cf78 String jsonType.label +013a3f59-6a7f-42e4-9fce-4fc420a1b3ea true userinfo.token.claim +013a3f59-6a7f-42e4-9fce-4fc420a1b3ea gender user.attribute +013a3f59-6a7f-42e4-9fce-4fc420a1b3ea true id.token.claim +013a3f59-6a7f-42e4-9fce-4fc420a1b3ea true access.token.claim +013a3f59-6a7f-42e4-9fce-4fc420a1b3ea gender claim.name +013a3f59-6a7f-42e4-9fce-4fc420a1b3ea String jsonType.label +04b7ca11-80bd-44a1-87c3-835e7fb9e9f5 true userinfo.token.claim +04b7ca11-80bd-44a1-87c3-835e7fb9e9f5 birthdate user.attribute +04b7ca11-80bd-44a1-87c3-835e7fb9e9f5 true id.token.claim +04b7ca11-80bd-44a1-87c3-835e7fb9e9f5 true access.token.claim +04b7ca11-80bd-44a1-87c3-835e7fb9e9f5 birthdate claim.name +04b7ca11-80bd-44a1-87c3-835e7fb9e9f5 String jsonType.label +49703eaa-a556-431d-b828-c64d8c791d00 true userinfo.token.claim +49703eaa-a556-431d-b828-c64d8c791d00 zoneinfo user.attribute +49703eaa-a556-431d-b828-c64d8c791d00 true id.token.claim +49703eaa-a556-431d-b828-c64d8c791d00 true access.token.claim +49703eaa-a556-431d-b828-c64d8c791d00 zoneinfo claim.name +49703eaa-a556-431d-b828-c64d8c791d00 String jsonType.label +2b9ace9b-a654-4178-bb28-c8062569453c true userinfo.token.claim +2b9ace9b-a654-4178-bb28-c8062569453c locale user.attribute +2b9ace9b-a654-4178-bb28-c8062569453c true id.token.claim +2b9ace9b-a654-4178-bb28-c8062569453c true access.token.claim +2b9ace9b-a654-4178-bb28-c8062569453c locale claim.name +2b9ace9b-a654-4178-bb28-c8062569453c String jsonType.label +60babdab-a8a4-41a4-98b0-08bd40182cdf true userinfo.token.claim +60babdab-a8a4-41a4-98b0-08bd40182cdf updatedAt user.attribute +60babdab-a8a4-41a4-98b0-08bd40182cdf true id.token.claim +60babdab-a8a4-41a4-98b0-08bd40182cdf true access.token.claim +60babdab-a8a4-41a4-98b0-08bd40182cdf updated_at claim.name +60babdab-a8a4-41a4-98b0-08bd40182cdf String jsonType.label +75ae2f8d-a382-47e7-978a-f51bf12b80ae true userinfo.token.claim +75ae2f8d-a382-47e7-978a-f51bf12b80ae email user.attribute +75ae2f8d-a382-47e7-978a-f51bf12b80ae true id.token.claim +75ae2f8d-a382-47e7-978a-f51bf12b80ae true access.token.claim +75ae2f8d-a382-47e7-978a-f51bf12b80ae email claim.name +75ae2f8d-a382-47e7-978a-f51bf12b80ae String jsonType.label +b75ba788-217a-47ad-bc81-2e8f4dcce913 true userinfo.token.claim +b75ba788-217a-47ad-bc81-2e8f4dcce913 emailVerified user.attribute +b75ba788-217a-47ad-bc81-2e8f4dcce913 true id.token.claim +b75ba788-217a-47ad-bc81-2e8f4dcce913 true access.token.claim +b75ba788-217a-47ad-bc81-2e8f4dcce913 email_verified claim.name +b75ba788-217a-47ad-bc81-2e8f4dcce913 boolean jsonType.label +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 formatted user.attribute.formatted +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 country user.attribute.country +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 postal_code user.attribute.postal_code +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 true userinfo.token.claim +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 street user.attribute.street +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 true id.token.claim +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 region user.attribute.region +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 true access.token.claim +c83418a1-6b68-4fd7-8b97-d22f0e2e0ad0 locality user.attribute.locality +13c34a80-7711-4a0d-97b0-b29a501294fa true userinfo.token.claim +13c34a80-7711-4a0d-97b0-b29a501294fa phoneNumber user.attribute +13c34a80-7711-4a0d-97b0-b29a501294fa true id.token.claim +13c34a80-7711-4a0d-97b0-b29a501294fa true access.token.claim +13c34a80-7711-4a0d-97b0-b29a501294fa phone_number claim.name +13c34a80-7711-4a0d-97b0-b29a501294fa String jsonType.label +b4854867-3bfb-409b-92a8-6ec37db17f99 true userinfo.token.claim +b4854867-3bfb-409b-92a8-6ec37db17f99 phoneNumberVerified user.attribute +b4854867-3bfb-409b-92a8-6ec37db17f99 true id.token.claim +b4854867-3bfb-409b-92a8-6ec37db17f99 true access.token.claim +b4854867-3bfb-409b-92a8-6ec37db17f99 phone_number_verified claim.name +b4854867-3bfb-409b-92a8-6ec37db17f99 boolean jsonType.label +1fc8999a-04d9-421b-8557-e417a3750358 true multivalued +1fc8999a-04d9-421b-8557-e417a3750358 foo user.attribute +1fc8999a-04d9-421b-8557-e417a3750358 true access.token.claim +1fc8999a-04d9-421b-8557-e417a3750358 String jsonType.label +f03cac68-3f0e-4068-9adf-ee64567689a7 true userinfo.token.claim +f03cac68-3f0e-4068-9adf-ee64567689a7 username user.attribute +f03cac68-3f0e-4068-9adf-ee64567689a7 true id.token.claim +f03cac68-3f0e-4068-9adf-ee64567689a7 true access.token.claim +f03cac68-3f0e-4068-9adf-ee64567689a7 upn claim.name +f03cac68-3f0e-4068-9adf-ee64567689a7 String jsonType.label +04183ee1-b558-4f63-839f-922d30b34a9e true multivalued +04183ee1-b558-4f63-839f-922d30b34a9e foo user.attribute +04183ee1-b558-4f63-839f-922d30b34a9e true id.token.claim +04183ee1-b558-4f63-839f-922d30b34a9e true access.token.claim +04183ee1-b558-4f63-839f-922d30b34a9e groups claim.name +04183ee1-b558-4f63-839f-922d30b34a9e String jsonType.label +df78645e-c32b-4160-b79f-42e622d71982 true userinfo.token.claim +df78645e-c32b-4160-b79f-42e622d71982 locale user.attribute +df78645e-c32b-4160-b79f-42e622d71982 true id.token.claim +df78645e-c32b-4160-b79f-42e622d71982 true access.token.claim +df78645e-c32b-4160-b79f-42e622d71982 locale claim.name +df78645e-c32b-4160-b79f-42e622d71982 String jsonType.label +0108b99f-2f31-4e73-9597-cb29e0e8c486 true userinfo.token.claim +0108b99f-2f31-4e73-9597-cb29e0e8c486 username user.attribute +0108b99f-2f31-4e73-9597-cb29e0e8c486 true id.token.claim +0108b99f-2f31-4e73-9597-cb29e0e8c486 true access.token.claim +0108b99f-2f31-4e73-9597-cb29e0e8c486 preferred_username claim.name +0108b99f-2f31-4e73-9597-cb29e0e8c486 String jsonType.label +1fc8999a-04d9-421b-8557-e417a3750358 true userinfo.token.claim +1fc8999a-04d9-421b-8557-e417a3750358 roles claim.name +70b0a264-a7c3-43ff-b24f-14ca4f5f118e true userinfo.token.claim +70b0a264-a7c3-43ff-b24f-14ca4f5f118e username user.attribute +70b0a264-a7c3-43ff-b24f-14ca4f5f118e true id.token.claim +70b0a264-a7c3-43ff-b24f-14ca4f5f118e true access.token.claim +70b0a264-a7c3-43ff-b24f-14ca4f5f118e login claim.name +70b0a264-a7c3-43ff-b24f-14ca4f5f118e String jsonType.label +2f8ee9af-b6dd-4790-9e7b-cce83a603566 true id.token.claim +2f8ee9af-b6dd-4790-9e7b-cce83a603566 true access.token.claim +2f8ee9af-b6dd-4790-9e7b-cce83a603566 true userinfo.token.claim +1fc8999a-04d9-421b-8557-e417a3750358 true id.token.claim +\. + + +-- +-- Data for Name: realm; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm (id, access_code_lifespan, user_action_lifespan, access_token_lifespan, account_theme, admin_theme, email_theme, enabled, events_enabled, events_expiration, login_theme, name, not_before, password_policy, registration_allowed, remember_me, reset_password_allowed, social, ssl_required, sso_idle_timeout, sso_max_lifespan, update_profile_on_soc_login, verify_email, master_admin_client, login_lifespan, internationalization_enabled, default_locale, reg_email_as_username, admin_events_enabled, admin_events_details_enabled, edit_username_allowed, otp_policy_counter, otp_policy_window, otp_policy_period, otp_policy_digits, otp_policy_alg, otp_policy_type, browser_flow, registration_flow, direct_grant_flow, reset_credentials_flow, client_auth_flow, offline_session_idle_timeout, revoke_refresh_token, access_token_life_implicit, login_with_email_allowed, duplicate_emails_allowed, docker_auth_flow, refresh_token_max_reuse, allow_user_managed_access, sso_max_lifespan_remember_me, sso_idle_timeout_remember_me) FROM stdin; +master 60 300 60 \N \N \N t f 0 \N master 1643820855 \N f f f f EXTERNAL 1800 36000 f f 3cd285ea-0f6e-43b6-ab5c-d021c33a551b 1800 f \N f f f f 0 1 30 6 HmacSHA1 totp ef998ef5-ca12-45db-a252-2e71b1419039 1695e7d2-ad80-4502-8479-8121a6e2a2f0 5f6f801e-0588-4a6e-860a-35483f5c1ec7 954b046d-2b24-405e-84ee-c44ffe603df2 023dc515-c259-42bb-88a8-2e8d84abca92 2592000 f 900 t f 032b05cf-0007-44da-a370-b42039f6b762 0 f 0 0 +grafana 60 300 300 \N \N \N t f 0 \N grafana 1643820879 \N f f f f EXTERNAL 1800 36000 f f ef7f6eac-9fff-44aa-a86c-5125d52acc82 1800 f \N f f f f 0 1 30 6 HmacSHA1 totp a38aeb47-f27e-4e68-82ff-7cc7371a47a7 9d02badd-cb1c-4655-bf5e-f888861433ff b478ecfb-db7e-4797-a245-8fc3b4dec884 3085fb68-fc1f-4e1c-a8be-33fb45194b04 cbb4b3ca-ced6-4046-8b59-f1c3959c7948 2592000 f 900 t f 95e02703-f5bc-4e04-8bef-f6adc2d8173f 0 f 0 0 +\. + + +-- +-- Data for Name: realm_attribute; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_attribute (name, value, realm_id) FROM stdin; +_browser_header.contentSecurityPolicyReportOnly master +_browser_header.xContentTypeOptions nosniff master +_browser_header.xRobotsTag none master +_browser_header.xFrameOptions SAMEORIGIN master +_browser_header.contentSecurityPolicy frame-src 'self'; frame-ancestors 'self'; object-src 'none'; master +_browser_header.xXSSProtection 1; mode=block master +_browser_header.strictTransportSecurity max-age=31536000; includeSubDomains master +bruteForceProtected false master +permanentLockout false master +maxFailureWaitSeconds 900 master +minimumQuickLoginWaitSeconds 60 master +waitIncrementSeconds 60 master +quickLoginCheckMilliSeconds 1000 master +maxDeltaTimeSeconds 43200 master +failureFactor 30 master +displayName Keycloak master +displayNameHtml
Keycloak
master +offlineSessionMaxLifespanEnabled false master +offlineSessionMaxLifespan 5184000 master +_browser_header.contentSecurityPolicyReportOnly grafana +_browser_header.xContentTypeOptions nosniff grafana +_browser_header.xRobotsTag none grafana +_browser_header.xFrameOptions SAMEORIGIN grafana +_browser_header.contentSecurityPolicy frame-src 'self'; frame-ancestors 'self'; object-src 'none'; grafana +_browser_header.xXSSProtection 1; mode=block grafana +_browser_header.strictTransportSecurity max-age=31536000; includeSubDomains grafana +bruteForceProtected false grafana +permanentLockout false grafana +maxFailureWaitSeconds 900 grafana +minimumQuickLoginWaitSeconds 60 grafana +waitIncrementSeconds 60 grafana +quickLoginCheckMilliSeconds 1000 grafana +maxDeltaTimeSeconds 43200 grafana +failureFactor 30 grafana +offlineSessionMaxLifespanEnabled false grafana +offlineSessionMaxLifespan 5184000 grafana +actionTokenGeneratedByAdminLifespan 43200 grafana +actionTokenGeneratedByUserLifespan 300 grafana +webAuthnPolicyRpEntityName keycloak grafana +webAuthnPolicySignatureAlgorithms ES256 grafana +webAuthnPolicyRpId grafana +webAuthnPolicyAttestationConveyancePreference not specified grafana +webAuthnPolicyAuthenticatorAttachment not specified grafana +webAuthnPolicyRequireResidentKey not specified grafana +webAuthnPolicyUserVerificationRequirement not specified grafana +webAuthnPolicyCreateTimeout 0 grafana +webAuthnPolicyAvoidSameAuthenticatorRegister false grafana +webAuthnPolicyRpEntityNamePasswordless keycloak grafana +webAuthnPolicySignatureAlgorithmsPasswordless ES256 grafana +webAuthnPolicyRpIdPasswordless grafana +webAuthnPolicyAttestationConveyancePreferencePasswordless not specified grafana +webAuthnPolicyAuthenticatorAttachmentPasswordless not specified grafana +webAuthnPolicyRequireResidentKeyPasswordless not specified grafana +webAuthnPolicyUserVerificationRequirementPasswordless not specified grafana +webAuthnPolicyCreateTimeoutPasswordless 0 grafana +webAuthnPolicyAvoidSameAuthenticatorRegisterPasswordless false grafana +\. + + +-- +-- Data for Name: realm_default_groups; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_default_groups (realm_id, group_id) FROM stdin; +\. + + +-- +-- Data for Name: realm_default_roles; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_default_roles (realm_id, role_id) FROM stdin; +master 16d5987b-dcbb-4650-8f52-3469f3974846 +master c014bfd1-a210-4e7a-8a26-35d1f5e8f1ed +grafana c49bddc6-ec92-4caa-bc04-57ba80a92eb9 +grafana 0f3d47bb-002a-4cd0-a502-725f224308a7 +\. + + +-- +-- Data for Name: realm_enabled_event_types; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_enabled_event_types (realm_id, value) FROM stdin; +\. + + +-- +-- Data for Name: realm_events_listeners; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_events_listeners (realm_id, value) FROM stdin; +master jboss-logging +grafana jboss-logging +\. + + +-- +-- Data for Name: realm_localizations; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_localizations (realm_id, locale, texts) FROM stdin; +\. + + +-- +-- Data for Name: realm_required_credential; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_required_credential (type, form_label, input, secret, realm_id) FROM stdin; +password password t t master +password password t t grafana +\. + + +-- +-- Data for Name: realm_smtp_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_smtp_config (realm_id, value, name) FROM stdin; +\. + + +-- +-- Data for Name: realm_supported_locales; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.realm_supported_locales (realm_id, value) FROM stdin; +\. + + +-- +-- Data for Name: redirect_uris; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.redirect_uris (client_id, value) FROM stdin; +eed689c6-49da-4d91-98eb-cd495bcc07a3 /realms/master/account/* +11c67f5b-dde7-4680-b05b-c9c59d78bda4 /realms/master/account/* +2f521d09-7304-4b5e-a94b-7cc7300b8b50 /admin/master/console/* +a5a8fed6-0bca-4646-9946-2fe84175353b /realms/grafana/account/* +230081b5-9161-45c3-9e08-9eda5412f7f7 /realms/grafana/account/* +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 /admin/grafana/console/* +09b79548-8426-4c0e-8e0b-7488467532c7 http://localhost:3000/* +\. + + +-- +-- Data for Name: required_action_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.required_action_config (required_action_id, value, name) FROM stdin; +\. + + +-- +-- Data for Name: required_action_provider; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.required_action_provider (id, alias, name, realm_id, enabled, default_action, provider_id, priority) FROM stdin; +ad4dfd2c-307a-4563-b93a-0bb726b4ccaa VERIFY_EMAIL Verify Email master t f VERIFY_EMAIL 50 +2c7fffa4-ff20-4015-9a97-cc6a19e698ba UPDATE_PROFILE Update Profile master t f UPDATE_PROFILE 40 +c76d17f4-eacf-497a-ab5a-f78936bbc50e CONFIGURE_TOTP Configure OTP master t f CONFIGURE_TOTP 10 +83de9f97-43df-4265-982c-5414a2b19985 UPDATE_PASSWORD Update Password master t f UPDATE_PASSWORD 30 +9f538737-770e-4731-abd9-e98172a85d2f terms_and_conditions Terms and Conditions master f f terms_and_conditions 20 +306fc47e-d8ae-4bb1-b2bc-53608a44536c update_user_locale Update User Locale master t f update_user_locale 1000 +f158f7d8-8b7f-414c-b1bd-0dde83c91133 delete_account Delete Account master f f delete_account 60 +969a57d1-c906-4f49-87d6-3cbba2f3898a VERIFY_EMAIL Verify Email grafana t f VERIFY_EMAIL 50 +233d5b8e-6f36-450f-bffd-43b82e27295c UPDATE_PROFILE Update Profile grafana t f UPDATE_PROFILE 40 +ab3a9aa7-3d1b-4fb1-93ad-9412142deed3 CONFIGURE_TOTP Configure OTP grafana t f CONFIGURE_TOTP 10 +988d8e0d-35ef-4e6a-8b48-821cca56acf2 UPDATE_PASSWORD Update Password grafana t f UPDATE_PASSWORD 30 +0e2b6144-5c2c-4dcb-92d8-00529b19a7a5 terms_and_conditions Terms and Conditions grafana f f terms_and_conditions 20 +94993a02-f883-4f8a-a549-d48f95aabed2 update_user_locale Update User Locale grafana t f update_user_locale 1000 +72d09b7f-acde-4b90-af9a-ea3c642a2f6d delete_account Delete Account grafana f f delete_account 60 +\. + + +-- +-- Data for Name: resource_attribute; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_attribute (id, name, value, resource_id) FROM stdin; +\. + + +-- +-- Data for Name: resource_policy; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_policy (resource_id, policy_id) FROM stdin; +\. + + +-- +-- Data for Name: resource_scope; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_scope (resource_id, scope_id) FROM stdin; +\. + + +-- +-- Data for Name: resource_server; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_server (id, allow_rs_remote_mgmt, policy_enforce_mode, decision_strategy) FROM stdin; +\. + + +-- +-- Data for Name: resource_server_perm_ticket; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_server_perm_ticket (id, owner, requester, created_timestamp, granted_timestamp, resource_id, scope_id, resource_server_id, policy_id) FROM stdin; +\. + + +-- +-- Data for Name: resource_server_policy; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_server_policy (id, name, description, type, decision_strategy, logic, resource_server_id, owner) FROM stdin; +\. + + +-- +-- Data for Name: resource_server_resource; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_server_resource (id, name, type, icon_uri, owner, resource_server_id, owner_managed_access, display_name) FROM stdin; +\. + + +-- +-- Data for Name: resource_server_scope; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_server_scope (id, name, icon_uri, resource_server_id, display_name) FROM stdin; +\. + + +-- +-- Data for Name: resource_uris; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.resource_uris (resource_id, value) FROM stdin; +\. + + +-- +-- Data for Name: role_attribute; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.role_attribute (id, role_id, name, value) FROM stdin; +\. + + +-- +-- Data for Name: scope_mapping; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.scope_mapping (client_id, role_id) FROM stdin; +11c67f5b-dde7-4680-b05b-c9c59d78bda4 619ba870-921e-4f28-b26c-89b11f39dddf +230081b5-9161-45c3-9e08-9eda5412f7f7 18a7066b-fe71-410e-9581-69f78347ec29 +\. + + +-- +-- Data for Name: scope_policy; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.scope_policy (scope_id, policy_id) FROM stdin; +\. + + +-- +-- Data for Name: user_attribute; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_attribute (name, value, user_id, id) FROM stdin; +role Viewer bdce2246-bb51-4f55-bb81-b7b8856225bc 6110e90d-fb7c-4910-a283-b0518a1a25f5 +login oauth-viewer bdce2246-bb51-4f55-bb81-b7b8856225bc 4ff52580-a54a-40ea-838d-b58b5ed120d2 +\. + + +-- +-- Data for Name: user_consent; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_consent (id, client_id, user_id, created_date, last_updated_date, client_storage_provider, external_client_id) FROM stdin; +\. + + +-- +-- Data for Name: user_consent_client_scope; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_consent_client_scope (user_consent_id, scope_id) FROM stdin; +\. + + +-- +-- Data for Name: user_entity; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_entity (id, email, email_constraint, email_verified, enabled, federation_link, first_name, last_name, realm_id, username, created_timestamp, service_account_client_link, not_before) FROM stdin; +74e29604-ff35-42bb-a26d-4d0b81ef0917 \N c8a5d425-4bad-4b76-8828-0e39bae03b67 f t \N \N \N master admin 1643820449683 \N 0 +c685749a-645e-4396-b9ee-6eedbfd89d5e oauth-admin@example.org oauth-admin@example.org f t \N Admin Oauth grafana oauth-admin 1656418530879 \N 0 +56eff2b3-e36a-4e3e-84a1-361ad312667b oauth-editor@example.org oauth-editor@example.org f t \N Editor Oauth grafana oauth-editor 1656418563005 \N 0 +bdce2246-bb51-4f55-bb81-b7b8856225bc oauth-viewer@example.org oauth-viewer@example.org f t \N Viewer Oauth grafana oauth-viewer 1656425237046 \N 0 +\. + + +-- +-- Data for Name: user_federation_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_federation_config (user_federation_provider_id, value, name) FROM stdin; +\. + + +-- +-- Data for Name: user_federation_mapper; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_federation_mapper (id, name, federation_provider_id, federation_mapper_type, realm_id) FROM stdin; +\. + + +-- +-- Data for Name: user_federation_mapper_config; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_federation_mapper_config (user_federation_mapper_id, value, name) FROM stdin; +\. + + +-- +-- Data for Name: user_federation_provider; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_federation_provider (id, changed_sync_period, display_name, full_sync_period, last_sync, priority, provider_name, realm_id) FROM stdin; +\. + + +-- +-- Data for Name: user_group_membership; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_group_membership (group_id, user_id) FROM stdin; +\. + + +-- +-- Data for Name: user_required_action; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_required_action (user_id, required_action) FROM stdin; +\. + + +-- +-- Data for Name: user_role_mapping; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_role_mapping (role_id, user_id) FROM stdin; +16d5987b-dcbb-4650-8f52-3469f3974846 74e29604-ff35-42bb-a26d-4d0b81ef0917 +c014bfd1-a210-4e7a-8a26-35d1f5e8f1ed 74e29604-ff35-42bb-a26d-4d0b81ef0917 +86a4b6a9-93db-4177-a72f-95fd937a2c8d 74e29604-ff35-42bb-a26d-4d0b81ef0917 +619ba870-921e-4f28-b26c-89b11f39dddf 74e29604-ff35-42bb-a26d-4d0b81ef0917 +4a3204aa-320e-4584-b8ee-ea2989b3f330 74e29604-ff35-42bb-a26d-4d0b81ef0917 +c49bddc6-ec92-4caa-bc04-57ba80a92eb9 c685749a-645e-4396-b9ee-6eedbfd89d5e +0f3d47bb-002a-4cd0-a502-725f224308a7 c685749a-645e-4396-b9ee-6eedbfd89d5e +f1311ecb-6a6a-49d6-bb16-5132daf93a64 c685749a-645e-4396-b9ee-6eedbfd89d5e +18a7066b-fe71-410e-9581-69f78347ec29 c685749a-645e-4396-b9ee-6eedbfd89d5e +c49bddc6-ec92-4caa-bc04-57ba80a92eb9 56eff2b3-e36a-4e3e-84a1-361ad312667b +0f3d47bb-002a-4cd0-a502-725f224308a7 56eff2b3-e36a-4e3e-84a1-361ad312667b +f1311ecb-6a6a-49d6-bb16-5132daf93a64 56eff2b3-e36a-4e3e-84a1-361ad312667b +18a7066b-fe71-410e-9581-69f78347ec29 56eff2b3-e36a-4e3e-84a1-361ad312667b +60f1b1ea-9059-41ea-acef-573643b24709 c685749a-645e-4396-b9ee-6eedbfd89d5e +c4c74006-c346-48cf-8cf1-1617e3e1cde1 56eff2b3-e36a-4e3e-84a1-361ad312667b +c49bddc6-ec92-4caa-bc04-57ba80a92eb9 bdce2246-bb51-4f55-bb81-b7b8856225bc +0f3d47bb-002a-4cd0-a502-725f224308a7 bdce2246-bb51-4f55-bb81-b7b8856225bc +f1311ecb-6a6a-49d6-bb16-5132daf93a64 bdce2246-bb51-4f55-bb81-b7b8856225bc +18a7066b-fe71-410e-9581-69f78347ec29 bdce2246-bb51-4f55-bb81-b7b8856225bc +\. + + +-- +-- Data for Name: user_session; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_session (id, auth_method, ip_address, last_session_refresh, login_username, realm_id, remember_me, started, user_id, user_session_state, broker_session_id, broker_user_id) FROM stdin; +\. + + +-- +-- Data for Name: user_session_note; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.user_session_note (user_session, name, value) FROM stdin; +\. + + +-- +-- Data for Name: username_login_failure; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.username_login_failure (realm_id, username, failed_login_not_before, last_failure, last_ip_failure, num_failures) FROM stdin; +\. + + +-- +-- Data for Name: web_origins; Type: TABLE DATA; Schema: public; Owner: keycloak +-- + +COPY public.web_origins (client_id, value) FROM stdin; +2f521d09-7304-4b5e-a94b-7cc7300b8b50 + +805aebc8-9d01-42b6-bcce-6ce48ca63ef0 + +09b79548-8426-4c0e-8e0b-7488467532c7 http://localhost:3000 +\. + + +-- +-- Name: username_login_failure CONSTRAINT_17-2; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.username_login_failure + ADD CONSTRAINT "CONSTRAINT_17-2" PRIMARY KEY (realm_id, username); + + +-- +-- Name: keycloak_role UK_J3RWUVD56ONTGSUHOGM184WW2-2; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.keycloak_role + ADD CONSTRAINT "UK_J3RWUVD56ONTGSUHOGM184WW2-2" UNIQUE (name, client_realm_constraint); + + +-- +-- Name: client_auth_flow_bindings c_cli_flow_bind; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_auth_flow_bindings + ADD CONSTRAINT c_cli_flow_bind PRIMARY KEY (client_id, binding_name); + + +-- +-- Name: client_scope_client c_cli_scope_bind; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope_client + ADD CONSTRAINT c_cli_scope_bind PRIMARY KEY (client_id, scope_id); + + +-- +-- Name: client_initial_access cnstr_client_init_acc_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_initial_access + ADD CONSTRAINT cnstr_client_init_acc_pk PRIMARY KEY (id); + + +-- +-- Name: realm_default_groups con_group_id_def_groups; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_default_groups + ADD CONSTRAINT con_group_id_def_groups UNIQUE (group_id); + + +-- +-- Name: broker_link constr_broker_link_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.broker_link + ADD CONSTRAINT constr_broker_link_pk PRIMARY KEY (identity_provider, user_id); + + +-- +-- Name: client_user_session_note constr_cl_usr_ses_note; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_user_session_note + ADD CONSTRAINT constr_cl_usr_ses_note PRIMARY KEY (client_session, name); + + +-- +-- Name: client_default_roles constr_client_default_roles; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_default_roles + ADD CONSTRAINT constr_client_default_roles PRIMARY KEY (client_id, role_id); + + +-- +-- Name: component_config constr_component_config_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.component_config + ADD CONSTRAINT constr_component_config_pk PRIMARY KEY (id); + + +-- +-- Name: component constr_component_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.component + ADD CONSTRAINT constr_component_pk PRIMARY KEY (id); + + +-- +-- Name: fed_user_required_action constr_fed_required_action; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.fed_user_required_action + ADD CONSTRAINT constr_fed_required_action PRIMARY KEY (required_action, user_id); + + +-- +-- Name: fed_user_attribute constr_fed_user_attr_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.fed_user_attribute + ADD CONSTRAINT constr_fed_user_attr_pk PRIMARY KEY (id); + + +-- +-- Name: fed_user_consent constr_fed_user_consent_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.fed_user_consent + ADD CONSTRAINT constr_fed_user_consent_pk PRIMARY KEY (id); + + +-- +-- Name: fed_user_credential constr_fed_user_cred_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.fed_user_credential + ADD CONSTRAINT constr_fed_user_cred_pk PRIMARY KEY (id); + + +-- +-- Name: fed_user_group_membership constr_fed_user_group; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.fed_user_group_membership + ADD CONSTRAINT constr_fed_user_group PRIMARY KEY (group_id, user_id); + + +-- +-- Name: fed_user_role_mapping constr_fed_user_role; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.fed_user_role_mapping + ADD CONSTRAINT constr_fed_user_role PRIMARY KEY (role_id, user_id); + + +-- +-- Name: federated_user constr_federated_user; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.federated_user + ADD CONSTRAINT constr_federated_user PRIMARY KEY (id); + + +-- +-- Name: realm_default_groups constr_realm_default_groups; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_default_groups + ADD CONSTRAINT constr_realm_default_groups PRIMARY KEY (realm_id, group_id); + + +-- +-- Name: realm_enabled_event_types constr_realm_enabl_event_types; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_enabled_event_types + ADD CONSTRAINT constr_realm_enabl_event_types PRIMARY KEY (realm_id, value); + + +-- +-- Name: realm_events_listeners constr_realm_events_listeners; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_events_listeners + ADD CONSTRAINT constr_realm_events_listeners PRIMARY KEY (realm_id, value); + + +-- +-- Name: realm_supported_locales constr_realm_supported_locales; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_supported_locales + ADD CONSTRAINT constr_realm_supported_locales PRIMARY KEY (realm_id, value); + + +-- +-- Name: identity_provider constraint_2b; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.identity_provider + ADD CONSTRAINT constraint_2b PRIMARY KEY (internal_id); + + +-- +-- Name: client_attributes constraint_3c; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_attributes + ADD CONSTRAINT constraint_3c PRIMARY KEY (client_id, name); + + +-- +-- Name: event_entity constraint_4; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.event_entity + ADD CONSTRAINT constraint_4 PRIMARY KEY (id); + + +-- +-- Name: federated_identity constraint_40; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.federated_identity + ADD CONSTRAINT constraint_40 PRIMARY KEY (identity_provider, user_id); + + +-- +-- Name: realm constraint_4a; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm + ADD CONSTRAINT constraint_4a PRIMARY KEY (id); + + +-- +-- Name: client_session_role constraint_5; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session_role + ADD CONSTRAINT constraint_5 PRIMARY KEY (client_session, role_id); + + +-- +-- Name: user_session constraint_57; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_session + ADD CONSTRAINT constraint_57 PRIMARY KEY (id); + + +-- +-- Name: user_federation_provider constraint_5c; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_provider + ADD CONSTRAINT constraint_5c PRIMARY KEY (id); + + +-- +-- Name: client_session_note constraint_5e; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session_note + ADD CONSTRAINT constraint_5e PRIMARY KEY (client_session, name); + + +-- +-- Name: client constraint_7; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client + ADD CONSTRAINT constraint_7 PRIMARY KEY (id); + + +-- +-- Name: client_session constraint_8; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session + ADD CONSTRAINT constraint_8 PRIMARY KEY (id); + + +-- +-- Name: scope_mapping constraint_81; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.scope_mapping + ADD CONSTRAINT constraint_81 PRIMARY KEY (client_id, role_id); + + +-- +-- Name: client_node_registrations constraint_84; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_node_registrations + ADD CONSTRAINT constraint_84 PRIMARY KEY (client_id, name); + + +-- +-- Name: realm_attribute constraint_9; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_attribute + ADD CONSTRAINT constraint_9 PRIMARY KEY (name, realm_id); + + +-- +-- Name: realm_required_credential constraint_92; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_required_credential + ADD CONSTRAINT constraint_92 PRIMARY KEY (realm_id, type); + + +-- +-- Name: keycloak_role constraint_a; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.keycloak_role + ADD CONSTRAINT constraint_a PRIMARY KEY (id); + + +-- +-- Name: admin_event_entity constraint_admin_event_entity; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.admin_event_entity + ADD CONSTRAINT constraint_admin_event_entity PRIMARY KEY (id); + + +-- +-- Name: authenticator_config_entry constraint_auth_cfg_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.authenticator_config_entry + ADD CONSTRAINT constraint_auth_cfg_pk PRIMARY KEY (authenticator_id, name); + + +-- +-- Name: authentication_execution constraint_auth_exec_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.authentication_execution + ADD CONSTRAINT constraint_auth_exec_pk PRIMARY KEY (id); + + +-- +-- Name: authentication_flow constraint_auth_flow_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.authentication_flow + ADD CONSTRAINT constraint_auth_flow_pk PRIMARY KEY (id); + + +-- +-- Name: authenticator_config constraint_auth_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.authenticator_config + ADD CONSTRAINT constraint_auth_pk PRIMARY KEY (id); + + +-- +-- Name: client_session_auth_status constraint_auth_status_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session_auth_status + ADD CONSTRAINT constraint_auth_status_pk PRIMARY KEY (client_session, authenticator); + + +-- +-- Name: user_role_mapping constraint_c; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_role_mapping + ADD CONSTRAINT constraint_c PRIMARY KEY (role_id, user_id); + + +-- +-- Name: composite_role constraint_composite_role; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.composite_role + ADD CONSTRAINT constraint_composite_role PRIMARY KEY (composite, child_role); + + +-- +-- Name: client_session_prot_mapper constraint_cs_pmp_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session_prot_mapper + ADD CONSTRAINT constraint_cs_pmp_pk PRIMARY KEY (client_session, protocol_mapper_id); + + +-- +-- Name: identity_provider_config constraint_d; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.identity_provider_config + ADD CONSTRAINT constraint_d PRIMARY KEY (identity_provider_id, name); + + +-- +-- Name: policy_config constraint_dpc; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.policy_config + ADD CONSTRAINT constraint_dpc PRIMARY KEY (policy_id, name); + + +-- +-- Name: realm_smtp_config constraint_e; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_smtp_config + ADD CONSTRAINT constraint_e PRIMARY KEY (realm_id, name); + + +-- +-- Name: credential constraint_f; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.credential + ADD CONSTRAINT constraint_f PRIMARY KEY (id); + + +-- +-- Name: user_federation_config constraint_f9; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_config + ADD CONSTRAINT constraint_f9 PRIMARY KEY (user_federation_provider_id, name); + + +-- +-- Name: resource_server_perm_ticket constraint_fapmt; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_perm_ticket + ADD CONSTRAINT constraint_fapmt PRIMARY KEY (id); + + +-- +-- Name: resource_server_resource constraint_farsr; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_resource + ADD CONSTRAINT constraint_farsr PRIMARY KEY (id); + + +-- +-- Name: resource_server_policy constraint_farsrp; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_policy + ADD CONSTRAINT constraint_farsrp PRIMARY KEY (id); + + +-- +-- Name: associated_policy constraint_farsrpap; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.associated_policy + ADD CONSTRAINT constraint_farsrpap PRIMARY KEY (policy_id, associated_policy_id); + + +-- +-- Name: resource_policy constraint_farsrpp; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_policy + ADD CONSTRAINT constraint_farsrpp PRIMARY KEY (resource_id, policy_id); + + +-- +-- Name: resource_server_scope constraint_farsrs; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_scope + ADD CONSTRAINT constraint_farsrs PRIMARY KEY (id); + + +-- +-- Name: resource_scope constraint_farsrsp; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_scope + ADD CONSTRAINT constraint_farsrsp PRIMARY KEY (resource_id, scope_id); + + +-- +-- Name: scope_policy constraint_farsrsps; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.scope_policy + ADD CONSTRAINT constraint_farsrsps PRIMARY KEY (scope_id, policy_id); + + +-- +-- Name: user_entity constraint_fb; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_entity + ADD CONSTRAINT constraint_fb PRIMARY KEY (id); + + +-- +-- Name: user_federation_mapper_config constraint_fedmapper_cfg_pm; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_mapper_config + ADD CONSTRAINT constraint_fedmapper_cfg_pm PRIMARY KEY (user_federation_mapper_id, name); + + +-- +-- Name: user_federation_mapper constraint_fedmapperpm; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_mapper + ADD CONSTRAINT constraint_fedmapperpm PRIMARY KEY (id); + + +-- +-- Name: fed_user_consent_cl_scope constraint_fgrntcsnt_clsc_pm; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.fed_user_consent_cl_scope + ADD CONSTRAINT constraint_fgrntcsnt_clsc_pm PRIMARY KEY (user_consent_id, scope_id); + + +-- +-- Name: user_consent_client_scope constraint_grntcsnt_clsc_pm; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_consent_client_scope + ADD CONSTRAINT constraint_grntcsnt_clsc_pm PRIMARY KEY (user_consent_id, scope_id); + + +-- +-- Name: user_consent constraint_grntcsnt_pm; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_consent + ADD CONSTRAINT constraint_grntcsnt_pm PRIMARY KEY (id); + + +-- +-- Name: keycloak_group constraint_group; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.keycloak_group + ADD CONSTRAINT constraint_group PRIMARY KEY (id); + + +-- +-- Name: group_attribute constraint_group_attribute_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.group_attribute + ADD CONSTRAINT constraint_group_attribute_pk PRIMARY KEY (id); + + +-- +-- Name: group_role_mapping constraint_group_role; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.group_role_mapping + ADD CONSTRAINT constraint_group_role PRIMARY KEY (role_id, group_id); + + +-- +-- Name: identity_provider_mapper constraint_idpm; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.identity_provider_mapper + ADD CONSTRAINT constraint_idpm PRIMARY KEY (id); + + +-- +-- Name: idp_mapper_config constraint_idpmconfig; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.idp_mapper_config + ADD CONSTRAINT constraint_idpmconfig PRIMARY KEY (idp_mapper_id, name); + + +-- +-- Name: migration_model constraint_migmod; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.migration_model + ADD CONSTRAINT constraint_migmod PRIMARY KEY (id); + + +-- +-- Name: offline_client_session constraint_offl_cl_ses_pk3; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.offline_client_session + ADD CONSTRAINT constraint_offl_cl_ses_pk3 PRIMARY KEY (user_session_id, client_id, client_storage_provider, external_client_id, offline_flag); + + +-- +-- Name: offline_user_session constraint_offl_us_ses_pk2; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.offline_user_session + ADD CONSTRAINT constraint_offl_us_ses_pk2 PRIMARY KEY (user_session_id, offline_flag); + + +-- +-- Name: protocol_mapper constraint_pcm; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.protocol_mapper + ADD CONSTRAINT constraint_pcm PRIMARY KEY (id); + + +-- +-- Name: protocol_mapper_config constraint_pmconfig; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.protocol_mapper_config + ADD CONSTRAINT constraint_pmconfig PRIMARY KEY (protocol_mapper_id, name); + + +-- +-- Name: realm_default_roles constraint_realm_default_roles; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_default_roles + ADD CONSTRAINT constraint_realm_default_roles PRIMARY KEY (realm_id, role_id); + + +-- +-- Name: redirect_uris constraint_redirect_uris; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.redirect_uris + ADD CONSTRAINT constraint_redirect_uris PRIMARY KEY (client_id, value); + + +-- +-- Name: required_action_config constraint_req_act_cfg_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.required_action_config + ADD CONSTRAINT constraint_req_act_cfg_pk PRIMARY KEY (required_action_id, name); + + +-- +-- Name: required_action_provider constraint_req_act_prv_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.required_action_provider + ADD CONSTRAINT constraint_req_act_prv_pk PRIMARY KEY (id); + + +-- +-- Name: user_required_action constraint_required_action; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_required_action + ADD CONSTRAINT constraint_required_action PRIMARY KEY (required_action, user_id); + + +-- +-- Name: resource_uris constraint_resour_uris_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_uris + ADD CONSTRAINT constraint_resour_uris_pk PRIMARY KEY (resource_id, value); + + +-- +-- Name: role_attribute constraint_role_attribute_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.role_attribute + ADD CONSTRAINT constraint_role_attribute_pk PRIMARY KEY (id); + + +-- +-- Name: user_attribute constraint_user_attribute_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_attribute + ADD CONSTRAINT constraint_user_attribute_pk PRIMARY KEY (id); + + +-- +-- Name: user_group_membership constraint_user_group; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_group_membership + ADD CONSTRAINT constraint_user_group PRIMARY KEY (group_id, user_id); + + +-- +-- Name: user_session_note constraint_usn_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_session_note + ADD CONSTRAINT constraint_usn_pk PRIMARY KEY (user_session, name); + + +-- +-- Name: web_origins constraint_web_origins; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.web_origins + ADD CONSTRAINT constraint_web_origins PRIMARY KEY (client_id, value); + + +-- +-- Name: client_scope_attributes pk_cl_tmpl_attr; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope_attributes + ADD CONSTRAINT pk_cl_tmpl_attr PRIMARY KEY (scope_id, name); + + +-- +-- Name: client_scope pk_cli_template; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope + ADD CONSTRAINT pk_cli_template PRIMARY KEY (id); + + +-- +-- Name: databasechangeloglock pk_databasechangeloglock; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.databasechangeloglock + ADD CONSTRAINT pk_databasechangeloglock PRIMARY KEY (id); + + +-- +-- Name: resource_server pk_resource_server; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server + ADD CONSTRAINT pk_resource_server PRIMARY KEY (id); + + +-- +-- Name: client_scope_role_mapping pk_template_scope; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope_role_mapping + ADD CONSTRAINT pk_template_scope PRIMARY KEY (scope_id, role_id); + + +-- +-- Name: default_client_scope r_def_cli_scope_bind; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.default_client_scope + ADD CONSTRAINT r_def_cli_scope_bind PRIMARY KEY (realm_id, scope_id); + + +-- +-- Name: realm_localizations realm_localizations_pkey; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_localizations + ADD CONSTRAINT realm_localizations_pkey PRIMARY KEY (realm_id, locale); + + +-- +-- Name: resource_attribute res_attr_pk; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_attribute + ADD CONSTRAINT res_attr_pk PRIMARY KEY (id); + + +-- +-- Name: keycloak_group sibling_names; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.keycloak_group + ADD CONSTRAINT sibling_names UNIQUE (realm_id, parent_group, name); + + +-- +-- Name: identity_provider uk_2daelwnibji49avxsrtuf6xj33; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.identity_provider + ADD CONSTRAINT uk_2daelwnibji49avxsrtuf6xj33 UNIQUE (provider_alias, realm_id); + + +-- +-- Name: client_default_roles uk_8aelwnibji49avxsrtuf6xjow; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_default_roles + ADD CONSTRAINT uk_8aelwnibji49avxsrtuf6xjow UNIQUE (role_id); + + +-- +-- Name: client uk_b71cjlbenv945rb6gcon438at; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client + ADD CONSTRAINT uk_b71cjlbenv945rb6gcon438at UNIQUE (realm_id, client_id); + + +-- +-- Name: client_scope uk_cli_scope; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope + ADD CONSTRAINT uk_cli_scope UNIQUE (realm_id, name); + + +-- +-- Name: user_entity uk_dykn684sl8up1crfei6eckhd7; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_entity + ADD CONSTRAINT uk_dykn684sl8up1crfei6eckhd7 UNIQUE (realm_id, email_constraint); + + +-- +-- Name: resource_server_resource uk_frsr6t700s9v50bu18ws5ha6; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_resource + ADD CONSTRAINT uk_frsr6t700s9v50bu18ws5ha6 UNIQUE (name, owner, resource_server_id); + + +-- +-- Name: resource_server_perm_ticket uk_frsr6t700s9v50bu18ws5pmt; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_perm_ticket + ADD CONSTRAINT uk_frsr6t700s9v50bu18ws5pmt UNIQUE (owner, requester, resource_server_id, resource_id, scope_id); + + +-- +-- Name: resource_server_policy uk_frsrpt700s9v50bu18ws5ha6; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_policy + ADD CONSTRAINT uk_frsrpt700s9v50bu18ws5ha6 UNIQUE (name, resource_server_id); + + +-- +-- Name: resource_server_scope uk_frsrst700s9v50bu18ws5ha6; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_scope + ADD CONSTRAINT uk_frsrst700s9v50bu18ws5ha6 UNIQUE (name, resource_server_id); + + +-- +-- Name: realm_default_roles uk_h4wpd7w4hsoolni3h0sw7btje; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_default_roles + ADD CONSTRAINT uk_h4wpd7w4hsoolni3h0sw7btje UNIQUE (role_id); + + +-- +-- Name: user_consent uk_jkuwuvd56ontgsuhogm8uewrt; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_consent + ADD CONSTRAINT uk_jkuwuvd56ontgsuhogm8uewrt UNIQUE (client_id, client_storage_provider, external_client_id, user_id); + + +-- +-- Name: realm uk_orvsdmla56612eaefiq6wl5oi; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm + ADD CONSTRAINT uk_orvsdmla56612eaefiq6wl5oi UNIQUE (name); + + +-- +-- Name: user_entity uk_ru8tt6t700s9v50bu18ws5ha6; Type: CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_entity + ADD CONSTRAINT uk_ru8tt6t700s9v50bu18ws5ha6 UNIQUE (realm_id, username); + + +-- +-- Name: idx_assoc_pol_assoc_pol_id; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_assoc_pol_assoc_pol_id ON public.associated_policy USING btree (associated_policy_id); + + +-- +-- Name: idx_auth_config_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_auth_config_realm ON public.authenticator_config USING btree (realm_id); + + +-- +-- Name: idx_auth_exec_flow; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_auth_exec_flow ON public.authentication_execution USING btree (flow_id); + + +-- +-- Name: idx_auth_exec_realm_flow; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_auth_exec_realm_flow ON public.authentication_execution USING btree (realm_id, flow_id); + + +-- +-- Name: idx_auth_flow_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_auth_flow_realm ON public.authentication_flow USING btree (realm_id); + + +-- +-- Name: idx_cl_clscope; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_cl_clscope ON public.client_scope_client USING btree (scope_id); + + +-- +-- Name: idx_client_def_roles_client; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_client_def_roles_client ON public.client_default_roles USING btree (client_id); + + +-- +-- Name: idx_client_id; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_client_id ON public.client USING btree (client_id); + + +-- +-- Name: idx_client_init_acc_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_client_init_acc_realm ON public.client_initial_access USING btree (realm_id); + + +-- +-- Name: idx_client_session_session; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_client_session_session ON public.client_session USING btree (session_id); + + +-- +-- Name: idx_clscope_attrs; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_clscope_attrs ON public.client_scope_attributes USING btree (scope_id); + + +-- +-- Name: idx_clscope_cl; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_clscope_cl ON public.client_scope_client USING btree (client_id); + + +-- +-- Name: idx_clscope_protmap; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_clscope_protmap ON public.protocol_mapper USING btree (client_scope_id); + + +-- +-- Name: idx_clscope_role; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_clscope_role ON public.client_scope_role_mapping USING btree (scope_id); + + +-- +-- Name: idx_compo_config_compo; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_compo_config_compo ON public.component_config USING btree (component_id); + + +-- +-- Name: idx_component_provider_type; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_component_provider_type ON public.component USING btree (provider_type); + + +-- +-- Name: idx_component_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_component_realm ON public.component USING btree (realm_id); + + +-- +-- Name: idx_composite; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_composite ON public.composite_role USING btree (composite); + + +-- +-- Name: idx_composite_child; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_composite_child ON public.composite_role USING btree (child_role); + + +-- +-- Name: idx_defcls_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_defcls_realm ON public.default_client_scope USING btree (realm_id); + + +-- +-- Name: idx_defcls_scope; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_defcls_scope ON public.default_client_scope USING btree (scope_id); + + +-- +-- Name: idx_event_time; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_event_time ON public.event_entity USING btree (realm_id, event_time); + + +-- +-- Name: idx_fedidentity_feduser; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fedidentity_feduser ON public.federated_identity USING btree (federated_user_id); + + +-- +-- Name: idx_fedidentity_user; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fedidentity_user ON public.federated_identity USING btree (user_id); + + +-- +-- Name: idx_fu_attribute; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_attribute ON public.fed_user_attribute USING btree (user_id, realm_id, name); + + +-- +-- Name: idx_fu_cnsnt_ext; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_cnsnt_ext ON public.fed_user_consent USING btree (user_id, client_storage_provider, external_client_id); + + +-- +-- Name: idx_fu_consent; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_consent ON public.fed_user_consent USING btree (user_id, client_id); + + +-- +-- Name: idx_fu_consent_ru; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_consent_ru ON public.fed_user_consent USING btree (realm_id, user_id); + + +-- +-- Name: idx_fu_credential; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_credential ON public.fed_user_credential USING btree (user_id, type); + + +-- +-- Name: idx_fu_credential_ru; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_credential_ru ON public.fed_user_credential USING btree (realm_id, user_id); + + +-- +-- Name: idx_fu_group_membership; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_group_membership ON public.fed_user_group_membership USING btree (user_id, group_id); + + +-- +-- Name: idx_fu_group_membership_ru; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_group_membership_ru ON public.fed_user_group_membership USING btree (realm_id, user_id); + + +-- +-- Name: idx_fu_required_action; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_required_action ON public.fed_user_required_action USING btree (user_id, required_action); + + +-- +-- Name: idx_fu_required_action_ru; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_required_action_ru ON public.fed_user_required_action USING btree (realm_id, user_id); + + +-- +-- Name: idx_fu_role_mapping; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_role_mapping ON public.fed_user_role_mapping USING btree (user_id, role_id); + + +-- +-- Name: idx_fu_role_mapping_ru; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_fu_role_mapping_ru ON public.fed_user_role_mapping USING btree (realm_id, user_id); + + +-- +-- Name: idx_group_attr_group; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_group_attr_group ON public.group_attribute USING btree (group_id); + + +-- +-- Name: idx_group_role_mapp_group; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_group_role_mapp_group ON public.group_role_mapping USING btree (group_id); + + +-- +-- Name: idx_id_prov_mapp_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_id_prov_mapp_realm ON public.identity_provider_mapper USING btree (realm_id); + + +-- +-- Name: idx_ident_prov_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_ident_prov_realm ON public.identity_provider USING btree (realm_id); + + +-- +-- Name: idx_keycloak_role_client; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_keycloak_role_client ON public.keycloak_role USING btree (client); + + +-- +-- Name: idx_keycloak_role_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_keycloak_role_realm ON public.keycloak_role USING btree (realm); + + +-- +-- Name: idx_offline_uss_createdon; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_offline_uss_createdon ON public.offline_user_session USING btree (created_on); + + +-- +-- Name: idx_protocol_mapper_client; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_protocol_mapper_client ON public.protocol_mapper USING btree (client_id); + + +-- +-- Name: idx_realm_attr_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_realm_attr_realm ON public.realm_attribute USING btree (realm_id); + + +-- +-- Name: idx_realm_clscope; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_realm_clscope ON public.client_scope USING btree (realm_id); + + +-- +-- Name: idx_realm_def_grp_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_realm_def_grp_realm ON public.realm_default_groups USING btree (realm_id); + + +-- +-- Name: idx_realm_def_roles_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_realm_def_roles_realm ON public.realm_default_roles USING btree (realm_id); + + +-- +-- Name: idx_realm_evt_list_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_realm_evt_list_realm ON public.realm_events_listeners USING btree (realm_id); + + +-- +-- Name: idx_realm_evt_types_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_realm_evt_types_realm ON public.realm_enabled_event_types USING btree (realm_id); + + +-- +-- Name: idx_realm_master_adm_cli; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_realm_master_adm_cli ON public.realm USING btree (master_admin_client); + + +-- +-- Name: idx_realm_supp_local_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_realm_supp_local_realm ON public.realm_supported_locales USING btree (realm_id); + + +-- +-- Name: idx_redir_uri_client; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_redir_uri_client ON public.redirect_uris USING btree (client_id); + + +-- +-- Name: idx_req_act_prov_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_req_act_prov_realm ON public.required_action_provider USING btree (realm_id); + + +-- +-- Name: idx_res_policy_policy; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_res_policy_policy ON public.resource_policy USING btree (policy_id); + + +-- +-- Name: idx_res_scope_scope; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_res_scope_scope ON public.resource_scope USING btree (scope_id); + + +-- +-- Name: idx_res_serv_pol_res_serv; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_res_serv_pol_res_serv ON public.resource_server_policy USING btree (resource_server_id); + + +-- +-- Name: idx_res_srv_res_res_srv; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_res_srv_res_res_srv ON public.resource_server_resource USING btree (resource_server_id); + + +-- +-- Name: idx_res_srv_scope_res_srv; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_res_srv_scope_res_srv ON public.resource_server_scope USING btree (resource_server_id); + + +-- +-- Name: idx_role_attribute; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_role_attribute ON public.role_attribute USING btree (role_id); + + +-- +-- Name: idx_role_clscope; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_role_clscope ON public.client_scope_role_mapping USING btree (role_id); + + +-- +-- Name: idx_scope_mapping_role; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_scope_mapping_role ON public.scope_mapping USING btree (role_id); + + +-- +-- Name: idx_scope_policy_policy; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_scope_policy_policy ON public.scope_policy USING btree (policy_id); + + +-- +-- Name: idx_update_time; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_update_time ON public.migration_model USING btree (update_time); + + +-- +-- Name: idx_us_sess_id_on_cl_sess; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_us_sess_id_on_cl_sess ON public.offline_client_session USING btree (user_session_id); + + +-- +-- Name: idx_usconsent_clscope; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_usconsent_clscope ON public.user_consent_client_scope USING btree (user_consent_id); + + +-- +-- Name: idx_user_attribute; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_user_attribute ON public.user_attribute USING btree (user_id); + + +-- +-- Name: idx_user_consent; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_user_consent ON public.user_consent USING btree (user_id); + + +-- +-- Name: idx_user_credential; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_user_credential ON public.credential USING btree (user_id); + + +-- +-- Name: idx_user_email; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_user_email ON public.user_entity USING btree (email); + + +-- +-- Name: idx_user_group_mapping; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_user_group_mapping ON public.user_group_membership USING btree (user_id); + + +-- +-- Name: idx_user_reqactions; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_user_reqactions ON public.user_required_action USING btree (user_id); + + +-- +-- Name: idx_user_role_mapping; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_user_role_mapping ON public.user_role_mapping USING btree (user_id); + + +-- +-- Name: idx_usr_fed_map_fed_prv; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_usr_fed_map_fed_prv ON public.user_federation_mapper USING btree (federation_provider_id); + + +-- +-- Name: idx_usr_fed_map_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_usr_fed_map_realm ON public.user_federation_mapper USING btree (realm_id); + + +-- +-- Name: idx_usr_fed_prv_realm; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_usr_fed_prv_realm ON public.user_federation_provider USING btree (realm_id); + + +-- +-- Name: idx_web_orig_client; Type: INDEX; Schema: public; Owner: keycloak +-- + +CREATE INDEX idx_web_orig_client ON public.web_origins USING btree (client_id); + + +-- +-- Name: client_session_auth_status auth_status_constraint; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session_auth_status + ADD CONSTRAINT auth_status_constraint FOREIGN KEY (client_session) REFERENCES public.client_session(id); + + +-- +-- Name: identity_provider fk2b4ebc52ae5c3b34; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.identity_provider + ADD CONSTRAINT fk2b4ebc52ae5c3b34 FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: client_attributes fk3c47c64beacca966; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_attributes + ADD CONSTRAINT fk3c47c64beacca966 FOREIGN KEY (client_id) REFERENCES public.client(id); + + +-- +-- Name: federated_identity fk404288b92ef007a6; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.federated_identity + ADD CONSTRAINT fk404288b92ef007a6 FOREIGN KEY (user_id) REFERENCES public.user_entity(id); + + +-- +-- Name: client_node_registrations fk4129723ba992f594; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_node_registrations + ADD CONSTRAINT fk4129723ba992f594 FOREIGN KEY (client_id) REFERENCES public.client(id); + + +-- +-- Name: client_session_note fk5edfb00ff51c2736; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session_note + ADD CONSTRAINT fk5edfb00ff51c2736 FOREIGN KEY (client_session) REFERENCES public.client_session(id); + + +-- +-- Name: user_session_note fk5edfb00ff51d3472; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_session_note + ADD CONSTRAINT fk5edfb00ff51d3472 FOREIGN KEY (user_session) REFERENCES public.user_session(id); + + +-- +-- Name: client_session_role fk_11b7sgqw18i532811v7o2dv76; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session_role + ADD CONSTRAINT fk_11b7sgqw18i532811v7o2dv76 FOREIGN KEY (client_session) REFERENCES public.client_session(id); + + +-- +-- Name: redirect_uris fk_1burs8pb4ouj97h5wuppahv9f; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.redirect_uris + ADD CONSTRAINT fk_1burs8pb4ouj97h5wuppahv9f FOREIGN KEY (client_id) REFERENCES public.client(id); + + +-- +-- Name: user_federation_provider fk_1fj32f6ptolw2qy60cd8n01e8; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_provider + ADD CONSTRAINT fk_1fj32f6ptolw2qy60cd8n01e8 FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: client_session_prot_mapper fk_33a8sgqw18i532811v7o2dk89; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session_prot_mapper + ADD CONSTRAINT fk_33a8sgqw18i532811v7o2dk89 FOREIGN KEY (client_session) REFERENCES public.client_session(id); + + +-- +-- Name: realm_required_credential fk_5hg65lybevavkqfki3kponh9v; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_required_credential + ADD CONSTRAINT fk_5hg65lybevavkqfki3kponh9v FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: resource_attribute fk_5hrm2vlf9ql5fu022kqepovbr; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_attribute + ADD CONSTRAINT fk_5hrm2vlf9ql5fu022kqepovbr FOREIGN KEY (resource_id) REFERENCES public.resource_server_resource(id); + + +-- +-- Name: user_attribute fk_5hrm2vlf9ql5fu043kqepovbr; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_attribute + ADD CONSTRAINT fk_5hrm2vlf9ql5fu043kqepovbr FOREIGN KEY (user_id) REFERENCES public.user_entity(id); + + +-- +-- Name: user_required_action fk_6qj3w1jw9cvafhe19bwsiuvmd; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_required_action + ADD CONSTRAINT fk_6qj3w1jw9cvafhe19bwsiuvmd FOREIGN KEY (user_id) REFERENCES public.user_entity(id); + + +-- +-- Name: keycloak_role fk_6vyqfe4cn4wlq8r6kt5vdsj5c; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.keycloak_role + ADD CONSTRAINT fk_6vyqfe4cn4wlq8r6kt5vdsj5c FOREIGN KEY (realm) REFERENCES public.realm(id); + + +-- +-- Name: realm_smtp_config fk_70ej8xdxgxd0b9hh6180irr0o; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_smtp_config + ADD CONSTRAINT fk_70ej8xdxgxd0b9hh6180irr0o FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: realm_attribute fk_8shxd6l3e9atqukacxgpffptw; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_attribute + ADD CONSTRAINT fk_8shxd6l3e9atqukacxgpffptw FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: composite_role fk_a63wvekftu8jo1pnj81e7mce2; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.composite_role + ADD CONSTRAINT fk_a63wvekftu8jo1pnj81e7mce2 FOREIGN KEY (composite) REFERENCES public.keycloak_role(id); + + +-- +-- Name: authentication_execution fk_auth_exec_flow; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.authentication_execution + ADD CONSTRAINT fk_auth_exec_flow FOREIGN KEY (flow_id) REFERENCES public.authentication_flow(id); + + +-- +-- Name: authentication_execution fk_auth_exec_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.authentication_execution + ADD CONSTRAINT fk_auth_exec_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: authentication_flow fk_auth_flow_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.authentication_flow + ADD CONSTRAINT fk_auth_flow_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: authenticator_config fk_auth_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.authenticator_config + ADD CONSTRAINT fk_auth_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: client_session fk_b4ao2vcvat6ukau74wbwtfqo1; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_session + ADD CONSTRAINT fk_b4ao2vcvat6ukau74wbwtfqo1 FOREIGN KEY (session_id) REFERENCES public.user_session(id); + + +-- +-- Name: user_role_mapping fk_c4fqv34p1mbylloxang7b1q3l; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_role_mapping + ADD CONSTRAINT fk_c4fqv34p1mbylloxang7b1q3l FOREIGN KEY (user_id) REFERENCES public.user_entity(id); + + +-- +-- Name: client_scope_client fk_c_cli_scope_client; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope_client + ADD CONSTRAINT fk_c_cli_scope_client FOREIGN KEY (client_id) REFERENCES public.client(id); + + +-- +-- Name: client_scope_client fk_c_cli_scope_scope; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope_client + ADD CONSTRAINT fk_c_cli_scope_scope FOREIGN KEY (scope_id) REFERENCES public.client_scope(id); + + +-- +-- Name: client_scope_attributes fk_cl_scope_attr_scope; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope_attributes + ADD CONSTRAINT fk_cl_scope_attr_scope FOREIGN KEY (scope_id) REFERENCES public.client_scope(id); + + +-- +-- Name: client_scope_role_mapping fk_cl_scope_rm_scope; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope_role_mapping + ADD CONSTRAINT fk_cl_scope_rm_scope FOREIGN KEY (scope_id) REFERENCES public.client_scope(id); + + +-- +-- Name: client_user_session_note fk_cl_usr_ses_note; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_user_session_note + ADD CONSTRAINT fk_cl_usr_ses_note FOREIGN KEY (client_session) REFERENCES public.client_session(id); + + +-- +-- Name: protocol_mapper fk_cli_scope_mapper; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.protocol_mapper + ADD CONSTRAINT fk_cli_scope_mapper FOREIGN KEY (client_scope_id) REFERENCES public.client_scope(id); + + +-- +-- Name: client_initial_access fk_client_init_acc_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_initial_access + ADD CONSTRAINT fk_client_init_acc_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: component_config fk_component_config; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.component_config + ADD CONSTRAINT fk_component_config FOREIGN KEY (component_id) REFERENCES public.component(id); + + +-- +-- Name: component fk_component_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.component + ADD CONSTRAINT fk_component_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: realm_default_groups fk_def_groups_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_default_groups + ADD CONSTRAINT fk_def_groups_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: realm_default_roles fk_evudb1ppw84oxfax2drs03icc; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_default_roles + ADD CONSTRAINT fk_evudb1ppw84oxfax2drs03icc FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: user_federation_mapper_config fk_fedmapper_cfg; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_mapper_config + ADD CONSTRAINT fk_fedmapper_cfg FOREIGN KEY (user_federation_mapper_id) REFERENCES public.user_federation_mapper(id); + + +-- +-- Name: user_federation_mapper fk_fedmapperpm_fedprv; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_mapper + ADD CONSTRAINT fk_fedmapperpm_fedprv FOREIGN KEY (federation_provider_id) REFERENCES public.user_federation_provider(id); + + +-- +-- Name: user_federation_mapper fk_fedmapperpm_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_mapper + ADD CONSTRAINT fk_fedmapperpm_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: associated_policy fk_frsr5s213xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.associated_policy + ADD CONSTRAINT fk_frsr5s213xcx4wnkog82ssrfy FOREIGN KEY (associated_policy_id) REFERENCES public.resource_server_policy(id); + + +-- +-- Name: scope_policy fk_frsrasp13xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.scope_policy + ADD CONSTRAINT fk_frsrasp13xcx4wnkog82ssrfy FOREIGN KEY (policy_id) REFERENCES public.resource_server_policy(id); + + +-- +-- Name: resource_server_perm_ticket fk_frsrho213xcx4wnkog82sspmt; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_perm_ticket + ADD CONSTRAINT fk_frsrho213xcx4wnkog82sspmt FOREIGN KEY (resource_server_id) REFERENCES public.resource_server(id); + + +-- +-- Name: resource_server_resource fk_frsrho213xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_resource + ADD CONSTRAINT fk_frsrho213xcx4wnkog82ssrfy FOREIGN KEY (resource_server_id) REFERENCES public.resource_server(id); + + +-- +-- Name: resource_server_perm_ticket fk_frsrho213xcx4wnkog83sspmt; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_perm_ticket + ADD CONSTRAINT fk_frsrho213xcx4wnkog83sspmt FOREIGN KEY (resource_id) REFERENCES public.resource_server_resource(id); + + +-- +-- Name: resource_server_perm_ticket fk_frsrho213xcx4wnkog84sspmt; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_perm_ticket + ADD CONSTRAINT fk_frsrho213xcx4wnkog84sspmt FOREIGN KEY (scope_id) REFERENCES public.resource_server_scope(id); + + +-- +-- Name: associated_policy fk_frsrpas14xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.associated_policy + ADD CONSTRAINT fk_frsrpas14xcx4wnkog82ssrfy FOREIGN KEY (policy_id) REFERENCES public.resource_server_policy(id); + + +-- +-- Name: scope_policy fk_frsrpass3xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.scope_policy + ADD CONSTRAINT fk_frsrpass3xcx4wnkog82ssrfy FOREIGN KEY (scope_id) REFERENCES public.resource_server_scope(id); + + +-- +-- Name: resource_server_perm_ticket fk_frsrpo2128cx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_perm_ticket + ADD CONSTRAINT fk_frsrpo2128cx4wnkog82ssrfy FOREIGN KEY (policy_id) REFERENCES public.resource_server_policy(id); + + +-- +-- Name: resource_server_policy fk_frsrpo213xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_policy + ADD CONSTRAINT fk_frsrpo213xcx4wnkog82ssrfy FOREIGN KEY (resource_server_id) REFERENCES public.resource_server(id); + + +-- +-- Name: resource_scope fk_frsrpos13xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_scope + ADD CONSTRAINT fk_frsrpos13xcx4wnkog82ssrfy FOREIGN KEY (resource_id) REFERENCES public.resource_server_resource(id); + + +-- +-- Name: resource_policy fk_frsrpos53xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_policy + ADD CONSTRAINT fk_frsrpos53xcx4wnkog82ssrfy FOREIGN KEY (resource_id) REFERENCES public.resource_server_resource(id); + + +-- +-- Name: resource_policy fk_frsrpp213xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_policy + ADD CONSTRAINT fk_frsrpp213xcx4wnkog82ssrfy FOREIGN KEY (policy_id) REFERENCES public.resource_server_policy(id); + + +-- +-- Name: resource_scope fk_frsrps213xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_scope + ADD CONSTRAINT fk_frsrps213xcx4wnkog82ssrfy FOREIGN KEY (scope_id) REFERENCES public.resource_server_scope(id); + + +-- +-- Name: resource_server_scope fk_frsrso213xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_server_scope + ADD CONSTRAINT fk_frsrso213xcx4wnkog82ssrfy FOREIGN KEY (resource_server_id) REFERENCES public.resource_server(id); + + +-- +-- Name: composite_role fk_gr7thllb9lu8q4vqa4524jjy8; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.composite_role + ADD CONSTRAINT fk_gr7thllb9lu8q4vqa4524jjy8 FOREIGN KEY (child_role) REFERENCES public.keycloak_role(id); + + +-- +-- Name: user_consent_client_scope fk_grntcsnt_clsc_usc; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_consent_client_scope + ADD CONSTRAINT fk_grntcsnt_clsc_usc FOREIGN KEY (user_consent_id) REFERENCES public.user_consent(id); + + +-- +-- Name: user_consent fk_grntcsnt_user; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_consent + ADD CONSTRAINT fk_grntcsnt_user FOREIGN KEY (user_id) REFERENCES public.user_entity(id); + + +-- +-- Name: group_attribute fk_group_attribute_group; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.group_attribute + ADD CONSTRAINT fk_group_attribute_group FOREIGN KEY (group_id) REFERENCES public.keycloak_group(id); + + +-- +-- Name: keycloak_group fk_group_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.keycloak_group + ADD CONSTRAINT fk_group_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: group_role_mapping fk_group_role_group; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.group_role_mapping + ADD CONSTRAINT fk_group_role_group FOREIGN KEY (group_id) REFERENCES public.keycloak_group(id); + + +-- +-- Name: realm_enabled_event_types fk_h846o4h0w8epx5nwedrf5y69j; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_enabled_event_types + ADD CONSTRAINT fk_h846o4h0w8epx5nwedrf5y69j FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: realm_events_listeners fk_h846o4h0w8epx5nxev9f5y69j; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_events_listeners + ADD CONSTRAINT fk_h846o4h0w8epx5nxev9f5y69j FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: identity_provider_mapper fk_idpm_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.identity_provider_mapper + ADD CONSTRAINT fk_idpm_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: idp_mapper_config fk_idpmconfig; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.idp_mapper_config + ADD CONSTRAINT fk_idpmconfig FOREIGN KEY (idp_mapper_id) REFERENCES public.identity_provider_mapper(id); + + +-- +-- Name: web_origins fk_lojpho213xcx4wnkog82ssrfy; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.web_origins + ADD CONSTRAINT fk_lojpho213xcx4wnkog82ssrfy FOREIGN KEY (client_id) REFERENCES public.client(id); + + +-- +-- Name: client_default_roles fk_nuilts7klwqw2h8m2b5joytky; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_default_roles + ADD CONSTRAINT fk_nuilts7klwqw2h8m2b5joytky FOREIGN KEY (client_id) REFERENCES public.client(id); + + +-- +-- Name: scope_mapping fk_ouse064plmlr732lxjcn1q5f1; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.scope_mapping + ADD CONSTRAINT fk_ouse064plmlr732lxjcn1q5f1 FOREIGN KEY (client_id) REFERENCES public.client(id); + + +-- +-- Name: client fk_p56ctinxxb9gsk57fo49f9tac; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client + ADD CONSTRAINT fk_p56ctinxxb9gsk57fo49f9tac FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: protocol_mapper fk_pcm_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.protocol_mapper + ADD CONSTRAINT fk_pcm_realm FOREIGN KEY (client_id) REFERENCES public.client(id); + + +-- +-- Name: credential fk_pfyr0glasqyl0dei3kl69r6v0; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.credential + ADD CONSTRAINT fk_pfyr0glasqyl0dei3kl69r6v0 FOREIGN KEY (user_id) REFERENCES public.user_entity(id); + + +-- +-- Name: protocol_mapper_config fk_pmconfig; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.protocol_mapper_config + ADD CONSTRAINT fk_pmconfig FOREIGN KEY (protocol_mapper_id) REFERENCES public.protocol_mapper(id); + + +-- +-- Name: default_client_scope fk_r_def_cli_scope_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.default_client_scope + ADD CONSTRAINT fk_r_def_cli_scope_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: default_client_scope fk_r_def_cli_scope_scope; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.default_client_scope + ADD CONSTRAINT fk_r_def_cli_scope_scope FOREIGN KEY (scope_id) REFERENCES public.client_scope(id); + + +-- +-- Name: client_scope fk_realm_cli_scope; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.client_scope + ADD CONSTRAINT fk_realm_cli_scope FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: required_action_provider fk_req_act_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.required_action_provider + ADD CONSTRAINT fk_req_act_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: resource_uris fk_resource_server_uris; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.resource_uris + ADD CONSTRAINT fk_resource_server_uris FOREIGN KEY (resource_id) REFERENCES public.resource_server_resource(id); + + +-- +-- Name: role_attribute fk_role_attribute_id; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.role_attribute + ADD CONSTRAINT fk_role_attribute_id FOREIGN KEY (role_id) REFERENCES public.keycloak_role(id); + + +-- +-- Name: realm_supported_locales fk_supported_locales_realm; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.realm_supported_locales + ADD CONSTRAINT fk_supported_locales_realm FOREIGN KEY (realm_id) REFERENCES public.realm(id); + + +-- +-- Name: user_federation_config fk_t13hpu1j94r2ebpekr39x5eu5; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_federation_config + ADD CONSTRAINT fk_t13hpu1j94r2ebpekr39x5eu5 FOREIGN KEY (user_federation_provider_id) REFERENCES public.user_federation_provider(id); + + +-- +-- Name: user_group_membership fk_user_group_user; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.user_group_membership + ADD CONSTRAINT fk_user_group_user FOREIGN KEY (user_id) REFERENCES public.user_entity(id); + + +-- +-- Name: policy_config fkdc34197cf864c4e43; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.policy_config + ADD CONSTRAINT fkdc34197cf864c4e43 FOREIGN KEY (policy_id) REFERENCES public.resource_server_policy(id); + + +-- +-- Name: identity_provider_config fkdc4897cf864c4e43; Type: FK CONSTRAINT; Schema: public; Owner: keycloak +-- + +ALTER TABLE ONLY public.identity_provider_config + ADD CONSTRAINT fkdc4897cf864c4e43 FOREIGN KEY (identity_provider_id) REFERENCES public.identity_provider(internal_id); + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/devenv/docker/blocks/oauth/docker-build-keycloak-m1-image.sh b/devenv/docker/blocks/oauth/docker-build-keycloak-m1-image.sh new file mode 100644 index 00000000000..46c1ef09fa3 --- /dev/null +++ b/devenv/docker/blocks/oauth/docker-build-keycloak-m1-image.sh @@ -0,0 +1,9 @@ +#/bin/sh + +VERSION=12.0.1 # set version here + +cd /tmp +git clone git@github.com:keycloak/keycloak-containers.git +cd keycloak-containers/server +git checkout $VERSION +docker build -t "quay.io/keycloak/keycloak:${VERSION}" . diff --git a/devenv/docker/blocks/oauth/docker-compose.yaml b/devenv/docker/blocks/oauth/docker-compose.yaml new file mode 100644 index 00000000000..ac42a218068 --- /dev/null +++ b/devenv/docker/blocks/oauth/docker-compose.yaml @@ -0,0 +1,30 @@ + oauthkeycloakdb: + image: postgres:12.2 + container_name: oauthkeycloakdb + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: password + volumes: + - ./docker/blocks/oauth/cloak.sql:/docker-entrypoint-initdb.d/cloak.sql + restart: unless-stopped + + oauthkeycloak: + image: quay.io/keycloak/keycloak:12.0.1 + container_name: oauthkeycloak + environment: + DB_VENDOR: POSTGRES + DB_ADDR: oauthkeycloakdb + DB_DATABASE: keycloak + DB_USER: keycloak + DB_PASSWORD: password + KEYCLOAK_USER: admin + KEYCLOAK_PASSWORD: admin + PROXY_ADDRESS_FORWARDING: "true" + ports: + - 8087:8080 + depends_on: + - oauthkeycloakdb + links: + - "oauthkeycloakdb:oauthkeycloakdb" + restart: unless-stopped diff --git a/devenv/docker/blocks/oauth/readme.md b/devenv/docker/blocks/oauth/readme.md new file mode 100644 index 00000000000..a40f212a2a8 --- /dev/null +++ b/devenv/docker/blocks/oauth/readme.md @@ -0,0 +1,65 @@ +# OAUTH BLOCK + +## Devenv setup + +To launch the block, use the oauth source. Ex: +```bash +make devenv sources="oauth" +``` + +Here is the conf you need to add to your configuration file (conf/custom.ini): + +```ini +[auth] +signout_redirect_url = http://localhost:8087/auth/realms/grafana/protocol/openid-connect/logout?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Flogin + +[auth.generic_oauth] +enabled = true +name = Keycloak-OAuth +allow_sign_up = true +client_id = grafana-oauth +client_secret = d17b9ea9-bcb1-43d2-b132-d339e55872a8 +empty_scopes = true +email_attribute_path = email +login_attribute_path = login +name_attribute_path = name +auth_url = http://localhost:8087/auth/realms/grafana/protocol/openid-connect/auth +token_url = http://localhost:8087/auth/realms/grafana/protocol/openid-connect/token +api_url = http://localhost:8087/auth/realms/grafana/protocol/openid-connect/userinfo +role_attribute_path = contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer' +``` + +## Backing up keycloak DB + +In case you want to make changes to the devenv setup, you can dump keycloack's DB: + +```bash +cd devenv; +docker-compose exec -T oauthkeycloakdb bash -c "pg_dump -U keycloak keycloak" > docker/blocks/oauth/cloak.sql +``` + +## Connecting to keycloack: + +- keycloak admin: http://localhost:8087 +- keycloak admin login: admin:admin +- grafana oauth viewer login: oauth-viewer:grafana +- grafana oauth editor login: oauth-editor:grafana +- grafana oauth admin login: oauth-admin:grafana + +# Troubleshooting + +## Mac M1 Users + +The new arm64 architecture does not build for the latest docker image of keycloack. Refer to https://github.com/docker/for-mac/issues/5310 for the issue to see if it resolved. +Until then you need to build the docker image locally and then run `devenv`. + +1. Remove any lingering keycloack image +```sh +$ docker rmi $(docker images | grep 'keycloack') +``` +1. Build keycloack image locally +```sh +$ ./docker-build-keycloack-m1-image.sh +``` +1. Start from beginning of this readme + diff --git a/docs/sources/administration/provisioning/_index.md b/docs/sources/administration/provisioning/_index.md index 721a565aa34..befc09072bc 100644 --- a/docs/sources/administration/provisioning/_index.md +++ b/docs/sources/administration/provisioning/_index.md @@ -233,7 +233,10 @@ datasources: > This feature is available from v7.1 -You can manage plugins in Grafana by adding one or more YAML config files in the [`provisioning/plugins`]({{< relref "../../setup-grafana/configure-grafana/#provisioning" >}}) directory. Each config file can contain a list of `apps` that will be updated during start up. Grafana updates each app to match the configuration file. +You can manage plugin applications in Grafana by adding one or more YAML config files in the [`provisioning/plugins`]({{< relref "../../setup-grafana/configure-grafana/#provisioning" >}}) directory. Each config file can contain a list of `apps` that will be updated during start up. Grafana updates each app to match the configuration file. + +> **Note:** This feature enables you to provision plugin configurations, not the plugins themselves. +> The plugins must already be installed on the grafana instance ### Example plugin configuration file diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions.md index b69f9c702b0..28e3437b11f 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions.md @@ -74,7 +74,7 @@ The following tables list permissions associated with basic and fixed roles. | `fixed:settings:reader` | `settings:read` | Read Grafana instance settings. | | `fixed:settings:writer` | All permissions from `fixed:settings:reader` and
`settings:write` | Read and update Grafana instance settings. | | `fixed:stats:reader` | `server.stats:read` | Read Grafana instance statistics. | -| `fixed:teams:creator` | `teams:create`
`org.users:read` | Create a team and list organization users (required to manage the created team). | +| `fixed:teams:creator` | `teams:create`
`org.users:read`
`serviceaccounts:read` | Create a team and list organization users and service accounts (required to manage the created team). | | `fixed:teams:writer` | `teams:create`
`teams:delete`
`teams:read`
`teams:write`
`teams.permissions:read`
`teams.permissions:write` | Create, read, update and delete teams and manage team memberships. | | `fixed:users:reader` | `users:read`
`users.quotas:read`
`users.authtoken:read`
` | Read all users and their information, such as team memberships, authentication tokens, and quotas. | | `fixed:users:writer` | All permissions from `fixed:users:reader` and
`users:write`
`users:create`
`users:delete`
`users:enable`
`users:disable`
`users.password:write`
`users.permissions:write`
`users:logout`
`users.authtoken:write`
`users.quotas:write` | Read and update all attributes and settings for all users in Grafana: update user information, read user information, create or enable or disable a user, make a user a Grafana administrator, sign out a user, update a user’s authentication token, or update quotas for all users. | diff --git a/docs/sources/alerting/_index.md b/docs/sources/alerting/_index.md index f1102173c9e..87cb3c45bdd 100644 --- a/docs/sources/alerting/_index.md +++ b/docs/sources/alerting/_index.md @@ -19,7 +19,7 @@ Watch this video to learn more about Grafana Alerting: {{< vimeo 720001629 >}} The following diagram gives you an overview of how Grafana Alerting works and introduces you to some of the key concepts that work together and form the core of our flexible and powerful alerting engine. -{{< figure src="/static/img/docs/alerting/unified/about-alerting-flow-diagram.jpg" caption="Grafana Alerting overview" >}} +{{< figure src="/static/img/docs/alerting/unified/about-alerting-flow-diagram-latest.png" caption="Grafana Alerting overview" >}} 1. Alert rules diff --git a/docs/sources/alerting/fundamentals/annotation-label/_index.md b/docs/sources/alerting/fundamentals/annotation-label/_index.md index 4d2dd03bf7e..8270a4ce178 100644 --- a/docs/sources/alerting/fundamentals/annotation-label/_index.md +++ b/docs/sources/alerting/fundamentals/annotation-label/_index.md @@ -16,7 +16,7 @@ weight: 401 # Annotations and labels for alerting rules -Annotations and labels are key value pairs associated with alerts originating from the alerting rule, datasource response, and as a result of alerting rule evaluation. They can be used in alert notifications directly or in [templates]({{< relref "../../contact-points/message-templating/" >}}) and [template functions]({{< relref "../../contact-points/message-templating/template-functions/" >}}) to create notification contact dynamically. +Annotations and labels are key value pairs associated with alerts originating from the alerting rule, datasource response, and as a result of alerting rule evaluation. They can be used in alert notifications directly or in [templates]({{< relref "../../contact-points/message-templating/" >}}) and [template functions]({{< relref "../../contact-points/fundamentals/annotation-label/template-functions/" >}}) to create notification contact dynamically. ## Annotations diff --git a/docs/sources/alerting/contact-points/message-templating/example-template-functions.md b/docs/sources/alerting/fundamentals/annotation-label/example-template-functions.md similarity index 89% rename from docs/sources/alerting/contact-points/message-templating/example-template-functions.md rename to docs/sources/alerting/fundamentals/annotation-label/example-template-functions.md index 76693eda54d..b6f13464476 100644 --- a/docs/sources/alerting/contact-points/message-templating/example-template-functions.md +++ b/docs/sources/alerting/fundamentals/annotation-label/example-template-functions.md @@ -1,9 +1,7 @@ --- aliases: - /docs/grafana/latest/alerting/contact-points/message-templating/example-template-functions/ - - /docs/grafana/latest/alerting/contact-points/message-templating/template-functions/ - - /docs/grafana/latest/alerting/message-templating/template-functions/ - - /docs/grafana/latest/alerting/unified-alerting/message-templating/template-functions/ + - /docs/grafana/latest/alerting/fundamentals/annotation-label/example-template-functions/ keywords: - grafana - alerting diff --git a/docs/sources/alerting/contact-points/message-templating/template-functions.md b/docs/sources/alerting/fundamentals/annotation-label/template-functions.md similarity index 96% rename from docs/sources/alerting/contact-points/message-templating/template-functions.md rename to docs/sources/alerting/fundamentals/annotation-label/template-functions.md index 567f0452703..c7c3635c525 100644 --- a/docs/sources/alerting/contact-points/message-templating/template-functions.md +++ b/docs/sources/alerting/fundamentals/annotation-label/template-functions.md @@ -3,6 +3,8 @@ aliases: - /docs/grafana/latest/alerting/contact-points/message-templating/template-functions/ - /docs/grafana/latest/alerting/message-templating/template-functions/ - /docs/grafana/latest/alerting/unified-alerting/message-templating/template-functions/ + - /docs/grafana/latest/alerting/fundamentals/annotation-label/template-functions/ + - /docs/grafana/latest/alerting/unified-alerting/fundamentals/annotation-label/template-functions/ keywords: - grafana - alerting @@ -15,7 +17,7 @@ weight: 125 # Template Functions -Template functions allow you to process labels and annotations to generate dynamic notifications. +Template functions allow you to process alert evaluation results to generate dynamic notifications. | Name | Argument type | Return type | Description | | ----------------------------------------- | ------------------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/docs/sources/alerting/fundamentals/data-source-alerting.md b/docs/sources/alerting/fundamentals/data-source-alerting.md new file mode 100644 index 00000000000..08ad66165e7 --- /dev/null +++ b/docs/sources/alerting/fundamentals/data-source-alerting.md @@ -0,0 +1,39 @@ +--- +aliases: + - /docs/grafana/latest/alerting/fundamentals/data-source-alerting/ +description: Data sources in Grafana Alerting +title: Data sources +weight: 100 +--- + +# Data sources + +There are a number of data sources that are compatible with Grafana Alerting. Each data source is supported by a plugin. You can use one of the built-in data sources listed below, use [external data source plugins](https://grafana.com/grafana/plugins/?type=datasource), or create your own data source plugin. + +If you are creating your own data source plugin, make sure it is a backend plugin as Grafana Alerting requires this in order to be able to evaluate rules using the data source. Frontend data sources are not supported, because the evaluation engine runs on the backend. + +Specifying { "alerting": true, “backend”: true } in the plugin.json file indicates that the data source plugin is compatible with Grafana Alerting and includes the backend data-fetching code. For more information, refer to [Build a data source backend plugin](https://grafana.com/tutorials/build-a-data-source-backend-plugin/). + +These are the data sources that are compatible with and supported by Grafana Alerting. + +- [AWS CloudWatch]({{< relref "../../datasources/aws-cloudwatch/" >}}) +- [Azure Monitor]({{< relref "../../datasources/azuremonitor/" >}}) +- [Elasticsearch]({{< relref "../../datasources/elasticsearch/" >}}) +- [Google Cloud Monitoring]({{< relref "../../google-cloud-monitoring/" >}}) +- [Graphite]({{< relref "../../datasources/graphite/" >}}) +- [InfluxDB]({{< relref "influxdb/" >}}) +- [Loki]({{< relref "../../datasources/loki/" >}}) +- ]Microsoft SQL Server (MSSQL)]({{< relref "../../datasources/mssql/" >}}) +- [MySQL]({{< relref "../../datasources/mysql/" >}}) +- [Open TSDB]({{< relref "../../datasources/opentsdb/" >}}) +- [PostgreSQL]({{< relref "../../datasources/postgres/" >}}) +- [Prometheus]({{< relref "../../datasources/prometheus/" >}}) +- [Jaeger]({{< relref "../../datasources/jaeger/" >}}) +- [Zipkin]({{< relref "../../datasources/zipkin/" >}}) +- [Tempo]({{< relref "../../datasources/tempo/" >}}) +- [Testdata]({{< relref "../../datasources/testdata/" >}}) + +## Useful links + +- [Grafana data sources]({{< relref "../../data-sources/" >}}) +- [Add a data source]({{< relref "../../data-sources/add-a-data-source/" >}}) diff --git a/docs/sources/best-practices/best-practices-for-creating-dashboards.md b/docs/sources/best-practices/best-practices-for-creating-dashboards.md index 932946faa84..91b765d8e20 100644 --- a/docs/sources/best-practices/best-practices-for-creating-dashboards.md +++ b/docs/sources/best-practices/best-practices-for-creating-dashboards.md @@ -51,6 +51,6 @@ Once you have a strategy or design guidelines, write them down to help maintain - Use the left and right Y-axes when displaying time series with different units or ranges. - Add documentation to dashboards and panels. - To add documentation to a dashboard, add a [Text panel visualization]({{< relref "../visualizations/text-panel/" >}}) to the dashboard. Record things like the purpose of the dashboard, useful resource links, and any instructions users might need to interact with the dashboard. Check out this [Wikimedia example](https://grafana.wikimedia.org/d/000000066/resourceloader?orgId=1). - - To add documentation to a panel, [edit the panel settings]({{< relref "../panels/working-with-panels/add-panel/" >}}) and add a description. Any text you add will appear if you hover your cursor over the small `i` in the top left corner of the panel. + - To add documentation to a panel, edit the panel settings and add a description. Any text you add will appear if you hover your cursor over the small `i` in the top left corner of the panel. - Reuse your dashboards and enforce consistency by using [templates and variables]({{< relref "../variables/" >}}). - Be careful with stacking graph data. The visualizations can be misleading, and hide important data. We recommend turning it off in most cases. diff --git a/docs/sources/dashboards/add-organize-panels.md b/docs/sources/dashboards/add-organize-panels.md new file mode 100644 index 00000000000..52854224f1c --- /dev/null +++ b/docs/sources/dashboards/add-organize-panels.md @@ -0,0 +1,108 @@ +--- +aliases: + - /docs/grafana/latest/panels/working-with-panels/navigate-panel-editor/ + - /docs/grafana/latest/panels/working-with-panels/navigate-inspector-panel/ + - /docs/grafana/latest/dashboards/dashboard-create/ + - /docs/grafana/latest/features/dashboard/dashboards/ + - /docs/grafana/latest/panels/working-with-panels/add-panel/ + - /docs/grafana/latest/dashboards/add-organize-panels/ +title: Add and organize panels +menuTitle: Add and organize panels +weight: 2 +--- + +# Add and organize panels + +This section describes the areas of the Grafana panel editor. + +1. Panel header: The header section lists the dashboard in which the panel appears and the following controls: + + - **Dashboard settings (gear) icon -** Click to access the dashboard settings. + - **Discard -** Discards changes you have made to the panel since you last saved the dashboard. + - **Save -** Saves changes you made to the panel. + - **Apply -** Applies changes you made and closes the panel editor, returning you to the dashboard. You will have to save the dashboard to persist the applied changes. + +1. Visualization preview: The visualization preview section contains the following options: + + - **Table view -** Convert any visualization to a table so that you can see the data. Table views are useful for troubleshooting. + - **Fill -** The visualization preview fills the available space. If you change the width of the side pane or height of the bottom pane the visualization changes to fill the available space. + - **Actual -** The visualization preview will have the exact size as the size on the dashboard. If not enough space is available, the visualization will scale down preserving the aspect ratio. + - **Time range controls -** For more information, refer to [Time range controls]({{< relref "time-range-controls/" >}}). + +1. Data section: The data section contains tabs where you enter queries, transform your data, and create alert rules (if applicable). + + - **Query tab -** Select your data source and enter queries here. For more information, refer to [Add a query]({{< relref "../panels/query-a-data-source/add-a-query/" >}}). + - **Transform tab -** Apply data transformations. For more information, refer to [Transform data]({{< relref "../panels/transform-data/" >}}). + - **Alert tab -** Write alert rules. For more information, refer to [Overview of Grafana 8 alerting]({{< relref "../alerting/" >}}). + +1. Panel display options: The display options section contains tabs where you configure almost every aspect of your data visualization, including: + + - [Apply color to series and fields]({{< relref "../panels/working-with-panels/apply-color-to-series/" >}}) + - [Format a standard field]({{< relref "../panels/working-with-panels/format-standard-fields/" >}}) + - [Add a title and description to a panel]({{< relref "../panels/working-with-panels/add-title-and-description/" >}}) + +> Not all options are available for each visualization. + +{{< figure src="/static/img/docs/panel-editor/panel-editor-8-0.png" class="docs-image--no-shadow" max-width="1500px" >}} + +## Open the panel inspect drawer + +The inspect drawer helps you understand and troubleshoot your panels. You can view the raw data for any panel, export that data to a comma-separated values (CSV) file, view query requests, and export panel and data JSON. + +> **Note:** Not all panel types include all tabs. For example, dashboard list panels do not have raw data to inspect, so they do not display the Stats, Data, or Query tabs. + +The panel inspector consists of the following options: + +1. The panel inspect drawer displays opens a drawer on the right side. Click the arrow in the upper right corner to expand or reduce the drawer pane. + +1. **Data tab -** Shows the raw data returned by the query with transformations applied. Field options such as overrides and value mappings are not applied by default. + +1. **Stats tab -** Shows how long your query takes and how much it returns. + +1. **JSON tab -** Allows you to view and copy the panel JSON, panel data JSON, and data frame structure JSON. This is useful if you are provisioning or administering Grafana. + +1. **Query tab -** Shows you the requests to the server sent when Grafana queries the data source. + +1. **Error tab -** Shows the error. Only visible when query returns error. + +## Create a dashboard and add a panel + +Dashboards and panels allow you to show your data in visual form. Each panel needs at least one query to display a visualization. + +**Before you begin:** + +- Ensure that you have the proper permissions. For more information about permissions, refer to [About users and permissions]({{< relref "../administration/roles-and-permissions/" >}}). +- Identify the dashboard to which you want to add the panel. +- Understand the query language of the target data source. +- Ensure that data source for which you are writing a query has been added. For more information about adding a data source, refer to [Add a data source]({{< relref "../datasources/add-a-data-source/" >}}) if you need instructions. + +**To create a dashboard and add a panel**: + +1. Sign in to Grafana, hover your cursor over **Dashboard**, and click **+ New Dashboard**. +1. Click **Add a new panel**. +1. In the first line of the **Query** tab, click the drop-down list and select a data source. +1. Write or construct a query in the query language of your data source. + + For more information about data sources, refer to [Data sources]({{< relref "../datasources/" >}}) for specific guidelines. + +1. In the Visualization list, select a visualization type. + + Grafana displays a preview of your query results with the visualization applied. + + ![](/static/img/docs/panel-editor/select-visualization-8-0.png) + + For more information about individual visualizations, refer to [Visualizations options]({{< relref "../visualizations/" >}}). + +1. Refer to the following documentation for ways you can adjust panel settings. + + While not required, most visualizations need some adjustment before they properly display the information that you need. + + - [Format data using value mapping]({{< relref "../panels/format-data/about-value-mapping/" >}}) + - [Visualization-specific options]({{< relref "../visualizations/" >}}) + - [Override field values]({{< relref "../panels/override-field-values/about-field-overrides/" >}}) + - [Configure thresholds]({{< relref "../panels/configure-thresholds/" >}}) + - [Apply color to series and fields]({{< relref "../panels/working-with-panels/apply-color-to-series/" >}}) + +1. Add a note to describe the visualization (or describe your changes) and then click **Save** in the upper-right corner of the page. + + Notes can be helpful if you need to revert the dashboard to a previous version. diff --git a/docs/sources/dashboards/manage-library-panels.md b/docs/sources/dashboards/manage-library-panels.md new file mode 100644 index 00000000000..12b718837ae --- /dev/null +++ b/docs/sources/dashboards/manage-library-panels.md @@ -0,0 +1,87 @@ +--- +aliases: + - /docs/grafana/latest/panels/panel-library/ + - /docs/grafana/latest/panels/library-panels/ + - /docs/grafana/latest/panels/library-panels/create-library-panel/ + - /docs/grafana/latest/panels/library-panels/add-library-panel/ + - /docs/grafana/latest/panels/library-panels/unlink-library-panel/ + - /docs/grafana/latest/panels/library-panels/manage-library-panel/ + - /docs/grafana/latest/panels/library-panels/delete-library-panel/ + - /docs/grafana/latest/dashboards/manage-library-panels/ +title: Manage Grafana library panels +menuTitle: Manage library panels +weight: 3 +--- + +# Manage Grafana library panels + +A library panel is a reusable panel that you can use in any dashboard. When you make a change to a library panel, that change propagates to all instances of where the panel is used. Library panels streamline reuse of panels across multiple dashboards. + +You can save a library panel in a folder alongside saved dashboards. + +## Create a library panel + +When you create a library panel, the panel on the source dashboard is converted to a library panel as well. You need to save the original dashboard once a panel is converted. + +1. Open a panel in edit mode. +1. In the panel display options, click the down arrow option to bring changes to the visualization. + {{< figure src="/static/img/docs/library-panels/create-lib-panel-from-edit-8-0.png" class="docs-image--no-shadow" max-width= "800px" caption="Screenshot of the edit panel" >}} +1. Click the **Library panels** option, and then click **Create library panel** to open the create dialog. + {{< figure src="/static/img/docs/library-panels/create-lib-panel-8-0.png" class="docs-image--no-shadow" max-width= "500px" caption="Screenshot of the create library panel dialog" >}} +1. In **Library panel name**, enter the name. +1. In **Save in folder**, select the folder to save the library panel. +1. Click **Create library panel** to save your changes. +1. Save the dashboard. + +Once created, you can modify the library panel using any dashboard on which it appears. After you save the changes, all instances of the library panel reflect these modifications. + +{{< figure src="/static/img/docs/library-panels/create-from-more-8-0.png" class="docs-image--no-shadow" max-width= "900px" caption="Screenshot of the edit panel" >}} + +## Add a library panel to a dashboard + +Add a Grafana library panel to a dashboard when you want to provide visualizations to other dashboard users. + +1. Hover over the **Dashboards** option on the left menu, then select **New dashboard** from the drop-down options. + + The **Add** panel dialog opens. + {{< figure src="/static/img/docs/library-panels/add-library-panel-8-0.png" class="docs-image--no-shadow" max-width= "900px" caption="Screenshot of the edit panel" >}} + +1. Click the **Add a panel from the panel library** option. + + You will see a list of your library panels. + +1. Filter the list or search to find the panel you want to add. +1. Click a panel to add it to the dashboard. + +## Unlink a library panel + +Unlink a library panel when you want to make a change to the panel and not affect other instances of the library panel. + +1. Hover over **Dashboard** on the left menu, and then click **Library panels**. +1. Select a library panel that is being used in different dashboards. +1. Select the panel you want to unlink. +1. Click the title of the panel and then click **Edit**. The panel opens in edit mode. +1. Click the **Unlink** option on the top right corner of the page. + +## View a list of library panels + +You can view a list of available library panels and search for a library panel. + +1. Hover over the **Dashboard** option on the left menu, then click **Library panels**. + + You can see a list of previously defined library panels. + {{< figure src="/static/img/docs/library-panels/library-panel-list-8-0.png" class="docs-image--no-shadow" max-width= "900px" caption="Screenshot of the edit panel" >}} + +1. Search for a specific library panel if you know its name. + + You can also filter the panels by folder or type. + +## Delete a library panel + +Delete a library panel when you no longer need it. + +1. Hover over **Dashboard** on the left menu, and select **Library panels**. + +1. Select the panel you want to delete. + +1. Click the delete icon next to the library panel name. diff --git a/docs/sources/dashboards/use-dashboards.md b/docs/sources/dashboards/use-dashboards.md index b8c11c162f6..213ae424317 100644 --- a/docs/sources/dashboards/use-dashboards.md +++ b/docs/sources/dashboards/use-dashboards.md @@ -10,7 +10,7 @@ aliases: - /docs/grafana/latest/reference/search/ title: 'Use dashboards' menuTitle: Use dashboards -weight: 2 +weight: 1 keywords: - dashboard - search diff --git a/docs/sources/datasources/azuremonitor/_index.md b/docs/sources/datasources/azuremonitor/_index.md index 095824c0798..3df80deaf61 100644 --- a/docs/sources/datasources/azuremonitor/_index.md +++ b/docs/sources/datasources/azuremonitor/_index.md @@ -103,7 +103,7 @@ Further documentation on multi-dimensional metrics is available [here](https://d #### Supported Azure Monitor metrics -Not all metrics returned by the Azure Monitor Metrics API have values. To make it easier for you when building a query, the Grafana data source has a list of supported metrics and ignores metrics which will never have values. This list is updated regularly as new services and metrics are added to the Azure cloud. For more information about the list of metrics, refer to [current supported namespaces](https://github.com/grafana/grafana/blob/main/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts). +Not all metrics returned by the Azure Monitor Metrics API have values. To make it easier for you when building a query, the Grafana data source has a list of supported metrics and ignores metrics which will never have values. This list is updated regularly as new services and metrics are added to the Azure cloud. For more information about the list of metrics, refer to [current supported namespaces](https://github.com/grafana/grafana/blob/main/public/app/plugins/datasource/grafana-azure-monitor-datasource/azureMetadata/metricNamespaces.ts). ### Querying Azure Monitor Logs diff --git a/docs/sources/datasources/jaeger.md b/docs/sources/datasources/jaeger.md index 20ab026c23f..8c67c20fc4d 100644 --- a/docs/sources/datasources/jaeger.md +++ b/docs/sources/datasources/jaeger.md @@ -66,6 +66,16 @@ This is a configuration for the beta Node Graph visualization. The Node Graph is -- **Enable Node Graph -** Enables the Node Graph visualization. +### Span bar label + +You can configure the span bar label. The span bar label allows you add additional information to the span bar row. + +Select one of the following four options. The default selection is Duration. + +- **None -** Do not show any additional information on the span bar row. +- **Duration -** Show the span duration on the span bar row. +- **Tag -** Show the span tag on the span bar row. Note: You will also need to specify the tag key to use to get the tag value. For example, `span.kind`. + ## Query traces You can query and display traces from Jaeger via [Explore]({{< relref "../explore/" >}}). diff --git a/docs/sources/datasources/tempo.md b/docs/sources/datasources/tempo.md index 5c5ad785c87..8442e65f72f 100644 --- a/docs/sources/datasources/tempo.md +++ b/docs/sources/datasources/tempo.md @@ -84,6 +84,16 @@ This is a configuration for the Loki search query type. -- **Data source -** The Loki instance in which you want to search traces. You must configure derived fields in the Loki instance. +### Span bar label + +You can configure the span bar label. The span bar label allows you add additional information to the span bar row. + +Select one of the following four options. The default selection is Duration. + +- **None -** Do not show any additional information on the span bar row. +- **Duration -** Show the span duration on the span bar row. +- **Tag -** Show the span tag on the span bar row. Note: You will also need to specify the tag key to use to get the tag value. For example, `span.kind`. + ## Query traces You can query and display traces from Tempo via [Explore]({{< relref "../explore/" >}}). diff --git a/docs/sources/datasources/zipkin.md b/docs/sources/datasources/zipkin.md index d9fbcdd53c5..48a543cf8ce 100644 --- a/docs/sources/datasources/zipkin.md +++ b/docs/sources/datasources/zipkin.md @@ -65,6 +65,16 @@ This is a configuration for the beta Node Graph visualization. The Node Graph is -- **Enable Node Graph -** Enables the Node Graph visualization. +### Span bar label + +You can configure the span bar label. The span bar label allows you add additional information to the span bar row. + +Select one of the following four options. The default selection is Duration. + +- **None -** Do not show any additional information on the span bar row. +- **Duration -** Show the span duration on the span bar row. +- **Tag -** Show the span tag on the span bar row. Note: You will also need to specify the tag key to use to get the tag value. For example, `span.kind`. + ## Query traces Querying and displaying traces from Zipkin is available via [Explore]({{< relref "../explore/" >}}). diff --git a/docs/sources/panels/format-data/edit-value-mapping.md b/docs/sources/panels/format-data/edit-value-mapping.md index 4b2d3b2c13d..16ca9fc996f 100644 --- a/docs/sources/panels/format-data/edit-value-mapping.md +++ b/docs/sources/panels/format-data/edit-value-mapping.md @@ -12,7 +12,6 @@ You can change a value mapping at any time. ## Before you begin -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). - Ensure you have an existing value mapping to edit. **To edit a value mapping**: diff --git a/docs/sources/panels/format-data/map-a-range.md b/docs/sources/panels/format-data/map-a-range.md index f11b22bf920..bc86d1fbc6e 100644 --- a/docs/sources/panels/format-data/map-a-range.md +++ b/docs/sources/panels/format-data/map-a-range.md @@ -10,12 +10,6 @@ weight: 30 Map a range of values when you want to format multiple, continuous values. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). - -**To map a range**: - 1. Edit the panel for which you want to map a range of values. 1. In panel display options, in the **Value mappings** section, click **Add value mappings**. 1. Click **Add a new mapping** and then select **Range**. diff --git a/docs/sources/panels/format-data/map-a-regular-expression.md b/docs/sources/panels/format-data/map-a-regular-expression.md index 6e0792b9062..75879c7c91b 100644 --- a/docs/sources/panels/format-data/map-a-regular-expression.md +++ b/docs/sources/panels/format-data/map-a-regular-expression.md @@ -10,14 +10,8 @@ weight: 40 Map a regular expression when you want to format the text and color of a regular expression value. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). - -**To map a regular expression**: - 1. Edit the panel for which you want to map a regular expression. -1. In the panel display options, in the **Value mappings** section, click **Add value mappings**. +1. In the **Value mappings** section of the panel display options, click **Add value mappings**. 1. Click **Add a new mapping** and then select **Regex**. 1. Enter the regular expression pattern for Grafana to match. 1. (Optional) Enter display text. diff --git a/docs/sources/panels/format-data/map-a-special-value.md b/docs/sources/panels/format-data/map-a-special-value.md index e7001a108a4..b4a3005f1c8 100644 --- a/docs/sources/panels/format-data/map-a-special-value.md +++ b/docs/sources/panels/format-data/map-a-special-value.md @@ -10,12 +10,6 @@ weight: 50 Map a special value when you want to format uncommon, boolean, or empty values. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). - -**To map a special value**: - 1. Edit the panel for which you want to map a special value. 1. In panel display options, locate the **Value mappings** section and click **Add value mappings**. 1. Click **Add a new mapping** and then select **Special**. diff --git a/docs/sources/panels/format-data/map-a-value.md b/docs/sources/panels/format-data/map-a-value.md index db848227bb6..02e39594fe8 100644 --- a/docs/sources/panels/format-data/map-a-value.md +++ b/docs/sources/panels/format-data/map-a-value.md @@ -10,12 +10,6 @@ weight: 20 Map a value when you want to format a single value. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). - -**To map a value**: - 1. Open a panel for which you want to map a value. 1. In panel display options, locate the **Value mappings** section and click **Add value mappings**. 1. Click **Add a new mapping** and then select **Value**. diff --git a/docs/sources/panels/library-panels/_index.md b/docs/sources/panels/library-panels/_index.md deleted file mode 100644 index 7e35d457ecf..00000000000 --- a/docs/sources/panels/library-panels/_index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/library-panels/ - - /docs/grafana/latest/panels/panel-library/ - - /docs/sources/panels/library-panels/ -title: Create reusable Grafana panels -weight: 700 ---- - -# Create reusable Grafana panels - -Library panels enable you to create reusable panels that you can add to any dashboard in your organization. - -{{< section >}} diff --git a/docs/sources/panels/library-panels/about-library-panels.md b/docs/sources/panels/library-panels/about-library-panels.md deleted file mode 100644 index 5a1bd2c905a..00000000000 --- a/docs/sources/panels/library-panels/about-library-panels.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/library-panels/about-library-panels/ - - /docs/sources/panels/library-panels/about-library-panels/ -title: About Grafana library panels -weight: 10 ---- - -# About Grafana panel libraries - -A library panel is a reusable panel that you can use in any dashboard. When you make a change to a library panel, that change propagates to all instances of where the panel is used. Library panels streamline reuse of panels across multiple dashboards. - -You can save a library panel in a folder alongside saved dashboards. diff --git a/docs/sources/panels/library-panels/add-library-panel.md b/docs/sources/panels/library-panels/add-library-panel.md deleted file mode 100644 index 49daa67e8fc..00000000000 --- a/docs/sources/panels/library-panels/add-library-panel.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/library-panels/add-library-panel/ - - /docs/sources/panels/library-panels/add-library-panel/ -title: Add a Grafana library panel to a dashboard -weight: 30 ---- - -# Add a Grafana library panel to a dashboard - -Add a Grafana library panel to a dashboard when you want to provide visualizations to other dashboard users. - -## Before you begin - -- [Create a library panel]({{< relref "create-library-panel/" >}}). - -**To add a library panel to a dashboard**: - -1. Hover over the **Dashboards** option on the left menu, then select **New dashboard** from the drop-down options. - - The **Add** panel dialog opens. - {{< figure src="/static/img/docs/library-panels/add-library-panel-8-0.png" class="docs-image--no-shadow" max-width= "900px" caption="Screenshot of the edit panel" >}} - -1. Click the **Add a panel from the panel library** option. - - You will see a list of your library panels. - -1. Filter the list or search to find the panel you want to add. -1. Click a panel to add it to the dashboard. diff --git a/docs/sources/panels/library-panels/create-library-panel.md b/docs/sources/panels/library-panels/create-library-panel.md deleted file mode 100644 index d8f8d18eb97..00000000000 --- a/docs/sources/panels/library-panels/create-library-panel.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/library-panels/create-library-panel/ - - /docs/sources/panels/library-panels/create-library-panel/ -title: Create a Grafana library panel -weight: 20 ---- - -# Create a Grafana library panel - -When you create a library panel, the panel on the source dashboard is converted to a library panel as well. You need to save the original dashboard once a panel is converted. - -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). - -**To create a library panel**: - -1. Open a panel in edit mode. -1. In the panel display options, click the down arrow option to bring changes to the visualization. - {{< figure src="/static/img/docs/library-panels/create-lib-panel-from-edit-8-0.png" class="docs-image--no-shadow" max-width= "800px" caption="Screenshot of the edit panel" >}} -1. Click the **Library panels** option, and then click **Create new library panel** to open the create dialog. - {{< figure src="/static/img/docs/library-panels/create-lib-panel-8-0.png" class="docs-image--no-shadow" max-width= "500px" caption="Screenshot of the create library panel dialog" >}} -1. In **Library panel name**, enter the name. -1. In **Save in folder**, select the folder to save the library panel. -1. Click **Create library panel** to save your changes. -1. Save the dashboard. - -Once created, you can modify the library panel using any dashboard on which it appears. After you save the changes, all instances of the library panel reflect these modifications. - -{{< figure src="/static/img/docs/library-panels/create-from-more-8-0.png" class="docs-image--no-shadow" max-width= "900px" caption="Screenshot of the edit panel" >}} diff --git a/docs/sources/panels/library-panels/delete-library-panel.md b/docs/sources/panels/library-panels/delete-library-panel.md deleted file mode 100644 index 2d799887449..00000000000 --- a/docs/sources/panels/library-panels/delete-library-panel.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/library-panels/delete-library-panel/ - - /docs/sources/panels/library-panels/delete-library-panel/ -title: Delete a Grafana library panel -weight: 60 ---- - -# Delete a Grafana library panel - -Delete a library panel when you no longer need it. - -## Before you begin - -- Verify that the panel does not appear on a dashboard. - -**To delete a library panel**: - -1. Hover over **Dashboard** on the left menu, and select Library panels from the drop-down options. -1. Select a library panel that is being used in different dashboards. - - You will see a list of all the dashboards. - -1. Select the panel you want to delete. -1. Click the delete icon next to the library panel name. diff --git a/docs/sources/panels/library-panels/manage-library-panel.md b/docs/sources/panels/library-panels/manage-library-panel.md deleted file mode 100644 index f15c9d4faca..00000000000 --- a/docs/sources/panels/library-panels/manage-library-panel.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/library-panels/manage-library-panel/ - - /docs/sources/panels/library-panels/manage-library-panel/ -title: Manage Grafana library panels -weight: 40 ---- - -# Manage Grafana library panels - -You can adjust library panel configuration at any time. - -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). - -**To view and manage a library panel**: - -1. Hover over the **Dashboard** option on the left menu, then click **Library panels**. - - You can see a list of previously defined library panels. - {{< figure src="/static/img/docs/library-panels/library-panel-list-8-0.png" class="docs-image--no-shadow" max-width= "900px" caption="Screenshot of the edit panel" >}} - -1. Search for a specific library panel if you know its name. - - You can also filter the panels by folder or type. diff --git a/docs/sources/panels/library-panels/unlink-library-panel.md b/docs/sources/panels/library-panels/unlink-library-panel.md deleted file mode 100644 index 06173090d11..00000000000 --- a/docs/sources/panels/library-panels/unlink-library-panel.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/library-panels/unlink-library-panel/ - - /docs/sources/panels/library-panels/unlink-library-panel/ -title: Unlink a Grafana library panel -weight: 50 ---- - -# Unlink a Grafana library panel - -Unlink a library panel when you want to make a change to the panel and not affect other instances of the library panel. - -## Before you begin - -- Identify the panel you want to unlink. - -**To unlink a library panel from a dashboard**: - -1. Hover over **Dashboard** on the left menu, and then click **Library panels**. -1. Select a library panel that is being used in different dashboards. -1. Select the panel you want to unlink. -1. Click the title of the panel and then click **Edit**. The panel opens in edit mode. -1. Click the **Unlink** option on the top right corner of the page. diff --git a/docs/sources/panels/override-field-values/add-a-field-override.md b/docs/sources/panels/override-field-values/add-a-field-override.md index aa64da1546d..c89a37e302e 100644 --- a/docs/sources/panels/override-field-values/add-a-field-override.md +++ b/docs/sources/panels/override-field-values/add-a-field-override.md @@ -10,12 +10,6 @@ weight: 30 You can override a field when you want to change the display of the value in the visualization. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). - -**To add a field override**: - 1. Edit the panel to which you want to add an override. 1. In the panel display options, in the **Overrides** section, click **Add field override**. diff --git a/docs/sources/panels/override-field-values/delete-a-field-override.md b/docs/sources/panels/override-field-values/delete-a-field-override.md index 3557932847f..887a09a7b2f 100644 --- a/docs/sources/panels/override-field-values/delete-a-field-override.md +++ b/docs/sources/panels/override-field-values/delete-a-field-override.md @@ -12,13 +12,6 @@ Delete a field override when you no longer need it. When you delete an override, the appearance of value defaults to its original format. This change impacts dashboards and dashboard users that rely on an affected panel. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). -- [Add a field override]({{< relref "add-a-field-override/" >}}). - -**To delete a field override**: - 1. Edit the panel that contains the override you want to delete. 1. In panel display options, click the **Overrides** tab. 1. Click the override you want to delete and then click the associated trash icon. diff --git a/docs/sources/panels/override-field-values/edit-field-override.md b/docs/sources/panels/override-field-values/edit-field-override.md index 87299454e9b..64c3ea0b620 100644 --- a/docs/sources/panels/override-field-values/edit-field-override.md +++ b/docs/sources/panels/override-field-values/edit-field-override.md @@ -10,11 +10,6 @@ weight: 40 Edit a field override when you want to make changes to an override setting. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). -- [Add a field override]({{< relref "add-a-field-override/" >}}). - **To edit a field override**: 1. Edit the panel that contains the overrides you want to edit. diff --git a/docs/sources/panels/override-field-values/view-field-override.md b/docs/sources/panels/override-field-values/view-field-override.md index 6c8de2c741f..2fbe8f609a0 100644 --- a/docs/sources/panels/override-field-values/view-field-override.md +++ b/docs/sources/panels/override-field-values/view-field-override.md @@ -10,11 +10,6 @@ weight: 20 You can view field overrides in the panel display options. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). -- [Add a field override]({{< relref "add-a-field-override/" >}}). - **To view field overrides**: 1. Open for edit the panel that contains the overrides you want to view. diff --git a/docs/sources/panels/query-a-data-source/add-a-query.md b/docs/sources/panels/query-a-data-source/add-a-query.md index 6fea5855c44..61eb69897bf 100644 --- a/docs/sources/panels/query-a-data-source/add-a-query.md +++ b/docs/sources/panels/query-a-data-source/add-a-query.md @@ -14,7 +14,6 @@ A query returns data that Grafana visualizes in dashboards. When you create a pa - [Add a data source](../../../datasources/add-a-data-source). - Ensure that you know the query language of the data source. -- [Add a panel]({{< relref "../working-with-panels/add-panel/" >}}). **To add a query**: diff --git a/docs/sources/panels/query-a-data-source/download-raw-query-results.md b/docs/sources/panels/query-a-data-source/download-raw-query-results.md index 6205539ae03..f607a1cf915 100644 --- a/docs/sources/panels/query-a-data-source/download-raw-query-results.md +++ b/docs/sources/panels/query-a-data-source/download-raw-query-results.md @@ -10,13 +10,6 @@ weight: 70 Grafana generates a CSV file that contains your data, including any transformations to that data. You can choose to view the data before or after the panel applies field options or field option overrides. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). -- [Add a query]({{< relref "add-a-query/" >}}). - -**To download raw query results**: - 1. Edit the panel that contains the query data you want to download. 1. In the query editor, click **Query Inspector**. 1. Click **Data**. diff --git a/docs/sources/panels/query-a-data-source/inspect-query-performance.md b/docs/sources/panels/query-a-data-source/inspect-query-performance.md index 367b956254c..dede421aab6 100644 --- a/docs/sources/panels/query-a-data-source/inspect-query-performance.md +++ b/docs/sources/panels/query-a-data-source/inspect-query-performance.md @@ -10,13 +10,6 @@ weight: 80 The **Stats** tab displays statistics that tell you how long your query takes, how many queries you send, and the number of rows returned. This information can help you troubleshoot your queries, especially if any of the numbers are unexpectedly high or low. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). -- [Add a query]({{< relref "add-a-query/" >}}). - -**To inspect query performance**: - 1. Edit the panel that contains the query with performance you want to inspect. 1. In the query editor, click **Query Inspector**. 1. Click **Stats**. diff --git a/docs/sources/panels/query-a-data-source/inspect-request-and-response-data.md b/docs/sources/panels/query-a-data-source/inspect-request-and-response-data.md index 9b241cd61ae..2214db2fc3d 100644 --- a/docs/sources/panels/query-a-data-source/inspect-request-and-response-data.md +++ b/docs/sources/panels/query-a-data-source/inspect-request-and-response-data.md @@ -10,13 +10,6 @@ weight: 90 Inspect query request and response data when you want to troubleshoot a query that returns unexpected results, or fails to return expected results. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). -- [Add a query]({{< relref "add-a-query/" >}}). - -**To inspect query request and response data**: - 1. Edit the panel that contains the query you want to export. 1. In the query editor, click **Query Inspector**. 1. Click **Refresh**. diff --git a/docs/sources/panels/query-a-data-source/share-query.md b/docs/sources/panels/query-a-data-source/share-query.md index 13ebda3724e..fcd3caff58a 100644 --- a/docs/sources/panels/query-a-data-source/share-query.md +++ b/docs/sources/panels/query-a-data-source/share-query.md @@ -14,9 +14,7 @@ The Dashboard data source lets you select a panel in your dashboard that contain This strategy can drastically reduce the number of queries being made when you for example have several panels visualizing the same data. -**To share data source queries with another panel**: - -1. [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel/" >}}). +1. [Create a dashboard and add a panel]({{< relref "../../dashboards/add-organize-panels/#create-a-dashboard-and-add-a-panel" >}}). 1. Change the title to "Source panel". You'll use this panel as a source for the other panels. 1. Define the [query]({{< relref "add-a-query/" >}}) or queries that you want share. diff --git a/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/write-an-expression.md b/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/write-an-expression.md index eec926894e4..42eb4d76b1f 100644 --- a/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/write-an-expression.md +++ b/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/write-an-expression.md @@ -12,12 +12,6 @@ If your data source supports them, then Grafana displays the **Expression** butt For more information about expressions, refer to [About expressions]({{< relref "about-expressions/" >}}). -## Before you begin - -- [Add a panel]({{< relref "../../working-with-panels/add-panel/" >}}). - -**To write an expression**: - 1. Open the panel. 1. Below the query, click **Expression**. 1. In the **Operation** field, select the type of expression you want to write. diff --git a/docs/sources/panels/working-with-panels/add-panel.md b/docs/sources/panels/working-with-panels/add-panel.md deleted file mode 100644 index 5359473e3b4..00000000000 --- a/docs/sources/panels/working-with-panels/add-panel.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/working-with-panels/add-panel/ - - /docs/sources/panels/working-with-panels/add-panel/ -title: Add a panel to a dashboard -weight: 20 ---- - -# Add a panel to a dashboard - -Panels allow you to show your data in visual form. Each panel needs at least one query to display a visualization. - -## Before you begin - -- Ensure that you have the proper permissions. For more information about permissions, refer to [About users and permissions]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions/" >}}). -- Identify the dashboard to which you want to add the panel. -- Understand the query language of the target data source. -- Ensure that data source for which you are writing a query has been added. For more information about adding a data source, refer to [Add a data source]({{< relref "../../datasources/add-a-data-source/" >}}) if you need instructions. - -**To add a panel to a dashboard**: - -1. Navigate to the dashboard to which you want to add a panel. -1. Click the **Add panel** icon. - - ![](/static/img/docs/panels/add-panel-icon-7-0.png) - -1. Click **Add an empty panel**. - - Grafana creates an empty time-series panel and selects the default data source. - -1. In the first line of the **Query** tab, click the drop-down list and select a data source. - -1. Write or construct a query in the query language of your data source. - - For more information about data sources, refer to [Data sources]({{< relref "../../datasources/" >}}) for specific guidelines. - -1. In the Visualization list, select a visualization type. - - Grafana displays a preview of your query results with the visualization applied. - - ![](/static/img/docs/panel-editor/select-visualization-8-0.png) - - For more information about individual visualizations, refer to [Visualizations options]({{< relref "../../visualizations/" >}}). - -1. Refer to the following documentation for ways you adjust panel settings. - - While not required, most visualizations need some adjustment before they properly display the information that you need. - - - [Format data using value mapping]({{< relref "../format-data/about-value-mapping/" >}}) - - [Visualization-specific options]({{< relref "../../visualizations/" >}}) - - [Override field values]({{< relref "../override-field-values/about-field-overrides/" >}}) - - [Configure thresholds]({{< relref "../configure-thresholds/" >}}) - - [Apply color to series and fields]({{< relref "apply-color-to-series/" >}}) - -1. Add a note to describe the visualization (or describe your changes) and then click **Save** in the upper-right corner of the page. - - Notes can be helpful if you need to revert the dashboard to a previous version. diff --git a/docs/sources/panels/working-with-panels/add-title-and-description.md b/docs/sources/panels/working-with-panels/add-title-and-description.md index f6ca5e9f76d..e08e2010362 100644 --- a/docs/sources/panels/working-with-panels/add-title-and-description.md +++ b/docs/sources/panels/working-with-panels/add-title-and-description.md @@ -10,12 +10,6 @@ weight: 30 Add a title and description to a panel to share with users any important information about the visualization. For example, use the description to document the purpose of the visualization. -## Before you begin: - -- [Add a panel to a dashboard]({{< relref "add-panel/" >}}). - -**To add a title and description to a panel**: - 1. Open a panel. 1. In the panel display options pane, locate the **Panel options** section. diff --git a/docs/sources/panels/working-with-panels/apply-color-to-series.md b/docs/sources/panels/working-with-panels/apply-color-to-series.md index ba03bd60105..26e30345905 100644 --- a/docs/sources/panels/working-with-panels/apply-color-to-series.md +++ b/docs/sources/panels/working-with-panels/apply-color-to-series.md @@ -13,12 +13,6 @@ In addition to specifying color based on thresholds, you can configure the color You can specify a single color, or select a continuous (gradient) color schemes, based on a value. Continuous color interpolates a color using the percentage of a value relative to min and max. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "add-panel/" >}}). - -**To apply color to series and fields**: - 1. In panel display options, scroll to the **Standard options** or **override** section. 1. Click the **Standard options Color scheme** drop-down, and select one of the following palettes: diff --git a/docs/sources/panels/working-with-panels/configure-legend.md b/docs/sources/panels/working-with-panels/configure-legend.md index d34df9f4f81..008bd996556 100644 --- a/docs/sources/panels/working-with-panels/configure-legend.md +++ b/docs/sources/panels/working-with-panels/configure-legend.md @@ -16,12 +16,6 @@ Visualizations can often be visually complex, and include many data series. You When you apply your changes, the visualization changes appear to all users of the panel. -### Before you begin - -- [Add a panel to a dashboard]({{< relref "add-panel/" >}}). - -**To isolate series data in a visualization**: - 1. Open the panel. 1. In the legend, click the label of the series you want to isolate. diff --git a/docs/sources/panels/working-with-panels/format-standard-fields.md b/docs/sources/panels/working-with-panels/format-standard-fields.md index 6d83f8d9857..ee0404ac817 100644 --- a/docs/sources/panels/working-with-panels/format-standard-fields.md +++ b/docs/sources/panels/working-with-panels/format-standard-fields.md @@ -16,12 +16,6 @@ For a complete list of field formatting options, refer to [Standard field defini > You can apply standard options to most built-in Grafana panels. Some older panels and community panels that have not updated to the new panel and data model will be missing either all or some of these field options. -## Before you begin - -- [Add a panel to a dashboard]({{< relref "add-panel/" >}}). - -**To format a standard field**: - 1. Open a dashboard, click the panel title, and click **Edit**. 1. In the panel display options pane, locate the **Standard options** section. diff --git a/docs/sources/panels/working-with-panels/navigate-panel-editor.md b/docs/sources/panels/working-with-panels/navigate-panel-editor.md deleted file mode 100644 index 4fe5faadee2..00000000000 --- a/docs/sources/panels/working-with-panels/navigate-panel-editor.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -aliases: - - /docs/grafana/latest/panels/working-with-panels/navigate-panel-editor/ - - /docs/sources/panels/working-with-panels/navigate-panel-editor/ -title: Navigate the Grafana panel editor -weight: 10 ---- - -# Navigate the Grafana panel editor - -This page describes the parts of the Grafana panel editor. - -1. Panel header: The header section lists the dashboard in which the panel appears and the following controls: - - - **Dashboard settings (gear) icon -** Click to access the dashboard settings. - - **Discard -** Discards changes you have made to the panel since you last saved the dashboard. - - **Save -** Saves changes you made to the panel. - - **Apply -** Applies changes you made and closes the panel editor, returning you to the dashboard. You will have to save the dashboard to persist the applied changes. - -2. Visualization preview: The visualization preview section contains the following options: - - - **Table view -** Convert any visualization to a table so that you can see the data. Table views are useful for troubleshooting. - - **Fill -** The visualization preview fills the available space. If you change the width of the side pane or height of the bottom pane the visualization changes to fill the available space. - - **Actual -** The visualization preview will have the exact size as the size on the dashboard. If not enough space is available, the visualization will scale down preserving the aspect ratio. - - **Time range controls -** For more information, refer to [Time range controls]({{< relref "../../dashboards/time-range-controls/" >}}). - -3. Data section: The data section contains tabs where you enter queries, transform your data, and create alert rules (if applicable). - - - **Query tab -** Select your data source and enter queries here. For more information, refer to [Add a query]({{< relref "../query-a-data-source/add-a-query/" >}}). - - - **Transform tab -** Apply data transformations. For more information, refer to [Transform data]({{< relref "../transform-data/" >}}). - - **Alert tab -** Write alert rules. For more information, refer to [Overview of Grafana 8 alerting]({{< relref "../../alerting/" >}}). - -4. Panel display options: The display options section contains tabs where you configure almost every aspect of your data visualization, including: - - - [Apply color to series and fields]({{< relref "apply-color-to-series/" >}}) - - [Format a standard field]({{< relref "format-standard-fields/" >}}) - - [Add a title and description to a panel]({{< relref "add-title-and-description/" >}}) - -> Not all options are available for each visualization. - -{{< figure src="/static/img/docs/panel-editor/panel-editor-8-0.png" class="docs-image--no-shadow" max-width="1500px" >}} diff --git a/docs/sources/panels/working-with-panels/view-json-model.md b/docs/sources/panels/working-with-panels/view-json-model.md index be52a641d5b..9220c684720 100644 --- a/docs/sources/panels/working-with-panels/view-json-model.md +++ b/docs/sources/panels/working-with-panels/view-json-model.md @@ -10,12 +10,6 @@ weight: 100 Explore and export panel, panel data, and data frame JSON models. -## Before you begin: - -- [Add a panel to a dashboard]({{< relref "add-panel/" >}}). - -**To view a panel JSON model**: - 1. Open the panel inspector and then click the **JSON** tab or in the panel menu click **Inspect > Panel JSON**. 1. In Select source, choose one of the following options: diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth.md b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth.md index 6e3c5acd406..4033bfc3942 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth.md @@ -51,6 +51,7 @@ tls_client_cert = tls_client_key = tls_client_ca = use_pkce = true +auth_style = ``` Set `api_url` to the resource that returns [OpenID UserInfo](https://connect2id.com/products/server/docs/api/userinfo) compatible information. @@ -63,6 +64,9 @@ You can also specify the SSL/TLS configuration used by the client. `tls_skip_verify_insecure` controls whether a client verifies the server's certificate chain and host name. If it is true, then SSL/TLS accepts any certificate presented by the server and any host name in that certificate. _You should only use this for testing_, because this mode leaves SSL/TLS susceptible to man-in-the-middle attacks. +`auth_style` controls which [OAuth2 AuthStyle](https://pkg.go.dev/golang.org/x/oauth2#AuthStyle) is used when token is requested from OAuth provider. It determines how `client_id` and `client_secret` are sent to Oauth provider. +Available values are `AutoDetect`, `InParams` and `InHeader`. By default, `AutoDetect` is used. + Set `empty_scopes` to true to use an empty scope during authentication. By default, Grafana uses `user:email` as scope. ### Email address diff --git a/docs/sources/visualizations/_index.md b/docs/sources/visualizations/_index.md index 8a1f0bb7444..c44f3cfb368 100644 --- a/docs/sources/visualizations/_index.md +++ b/docs/sources/visualizations/_index.md @@ -10,7 +10,7 @@ weight: 75 Grafana offers a variety of visualizations to support different use cases. This section of the documentation highlights the built-in panels, their options and typical usage. -> **Note:** If you are unsure which visualization to pick, Grafana can provide visualization suggestions based on the panel query. When you select a visualization, Grafana will show a preview with that visualization applied. For more information, see the [add a panel]({{< relref "../panels/working-with-panels/add-panel/" >}}) documentation. +> **Note:** If you are unsure which visualization to pick, Grafana can provide visualization suggestions based on the panel query. When you select a visualization, Grafana will show a preview with that visualization applied. - Graphs & charts - [Time series]({{< relref "time-series/" >}}) is the default and main Graph visualization. diff --git a/docs/sources/visualizations/time-series/graph-time-series-as-bars.md b/docs/sources/visualizations/time-series/graph-time-series-as-bars.md index 338c26dea6d..aeb0ea82af0 100644 --- a/docs/sources/visualizations/time-series/graph-time-series-as-bars.md +++ b/docs/sources/visualizations/time-series/graph-time-series-as-bars.md @@ -18,9 +18,7 @@ This section explains how to use Time series field options to visualize time ser For more information about the time series visualization, refer to [Time series]({{< relref "_index.md" >}}). -## Create the panel - -1. [Add a panel]({{< relref "../../panels/working-with-panels/add-panel/" >}}). +1. [Create a dashboard and add a panel]({{< relref "../../dashboards/add-organize-panels/#create-a-dashboard-and-add-a-panel" >}}). 1. Select the **Time series** visualization. 1. In the Panel editor side pane, click **Graph styles** to expand it. 1. In Style, click **Bars**. diff --git a/docs/sources/visualizations/time-series/graph-time-series-as-lines.md b/docs/sources/visualizations/time-series/graph-time-series-as-lines.md index df3ff48e5a6..d35f40b3bb2 100644 --- a/docs/sources/visualizations/time-series/graph-time-series-as-lines.md +++ b/docs/sources/visualizations/time-series/graph-time-series-as-lines.md @@ -16,9 +16,8 @@ weight: 200 This section explains how to use Time series field options to visualize time series data as lines and illustrates what the options do. -## Create the panel - -1. [Add a panel]({{< relref "../../panels/working-with-panels/add-panel/" >}}). Select the [Time series]({{< relref "_index.md" >}}) visualization. +1. [Create a dashboard and add a panel]({{< relref "../../dashboards/add-organize-panels/#create-a-dashboard-and-add-a-panel" >}}). +1. Select the [Time series]({{< relref "_index.md" >}}) visualization. 1. In the Panel editor side pane, click **Graph styles** to expand it. 1. In Style, click **Lines**. diff --git a/docs/sources/visualizations/time-series/graph-time-series-as-points.md b/docs/sources/visualizations/time-series/graph-time-series-as-points.md index c59bc1f84bd..a3ecd032ada 100644 --- a/docs/sources/visualizations/time-series/graph-time-series-as-points.md +++ b/docs/sources/visualizations/time-series/graph-time-series-as-points.md @@ -16,9 +16,8 @@ weight: 300 This section explains how to use Time series field options to visualize time series data as points and illustrates what the options do. -## Create the panel - -1. [Add a panel]({{< relref "../../panels/working-with-panels/add-panel/" >}}). Select the [Time series]({{< relref "_index.md" >}}) visualization. +1. [Create a dashboard and add a panel]({{< relref "../../dashboards/add-organize-panels/#create-a-dashboard-and-add-a-panel" >}}). +1. Select the [Time series]({{< relref "_index.md" >}}) visualization. 1. In the Panel editor side pane, click **Graph styles** to expand it. 1. In Style, click **Points**. diff --git a/docs/sources/whatsnew/whats-new-in-v7-5.md b/docs/sources/whatsnew/whats-new-in-v7-5.md index 21c1aa783f8..0e07db2ee76 100644 --- a/docs/sources/whatsnew/whats-new-in-v7-5.md +++ b/docs/sources/whatsnew/whats-new-in-v7-5.md @@ -15,7 +15,7 @@ title: What's new in Grafana v7.5 weight: -32 --- -# What’s new in Grafana v7.5 +# What's new in Grafana v7.5 This topic includes the release notes for Grafana v7.5. For all details, read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md). diff --git a/docs/sources/whatsnew/whats-new-in-v8-0.md b/docs/sources/whatsnew/whats-new-in-v8-0.md index 1ab026e6fb7..41cbc4d84e8 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-0.md +++ b/docs/sources/whatsnew/whats-new-in-v8-0.md @@ -15,7 +15,7 @@ title: What's new in Grafana v8.0 weight: -33 --- -# What’s new in Grafana v8.0 +# What's new in Grafana v8.0 This topic includes the release notes for Grafana v8.0. For all details, read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md). diff --git a/docs/sources/whatsnew/whats-new-in-v8-1.md b/docs/sources/whatsnew/whats-new-in-v8-1.md index 3e7327dbd33..3c8ba92d9f7 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-1.md +++ b/docs/sources/whatsnew/whats-new-in-v8-1.md @@ -15,7 +15,7 @@ title: What's new in Grafana v8.1 weight: -33 --- -# What’s new in Grafana v8.1 +# What's new in Grafana v8.1 > **Note:** This topic will be updated frequently between now and the final release. diff --git a/docs/sources/whatsnew/whats-new-in-v8-2.md b/docs/sources/whatsnew/whats-new-in-v8-2.md index 5717519ff30..c5f06327920 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-2.md +++ b/docs/sources/whatsnew/whats-new-in-v8-2.md @@ -15,7 +15,7 @@ title: What's new in Grafana v8.2 weight: -33 --- -# What’s new in Grafana v8.2 +# What's new in Grafana v8.2 Grafana 8.2 continues to build on the foundation of Grafana 8.0 & 8.1. Grafana 8.2 also marks the start of our work to bring Grafana closer to all users with a focus on increasing Grafana’s accessibility, part of its continuing mission to democratize metrics _for everyone_. diff --git a/docs/sources/whatsnew/whats-new-in-v8-3.md b/docs/sources/whatsnew/whats-new-in-v8-3.md index 31e86ab89b5..c609c24c4f0 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-3.md +++ b/docs/sources/whatsnew/whats-new-in-v8-3.md @@ -15,7 +15,7 @@ title: What's new in Grafana v8.3 weight: -33 --- -# What’s new in Grafana v8.3 +# What's new in Grafana v8.3 Grafana 8.3 is an exciting release for Grafana Labs. This release includes the new Candlestick Panel, a new visualization suggestions engine and, for enterprise users, Recorded Queries. diff --git a/docs/sources/whatsnew/whats-new-in-v8-4.md b/docs/sources/whatsnew/whats-new-in-v8-4.md index 45ba5fdbb4c..a1de0eef169 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-4.md +++ b/docs/sources/whatsnew/whats-new-in-v8-4.md @@ -15,7 +15,7 @@ title: What's new in Grafana v8.4 weight: -33 --- -# What’s new in Grafana v8.4 +# What's new in Grafana v8.4 We’re excited to announce Grafana v8.4, with a variety of improvements that focus on Grafana’s usability, performance, and security. Read on to learn about Alerting enhancements like a WeCom contact point, improved Alert panel and custom mute timings, as well as visualization improvements and details to help you share playlists more easily. In Grafana Enterprise, we’ve made caching more powerful to save you time and money while loading dashboards, boosted database encryption to keep secrets safe in your Grafana database, and made usability improvements to Recorded Queries, which allow you to track any data point over time. diff --git a/docs/sources/whatsnew/whats-new-in-v8-5.md b/docs/sources/whatsnew/whats-new-in-v8-5.md index 96b19a7deac..1d7fefd60f9 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-5.md +++ b/docs/sources/whatsnew/whats-new-in-v8-5.md @@ -15,7 +15,7 @@ title: What's new in Grafana v8.5 weight: -33 --- -# What’s new in Grafana v8.5 +# What's new in Grafana v8.5 We’re excited to announce Grafana v8.5, with a variety of improvements that focus on Grafana’s usability, performance, and security. diff --git a/docs/sources/whatsnew/whats-new-in-v9-0.md b/docs/sources/whatsnew/whats-new-in-v9-0.md index 2b6a8a98dba..74b1017bdc7 100644 --- a/docs/sources/whatsnew/whats-new-in-v9-0.md +++ b/docs/sources/whatsnew/whats-new-in-v9-0.md @@ -15,7 +15,7 @@ title: What's new in Grafana v9.0 weight: -33 --- -# What’s new in Grafana v9.0 +# What's new in Grafana v9.0 As tradition goes, GrafanaCon — our yearly community event for Grafana open source users — is also where we launch the latest software release of Grafana. Keeping up with tradition, we are excited to be announcing Grafana v9.0: a release that elevates Grafana’s ease of use, discovery of data through new and improved visualizations and a default Grafana Alerting experience. diff --git a/go.mod b/go.mod index 6de92af8907..78270aecc63 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/fatih/color v1.13.0 github.com/gchaincl/sqlhooks v1.3.0 github.com/getsentry/sentry-go v0.13.0 + github.com/go-git/go-git/v5 v5.4.2 github.com/go-kit/kit v0.11.0 github.com/go-openapi/strfmt v0.20.2 github.com/go-redis/redis/v8 v8.11.4 @@ -41,7 +42,7 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/go-stack/stack v1.8.0 github.com/gobwas/glob v0.2.3 - github.com/gofrs/uuid v4.0.0+incompatible + github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 github.com/golang/mock v1.6.0 github.com/golang/snappy v0.0.4 @@ -213,7 +214,7 @@ require ( github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/encoding v0.3.2 github.com/sercand/kuberesolver v2.4.0+incompatible // indirect - github.com/sergi/go-diff v1.0.0 // indirect + github.com/sergi/go-diff v1.1.0 // indirect github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect github.com/sirupsen/logrus v1.8.1 // indirect @@ -240,8 +241,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.4.0 github.com/Azure/go-autorest/autorest/adal v0.9.17 github.com/armon/go-radix v1.0.0 - github.com/blugelabs/bluge v0.2.1 - github.com/blugelabs/bluge_segment_api v0.2.0 + github.com/blugelabs/bluge v0.1.9 github.com/getkin/kin-openapi v0.94.0 github.com/golang-migrate/migrate/v4 v4.7.0 github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f @@ -256,6 +256,7 @@ require ( require ( cloud.google.com/go v0.100.2 // indirect github.com/armon/go-metrics v0.3.10 // indirect + github.com/blugelabs/bluge_segment_api v0.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/gosimple/unidecode v1.0.1 // indirect @@ -273,7 +274,9 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect github.com/RoaringBitmap/roaring v0.9.4 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f // indirect github.com/bits-and-blooms/bitset v1.2.0 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect @@ -282,24 +285,29 @@ require ( github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/vellum v1.0.7 // indirect github.com/blugelabs/ice v1.0.0 // indirect - github.com/blugelabs/ice/v2 v2.0.1 // indirect github.com/caio/go-tdigest v3.1.0+incompatible // indirect github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect github.com/containerd/containerd v1.6.6 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect + github.com/emirpasic/gods v1.12.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/imdario/mergo v0.3.12 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect github.com/klauspost/compress v1.15.2 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/labstack/echo/v4 v4.7.2 // indirect github.com/labstack/gommon v0.3.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect @@ -308,11 +316,13 @@ require ( github.com/smartystreets/goconvey v1.7.2 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect github.com/wk8/go-ordered-map v1.0.0 + github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.3 // indirect go.opentelemetry.io/proto/otlp v0.15.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/api v0.22.5 // indirect k8s.io/apimachinery v0.22.5 // indirect k8s.io/klog/v2 v2.30.0 // indirect diff --git a/go.sum b/go.sum index b4eb9f79648..df4a323117f 100644 --- a/go.sum +++ b/go.sum @@ -254,6 +254,8 @@ 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/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -275,6 +277,8 @@ github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrU github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0= github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -294,6 +298,8 @@ github.com/aliyun/aliyun-oss-go-sdk v2.0.4+incompatible/go.mod h1:T/Aws4fEfogEE9 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= @@ -316,6 +322,7 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -435,14 +442,13 @@ github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulN github.com/blevesearch/vellum v1.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+eMD5xZ2OyQ= github.com/blevesearch/vellum v1.0.7 h1:+vn8rfyCRHxKVRgDLeR0FAXej2+6mEb5Q15aQE/XESQ= github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE= -github.com/blugelabs/bluge v0.2.1 h1:lgd6IhAAIDoq6my9HQOwk3p6xqJBmR49+lpFmpeFHgU= -github.com/blugelabs/bluge v0.2.1/go.mod h1:am1LU9jS8dZgWkRzkGLQN3757EgMs3upWrU2fdN9foE= +github.com/blugelabs/bluge v0.1.9 h1:bPgXlcsWugrXNjzeoLdOnvfJpHsyODKpYaAndayl/SM= +github.com/blugelabs/bluge v0.1.9/go.mod h1:5d7LktUkQgvbh5Bmi6tPWtvo4+6uRTm6gAwP+5z6FqQ= github.com/blugelabs/bluge_segment_api v0.2.0 h1:cCX1Y2y8v0LZ7+EEJ6gH7dW6TtVTW4RhG0vp3R+N2Lo= github.com/blugelabs/bluge_segment_api v0.2.0/go.mod h1:95XA+ZXfRj/IXADm7gZ+iTcWOJPg5jQTY1EReIzl3LA= +github.com/blugelabs/ice v0.2.0/go.mod h1:7foiDf4V83FIYYnGh2LOoRWsbNoCqAAMNgKn879Iyu0= github.com/blugelabs/ice v1.0.0 h1:um7wf9e6jbkTVCrOyQq3tKK43fBMOvLUYxbj3Qtc4eo= github.com/blugelabs/ice v1.0.0/go.mod h1:gNfFPk5zM+yxJROhthxhVQYjpBO9amuxWXJQ2Lo+IbQ= -github.com/blugelabs/ice/v2 v2.0.1 h1:mzHbntLjk2v7eDRgoXCgzOsPKN1Tenu9Svo6l9cTLS4= -github.com/blugelabs/ice/v2 v2.0.1/go.mod h1:QxAWSPNwZwsIqS25c3lbIPFQrVvT1sphf5x5DfMLH5M= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= @@ -784,6 +790,8 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -817,6 +825,7 @@ github.com/felixge/fgprof v0.9.1/go.mod h1:7/HK6JFtFaARhIljgP2IV8rJLIoHDoOYoUphs github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -852,6 +861,8 @@ github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ER github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= @@ -864,6 +875,15 @@ github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0 github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -1558,6 +1578,8 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jaegertracing/jaeger v1.24.0/go.mod h1:mqdtFDA447va5j0UewDaAWyNlGreGQyhGxXVhbF58gQ= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= @@ -1619,6 +1641,8 @@ github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -1730,6 +1754,7 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= @@ -2227,8 +2252,9 @@ github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfP github.com/sercand/kuberesolver v2.1.0+incompatible/go.mod h1:lWF3GL0xptCB/vCiJPl/ZshwPsX/n4Y7u0CW9E7aQIQ= github.com/sercand/kuberesolver v2.4.0+incompatible h1:WE2OlRf6wjLxHwNkkFLQGaZcVLEXjMjBPjjEU5vksH8= github.com/sercand/kuberesolver v2.4.0+incompatible/go.mod h1:lWF3GL0xptCB/vCiJPl/ZshwPsX/n4Y7u0CW9E7aQIQ= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v3.21.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -2427,6 +2453,8 @@ github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8= github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= @@ -2610,6 +2638,7 @@ golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -2645,6 +2674,7 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -2786,6 +2816,7 @@ golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210324051636-2c4c8ecb7826/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= @@ -2985,6 +3016,7 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3460,6 +3492,7 @@ gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/package.json b/package.json index 91fdd0db85d..fc52866a763 100644 --- a/package.json +++ b/package.json @@ -217,7 +217,6 @@ "mutationobserver-shim": "0.3.7", "ngtemplate-loader": "2.1.0", "node-notifier": "10.0.1", - "nodemon": "2.0.16", "postcss": "8.4.14", "postcss-loader": "7.0.0", "postcss-reporter": "7.0.5", @@ -259,7 +258,7 @@ "@grafana/e2e-selectors": "workspace:*", "@grafana/experimental": "^0.0.2-canary.32", "@grafana/google-sdk": "0.0.3", - "@grafana/lezer-logql": "^0.0.12", + "@grafana/lezer-logql": "^0.0.13", "@grafana/runtime": "workspace:*", "@grafana/schema": "workspace:*", "@grafana/slate-react": "0.22.10-grafana", @@ -354,6 +353,7 @@ "rc-time-picker": "3.7.3", "re-resizable": "6.9.9", "react": "17.0.2", + "react-awesome-query-builder": "^5.1.2", "react-beautiful-dnd": "13.1.0", "react-diff-viewer": "^3.1.1", "react-dom": "17.0.2", @@ -385,7 +385,6 @@ "rst2html": "github:thoward/rst2html#990cb89f2a300cdd9151790be377c4c0840df809", "rxjs": "7.5.5", "sass": "link:./public/sass", - "search-query-parser": "1.6.0", "selecto": "1.16.2", "semver": "7.3.7", "slate": "0.47.8", @@ -404,10 +403,10 @@ "resolutions": { "underscore": "1.13.3", "@types/slate": "0.47.2", - "@microsoft/api-extractor-model": "7.20.3", + "@microsoft/api-extractor-model": "7.21.0", "@rushstack/node-core-library": "3.49.0", "@rushstack/rig-package": "0.3.11", - "@rushstack/ts-command-line": "4.11.0", + "@rushstack/ts-command-line": "4.12.1", "@storybook/react/webpack": "5.73.0", "node-fetch": "2.6.7" }, diff --git a/packages/grafana-data/src/transformations/transformers/histogram.ts b/packages/grafana-data/src/transformations/transformers/histogram.ts index 9dc72bcfd31..58bc951d2e2 100644 --- a/packages/grafana-data/src/transformations/transformers/histogram.ts +++ b/packages/grafana-data/src/transformations/transformers/histogram.ts @@ -160,13 +160,13 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform for (const frame of frames) { for (const field of frame.fields) { if (field.type === FieldType.number) { - allValues = allValues.concat( - field.values.toArray().map((val: number) => Number(val.toFixed(field.config.decimals ?? 0))) - ); + allValues = allValues.concat(field.values.toArray()); } } } + allValues = allValues.filter((v) => v != null); + allValues.sort((a, b) => a - b); let smallestDelta = Infinity; @@ -204,6 +204,9 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform const getBucket = (v: number) => incrRoundDn(v - bucketOffset, bucketSize!) + bucketOffset; + // guess number of decimals + let bucketDecimals = (('' + bucketSize).match(/\.\d+$/) ?? ['.'])[0].length - 1; + let histograms: AlignedData[] = []; let counts: Field[] = []; let config: FieldConfig | undefined = undefined; @@ -220,7 +223,7 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform unit: undefined, }, }); - if (!config && Object.keys(field.config).length) { + if (!config && field.config.unit) { config = field.config; } } @@ -251,7 +254,13 @@ export function buildHistogram(frames: DataFrame[], options?: HistogramTransform values: new ArrayVector(joinedHists[0]), type: FieldType.number, state: undefined, - config: config ?? {}, + config: + bucketDecimals === 0 + ? config ?? {} + : { + ...config, + decimals: bucketDecimals, + }, }; const bucketMax = { ...bucketMin, diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 3b118ec2efb..1be984a4dbd 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -483,7 +483,6 @@ export interface DataQueryRequest { timeInfo?: string; // The query time description (blue text in the upper right) panelId?: number; dashboardId?: number; - // Temporary prop for public dashboards, to be replaced by publicAccessKey publicDashboardAccessToken?: string; // Request Timing diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index ccf3e461d71..d28102166c2 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -56,6 +56,7 @@ export interface FeatureToggles { autoMigrateGraphPanels?: boolean; prometheusWideSeries?: boolean; canvasPanelNesting?: boolean; + scenes?: boolean; useLegacyHeatmapPanel?: boolean; cloudMonitoringExperimentalUI?: boolean; logRequestsInstrumentedAsUnknown?: boolean; diff --git a/packages/grafana-data/src/types/navModel.ts b/packages/grafana-data/src/types/navModel.ts index 58f79261528..453e1a1f572 100644 --- a/packages/grafana-data/src/types/navModel.ts +++ b/packages/grafana-data/src/types/navModel.ts @@ -56,10 +56,6 @@ export interface NavModel { * This is the current active tab/navigation. */ node: NavModelItem; - /** - * Describes breadcrumbs that are used in places such as data source settings., folder page and plugins page. - */ - breadcrumbs?: NavModelItem[]; } export interface NavModelBreadcrumb { diff --git a/packages/grafana-runtime/src/components/PanelRenderer.tsx b/packages/grafana-runtime/src/components/PanelRenderer.tsx index 8c0619ba3ba..a16377052c8 100644 --- a/packages/grafana-runtime/src/components/PanelRenderer.tsx +++ b/packages/grafana-runtime/src/components/PanelRenderer.tsx @@ -11,7 +11,7 @@ import { AbsoluteTimeRange, FieldConfigSource, PanelData } from '@grafana/data'; * @internal */ export interface PanelRendererProps

{ - data: PanelData; + data?: PanelData; pluginId: string; title: string; options?: Partial

; diff --git a/packages/grafana-runtime/src/services/backendSrv.ts b/packages/grafana-runtime/src/services/backendSrv.ts index b5bdd774a2f..27e743447dc 100644 --- a/packages/grafana-runtime/src/services/backendSrv.ts +++ b/packages/grafana-runtime/src/services/backendSrv.ts @@ -143,17 +143,17 @@ export function isFetchError(e: unknown): e is FetchError { * @public */ export interface BackendSrv { - get(url: string, params?: any, requestId?: string): Promise; - delete(url: string, data?: any): Promise; - post(url: string, data?: any): Promise; - patch(url: string, data?: any): Promise; - put(url: string, data?: any): Promise; + get(url: string, params?: any, requestId?: string): Promise; + delete(url: string, data?: any): Promise; + post(url: string, data?: any): Promise; + patch(url: string, data?: any): Promise; + put(url: string, data?: any): Promise; /** * @deprecated Use the fetch function instead. If you prefer to work with a promise * wrap the Observable returned by fetch with the lastValueFrom function. */ - request(options: BackendSrvRequest): Promise; + request(options: BackendSrvRequest): Promise; /** * Special function used to communicate with datasources that will emit core diff --git a/packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts b/packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts index c86ee702e7c..f72460483f8 100644 --- a/packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts +++ b/packages/grafana-runtime/src/utils/PublicDashboardDataSource.test.ts @@ -1,9 +1,14 @@ import { of } from 'rxjs'; import { BackendSrv, BackendSrvRequest } from 'src/services'; -import { DataQueryRequest, DataSourceRef } from '@grafana/data'; +import { DataQueryRequest, DataSourceInstanceSettings, DataSourceRef } from '@grafana/data'; -import { PublicDashboardDataSource } from '../../../../public/app/features/dashboard/services/PublicDashboardDataSource'; +import { + PUBLIC_DATASOURCE, + PublicDashboardDataSource, +} from '../../../../public/app/features/dashboard/services/PublicDashboardDataSource'; + +import { DataSourceWithBackend } from './DataSourceWithBackend'; const mockDatasourceRequest = jest.fn(); @@ -28,7 +33,7 @@ describe('PublicDashboardDatasource', () => { mockDatasourceRequest.mockReset(); mockDatasourceRequest.mockReturnValue(Promise.resolve({})); - const ds = new PublicDashboardDataSource(); + const ds = new PublicDashboardDataSource('public'); const panelId = 1; const publicDashboardAccessToken = 'abc123'; @@ -47,4 +52,27 @@ describe('PublicDashboardDatasource', () => { `/api/public/dashboards/${publicDashboardAccessToken}/panels/${panelId}/query` ); }); + + test('returns public datasource uid when datasource passed in is null', () => { + let ds = new PublicDashboardDataSource(null); + expect(ds.uid).toBe(PUBLIC_DATASOURCE); + }); + + test('returns datasource when datasource passed in is a string', () => { + let ds = new PublicDashboardDataSource('theDatasourceUid'); + expect(ds.uid).toBe('theDatasourceUid'); + }); + + test('returns datasource uid when datasource passed in is a DataSourceRef implementation', () => { + const datasource = { type: 'datasource', uid: 'abc123' }; + let ds = new PublicDashboardDataSource(datasource); + expect(ds.uid).toBe('abc123'); + }); + + test('returns datasource uid when datasource passed in is a DatasourceApi instance', () => { + const settings: DataSourceInstanceSettings = { id: 1, uid: 'abc123' } as DataSourceInstanceSettings; + const datasource = new DataSourceWithBackend(settings); + let ds = new PublicDashboardDataSource(datasource); + expect(ds.uid).toBe('abc123'); + }); }); diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci-e2e/scripts/deploy.sh b/packages/grafana-toolkit/docker/grafana-plugin-ci-e2e/scripts/deploy.sh index e5120dc6eaa..b4aa39c685c 100755 --- a/packages/grafana-toolkit/docker/grafana-plugin-ci-e2e/scripts/deploy.sh +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci-e2e/scripts/deploy.sh @@ -41,7 +41,7 @@ chmod 755 /usr/local/bin/golangci-lint # Install code climate get_file "https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64" \ "/usr/local/bin/cc-test-reporter" \ - "9fbe34cd207924d8f51ac0f3ffa690df4169e055cd7134a2370f6650f5b356e1" + "9e6c3e628b1258aefbc6e3550438a691abb0d1924d63c038881031d3d343c7d0" chmod 755 /usr/local/bin/cc-test-reporter wget -O /usr/local/bin/grabpl "https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl" diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index 2479687518a..bce87b5a739 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -9,6 +9,7 @@ import { IconName } from '../../types/icon'; import { ComponentSize } from '../../types/size'; import { getPropertiesForButtonSize } from '../Forms/commonStyles'; import { Icon } from '../Icon/Icon'; +import { PopoverContent, Tooltip, TooltipPlacement } from '../Tooltip'; export type ButtonVariant = 'primary' | 'secondary' | 'destructive'; export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'destructive']; @@ -24,6 +25,10 @@ type CommonProps = { children?: React.ReactNode; fullWidth?: boolean; type?: string; + /** Tooltip content to display on hover */ + tooltip?: PopoverContent; + /** Position of the tooltip */ + tooltipPlacement?: TooltipPlacement; }; export type ButtonProps = CommonProps & ButtonHTMLAttributes; @@ -79,6 +84,8 @@ export const LinkButton = React.forwardRef( onBlur, onFocus, disabled, + tooltip, + tooltipPlacement, ...otherProps }, ref @@ -103,12 +110,22 @@ export const LinkButton = React.forwardRef( className ); - return ( + const button = ( {icon && } {children && {children}} ); + + if (tooltip) { + return ( + + {button} + + ); + } + + return button; } ); diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx index 9524e497cad..bf91200c338 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import React from 'react'; +import React, { CSSProperties } from 'react'; import { LinkModel } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -12,6 +12,7 @@ import { MenuItem } from '../Menu/MenuItem'; interface DataLinksContextMenuProps { children: (props: DataLinksContextMenuApi) => JSX.Element; links: () => LinkModel[]; + style?: CSSProperties; } export interface DataLinksContextMenuApi { @@ -19,7 +20,7 @@ export interface DataLinksContextMenuApi { targetClassName?: string; } -export const DataLinksContextMenu: React.FC = ({ children, links }) => { +export const DataLinksContextMenu: React.FC = ({ children, links, style }) => { const itemsGroup: MenuItemsGroup[] = [{ items: linkModelToContextMenuItems(links), label: 'Data links' }]; const linksCounter = itemsGroup[0].items.length; const renderMenuGroupItems = () => { @@ -61,7 +62,7 @@ export const DataLinksContextMenu: React.FC = ({ chil onClick={linkModel.onClick} target={linkModel.target} title={linkModel.title} - style={{ display: 'flex', width: '100%' }} + style={style} aria-label={selectors.components.DataLinksContextMenu.singleLink} > {children({})} diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx index b4985badef9..5accaec58c6 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx @@ -118,7 +118,7 @@ export function UnthemedTimeRangePicker(props: TimeRangePickerProps): ReactEleme {isOpen && ( - +

+ +# Plugin Signature Badge + +The Plugin Signature Badge can be found in each information plugin card of the configuration plugins panel (Grafana menu > Configuration > Plugins). +This is a security measure to make sure plugins haven't been tampered with. Upon loading, Grafana checks to see the plugin signature status. + +## Badges: + +1.- Core: this badge indicates that the plugin has been built into Grafana. + + + + + +2.- Signed: the plugin's signature was successfully verified. + + + + + +3.- Invalid signature: this plugin has a invalid signature. + + + + + +4.- Modified signature: this plugin has changed since it was signed. This may indicate malicious intent. + + + + + +5.- Unsigned: the plugin is not signed. In this case, as upon loading Grafana checks the plugins signature, Grafana will not load or start it. Furthermore, it will write an error message to the server log. + + + + + + diff --git a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx index 0f35ecf1404..ed1d7d46c7a 100644 --- a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx +++ b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx @@ -6,6 +6,8 @@ import { PluginSignatureBadge } from '@grafana/ui'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import mdx from './PluginSignatureBadge.mdx'; + export default { title: 'Data Display/PluginSignatureBadge', decorators: [withCenteredStory], @@ -24,6 +26,11 @@ export default { ], }, }, + parameters: { + docs: { + page: mdx, + }, + }, }; export const Basic: Story = (args) => { diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.mdx b/packages/grafana-ui/src/components/SecretInput/SecretInput.mdx new file mode 100644 index 00000000000..565b879e946 --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.mdx @@ -0,0 +1,28 @@ +import { Props } from '@storybook/addon-docs/blocks'; +import { SecretInput } from './SecretInput'; + +# Secret Input + +Used for secret/password input. It has 2 states: **_configured_** and **_not configured_**. + +- If configured it will disable the input and add a reset button that will + clear the input and make it accessible. +- In non configured state it behaves like a normal password input. + +This is used for passwords or anything that is encrypted on the server and is +later returned encrypted to the user (like datasource passwords). + +### Usage + +```tsx +import {SecretInput} from '@grafana/ui'; + + +``` + + diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx index e19028188fc..57d94a2d70c 100644 --- a/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx @@ -4,12 +4,16 @@ import React, { useState, ChangeEvent } from 'react'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { SecretInput, Props } from './SecretInput'; +import mdx from './SecretInput.mdx'; -export default { +const meta: Meta = { title: 'Forms/SecretInput', component: SecretInput, decorators: [withCenteredStory], parameters: { + docs: { + page: mdx, + }, controls: { exclude: [ 'prefix', @@ -32,7 +36,9 @@ export default { argTypes: { width: { control: { type: 'range', min: 10, max: 200, step: 10 } }, }, -} as Meta; +}; + +export default meta; const Template: Story = (args) => { const [secret, setSecret] = useState(''); diff --git a/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx b/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx index 842a09ef47f..4e1c61f858e 100644 --- a/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx +++ b/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx @@ -50,7 +50,7 @@ export const BarGaugeCell: FC = (props) => { return field.getLinks({ valueRowIndex: row.index }); }; - const hasLinks = !!getLinks().length; + const hasLinks = Boolean(getLinks().length); const renderComponent = (menuProps: DataLinksContextMenuApi) => { const { openMenu, targetClassName } = menuProps; @@ -76,7 +76,11 @@ export const BarGaugeCell: FC = (props) => { return (
- {hasLinks && {(api) => renderComponent(api)}} + {hasLinks && ( + + {(api) => renderComponent(api)} + + )} {!hasLinks && ( = (props) => { const displayValue = field.display!(cell.value); - const hasLinks = getCellLinks(field, row)?.length; + const hasLinks = Boolean(getCellLinks(field, row)?.length); return (
diff --git a/packages/grafana-ui/src/components/Table/JSONViewCell.tsx b/packages/grafana-ui/src/components/Table/JSONViewCell.tsx index 029dd2abd2d..70c73fce64e 100644 --- a/packages/grafana-ui/src/components/Table/JSONViewCell.tsx +++ b/packages/grafana-ui/src/components/Table/JSONViewCell.tsx @@ -27,7 +27,7 @@ export function JSONViewCell(props: TableCellProps): JSX.Element { displayValue = JSON.stringify(value, null, ' '); } - const hasLinks = getCellLinks(field, row)?.length; + const hasLinks = Boolean(getCellLinks(field, row)?.length); return (
diff --git a/packages/grafana-ui/src/components/Table/styles.ts b/packages/grafana-ui/src/components/Table/styles.ts index 7bb9d5b5b21..bc7261316c2 100644 --- a/packages/grafana-ui/src/components/Table/styles.ts +++ b/packages/grafana-ui/src/components/Table/styles.ts @@ -165,7 +165,6 @@ export const getTableStyles = (theme: GrafanaTheme2) => { imageCellLink: css` cursor: pointer; overflow: hidden; - width: 100%; height: 100%; `, headerFilter: css` diff --git a/packages/grafana-ui/src/components/Tabs/VerticalTab.tsx b/packages/grafana-ui/src/components/Tabs/VerticalTab.tsx new file mode 100644 index 00000000000..c1fdeccff2b --- /dev/null +++ b/packages/grafana-ui/src/components/Tabs/VerticalTab.tsx @@ -0,0 +1,96 @@ +import { css, cx } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { useStyles2 } from '../../themes/ThemeContext'; +import { Icon } from '../Icon/Icon'; + +import { Counter } from './Counter'; +import { TabProps } from './Tab'; + +export const VerticalTab = React.forwardRef( + ({ label, active, icon, counter, className, suffix: Suffix, onChangeTab, href, ...otherProps }, ref) => { + const tabsStyles = useStyles2(getTabStyles); + const content = () => ( + <> + {icon && } + {label} + {typeof counter === 'number' && } + {Suffix && } + + ); + + const linkClass = cx(tabsStyles.link, active && tabsStyles.activeStyle); + + return ( +
  • + + {content()} + +
  • + ); + } +); + +VerticalTab.displayName = 'Tab'; + +const getTabStyles = (theme: GrafanaTheme2) => { + return { + item: css` + list-style: none; + margin-right: ${theme.spacing(2)}; + position: relative; + display: block; + margin-bottom: 4px; + `, + link: css` + padding: 6px 12px; + display: block; + height: 100%; + cursor: pointer; + + color: ${theme.colors.text.primary}; + + svg { + margin-right: ${theme.spacing(1)}; + } + + &:hover, + &:focus { + text-decoration: underline; + } + `, + activeStyle: css` + label: activeTabStyle; + color: ${theme.colors.text.maxContrast}; + font-weight: 500; + overflow: hidden; + + &::before { + display: block; + content: ' '; + position: absolute; + left: 0; + width: 4px; + bottom: 0; + top: 0; + border-radius: 2px; + background-image: linear-gradient(0deg, #f05a28 30%, #fbca0a 99%); + } + `, + suffix: css` + margin-left: ${theme.spacing(1)}; + `, + }; +}; diff --git a/packages/grafana-ui/src/components/Tags/Tag.mdx b/packages/grafana-ui/src/components/Tags/Tag.mdx index d7a1dd79371..a635ef2c88f 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.mdx +++ b/packages/grafana-ui/src/components/Tags/Tag.mdx @@ -1,6 +1,8 @@ import { Story, Preview, Props } from '@storybook/addon-docs/blocks'; import { Tag } from './Tag'; + + # Tag Used for displaying metadata, for example to add more details to search results. Background and border colors are generated from the tag name. diff --git a/packages/grafana-ui/src/components/Tags/Tag.story.tsx b/packages/grafana-ui/src/components/Tags/Tag.story.tsx index 07222066547..5ddda1be408 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.story.tsx +++ b/packages/grafana-ui/src/components/Tags/Tag.story.tsx @@ -1,10 +1,10 @@ import { action } from '@storybook/addon-actions'; +import { Story } from '@storybook/react'; import React from 'react'; -import { Tag } from '@grafana/ui'; - import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Props as TagProps, Tag } from './Tag'; import mdx from './Tag.mdx'; export default { @@ -15,9 +15,28 @@ export default { docs: { page: mdx, }, + controls: { + exclude: ['onClick'], + }, + }, + args: { + name: 'Tag', + colorIndex: 0, + showIcon: false, }, }; -export const single = () => { - return ; +interface StoryProps extends TagProps { + showIcon?: boolean; +} + +export const Single: Story = (args) => { + return ( + + ); }; diff --git a/packages/grafana-ui/src/components/Tags/Tag.tsx b/packages/grafana-ui/src/components/Tags/Tag.tsx index eee65fa0cd8..362fc04a26d 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.tsx +++ b/packages/grafana-ui/src/components/Tags/Tag.tsx @@ -64,13 +64,15 @@ const getTagStyles = (theme: GrafanaTheme, name: string, colorIndex?: number) => font-weight: ${theme.typography.weight.semibold}; font-size: ${theme.typography.size.sm}; line-height: ${theme.typography.lineHeight.xs}; - vertical-align: baseline; background-color: ${colors.color}; color: ${theme.palette.gray98}; white-space: nowrap; text-shadow: none; padding: 3px 6px; border-radius: ${theme.border.radius.md}; + display: flex; + align-items: center; + gap: 3px; `, hover: css` &:hover { diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 721d749c863..e9dd8b85ada 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -79,6 +79,7 @@ export { TableCellDisplayMode, TableSortByFieldState } from './Table/types'; export { TableInputCSV } from './TableInputCSV/TableInputCSV'; export { TabsBar } from './Tabs/TabsBar'; export { Tab } from './Tabs/Tab'; +export { VerticalTab } from './Tabs/VerticalTab'; export { TabContent } from './Tabs/TabContent'; export { Counter } from './Tabs/Counter'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js index e584f83fcfe..f3768546bc7 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js @@ -16,6 +16,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { NONE, DURATION, TAG } from '../settings/SpanBarSettings'; + import SpanBarRow from './SpanBarRow'; describe('', () => { @@ -43,11 +45,10 @@ describe('', () => { showErrorIcon: false, getViewedBounds: () => ({ start: 0, end: 1 }), span: { - duration: 'test-duration', + duration: 9000, hasChildren: true, process: { serviceName: 'service-name', - tags: [], }, spanID, logs: [], @@ -181,4 +182,89 @@ describe('', () => { ); expect(screen.getAllByTestId('SpanLinksMenu')).toHaveLength(1); }); + + describe('render span bar label', () => { + it('with default value', () => { + render(); + expect(screen.getByText('(9ms)')).toBeInTheDocument(); + }); + + it('with none value', () => { + const testProps = Object.assign( + { + spanBarOptions: { + type: NONE, + }, + }, + props + ); + render(); + expect(screen.queryByText('(9ms)')).not.toBeInTheDocument(); + }); + + it('with duration value', () => { + const testProps = Object.assign( + { + spanBarOptions: { + type: DURATION, + }, + }, + props + ); + render(); + expect(screen.getByText('(9ms)')).toBeInTheDocument(); + }); + + it('with tag value', () => { + const testProps = Object.assign( + { + spanBarOptions: { + type: TAG, + tag: 'tag', + }, + }, + { + ...props, + span: { + process: {}, + tags: [ + { + key: 'tag', + value: 'tag-value', + }, + ], + }, + } + ); + render(); + expect(screen.getByText('(tag-value)')).toBeInTheDocument(); + }); + + it('with process value', () => { + let testProps = Object.assign( + { + spanBarOptions: { + type: TAG, + tag: 'tag', + }, + }, + { + ...props, + span: { + process: { + tags: [ + { + key: 'tag', + value: 'process-value', + }, + ], + }, + tags: [], + }, + } + ); + render(); + expect(screen.getByText('(process-value)')).toBeInTheDocument(); + }); + }); }); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx index 7ace6c3f33d..8d6ee2d9808 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx @@ -18,11 +18,12 @@ import * as React from 'react'; import IoAlert from 'react-icons/lib/io/alert'; import IoArrowRightA from 'react-icons/lib/io/arrow-right-a'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, TraceKeyValuePair } from '@grafana/data'; import { stylesFactory, withTheme2 } from '@grafana/ui'; import { autoColor } from '../Theme'; -import { SpanLinkFunc, TNil } from '../types'; +import { DURATION, NONE, TAG } from '../settings/SpanBarSettings'; +import { SpanBarOptions, SpanLinkFunc, TNil } from '../types'; import { SpanLinks } from '../types/links'; import { TraceSpan } from '../types/trace'; @@ -290,6 +291,7 @@ type SpanBarRowProps = { className?: string; theme: GrafanaTheme2; color: string; + spanBarOptions: SpanBarOptions | undefined; columnDivision: number; isChildrenExpanded: boolean; isDetailExpanded: boolean; @@ -352,6 +354,7 @@ export class UnthemedSpanBarRow extends React.PureComponent { const { className, color, + spanBarOptions, columnDivision, isChildrenExpanded, isDetailExpanded, @@ -379,6 +382,7 @@ export class UnthemedSpanBarRow extends React.PureComponent { process: { serviceName }, } = span; const label = formatDuration(duration); + const viewBounds = getViewedBounds(span.startTime, span.startTime + span.duration); const viewStart = viewBounds.start; const viewEnd = viewBounds.end; @@ -473,7 +477,7 @@ export class UnthemedSpanBarRow extends React.PureComponent { )} {rpc ? rpc.operationName : operationName} - | {label} + {this.getSpanBarLabel(span, spanBarOptions, label)} {createSpanLink && (() => { @@ -542,6 +546,35 @@ export class UnthemedSpanBarRow extends React.PureComponent { ); } + + getSpanBarLabel = (span: TraceSpan, spanBarOptions: SpanBarOptions | undefined, duration: string) => { + const type = spanBarOptions?.type ?? ''; + + if (type === NONE) { + return ''; + } else if (type === '' || type === DURATION) { + return `(${duration})`; + } else if (type === TAG) { + const tagKey = spanBarOptions?.tag?.trim() ?? ''; + if (tagKey !== '' && span.tags) { + const tag = span.tags?.find((tag: TraceKeyValuePair) => { + return tag.key === tagKey; + }); + const process = span.process?.tags?.find((process: TraceKeyValuePair) => { + return process.key === tagKey; + }); + + if (tag) { + return `(${tag.value})`; + } + if (process) { + return `(${process.value})`; + } + } + } + + return ''; + }; } export default withTheme2(UnthemedSpanBarRow); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js index 947a805b633..592af162af1 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js @@ -56,7 +56,7 @@ describe('AccordianLogs tests', () => { it('shows the number of log entries', () => { setup(); - expect(screen.getByRole('switch', { name: 'Logs (2)' })).toBeInTheDocument(); + expect(screen.getByRole('switch', { name: 'Events (2)' })).toBeInTheDocument(); }); it('hides log entries when not expanded', () => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx index 268f617802b..065fda8cceb 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx @@ -90,7 +90,7 @@ export default function AccordianLogs(props: AccordianLogsProps) { return (
    - {arrow} Logs ({logs.length}) + {arrow} Events ({logs.length}) {isOpen && (
    diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js index e13a3550f97..b9423169915 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js @@ -146,14 +146,14 @@ describe('', () => { }); it('renders the span tags', () => { - const target = ; + const target = ; expect(wrapper.containsMatchingElement(target)).toBe(true); wrapper.find({ data: span.tags }).simulate('toggle'); expect(props.tagsToggle).toHaveBeenLastCalledWith(span.spanID); }); it('renders the process tags', () => { - const target = ; + const target = ; expect(wrapper.containsMatchingElement(target)).toBe(true); wrapper.find({ data: span.process.tags }).simulate('toggle'); expect(props.processToggle).toHaveBeenLastCalledWith(span.spanID); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx index c337bf45a7d..5bb0ceacf17 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx @@ -195,7 +195,7 @@ export default function SpanDetail(props: SpanDetailProps) { const focusSpanLink = createFocusSpanLink(traceID, spanID); return ( -
    +

    {operationName}

    @@ -213,7 +213,7 @@ export default function SpanDetail(props: SpanDetailProps) {
    tagsToggle(spanID)} @@ -222,7 +222,7 @@ export default function SpanDetail(props: SpanDetailProps) { processToggle(spanID)} diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js index c06156dc950..282ea1851f0 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js @@ -12,89 +12,69 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { createTheme } from '@grafana/data'; -import SpanDetail from './SpanDetail'; import DetailState from './SpanDetail/DetailState'; import { UnthemedSpanDetailRow } from './SpanDetailRow'; -import SpanTreeOffset from './SpanTreeOffset'; -jest.mock('./SpanTreeOffset'); - -describe('', () => { - const spanID = 'some-id'; +const testSpan = { + spanID: 'testSpanID', + traceID: 'testTraceID', + depth: 3, + process: { + serviceName: 'some-service', + tags: [{ key: 'tag-key', value: 'tag-value' }], + }, +}; +const setup = (propOverrides) => { const props = { color: 'some-color', columnDivision: 0.5, detailState: new DetailState(), onDetailToggled: jest.fn(), - linksGetter: jest.fn(), isFilteredOut: false, logItemToggle: jest.fn(), logsToggle: jest.fn(), processToggle: jest.fn(), - span: { spanID, depth: 3 }, + createFocusSpanLink: jest.fn(), + hoverIndentGuideIds: new Map(), + span: testSpan, tagsToggle: jest.fn(), traceStartTime: 1000, theme: createTheme(), + ...propOverrides, }; + return render(); +}; - let wrapper; - - beforeEach(() => { - props.onDetailToggled.mockReset(); - props.linksGetter.mockReset(); - props.logItemToggle.mockReset(); - props.logsToggle.mockReset(); - props.processToggle.mockReset(); - props.tagsToggle.mockReset(); - wrapper = shallow(); - }); - +describe('SpanDetailRow tests', () => { it('renders without exploding', () => { - expect(wrapper).toBeDefined(); + expect(() => setup()).not.toThrow(); }); - it('escalates toggle detail', () => { - const calls = props.onDetailToggled.mock.calls; - expect(calls.length).toBe(0); - wrapper.find('[data-test-id="detail-row-expanded-accent"]').prop('onClick')(); - expect(calls).toEqual([[spanID]]); + it('calls toggle on click', async () => { + const mockToggle = jest.fn(); + setup({ onDetailToggled: mockToggle }); + expect(mockToggle).not.toHaveBeenCalled(); + + const detailRow = screen.getByTestId('detail-row-expanded-accent'); + await userEvent.click(detailRow); + expect(mockToggle).toHaveBeenCalled(); }); it('renders the span tree offset', () => { - const spanTreeOffset = ; - expect(wrapper.contains(spanTreeOffset)).toBe(true); + setup(); + + expect(screen.getByTestId('SpanTreeOffset--indentGuide')).toBeInTheDocument(); }); it('renders the SpanDetail', () => { - const spanDetail = ( - - ); - expect(wrapper.contains(spanDetail)).toBe(true); - }); + setup(); - it('adds span when calling linksGetter', () => { - const spanDetail = wrapper.find(SpanDetail); - const linksGetter = spanDetail.prop('linksGetter'); - const tags = [{ key: 'myKey', value: 'myValue' }]; - const linksGetterResponse = {}; - props.linksGetter.mockReturnValueOnce(linksGetterResponse); - const result = linksGetter(tags, 0); - expect(result).toBe(linksGetterResponse); - expect(props.linksGetter).toHaveBeenCalledTimes(1); - expect(props.linksGetter).toHaveBeenCalledWith(props.span, tags, 0); + expect(screen.getByTestId('span-detail-component')).toBeInTheDocument(); }); }); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx index 8bd0944a9e3..16c34dcb12d 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx @@ -150,7 +150,7 @@ export class UnthemedSpanDetailRow extends React.PureComponent diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx index 728af808524..b8ebd36c4d5 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -23,7 +23,7 @@ import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui'; import { Accessors } from '../ScrollManager'; import { PEER_SERVICE } from '../constants/tag-keys'; -import { SpanLinkFunc, TNil } from '../types'; +import { SpanBarOptions, SpanLinkFunc, TNil } from '../types'; import TTraceTimeline from '../types/TTraceTimeline'; import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace'; import { getColorByKey } from '../utils/color-generator'; @@ -89,6 +89,7 @@ type TVirtualizedTraceViewOwnProps = { scrollToFirstVisibleSpan: () => void; registerAccessors: (accesors: Accessors) => void; trace: Trace; + spanBarOptions: SpanBarOptions | undefined; linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[]; childrenToggle: (spanID: string) => void; clearShouldScrollToFirstUiFindMatch: () => void; @@ -387,6 +388,7 @@ export class UnthemedVirtualizedTraceView extends React.Component void; traceTimeline: TTraceTimeline; trace: Trace; + spanBarOptions: SpanBarOptions | undefined; updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; updateViewRangeTime: TUpdateViewRangeTimeFunction; viewRange: ViewRange; diff --git a/packages/jaeger-ui-components/src/index.ts b/packages/jaeger-ui-components/src/index.ts index f5737eff1f5..bf4885b2b08 100644 --- a/packages/jaeger-ui-components/src/index.ts +++ b/packages/jaeger-ui-components/src/index.ts @@ -1,5 +1,6 @@ export { default as TraceTimelineViewer } from './TraceTimelineViewer'; export { default as TracePageHeader } from './TracePageHeader'; +export { default as SpanBarSettings } from './settings/SpanBarSettings'; export * from './types'; export * from './TraceTimelineViewer/types'; export { default as DetailState } from './TraceTimelineViewer/SpanDetail/DetailState'; diff --git a/packages/jaeger-ui-components/src/settings/SpanBarSettings.tsx b/packages/jaeger-ui-components/src/settings/SpanBarSettings.tsx new file mode 100644 index 00000000000..1de0c0e006d --- /dev/null +++ b/packages/jaeger-ui-components/src/settings/SpanBarSettings.tsx @@ -0,0 +1,90 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { + DataSourceJsonData, + DataSourcePluginOptionsEditorProps, + GrafanaTheme, + toOption, + updateDatasourcePluginJsonDataOption, +} from '@grafana/data'; +import { InlineField, InlineFieldRow, Input, Select, useStyles } from '@grafana/ui'; + +export interface SpanBarOptions { + type?: string; + tag?: string; +} + +export interface SpanBarOptionsData extends DataSourceJsonData { + spanBar?: SpanBarOptions; +} + +export const NONE = 'None'; +export const DURATION = 'Duration'; +export const TAG = 'Tag'; + +interface Props extends DataSourcePluginOptionsEditorProps {} + +export default function SpanBarSettings({ options, onOptionsChange }: Props) { + const styles = useStyles(getStyles); + const selectOptions = [NONE, DURATION, TAG].map(toOption); + + return ( +
    +

    Span bar label

    + +
    Span bar label lets you add additional info to the span bar row.
    + + + + + updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'spanBar', { + ...options.jsonData.spanBar, + tag: v.currentTarget.value, + }) + } + value={options.jsonData.spanBar?.tag || ''} + width={25} + /> + + + )} +
    + ); +} + +const getStyles = (theme: GrafanaTheme) => ({ + infoText: css` + label: infoText; + padding-bottom: ${theme.spacing.md}; + color: ${theme.colors.textSemiWeak}; + `, + + row: css` + label: row; + align-items: baseline; + `, +}); diff --git a/packages/jaeger-ui-components/src/types/index.tsx b/packages/jaeger-ui-components/src/types/index.tsx index 9f1aa8d9115..a491bb9df8b 100644 --- a/packages/jaeger-ui-components/src/types/index.tsx +++ b/packages/jaeger-ui-components/src/types/index.tsx @@ -13,6 +13,7 @@ // limitations under the License. export { TraceSpan, TraceResponse, Trace, TraceProcess, TraceKeyValuePair, TraceLink } from './trace'; -export { SpanLinkFunc, SpanLinkDef } from './links'; +export { SpanBarOptions, SpanBarOptionsData } from '../settings/SpanBarSettings'; export { default as TTraceTimeline } from './TTraceTimeline'; export { default as TNil } from './TNil'; +export { SpanLinkFunc, SpanLinkDef } from './links'; diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index b9efa36aeea..2b88967dec7 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -35,13 +35,13 @@ RUN apk add --no-cache openssl --repository=http://dl-cdn.alpinelinux.org/alpine RUN if [ `arch` = "x86_64" ]; then \ apk add --no-cache libaio libnsl && \ ln -s /usr/lib/libnsl.so.2 /usr/lib/libnsl.so.1 && \ - wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.30-r0/glibc-2.30-r0.apk \ - -O /tmp/glibc-2.30-r0.apk && \ - wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.30-r0/glibc-bin-2.30-r0.apk \ - -O /tmp/glibc-bin-2.30-r0.apk && \ - apk add --no-cache --allow-untrusted /tmp/glibc-2.30-r0.apk /tmp/glibc-bin-2.30-r0.apk && \ - rm -f /tmp/glibc-2.30-r0.apk && \ - rm -f /tmp/glibc-bin-2.30-r0.apk && \ + wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.35-r0/glibc-2.35-r0.apk \ + -O /tmp/glibc-2.35-r0.apk && \ + wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.35-r0/glibc-bin-2.35-r0.apk \ + -O /tmp/glibc-bin-2.35-r0.apk && \ + apk add --no-cache --allow-untrusted /tmp/glibc-2.35-r0.apk /tmp/glibc-bin-2.35-r0.apk && \ + rm -f /tmp/glibc-2.35-r0.apk && \ + rm -f /tmp/glibc-bin-2.35-r0.apk && \ rm -f /lib/ld-linux-x86-64.so.2 && \ rm -f /etc/ld.so.cache; \ fi diff --git a/packaging/docker/README.md b/packaging/docker/README.md index ea592592fce..d2b1a889262 100644 --- a/packaging/docker/README.md +++ b/packaging/docker/README.md @@ -16,6 +16,10 @@ Further documentation can be found at http://docs.grafana.org/installation/docke ## Changelog +### v9.0.3 + +- Upgraded glibc version to glibc [2.35](https://sourceware.org/pipermail/libc-alpha/2022-February/136040.html) [#51107](https://github.com/grafana/grafana/pull/51107/files) + ### v8.3.0-beta2 - Our Alpine based images have been upgraded to Alpine [3.14.3](https://alpinelinux.org/posts/Alpine-3.14.3-released.html) [#41922](https://github.com/grafana/grafana/pull/41922) [@hairyhenderson](https://github.com/hairyhenderson) diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index f834d307068..b750ab71410 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -223,11 +223,12 @@ func (hs *HTTPServer) declareFixedRoles() error { Role: ac.RoleDTO{ Name: "fixed:teams:creator", DisplayName: "Team creator", - Description: "Create teams and read organisation users (required to manage the created teams).", + Description: "Create teams and read organisation users and service accounts (required to manage the created teams).", Group: "Teams", Permissions: []ac.Permission{ {Action: ac.ActionTeamsCreate}, {Action: ac.ActionOrgUsersRead, Scope: ac.ScopeUsersAll}, + {Action: serviceaccounts.ActionRead, Scope: serviceaccounts.ScopeAll}, }, }, Grants: teamCreatorGrants, diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 492b87af29b..894f4382ead 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -53,7 +53,7 @@ func (hs *HTTPServer) GetAnnotations(c *models.ReqContext) response.Response { item.DashboardUID = val } else { query := models.GetDashboardQuery{Id: item.DashboardId, OrgId: c.OrgId} - err := hs.dashboardService.GetDashboard(c.Req.Context(), &query) + err := hs.DashboardService.GetDashboard(c.Req.Context(), &query) if err == nil && query.Result != nil { item.DashboardUID = &query.Result.Uid dashboardCache[item.DashboardId] = &query.Result.Uid @@ -82,7 +82,7 @@ func (hs *HTTPServer) PostAnnotation(c *models.ReqContext) response.Response { // overwrite dashboardId when dashboardUID is not empty if cmd.DashboardUID != "" { query := models.GetDashboardQuery{OrgId: c.OrgId, Uid: cmd.DashboardUID} - err := hs.dashboardService.GetDashboard(c.Req.Context(), &query) + err := hs.DashboardService.GetDashboard(c.Req.Context(), &query) if err == nil { cmd.DashboardId = query.Result.Id } @@ -291,7 +291,7 @@ func (hs *HTTPServer) MassDeleteAnnotations(c *models.ReqContext) response.Respo if cmd.DashboardUID != "" { query := models.GetDashboardQuery{OrgId: c.OrgId, Uid: cmd.DashboardUID} - err := hs.dashboardService.GetDashboard(c.Req.Context(), &query) + err := hs.DashboardService.GetDashboard(c.Req.Context(), &query) if err == nil { cmd.DashboardId = query.Result.Id } diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 8b97423c59d..c2bbd3baaeb 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -343,7 +343,7 @@ func postAnnotationScenario(t *testing.T, desc string, url string, routePattern t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { hs := setupSimpleHTTPServer(nil) hs.SQLStore = store - hs.dashboardService = dashSvc + hs.DashboardService = dashSvc sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { @@ -428,7 +428,7 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { hs := setupSimpleHTTPServer(nil) hs.SQLStore = store - hs.dashboardService = dashSvc + hs.DashboardService = dashSvc sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { diff --git a/pkg/api/api.go b/pkg/api/api.go index 88774b8ceba..a99d3af59e9 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api" "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/web" ) @@ -28,7 +29,7 @@ func (hs *HTTPServer) registerRoutes() { reqGrafanaAdmin := middleware.ReqGrafanaAdmin reqEditorRole := middleware.ReqEditorRole reqOrgAdmin := middleware.ReqOrgAdmin - reqOrgAdminFolderAdminOrTeamAdmin := middleware.OrgAdminFolderAdminOrTeamAdmin(hs.SQLStore, hs.dashboardService) + reqOrgAdminDashOrFolderAdminOrTeamAdmin := middleware.OrgAdminDashOrFolderAdminOrTeamAdmin(hs.SQLStore, hs.DashboardService) reqCanAccessTeams := middleware.AdminOrEditorAndFeatureEnabled(hs.Cfg.EditorsCanAdmin) reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg) redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg) @@ -75,6 +76,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/admin/orgs", authorizeInOrg(reqGrafanaAdmin, ac.UseGlobalOrg, orgsAccessEvaluator), hs.Index) r.Get("/admin/orgs/edit/:id", authorizeInOrg(reqGrafanaAdmin, ac.UseGlobalOrg, orgsAccessEvaluator), hs.Index) r.Get("/admin/stats", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionServerStatsRead)), hs.Index) + r.Get("/admin/storage/*", reqGrafanaAdmin, hs.Index) r.Get("/admin/ldap", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)), hs.Index) r.Get("/styleguide", reqSignedIn, hs.Index) @@ -102,6 +104,10 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/dashboards/*", reqSignedIn, hs.Index) r.Get("/goto/:uid", reqSignedIn, hs.redirectFromShortURL, hs.Index) + if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) { + r.Get("/public-dashboards/:accessToken", publicdashboardsapi.SetPublicDashboardFlag(), hs.Index) + } + r.Get("/explore", authorize(func(c *models.ReqContext) { if f, ok := reqSignedIn.(func(c *models.ReqContext)); ok { f(c) @@ -261,7 +267,7 @@ func (hs *HTTPServer) registerRoutes() { ac.EvalPermission(dashboards.ActionDashboardsPermissionsWrite), ) } - orgRoute.Get("/users/lookup", authorize(reqOrgAdminFolderAdminOrTeamAdmin, lookupEvaluator()), routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup)) + orgRoute.Get("/users/lookup", authorize(reqOrgAdminDashOrFolderAdminOrTeamAdmin, lookupEvaluator()), routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup)) }) // create new org @@ -332,10 +338,12 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Any("/plugins/:pluginId/resources/*", hs.CallResource) apiRoute.Get("/plugins/errors", routing.Wrap(hs.GetPluginErrorsList)) - apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) { - pluginRoute.Post("/:pluginId/install", routing.Wrap(hs.InstallPlugin)) - pluginRoute.Post("/:pluginId/uninstall", routing.Wrap(hs.UninstallPlugin)) - }, reqGrafanaAdmin) + if hs.Cfg.PluginAdminEnabled && !hs.Cfg.PluginAdminExternalManageEnabled { + apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) { + pluginRoute.Post("/:pluginId/install", routing.Wrap(hs.InstallPlugin)) + pluginRoute.Post("/:pluginId/uninstall", routing.Wrap(hs.UninstallPlugin)) + }, reqGrafanaAdmin) + } apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) { pluginRoute.Get("/:pluginId/dashboards/", routing.Wrap(hs.GetPluginDashboards)) @@ -393,11 +401,6 @@ func (hs *HTTPServer) registerRoutes() { }) dashboardRoute.Group("/uid/:uid", func(dashUidRoute routing.RouteRegister) { - if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) { - dashUidRoute.Get("/public-config", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.GetPublicDashboardConfig)) - dashUidRoute.Post("/public-config", authorize(reqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.SavePublicDashboardConfig)) - } - if hs.ThumbService != nil { dashUidRoute.Get("/img/:kind/:theme", hs.ThumbService.GetImage) if hs.Features.IsEnabled(featuremgmt.FlagDashboardPreviewsAdmin) { @@ -600,7 +603,7 @@ func (hs *HTTPServer) registerRoutes() { // grafana.net proxy r.Any("/api/gnet/*", reqSignedIn, hs.ProxyGnetRequest) - // Gravatar service. + // Gravatar service r.Get("/avatar/:hash", hs.AvatarCacheServer.Handler) // Snapshots @@ -610,13 +613,6 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, routing.Wrap(hs.DeleteDashboardSnapshotByDeleteKey)) r.Delete("/api/snapshots/:key", reqEditorRole, routing.Wrap(hs.DeleteDashboardSnapshot)) - // Public API - if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) { - r.Get("/public-dashboards/:accessToken", middleware.SetPublicDashboardFlag(), hs.Index) - r.Get("/api/public/dashboards/:accessToken", routing.Wrap(hs.GetPublicDashboard)) - r.Post("/api/public/dashboards/:accessToken/panels/:panelId/query", routing.Wrap(hs.QueryPublicDashboard)) - } - // Frontend logs sourceMapStore := frontendlogging.NewSourceMapStore(hs.Cfg, hs.pluginStaticRouteResolver, frontendlogging.ReadSourceMapFromFS) r.Post("/log", middleware.RateLimit(hs.Cfg.Sentry.EndpointRPS, hs.Cfg.Sentry.EndpointBurst, time.Now), diff --git a/pkg/api/apierrors/dashboard.go b/pkg/api/apierrors/dashboard.go index a4003646143..5ce72dabeab 100644 --- a/pkg/api/apierrors/dashboard.go +++ b/pkg/api/apierrors/dashboard.go @@ -7,15 +7,15 @@ import ( "net/http" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/util" ) // ToDashboardErrorResponse returns a different response status according to the dashboard error type func ToDashboardErrorResponse(ctx context.Context, pluginStore plugins.Store, err error) response.Response { - var dashboardErr models.DashboardErr + var dashboardErr dashboards.DashboardErr if ok := errors.As(err, &dashboardErr); ok { if body := dashboardErr.Body(); body != nil { return response.JSON(dashboardErr.StatusCode, body) @@ -26,7 +26,7 @@ func ToDashboardErrorResponse(ctx context.Context, pluginStore plugins.Store, er return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), nil) } - if errors.Is(err, models.ErrFolderNotFound) { + if errors.Is(err, dashboards.ErrFolderNotFound) { return response.Error(http.StatusBadRequest, err.Error(), nil) } @@ -35,7 +35,7 @@ func ToDashboardErrorResponse(ctx context.Context, pluginStore plugins.Store, er return response.Error(http.StatusUnprocessableEntity, validationErr.Error(), err) } - var pluginErr models.UpdatePluginDashboardError + var pluginErr dashboards.UpdatePluginDashboardError if ok := errors.As(err, &pluginErr); ok { message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId) // look up plugin name diff --git a/pkg/api/apierrors/folder.go b/pkg/api/apierrors/folder.go index da6fd4fa124..d6b8e84fc77 100644 --- a/pkg/api/apierrors/folder.go +++ b/pkg/api/apierrors/folder.go @@ -4,40 +4,40 @@ import ( "errors" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/util" ) // ToFolderErrorResponse returns a different response status according to the folder error type func ToFolderErrorResponse(err error) response.Response { - var dashboardErr models.DashboardErr + var dashboardErr dashboards.DashboardErr if ok := errors.As(err, &dashboardErr); ok { return response.Error(dashboardErr.StatusCode, err.Error(), err) } - if errors.Is(err, models.ErrFolderTitleEmpty) || - errors.Is(err, models.ErrDashboardTypeMismatch) || - errors.Is(err, models.ErrDashboardInvalidUid) || - errors.Is(err, models.ErrDashboardUidTooLong) || - errors.Is(err, models.ErrFolderContainsAlertRules) { + if errors.Is(err, dashboards.ErrFolderTitleEmpty) || + errors.Is(err, dashboards.ErrDashboardTypeMismatch) || + errors.Is(err, dashboards.ErrDashboardInvalidUid) || + errors.Is(err, dashboards.ErrDashboardUidTooLong) || + errors.Is(err, dashboards.ErrFolderContainsAlertRules) { return response.Error(400, err.Error(), nil) } - if errors.Is(err, models.ErrFolderAccessDenied) { + if errors.Is(err, dashboards.ErrFolderAccessDenied) { return response.Error(403, "Access denied", err) } - if errors.Is(err, models.ErrFolderNotFound) { - return response.JSON(404, util.DynMap{"status": "not-found", "message": models.ErrFolderNotFound.Error()}) + if errors.Is(err, dashboards.ErrFolderNotFound) { + return response.JSON(404, util.DynMap{"status": "not-found", "message": dashboards.ErrFolderNotFound.Error()}) } - if errors.Is(err, models.ErrFolderSameNameExists) || - errors.Is(err, models.ErrFolderWithSameUIDExists) { + if errors.Is(err, dashboards.ErrFolderSameNameExists) || + errors.Is(err, dashboards.ErrFolderWithSameUIDExists) { return response.Error(409, err.Error(), nil) } - if errors.Is(err, models.ErrFolderVersionMismatch) { - return response.JSON(412, util.DynMap{"status": "version-mismatch", "message": models.ErrFolderVersionMismatch.Error()}) + if errors.Is(err, dashboards.ErrFolderVersionMismatch) { + return response.JSON(412, util.DynMap{"status": "version-mismatch", "message": dashboards.ErrFolderVersionMismatch.Error()}) } return response.Error(500, "Folder API error", err) diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index e28a0758726..3111965b170 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -43,7 +43,6 @@ import ( "github.com/grafana/grafana/pkg/services/searchusers" "github.com/grafana/grafana/pkg/services/searchusers/filters" "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web/webtest" @@ -341,15 +340,6 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont return setupHTTPServerWithCfgDb(t, useFakeAccessControl, enableAccessControl, cfg, db, db, featuremgmt.WithFeatures()) } -func setupHTTPServerWithMockDb(t *testing.T, useFakeAccessControl, enableAccessControl bool, features *featuremgmt.FeatureManager) accessControlScenarioContext { - // Use a new conf - cfg := setting.NewCfg() - db := sqlstore.InitTestDB(t) - db.Cfg = setting.NewCfg() - - return setupHTTPServerWithCfgDb(t, useFakeAccessControl, enableAccessControl, cfg, db, mockstore.NewSQLStoreMock(), features) -} - func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessControl bool, cfg *setting.Cfg, db *sqlstore.SQLStore, store sqlstore.Store, features *featuremgmt.FeatureManager) accessControlScenarioContext { t.Helper() @@ -396,7 +386,7 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo AccessControl: ac, teamPermissionsService: teamPermissionService, searchUsersService: searchusers.ProvideUsersService(db, filters.ProvideOSSSearchUserFilter()), - dashboardService: dashboardservice.ProvideDashboardService( + DashboardService: dashboardservice.ProvideDashboardService( cfg, dashboardsStore, nil, features, accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), ac, ), diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 49d6f293f2a..95b88909641 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -144,8 +144,8 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response { // lookup folder title if dash.FolderId > 0 { query := models.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId} - if err := hs.dashboardService.GetDashboard(c.Req.Context(), &query); err != nil { - if errors.Is(err, models.ErrFolderNotFound) { + if err := hs.DashboardService.GetDashboard(c.Req.Context(), &query); err != nil { + if errors.Is(err, dashboards.ErrFolderNotFound) { return response.Error(404, "Folder not found", err) } return response.Error(500, "Dashboard folder could not be read", err) @@ -235,7 +235,7 @@ func (hs *HTTPServer) getDashboardHelper(ctx context.Context, orgID int64, id in query = models.GetDashboardQuery{Id: id, OrgId: orgID} } - if err := hs.dashboardService.GetDashboard(ctx, &query); err != nil { + if err := hs.DashboardService.GetDashboard(ctx, &query); err != nil { return nil, response.Error(404, "Dashboard not found", err) } @@ -262,11 +262,11 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response { hs.log.Error("Failed to disconnect library elements", "dashboard", dash.Id, "user", c.SignedInUser.UserId, "error", err) } - err = hs.dashboardService.DeleteDashboard(c.Req.Context(), dash.Id, c.OrgId) + err = hs.DashboardService.DeleteDashboard(c.Req.Context(), dash.Id, c.OrgId) if err != nil { - var dashboardErr models.DashboardErr + var dashboardErr dashboards.DashboardErr if ok := errors.As(err, &dashboardErr); ok { - if errors.Is(err, models.ErrDashboardCannotDeleteProvisionedDashboard) { + if errors.Is(err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) { return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err) } } @@ -333,7 +333,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa if cmd.FolderUid != "" { folder, err := hs.folderService.GetFolderByUID(ctx, c.SignedInUser, c.OrgId, cmd.FolderUid) if err != nil { - if errors.Is(err, models.ErrFolderNotFound) { + if errors.Is(err, dashboards.ErrFolderNotFound) { return response.Error(400, "Folder not found", err) } return response.Error(500, "Error while checking folder ID", err) @@ -362,7 +362,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa provisioningData = data } else if dash.Uid != "" { data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardUID(dash.OrgId, dash.Uid) - if err != nil && !errors.Is(err, models.ErrProvisionedDashboardNotFound) && !errors.Is(err, models.ErrDashboardNotFound) { + if err != nil && !errors.Is(err, dashboards.ErrProvisionedDashboardNotFound) && !errors.Is(err, dashboards.ErrDashboardNotFound) { return response.Error(500, "Error while checking if dashboard is provisioned", err) } provisioningData = data @@ -387,7 +387,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa Overwrite: cmd.Overwrite, } - dashboard, err := hs.dashboardService.SaveDashboard(alerting.WithUAEnabled(ctx, hs.Cfg.UnifiedAlerting.IsEnabled()), dashItem, allowUiUpdate) + dashboard, err := hs.DashboardService.SaveDashboard(alerting.WithUAEnabled(ctx, hs.Cfg.UnifiedAlerting.IsEnabled()), dashItem, allowUiUpdate) if dashboard != nil && hs.entityEventsService != nil { if err := hs.entityEventsService.SaveEvent(ctx, store.SaveEventCmd{ @@ -460,7 +460,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *models.ReqContext) response.Response { if preference.HomeDashboardID != 0 { slugQuery := models.GetDashboardRefByIdQuery{Id: preference.HomeDashboardID} - err := hs.dashboardService.GetDashboardUIDById(c.Req.Context(), &slugQuery) + err := hs.DashboardService.GetDashboardUIDById(c.Req.Context(), &slugQuery) if err == nil { url := models.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug) dashRedirect := dtos.DashboardRedirect{RedirectUri: url} @@ -546,7 +546,7 @@ func (hs *HTTPServer) GetDashboardVersions(c *models.ReqContext) response.Respon OrgId: c.SignedInUser.OrgId, Uid: dashUID, } - if err := hs.dashboardService.GetDashboard(c.Req.Context(), &q); err != nil { + if err := hs.DashboardService.GetDashboard(c.Req.Context(), &q); err != nil { return response.Error(http.StatusBadRequest, "failed to get dashboard by UID", err) } dashID = q.Result.Id @@ -605,7 +605,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *models.ReqContext) response.Respons OrgId: c.SignedInUser.OrgId, Uid: dashUID, } - if err := hs.dashboardService.GetDashboard(c.Req.Context(), &q); err != nil { + if err := hs.DashboardService.GetDashboard(c.Req.Context(), &q); err != nil { return response.Error(http.StatusBadRequest, "failed to get dashboard by UID", err) } dashID = q.Result.Id @@ -782,7 +782,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *models.ReqContext) response.Res func (hs *HTTPServer) GetDashboardTags(c *models.ReqContext) { query := models.GetDashboardTagsQuery{OrgId: c.OrgId} - err := hs.dashboardService.GetDashboardTags(c.Req.Context(), &query) + err := hs.DashboardService.GetDashboardTags(c.Req.Context(), &query) if err != nil { c.JsonApiErr(500, "Failed to get tags from database", err) return @@ -803,7 +803,7 @@ func (hs *HTTPServer) GetDashboardUIDs(c *models.ReqContext) { continue } q.Id = id - err = hs.dashboardService.GetDashboardUIDById(c.Req.Context(), q) + err = hs.DashboardService.GetDashboardUIDById(c.Req.Context(), q) if err != nil { continue } diff --git a/pkg/api/dashboard_permission.go b/pkg/api/dashboard_permission.go index 4ae1ce4d36e..6bbec530a18 100644 --- a/pkg/api/dashboard_permission.go +++ b/pkg/api/dashboard_permission.go @@ -143,7 +143,7 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response. return response.Success("Dashboard permissions updated") } - if err := hs.dashboardService.UpdateDashboardACL(c.Req.Context(), dashID, items); err != nil { + if err := hs.DashboardService.UpdateDashboardACL(c.Req.Context(), dashID, items); err != nil { if errors.Is(err, models.ErrDashboardAclInfoMissing) || errors.Is(err, models.ErrDashboardPermissionDashboardEmpty) { return response.Error(409, err.Error(), err) diff --git a/pkg/api/dashboard_permission_test.go b/pkg/api/dashboard_permission_test.go index 14cc1abc5cf..0b15fee9012 100644 --- a/pkg/api/dashboard_permission_test.go +++ b/pkg/api/dashboard_permission_test.go @@ -39,7 +39,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { Cfg: settings, SQLStore: mockSQLStore, Features: features, - dashboardService: dashboardservice.ProvideDashboardService( + DashboardService: dashboardservice.ProvideDashboardService( settings, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac, ), AccessControl: accesscontrolmock.New().WithDisabled(), diff --git a/pkg/api/dashboard_public.go b/pkg/api/dashboard_public.go deleted file mode 100644 index 5399f21a722..00000000000 --- a/pkg/api/dashboard_public.go +++ /dev/null @@ -1,141 +0,0 @@ -package api - -import ( - "errors" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/grafana/grafana/pkg/api/dtos" - "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/web" -) - -// gets public dashboard -func (hs *HTTPServer) GetPublicDashboard(c *models.ReqContext) response.Response { - accessToken := web.Params(c.Req)[":accessToken"] - - dash, err := hs.dashboardService.GetPublicDashboard(c.Req.Context(), accessToken) - if err != nil { - return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard", err) - } - - meta := dtos.DashboardMeta{ - Slug: dash.Slug, - Type: models.DashTypeDB, - CanStar: false, - CanSave: false, - CanEdit: false, - CanAdmin: false, - CanDelete: false, - Created: dash.Created, - Updated: dash.Updated, - Version: dash.Version, - IsFolder: false, - FolderId: dash.FolderId, - PublicDashboardAccessToken: accessToken, - } - - dto := dtos.DashboardFullWithMeta{Meta: meta, Dashboard: dash.Data} - - return response.JSON(http.StatusOK, dto) -} - -// gets public dashboard configuration for dashboard -func (hs *HTTPServer) GetPublicDashboardConfig(c *models.ReqContext) response.Response { - pdc, err := hs.dashboardService.GetPublicDashboardConfig(c.Req.Context(), c.OrgId, web.Params(c.Req)[":uid"]) - if err != nil { - return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard config", err) - } - return response.JSON(http.StatusOK, pdc) -} - -// sets public dashboard configuration for dashboard -func (hs *HTTPServer) SavePublicDashboardConfig(c *models.ReqContext) response.Response { - pubdash := &models.PublicDashboard{} - if err := web.Bind(c.Req, pubdash); err != nil { - return response.Error(http.StatusBadRequest, "bad request data", err) - } - - // Always set the org id to the current auth session orgId - pubdash.OrgId = c.OrgId - - dto := dashboards.SavePublicDashboardConfigDTO{ - OrgId: c.OrgId, - DashboardUid: web.Params(c.Req)[":uid"], - UserId: c.UserId, - PublicDashboard: pubdash, - } - - pubdash, err := hs.dashboardService.SavePublicDashboardConfig(c.Req.Context(), &dto) - if err != nil { - return handleDashboardErr(http.StatusInternalServerError, "Failed to save public dashboard configuration", err) - } - - return response.JSON(http.StatusOK, pubdash) -} - -// QueryPublicDashboard returns all results for a given panel on a public dashboard -// POST /api/public/dashboard/:accessToken/panels/:panelId/query -func (hs *HTTPServer) QueryPublicDashboard(c *models.ReqContext) response.Response { - panelId, err := strconv.ParseInt(web.Params(c.Req)[":panelId"], 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "invalid panel ID", err) - } - - dashboard, err := hs.dashboardService.GetPublicDashboard(c.Req.Context(), web.Params(c.Req)[":accessToken"]) - if err != nil { - return response.Error(http.StatusInternalServerError, "could not fetch dashboard", err) - } - - publicDashboard, err := hs.dashboardService.GetPublicDashboardConfig(c.Req.Context(), dashboard.OrgId, dashboard.Uid) - if err != nil { - return response.Error(http.StatusInternalServerError, "could not fetch public dashboard", err) - } - - reqDTO, err := hs.dashboardService.BuildPublicDashboardMetricRequest( - c.Req.Context(), - dashboard, - publicDashboard, - panelId, - ) - if err != nil { - return handleDashboardErr(http.StatusInternalServerError, "Failed to get queries for public dashboard", err) - } - - // Get all needed datasource UIDs from queries - var uids []string - for _, query := range reqDTO.Queries { - uids = append(uids, query.Get("datasource").Get("uid").MustString()) - } - - // Create a temp user with read-only datasource permissions - anonymousUser := &models.SignedInUser{OrgId: dashboard.OrgId, Permissions: make(map[int64]map[string][]string)} - permissions := make(map[string][]string) - datasourceScope := fmt.Sprintf("datasources:uid:%s", strings.Join(uids, ",")) - permissions[datasources.ActionQuery] = []string{datasourceScope} - permissions[datasources.ActionRead] = []string{datasourceScope} - anonymousUser.Permissions[dashboard.OrgId] = permissions - - resp, err := hs.queryDataService.QueryDataMultipleSources(c.Req.Context(), anonymousUser, c.SkipCache, reqDTO, true) - - if err != nil { - return hs.handleQueryMetricsError(err) - } - return hs.toJsonStreamingResponse(resp) -} - -// util to help us unpack a dashboard err or use default http code and message -func handleDashboardErr(defaultCode int, defaultMsg string, err error) response.Response { - var dashboardErr models.DashboardErr - - if ok := errors.As(err, &dashboardErr); ok { - return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr) - } - - return response.Error(defaultCode, defaultMsg, err) -} diff --git a/pkg/api/dashboard_public_test.go b/pkg/api/dashboard_public_test.go deleted file mode 100644 index 787e16f4813..00000000000 --- a/pkg/api/dashboard_public_test.go +++ /dev/null @@ -1,630 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "strings" - "testing" - - "github.com/gofrs/uuid" - "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/api/dtos" - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/infra/localcache" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/datasources" - fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" - "github.com/grafana/grafana/pkg/services/datasources/service" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/query" - "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/web/webtest" -) - -func TestAPIGetPublicDashboard(t *testing.T) { - t.Run("It should 404 if featureflag is not enabled", func(t *testing.T) { - sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures()) - dashSvc := dashboards.NewFakeDashboardService(t) - dashSvc.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")). - Return(&models.Dashboard{}, nil).Maybe() - sc.hs.dashboardService = dashSvc - - setInitCtxSignedInViewer(sc.initCtx) - response := callAPI( - sc.server, - http.MethodGet, - "/api/public/dashboards", - nil, - t, - ) - assert.Equal(t, http.StatusNotFound, response.Code) - response = callAPI( - sc.server, - http.MethodGet, - "/api/public/dashboards/asdf", - nil, - t, - ) - assert.Equal(t, http.StatusNotFound, response.Code) - }) - - DashboardUid := "dashboard-abcd1234" - token, err := uuid.NewV4() - require.NoError(t, err) - accessToken := fmt.Sprintf("%x", token) - - testCases := []struct { - Name string - AccessToken string - ExpectedHttpResponse int - publicDashboardResult *models.Dashboard - publicDashboardErr error - }{ - { - Name: "It gets a public dashboard", - AccessToken: accessToken, - ExpectedHttpResponse: http.StatusOK, - publicDashboardResult: &models.Dashboard{ - Data: simplejson.NewFromAny(map[string]interface{}{ - "Uid": DashboardUid, - }), - }, - publicDashboardErr: nil, - }, - { - Name: "It should return 404 if isPublicDashboard is false", - AccessToken: accessToken, - ExpectedHttpResponse: http.StatusNotFound, - publicDashboardResult: nil, - publicDashboardErr: models.ErrPublicDashboardNotFound, - }, - } - - for _, test := range testCases { - t.Run(test.Name, func(t *testing.T) { - sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards)) - dashSvc := dashboards.NewFakeDashboardService(t) - dashSvc.On("GetPublicDashboard", mock.Anything, mock.AnythingOfType("string")). - Return(test.publicDashboardResult, test.publicDashboardErr) - sc.hs.dashboardService = dashSvc - - setInitCtxSignedInViewer(sc.initCtx) - response := callAPI( - sc.server, - http.MethodGet, - fmt.Sprintf("/api/public/dashboards/%s", test.AccessToken), - nil, - t, - ) - - assert.Equal(t, test.ExpectedHttpResponse, response.Code) - - if test.publicDashboardErr == nil { - var dashResp dtos.DashboardFullWithMeta - err := json.Unmarshal(response.Body.Bytes(), &dashResp) - require.NoError(t, err) - - assert.Equal(t, DashboardUid, dashResp.Dashboard.Get("Uid").MustString()) - assert.Equal(t, false, dashResp.Meta.CanEdit) - assert.Equal(t, false, dashResp.Meta.CanDelete) - assert.Equal(t, false, dashResp.Meta.CanSave) - } else { - var errResp struct { - Error string `json:"error"` - } - err := json.Unmarshal(response.Body.Bytes(), &errResp) - require.NoError(t, err) - assert.Equal(t, test.publicDashboardErr.Error(), errResp.Error) - } - }) - } -} - -func TestAPIGetPublicDashboardConfig(t *testing.T) { - pubdash := &models.PublicDashboard{IsEnabled: true} - - testCases := []struct { - Name string - DashboardUid string - ExpectedHttpResponse int - PublicDashboardResult *models.PublicDashboard - PublicDashboardError error - }{ - { - Name: "retrieves public dashboard config when dashboard is found", - DashboardUid: "1", - ExpectedHttpResponse: http.StatusOK, - PublicDashboardResult: pubdash, - PublicDashboardError: nil, - }, - { - Name: "returns 404 when dashboard not found", - DashboardUid: "77777", - ExpectedHttpResponse: http.StatusNotFound, - PublicDashboardResult: nil, - PublicDashboardError: models.ErrDashboardNotFound, - }, - { - Name: "returns 500 when internal server error", - DashboardUid: "1", - ExpectedHttpResponse: http.StatusInternalServerError, - PublicDashboardResult: nil, - PublicDashboardError: errors.New("database broken"), - }, - } - - for _, test := range testCases { - t.Run(test.Name, func(t *testing.T) { - sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards)) - dashSvc := dashboards.NewFakeDashboardService(t) - dashSvc.On("GetPublicDashboardConfig", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")). - Return(test.PublicDashboardResult, test.PublicDashboardError) - sc.hs.dashboardService = dashSvc - - setInitCtxSignedInViewer(sc.initCtx) - response := callAPI( - sc.server, - http.MethodGet, - "/api/dashboards/uid/1/public-config", - nil, - t, - ) - - assert.Equal(t, test.ExpectedHttpResponse, response.Code) - - if response.Code == http.StatusOK { - var pdcResp models.PublicDashboard - err := json.Unmarshal(response.Body.Bytes(), &pdcResp) - require.NoError(t, err) - assert.Equal(t, test.PublicDashboardResult, &pdcResp) - } - }) - } -} - -func TestApiSavePublicDashboardConfig(t *testing.T) { - testCases := []struct { - Name string - DashboardUid string - publicDashboardConfig *models.PublicDashboard - ExpectedHttpResponse int - saveDashboardError error - }{ - { - Name: "returns 200 when update persists", - DashboardUid: "1", - publicDashboardConfig: &models.PublicDashboard{IsEnabled: true}, - ExpectedHttpResponse: http.StatusOK, - saveDashboardError: nil, - }, - { - Name: "returns 500 when not persisted", - ExpectedHttpResponse: http.StatusInternalServerError, - publicDashboardConfig: &models.PublicDashboard{}, - saveDashboardError: errors.New("backend failed to save"), - }, - { - Name: "returns 404 when dashboard not found", - ExpectedHttpResponse: http.StatusNotFound, - publicDashboardConfig: &models.PublicDashboard{}, - saveDashboardError: models.ErrDashboardNotFound, - }, - } - - for _, test := range testCases { - t.Run(test.Name, func(t *testing.T) { - sc := setupHTTPServerWithMockDb(t, false, false, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards)) - - dashSvc := dashboards.NewFakeDashboardService(t) - dashSvc.On("SavePublicDashboardConfig", mock.Anything, mock.AnythingOfType("*dashboards.SavePublicDashboardConfigDTO")). - Return(&models.PublicDashboard{IsEnabled: true}, test.saveDashboardError) - sc.hs.dashboardService = dashSvc - - setInitCtxSignedInViewer(sc.initCtx) - response := callAPI( - sc.server, - http.MethodPost, - "/api/dashboards/uid/1/public-config", - strings.NewReader(`{ "isPublic": true }`), - t, - ) - - assert.Equal(t, test.ExpectedHttpResponse, response.Code) - - // check the result if it's a 200 - if response.Code == http.StatusOK { - val, err := json.Marshal(test.publicDashboardConfig) - require.NoError(t, err) - assert.Equal(t, string(val), response.Body.String()) - } - }) - } -} - -// `/public/dashboards/:uid/query`` endpoint test -func TestAPIQueryPublicDashboard(t *testing.T) { - queryReturnsError := false - - qds := query.ProvideService( - nil, - &fakeDatasources.FakeCacheService{ - DataSources: []*datasources.DataSource{ - {Uid: "mysqlds"}, - {Uid: "promds"}, - {Uid: "promds2"}, - }, - }, - nil, - &fakePluginRequestValidator{}, - &fakeDatasources.FakeDataSourceService{}, - &fakePluginClient{ - QueryDataHandlerFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { - if queryReturnsError { - return nil, errors.New("error") - } - - resp := backend.Responses{} - - for _, query := range req.Queries { - resp[query.RefID] = backend.DataResponse{ - Frames: []*data.Frame{ - { - RefID: query.RefID, - Name: "query-" + query.RefID, - }, - }, - } - } - return &backend.QueryDataResponse{Responses: resp}, nil - }, - }, - &fakeOAuthTokenService{}, - ) - - setup := func(enabled bool) (*webtest.Server, *dashboards.FakeDashboardService) { - fakeDashboardService := &dashboards.FakeDashboardService{} - - return SetupAPITestServer(t, func(hs *HTTPServer) { - hs.queryDataService = qds - hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards, enabled) - hs.dashboardService = fakeDashboardService - }), fakeDashboardService - } - - t.Run("Status code is 404 when feature toggle is disabled", func(t *testing.T) { - server, _ := setup(false) - - req := server.NewPostRequest( - "/api/public/dashboards/abc123/panels/2/query", - strings.NewReader("{}"), - ) - resp, err := server.SendJSON(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - require.Equal(t, http.StatusNotFound, resp.StatusCode) - }) - - t.Run("Status code is 400 when the panel ID is invalid", func(t *testing.T) { - server, _ := setup(true) - - req := server.NewPostRequest( - "/api/public/dashboards/abc123/panels/notanumber/query", - strings.NewReader("{}"), - ) - resp, err := server.SendJSON(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - require.Equal(t, http.StatusBadRequest, resp.StatusCode) - }) - - t.Run("Returns query data when feature toggle is enabled", func(t *testing.T) { - server, fakeDashboardService := setup(true) - - fakeDashboardService.On("GetPublicDashboard", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil) - fakeDashboardService.On("GetPublicDashboardConfig", mock.Anything, mock.Anything, mock.Anything).Return(&models.PublicDashboard{}, nil) - - fakeDashboardService.On( - "BuildPublicDashboardMetricRequest", - mock.Anything, - mock.Anything, - mock.Anything, - int64(2), - ).Return(dtos.MetricRequest{ - Queries: []*simplejson.Json{ - simplejson.MustJson([]byte(` - { - "datasource": { - "type": "prometheus", - "uid": "promds" - }, - "exemplar": true, - "expr": "query_2_A", - "interval": "", - "legendFormat": "", - "refId": "A" - } - `)), - }, - }, nil) - req := server.NewPostRequest( - "/api/public/dashboards/abc123/panels/2/query", - strings.NewReader("{}"), - ) - resp, err := server.SendJSON(req) - require.NoError(t, err) - bodyBytes, err := ioutil.ReadAll(resp.Body) - require.NoError(t, err) - require.JSONEq( - t, - `{ - "results": { - "A": { - "frames": [ - { - "data": { - "values": [] - }, - "schema": { - "fields": [], - "refId": "A", - "name": "query-A" - } - } - ] - } - } - }`, - string(bodyBytes), - ) - require.NoError(t, resp.Body.Close()) - require.Equal(t, http.StatusOK, resp.StatusCode) - }) - - t.Run("Status code is 500 when the query fails", func(t *testing.T) { - server, fakeDashboardService := setup(true) - - fakeDashboardService.On("GetPublicDashboard", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil) - fakeDashboardService.On("GetPublicDashboardConfig", mock.Anything, mock.Anything, mock.Anything).Return(&models.PublicDashboard{}, nil) - fakeDashboardService.On( - "BuildPublicDashboardMetricRequest", - mock.Anything, - mock.Anything, - mock.Anything, - int64(2), - ).Return(dtos.MetricRequest{ - Queries: []*simplejson.Json{ - simplejson.MustJson([]byte(` - { - "datasource": { - "type": "prometheus", - "uid": "promds" - }, - "exemplar": true, - "expr": "query_2_A", - "interval": "", - "legendFormat": "", - "refId": "A" - } - `)), - }, - }, nil) - req := server.NewPostRequest( - "/api/public/dashboards/abc123/panels/2/query", - strings.NewReader("{}"), - ) - queryReturnsError = true - resp, err := server.SendJSON(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - require.Equal(t, http.StatusInternalServerError, resp.StatusCode) - queryReturnsError = false - }) - - t.Run("Status code is 200 when a panel has queries from multiple datasources", func(t *testing.T) { - server, fakeDashboardService := setup(true) - - fakeDashboardService.On("GetPublicDashboard", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil) - fakeDashboardService.On("GetPublicDashboardConfig", mock.Anything, mock.Anything, mock.Anything).Return(&models.PublicDashboard{}, nil) - fakeDashboardService.On( - "BuildPublicDashboardMetricRequest", - mock.Anything, - mock.Anything, - mock.Anything, - int64(2), - ).Return(dtos.MetricRequest{ - Queries: []*simplejson.Json{ - simplejson.MustJson([]byte(` - { - "datasource": { - "type": "prometheus", - "uid": "promds" - }, - "exemplar": true, - "expr": "query_2_A", - "interval": "", - "legendFormat": "", - "refId": "A" - } - `)), - simplejson.MustJson([]byte(` - { - "datasource": { - "type": "prometheus", - "uid": "promds2" - }, - "exemplar": true, - "expr": "query_2_B", - "interval": "", - "legendFormat": "", - "refId": "B" - } - `)), - }, - }, nil) - req := server.NewPostRequest( - "/api/public/dashboards/abc123/panels/2/query", - strings.NewReader("{}"), - ) - resp, err := server.SendJSON(req) - require.NoError(t, err) - bodyBytes, err := ioutil.ReadAll(resp.Body) - require.NoError(t, err) - require.JSONEq( - t, - `{ - "results": { - "A": { - "frames": [ - { - "data": { - "values": [] - }, - "schema": { - "fields": [], - "refId": "A", - "name": "query-A" - } - } - ] - }, - "B": { - "frames": [ - { - "data": { - "values": [] - }, - "schema": { - "fields": [], - "refId": "B", - "name": "query-B" - } - } - ] - } - } - }`, - string(bodyBytes), - ) - require.NoError(t, resp.Body.Close()) - require.Equal(t, http.StatusOK, resp.StatusCode) - }) -} - -func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) { - config := setting.NewCfg() - db := sqlstore.InitTestDB(t) - scenario := setupHTTPServerWithCfgDb(t, false, false, config, db, db, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards)) - scenario.initCtx.SkipCache = true - cacheService := service.ProvideCacheService(localcache.ProvideService(), db) - qds := query.ProvideService( - nil, - cacheService, - nil, - &fakePluginRequestValidator{}, - &fakeDatasources.FakeDataSourceService{}, - &fakePluginClient{ - QueryDataHandlerFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { - resp := backend.Responses{ - "A": backend.DataResponse{ - Frames: []*data.Frame{{}}, - }, - } - return &backend.QueryDataResponse{Responses: resp}, nil - }, - }, - &fakeOAuthTokenService{}, - ) - scenario.hs.queryDataService = qds - - _ = db.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ - Uid: "ds1", - OrgId: 1, - Name: "laban", - Type: datasources.DS_MYSQL, - Access: datasources.DS_ACCESS_DIRECT, - Url: "http://test", - Database: "site", - ReadOnly: true, - }) - - // Create Dashboard - saveDashboardCmd := models.SaveDashboardCommand{ - OrgId: 1, - FolderId: 1, - IsFolder: false, - Dashboard: simplejson.NewFromAny(map[string]interface{}{ - "id": nil, - "title": "test", - "panels": []map[string]interface{}{ - { - "id": 1, - "targets": []map[string]interface{}{ - { - "datasource": map[string]string{ - "type": "mysql", - "uid": "ds1", - }, - "refId": "A", - }, - }, - }, - }, - }), - } - dashboard, _ := scenario.dashboardsStore.SaveDashboard(saveDashboardCmd) - - // Create public dashboard - savePubDashboardCmd := &dashboards.SavePublicDashboardConfigDTO{ - DashboardUid: dashboard.Uid, - OrgId: dashboard.OrgId, - PublicDashboard: &models.PublicDashboard{ - IsEnabled: true, - }, - } - - pubdash, err := scenario.hs.dashboardService.SavePublicDashboardConfig(context.Background(), savePubDashboardCmd) - require.NoError(t, err) - - response := callAPI( - scenario.server, - http.MethodPost, - fmt.Sprintf("/api/public/dashboards/%s/panels/1/query", pubdash.AccessToken), - strings.NewReader(`{}`), - t, - ) - - require.Equal(t, http.StatusOK, response.Code) - bodyBytes, err := ioutil.ReadAll(response.Body) - require.NoError(t, err) - require.JSONEq( - t, - `{ - "results": { - "A": { - "frames": [ - { - "data": { - "values": [] - }, - "schema": { - "fields": [] - } - } - ] - } - } - }`, - string(bodyBytes), - ) -} diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index 79ae82d3930..2c2ae051586 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/setting" @@ -267,24 +268,28 @@ func (hs *HTTPServer) DeleteDashboardSnapshot(c *models.ReqContext) response.Res return response.Error(404, "Failed to get dashboard snapshot", nil) } - dashboardID := query.Result.Dashboard.Get("id").MustInt64() - - guardian := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser) - canEdit, err := guardian.CanEdit() - // check for permissions only if the dahboard is found - if err != nil && !errors.Is(err, models.ErrDashboardNotFound) { - return response.Error(500, "Error while checking permissions for snapshot", err) - } - - if !canEdit && query.Result.UserId != c.SignedInUser.UserId && !errors.Is(err, models.ErrDashboardNotFound) { - return response.Error(403, "Access denied to this snapshot", nil) - } - if query.Result.External { err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl) if err != nil { return response.Error(500, "Failed to delete external dashboard", err) } + } else { + // When creating an external snapshot, its dashboard content is empty. This means that the mustInt here returns a 0, + // which before RBAC would result in a dashboard which has no ACL. A dashboard without an ACL would fallback + // to the user’s org role, which for editors and admins would essentially always be allowed here. With RBAC, + // all permissions must be explicit, so the lack of a rule for dashboard 0 means the guardian will reject. + dashboardID := query.Result.Dashboard.Get("id").MustInt64() + + guardian := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser) + canEdit, err := guardian.CanEdit() + // check for permissions only if the dahboard is found + if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) { + return response.Error(500, "Error while checking permissions for snapshot", err) + } + + if !canEdit && query.Result.UserId != c.SignedInUser.UserId && !errors.Is(err, dashboards.ErrDashboardNotFound) { + return response.Error(403, "Access denied to this snapshot", nil) + } } cmd := &dashboardsnapshots.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey} diff --git a/pkg/api/dashboard_snapshot_test.go b/pkg/api/dashboard_snapshot_test.go index 5c09202cb09..43251646977 100644 --- a/pkg/api/dashboard_snapshot_test.go +++ b/pkg/api/dashboard_snapshot_test.go @@ -65,11 +65,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { t.Run("When user has editor role and is not in the ACL", func(t *testing.T) { loggedInUserScenarioWithRole(t, "Should not be able to delete snapshot when calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { - var externalRequest *http.Request - ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { - externalRequest = req - }) - hs := &HTTPServer{dashboardsnapshotsService: setUpSnapshotTest(t, 0, ts.URL)} + hs := &HTTPServer{dashboardsnapshotsService: setUpSnapshotTest(t, 0, "")} sc.handlerFunc = hs.DeleteDashboardSnapshot dashSvc := dashboards.NewFakeDashboardService(t) @@ -79,7 +75,6 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() assert.Equal(t, 403, sc.resp.Code) - require.Nil(t, externalRequest) }, sqlmock) }) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index d79fdbfab6f..d43a7383faf 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -9,7 +9,6 @@ import ( "net/http" "testing" - "github.com/grafana/grafana/pkg/framework/coremodel/registry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -18,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/framework/coremodel/registry" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/models" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" @@ -139,7 +139,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { SQLStore: mockSQLStore, AccessControl: accesscontrolmock.New(), Features: featuremgmt.WithFeatures(), - dashboardService: dashboardService, + DashboardService: dashboardService, dashboardVersionService: fakeDashboardVersionService, } hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t) @@ -259,7 +259,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { LibraryElementService: &mockLibraryElementService{}, SQLStore: mockSQLStore, AccessControl: accesscontrolmock.New(), - dashboardService: dashboardService, + DashboardService: dashboardService, dashboardVersionService: fakeDashboardVersionService, } hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t) @@ -679,24 +679,24 @@ func TestDashboardAPIEndpoint(t *testing.T) { SaveError error ExpectedStatusCode int }{ - {SaveError: models.ErrDashboardNotFound, ExpectedStatusCode: 404}, - {SaveError: models.ErrFolderNotFound, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412}, - {SaveError: models.ErrDashboardVersionMismatch, ExpectedStatusCode: 412}, - {SaveError: models.ErrDashboardTitleEmpty, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardNotFound, ExpectedStatusCode: 404}, + {SaveError: dashboards.ErrFolderNotFound, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412}, + {SaveError: dashboards.ErrDashboardVersionMismatch, ExpectedStatusCode: 412}, + {SaveError: dashboards.ErrDashboardTitleEmpty, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400}, {SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422}, - {SaveError: models.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500}, - {SaveError: models.ErrDashboardTypeMismatch, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardFolderNameExists, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403}, - {SaveError: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400}, - {SaveError: models.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412}, + {SaveError: dashboards.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500}, + {SaveError: dashboards.ErrDashboardTypeMismatch, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardFolderNameExists, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403}, + {SaveError: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400}, + {SaveError: dashboards.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412}, } cmd := models.SaveDashboardCommand{ @@ -900,7 +900,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { dashboardProvisioningService: mockDashboardProvisioningService{}, SQLStore: mockSQLStore, AccessControl: accesscontrolmock.New(), - dashboardService: dashboardService, + DashboardService: dashboardService, } hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t) hs.callGetDashboard(sc) @@ -954,7 +954,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr cfg, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac, ), - dashboardService: dashboardService, + DashboardService: dashboardService, } hs.CoremodelStaticRegistry, hs.CoremodelRegistry = setupDashboardCoremodel(t) @@ -986,7 +986,7 @@ func (hs *HTTPServer) callGetDashboardVersions(sc *scenarioContext) { func (hs *HTTPServer) callDeleteDashboardByUID(t *testing.T, sc *scenarioContext, mockDashboard *dashboards.FakeDashboardService) { - hs.dashboardService = mockDashboard + hs.DashboardService = mockDashboard sc.handlerFunc = hs.DeleteDashboardByUID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() } @@ -1018,7 +1018,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s pluginStore: &fakePluginStore{}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, - dashboardService: dashboardService, + DashboardService: dashboardService, folderService: folderService, Features: featuremgmt.WithFeatures(), } @@ -1088,7 +1088,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout QuotaService: "a.QuotaService{Cfg: cfg}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, - dashboardService: mock, + DashboardService: mock, SQLStore: sqlStore, Features: featuremgmt.WithFeatures(), dashboardVersionService: fakeDashboardVersionService, diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index c0fe84cb2dc..343b5f6c1b2 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -70,6 +70,8 @@ type MetricRequest struct { // required: false Debug bool `json:"debug"` + PublicDashboardAccessToken string `json:"publicDashboardAccessToken"` + HTTPRequest *http.Request `json:"-"` } diff --git a/pkg/api/fakes.go b/pkg/api/fakes.go index 23b5a89c52d..6b1eda6f2ff 100644 --- a/pkg/api/fakes.go +++ b/pkg/api/fakes.go @@ -6,6 +6,28 @@ import ( "github.com/grafana/grafana/pkg/plugins" ) +type fakePluginManager struct { + plugins map[string]fakePlugin +} + +type fakePlugin struct { + pluginID string + version string +} + +func (pm *fakePluginManager) Add(_ context.Context, pluginID, version string) error { + pm.plugins[pluginID] = fakePlugin{ + pluginID: pluginID, + version: version, + } + return nil +} + +func (pm *fakePluginManager) Remove(_ context.Context, pluginID string) error { + delete(pm.plugins, pluginID) + return nil +} + type fakePluginStore struct { plugins.Store diff --git a/pkg/api/folder_permission.go b/pkg/api/folder_permission.go index edaff924528..892e9e043aa 100644 --- a/pkg/api/folder_permission.go +++ b/pkg/api/folder_permission.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" @@ -24,7 +25,7 @@ func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Res g := guardian.New(c.Req.Context(), folder.Id, c.OrgId, c.SignedInUser) if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { - return apierrors.ToFolderErrorResponse(models.ErrFolderAccessDenied) + return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied) } acl, err := g.GetAcl() @@ -78,7 +79,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res } if !canAdmin { - return apierrors.ToFolderErrorResponse(models.ErrFolderAccessDenied) + return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied) } var items []*models.DashboardAcl @@ -125,7 +126,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res return response.Success("Dashboard permissions updated") } - if err := hs.dashboardService.UpdateDashboardACL(c.Req.Context(), folder.Id, items); err != nil { + if err := hs.DashboardService.UpdateDashboardACL(c.Req.Context(), folder.Id, items); err != nil { if errors.Is(err, models.ErrDashboardAclInfoMissing) { err = models.ErrFolderAclInfoMissing } diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go index 6ed7c2305ac..093522d2d9e 100644 --- a/pkg/api/folder_permission_test.go +++ b/pkg/api/folder_permission_test.go @@ -42,14 +42,14 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { folderService: folderService, folderPermissionsService: folderPermissions, dashboardPermissionsService: dashboardPermissions, - dashboardService: service.ProvideDashboardService( + DashboardService: service.ProvideDashboardService( settings, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac, ), AccessControl: accesscontrolmock.New().WithDisabled(), } t.Run("Given folder not exists", func(t *testing.T) { - folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrFolderNotFound).Twice() + folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, dashboards.ErrFolderNotFound).Twice() mockSQLStore := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { callGetFolderPermissions(sc, hs) @@ -81,7 +81,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { }) guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false}) - folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrFolderAccessDenied).Twice() + folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, dashboards.ErrFolderAccessDenied).Twice() mockSQLStore := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index 24a448e451d..e321cd0e7ca 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -7,6 +7,10 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" @@ -19,9 +23,6 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web/webtest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) func TestFoldersAPIEndpoint(t *testing.T) { @@ -55,15 +56,15 @@ func TestFoldersAPIEndpoint(t *testing.T) { Error error ExpectedStatusCode int }{ - {Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, - {Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, - {Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 409}, - {Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, - {Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, - {Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, - {Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, - {Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, + {Error: dashboards.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, + {Error: dashboards.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, + {Error: dashboards.ErrFolderSameNameExists, ExpectedStatusCode: 409}, + {Error: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, + {Error: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, + {Error: dashboards.ErrFolderAccessDenied, ExpectedStatusCode: 403}, + {Error: dashboards.ErrFolderNotFound, ExpectedStatusCode: 404}, + {Error: dashboards.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, + {Error: dashboards.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, } cmd := models.CreateFolderCommand{ @@ -110,15 +111,15 @@ func TestFoldersAPIEndpoint(t *testing.T) { Error error ExpectedStatusCode int }{ - {Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, - {Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, - {Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 409}, - {Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, - {Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, - {Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, - {Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, - {Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, + {Error: dashboards.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, + {Error: dashboards.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, + {Error: dashboards.ErrFolderSameNameExists, ExpectedStatusCode: 409}, + {Error: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, + {Error: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, + {Error: dashboards.ErrFolderAccessDenied, ExpectedStatusCode: 403}, + {Error: dashboards.ErrFolderNotFound, ExpectedStatusCode: 404}, + {Error: dashboards.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, + {Error: dashboards.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, } cmd := models.UpdateFolderCommand{ diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 5b285599d2e..ac80801de41 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -62,6 +62,8 @@ import ( pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service" pref "github.com/grafana/grafana/pkg/services/preference" "github.com/grafana/grafana/pkg/services/provisioning" + + publicdashboardsApi "github.com/grafana/grafana/pkg/services/publicdashboards/api" "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/queryhistory" "github.com/grafana/grafana/pkg/services/quota" @@ -148,7 +150,7 @@ type HTTPServer struct { authenticator loginpkg.Authenticator teamPermissionsService accesscontrol.TeamPermissionsService NotificationService *notifications.NotificationService - dashboardService dashboards.DashboardService + DashboardService dashboards.DashboardService dashboardProvisioningService dashboards.DashboardProvisioningService folderService dashboards.FolderService DatasourcePermissionsService permissions.DatasourcePermissionsService @@ -163,10 +165,12 @@ type HTTPServer struct { folderPermissionsService accesscontrol.FolderPermissionsService dashboardPermissionsService accesscontrol.DashboardPermissionsService dashboardVersionService dashver.Service + PublicDashboardsApi *publicdashboardsApi.Api starService star.Service CoremodelRegistry *registry.Generic CoremodelStaticRegistry *registry.Static kvStore kvstore.KVStore + secretsMigrator secrets.Migrator } type ServerOptions struct { @@ -201,7 +205,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi teamsPermissionsService accesscontrol.TeamPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService, dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service, starService star.Service, csrfService csrf.Service, coremodelRegistry *registry.Generic, coremodelStaticRegistry *registry.Static, - kvStore kvstore.KVStore, remoteSecretsCheck secretsKV.UseRemoteSecretsPluginCheck, + kvStore kvstore.KVStore, secretsMigrator secrets.Migrator, remoteSecretsCheck secretsKV.UseRemoteSecretsPluginCheck, publicDashboardsApi *publicdashboardsApi.Api, ) (*HTTPServer, error) { web.Env = cfg.Env m := web.New() @@ -266,7 +270,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi authInfoService: authInfoService, authenticator: authenticator, NotificationService: notificationService, - dashboardService: dashboardService, + DashboardService: dashboardService, dashboardProvisioningService: dashboardProvisioningService, folderService: folderService, DatasourcePermissionsService: datasourcePermissionsService, @@ -286,6 +290,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi CoremodelRegistry: coremodelRegistry, CoremodelStaticRegistry: coremodelStaticRegistry, kvStore: kvStore, + PublicDashboardsApi: publicDashboardsApi, + secretsMigrator: secretsMigrator, } if hs.Listener != nil { hs.log.Debug("Using provided listener") diff --git a/pkg/api/index.go b/pkg/api/index.go index aaca41afe05..00c375931c6 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -294,6 +294,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool, prefs * Url: hs.Cfg.AppSubURL + "/org/apikeys", }) } + if enableServiceAccount(hs, c) { configNodes = append(configNodes, &dtos.NavLink{ Text: "Service accounts", @@ -406,7 +407,7 @@ func (hs *HTTPServer) buildStarredItemsNavLinks(c *models.ReqContext, prefs *pre Id: dashboardId, OrgId: c.OrgId, } - err := hs.dashboardService.GetDashboard(c.Req.Context(), query) + err := hs.DashboardService.GetDashboard(c.Req.Context(), query) if err == nil { starredDashboards = append(starredDashboards, query.Result) } @@ -458,6 +459,15 @@ func (hs *HTTPServer) buildDashboardNavLinks(c *models.ReqContext, hasEditPerm b }) } + if hs.Features.IsEnabled(featuremgmt.FlagScenes) { + dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{ + Text: "Scenes", + Id: "scenes", + Url: hs.Cfg.AppSubURL + "/scenes", + Icon: "apps", + }) + } + if hasEditPerm { dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{ Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true, @@ -646,6 +656,16 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink { }) } + if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead)) && hs.Features.IsEnabled(featuremgmt.FlagStorage) { + adminNavLinks = append(adminNavLinks, &dtos.NavLink{ + Text: "Storage", + Id: "storage", + Description: "Manage file storage", + Icon: "cube", + Url: hs.Cfg.AppSubURL + "/admin/storage", + }) + } + if hs.Cfg.LDAPEnabled && hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)) { adminNavLinks = append(adminNavLinks, &dtos.NavLink{ Text: "LDAP", Id: "ldap", Url: hs.Cfg.AppSubURL + "/admin/ldap", Icon: "book", @@ -663,7 +683,7 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink { func (hs *HTTPServer) editorInAnyFolder(c *models.ReqContext) bool { hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} - if err := hs.dashboardService.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil { + if err := hs.DashboardService.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil { return false } return hasEditPermissionInFoldersQuery.Result diff --git a/pkg/api/org.go b/pkg/api/org.go index 2512938837b..d5b5c308059 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -34,10 +34,10 @@ func (hs *HTTPServer) GetOrgByName(c *models.ReqContext) response.Response { org, err := hs.SQLStore.GetOrgByName(web.Params(c.Req)[":name"]) if err != nil { if errors.Is(err, models.ErrOrgNotFound) { - return response.Error(404, "Organization not found", err) + return response.Error(http.StatusNotFound, "Organization not found", err) } - return response.Error(500, "Failed to get organization", err) + return response.Error(http.StatusInternalServerError, "Failed to get organization", err) } result := models.OrgDetailsDTO{ Id: org.Id, @@ -60,9 +60,9 @@ func (hs *HTTPServer) getOrgHelper(ctx context.Context, orgID int64) response.Re if err := hs.SQLStore.GetOrgById(ctx, &query); err != nil { if errors.Is(err, models.ErrOrgNotFound) { - return response.Error(404, "Organization not found", err) + return response.Error(http.StatusNotFound, "Organization not found", err) } - return response.Error(500, "Failed to get organization", err) + return response.Error(http.StatusInternalServerError, "Failed to get organization", err) } org := query.Result @@ -90,15 +90,15 @@ func (hs *HTTPServer) CreateOrg(c *models.ReqContext) response.Response { } acEnabled := !hs.AccessControl.IsDisabled() if !acEnabled && !(setting.AllowUserOrgCreate || c.IsGrafanaAdmin) { - return response.Error(403, "Access denied", nil) + return response.Error(http.StatusForbidden, "Access denied", nil) } cmd.UserId = c.UserId if err := hs.SQLStore.CreateOrg(c.Req.Context(), &cmd); err != nil { if errors.Is(err, models.ErrOrgNameTaken) { - return response.Error(409, "Organization name taken", err) + return response.Error(http.StatusConflict, "Organization name taken", err) } - return response.Error(500, "Failed to create organization", err) + return response.Error(http.StatusInternalServerError, "Failed to create organization", err) } metrics.MApiOrgCreate.Inc() @@ -135,9 +135,9 @@ func (hs *HTTPServer) updateOrgHelper(ctx context.Context, form dtos.UpdateOrgFo cmd := models.UpdateOrgCommand{Name: form.Name, OrgId: orgID} if err := hs.SQLStore.UpdateOrg(ctx, &cmd); err != nil { if errors.Is(err, models.ErrOrgNameTaken) { - return response.Error(400, "Organization name taken", err) + return response.Error(http.StatusBadRequest, "Organization name taken", err) } - return response.Error(500, "Failed to update organization", err) + return response.Error(http.StatusInternalServerError, "Failed to update organization", err) } return response.Success("Organization updated") @@ -179,7 +179,7 @@ func (hs *HTTPServer) updateOrgAddressHelper(ctx context.Context, form dtos.Upda } if err := hs.SQLStore.UpdateOrgAddress(ctx, &cmd); err != nil { - return response.Error(500, "Failed to update org address", err) + return response.Error(http.StatusInternalServerError, "Failed to update org address", err) } return response.Success("Address updated") @@ -193,14 +193,14 @@ func (hs *HTTPServer) DeleteOrgByID(c *models.ReqContext) response.Response { } // before deleting an org, check if user does not belong to the current org if c.OrgId == orgID { - return response.Error(400, "Can not delete org for current user", nil) + return response.Error(http.StatusBadRequest, "Can not delete org for current user", nil) } if err := hs.SQLStore.DeleteOrg(c.Req.Context(), &models.DeleteOrgCommand{Id: orgID}); err != nil { if errors.Is(err, models.ErrOrgNotFound) { - return response.Error(404, "Failed to delete organization. ID not found", nil) + return response.Error(http.StatusNotFound, "Failed to delete organization. ID not found", nil) } - return response.Error(500, "Failed to update organization", err) + return response.Error(http.StatusInternalServerError, "Failed to update organization", err) } return response.Success("Organization deleted") } @@ -221,7 +221,7 @@ func (hs *HTTPServer) SearchOrgs(c *models.ReqContext) response.Response { } if err := hs.SQLStore.SearchOrgs(c.Req.Context(), &query); err != nil { - return response.Error(500, "Failed to search orgs", err) + return response.Error(http.StatusInternalServerError, "Failed to search orgs", err) } return response.JSON(http.StatusOK, query.Result) diff --git a/pkg/api/playlist_play.go b/pkg/api/playlist_play.go index c75a550d579..035c4d402f4 100644 --- a/pkg/api/playlist_play.go +++ b/pkg/api/playlist_play.go @@ -16,7 +16,7 @@ func (hs *HTTPServer) populateDashboardsByID(ctx context.Context, dashboardByIDs if len(dashboardByIDs) > 0 { dashboardQuery := models.GetDashboardsQuery{DashboardIds: dashboardByIDs} - if err := hs.dashboardService.GetDashboards(ctx, &dashboardQuery); err != nil { + if err := hs.DashboardService.GetDashboards(ctx, &dashboardQuery); err != nil { return result, err } diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 9e869285efd..8aefeac8e62 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -4,25 +4,96 @@ import ( "context" "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "net/http/httptest" "os" "path/filepath" + "strings" "testing" - "github.com/grafana/grafana/pkg/infra/log/logtest" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/log/logtest" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/web/webtest" ) +func Test_PluginsInstallAndUninstall(t *testing.T) { + type tc struct { + pluginAdminEnabled bool + pluginAdminExternalManageEnabled bool + expectedHTTPStatus int + expectedHTTPBody string + } + tcs := []tc{ + {pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true, expectedHTTPStatus: 404, expectedHTTPBody: "404 page not found\n"}, + {pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false, expectedHTTPStatus: 200, expectedHTTPBody: ""}, + {pluginAdminEnabled: false, pluginAdminExternalManageEnabled: true, expectedHTTPStatus: 404, expectedHTTPBody: "404 page not found\n"}, + {pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false, expectedHTTPStatus: 404, expectedHTTPBody: "404 page not found\n"}, + } + + testName := func(action string, testCase tc) string { + return fmt.Sprintf("%s request returns %d when adminEnabled: %t and externalEnabled: %t", + action, testCase.expectedHTTPStatus, testCase.pluginAdminEnabled, testCase.pluginAdminExternalManageEnabled) + } + + pm := &fakePluginManager{ + plugins: make(map[string]fakePlugin), + } + for _, tc := range tcs { + srv := SetupAPITestServer(t, func(hs *HTTPServer) { + hs.Cfg = &setting.Cfg{ + PluginAdminEnabled: tc.pluginAdminEnabled, + PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled, + } + hs.pluginManager = pm + }) + + t.Run(testName("Install", tc), func(t *testing.T) { + req := srv.NewPostRequest("/api/plugins/test/install", strings.NewReader("{ \"version\": \"1.0.2\" }")) + webtest.RequestWithSignedInUser(req, &models.SignedInUser{UserId: 1, OrgId: 1, OrgRole: models.ROLE_EDITOR, IsGrafanaAdmin: true}) + resp, err := srv.SendJSON(req) + require.NoError(t, err) + + body := new(strings.Builder) + _, err = io.Copy(body, resp.Body) + require.NoError(t, err) + require.Equal(t, tc.expectedHTTPBody, body.String()) + require.NoError(t, resp.Body.Close()) + require.Equal(t, tc.expectedHTTPStatus, resp.StatusCode) + + if tc.expectedHTTPStatus == 200 { + require.Equal(t, fakePlugin{pluginID: "test", version: "1.0.2"}, pm.plugins["test"]) + } + }) + + t.Run(testName("Uninstall", tc), func(t *testing.T) { + req := srv.NewPostRequest("/api/plugins/test/uninstall", strings.NewReader("{}")) + webtest.RequestWithSignedInUser(req, &models.SignedInUser{UserId: 1, OrgId: 1, OrgRole: models.ROLE_VIEWER, IsGrafanaAdmin: true}) + resp, err := srv.SendJSON(req) + require.NoError(t, err) + + body := new(strings.Builder) + _, err = io.Copy(body, resp.Body) + require.NoError(t, err) + require.Equal(t, tc.expectedHTTPBody, body.String()) + require.NoError(t, resp.Body.Close()) + require.Equal(t, tc.expectedHTTPStatus, resp.StatusCode) + + if tc.expectedHTTPStatus == 200 { + require.Empty(t, pm.plugins) + } + }) + } +} + func Test_GetPluginAssets(t *testing.T) { pluginID := "test-plugin" pluginDir := "." diff --git a/pkg/api/preferences.go b/pkg/api/preferences.go index e1acad29947..e13f6b07837 100644 --- a/pkg/api/preferences.go +++ b/pkg/api/preferences.go @@ -31,7 +31,7 @@ func (hs *HTTPServer) SetHomeDashboard(c *models.ReqContext) response.Response { dashboardID := cmd.HomeDashboardID if cmd.HomeDashboardUID != nil { query := models.GetDashboardQuery{Uid: *cmd.HomeDashboardUID} - err := hs.dashboardService.GetDashboard(c.Req.Context(), &query) + err := hs.DashboardService.GetDashboard(c.Req.Context(), &query) if err != nil { return response.Error(404, "Dashboard not found", err) } @@ -65,7 +65,7 @@ func (hs *HTTPServer) getPreferencesFor(ctx context.Context, orgID, userID, team // when homedashboardID is 0, that means it is the default home dashboard, no UID would be returned in the response if preference.HomeDashboardID != 0 { query := models.GetDashboardQuery{Id: preference.HomeDashboardID, OrgId: orgID} - err = hs.dashboardService.GetDashboard(ctx, &query) + err = hs.DashboardService.GetDashboard(ctx, &query) if err == nil { dashboardUID = query.Result.Uid } @@ -105,7 +105,7 @@ func (hs *HTTPServer) updatePreferencesFor(ctx context.Context, orgID, userID, t dashboardID := dtoCmd.HomeDashboardID if dtoCmd.HomeDashboardUID != nil { query := models.GetDashboardQuery{Uid: *dtoCmd.HomeDashboardUID, OrgId: orgID} - err := hs.dashboardService.GetDashboard(ctx, &query) + err := hs.DashboardService.GetDashboard(ctx, &query) if err != nil { return response.Error(404, "Dashboard not found", err) } @@ -151,7 +151,7 @@ func (hs *HTTPServer) patchPreferencesFor(ctx context.Context, orgID, userID, te dashboardID := dtoCmd.HomeDashboardID if dtoCmd.HomeDashboardUID != nil { query := models.GetDashboardQuery{Uid: *dtoCmd.HomeDashboardUID, OrgId: orgID} - err := hs.dashboardService.GetDashboard(ctx, &query) + err := hs.DashboardService.GetDashboard(ctx, &query) if err != nil { return response.Error(404, "Dashboard not found", err) } diff --git a/pkg/api/preferences_test.go b/pkg/api/preferences_test.go index e62af23b504..5796ad1e867 100644 --- a/pkg/api/preferences_test.go +++ b/pkg/api/preferences_test.go @@ -40,7 +40,7 @@ func TestAPIEndpoint_GetCurrentOrgPreferences_LegacyAccessControl(t *testing.T) q.Result = &models.Dashboard{Uid: "home", Id: 1} }).Return(nil) - sc.hs.dashboardService = dashSvc + sc.hs.DashboardService = dashSvc prefService := preftest.NewPreferenceServiceFake() prefService.ExpectedPreference = &pref.Preference{HomeDashboardID: 1, Theme: "dark"} @@ -169,7 +169,7 @@ func TestAPIEndpoint_PatchUserPreferences(t *testing.T) { q := args.Get(1).(*models.GetDashboardQuery) q.Result = &models.Dashboard{Uid: "home", Id: 1} }).Return(nil) - sc.hs.dashboardService = dashSvc + sc.hs.DashboardService = dashSvc t.Run("Returns 200 on success", func(t *testing.T) { response := callAPI(sc.server, http.MethodPatch, patchUserPreferencesUrl, input, t) assert.Equal(t, http.StatusOK, response.Code) diff --git a/pkg/api/stars.go b/pkg/api/stars.go index 73eeae19ab1..fb8e76114ac 100644 --- a/pkg/api/stars.go +++ b/pkg/api/stars.go @@ -26,7 +26,7 @@ func (hs *HTTPServer) GetStars(c *models.ReqContext) response.Response { Id: dashboardId, OrgId: c.OrgId, } - err := hs.dashboardService.GetDashboard(c.Req.Context(), query) + err := hs.DashboardService.GetDashboard(c.Req.Context(), query) // Grafana admin users may have starred dashboards in multiple orgs. This will avoid returning errors when the dashboard is in another org if err == nil { diff --git a/pkg/cmd/grafana-cli/commands/secretsmigrations/reencrypt_deks.go b/pkg/cmd/grafana-cli/commands/secretsmigrations/reencrypt_deks.go deleted file mode 100644 index ae59e519259..00000000000 --- a/pkg/cmd/grafana-cli/commands/secretsmigrations/reencrypt_deks.go +++ /dev/null @@ -1,18 +0,0 @@ -package secretsmigrations - -import ( - "context" - - "github.com/grafana/grafana/pkg/cmd/grafana-cli/runner" - "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" - "github.com/grafana/grafana/pkg/services/featuremgmt" -) - -func ReEncryptDEKS(_ utils.CommandLine, runner runner.Runner) error { - if runner.Features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { - logger.Warn("Envelope encryption is not enabled, quitting...") - return nil - } - - return runner.SecretsService.ReEncryptDataKeys(context.Background()) -} diff --git a/pkg/cmd/grafana-cli/commands/secretsmigrations/secretsmigrations.go b/pkg/cmd/grafana-cli/commands/secretsmigrations/secretsmigrations.go index fa12ec55ee0..d78d3b9a5bd 100644 --- a/pkg/cmd/grafana-cli/commands/secretsmigrations/secretsmigrations.go +++ b/pkg/cmd/grafana-cli/commands/secretsmigrations/secretsmigrations.go @@ -1,31 +1,39 @@ package secretsmigrations import ( - "encoding/base64" - "time" + "context" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/runner" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/featuremgmt" ) -type simpleSecret struct { - tableName string - columnName string -} - -type b64Secret struct { - simpleSecret - hasUpdatedColumn bool - encoding *base64.Encoding -} - -type jsonSecret struct { - tableName string -} - -type alertingSecret struct{} - -func nowInUTC() string { - return time.Now().UTC().Format("2006-01-02 15:04:05") -} - var logger = log.New("secrets.migrations") + +func ReEncryptDEKS(_ utils.CommandLine, runner runner.Runner) error { + if runner.Features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + logger.Warn("Envelope encryption is not enabled, quitting...") + return nil + } + + return runner.SecretsService.ReEncryptDataKeys(context.Background()) +} + +func ReEncryptSecrets(_ utils.CommandLine, runner runner.Runner) error { + if runner.Features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + logger.Warn("Envelope encryption is not enabled, quitting...") + return nil + } + + return runner.SecretsMigrator.ReEncryptSecrets(context.Background()) +} + +func RollBackSecrets(_ utils.CommandLine, runner runner.Runner) error { + if runner.Features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + logger.Warn("Envelope encryption is not enabled, quitting...") + return nil + } + + return runner.SecretsMigrator.RollBackSecrets(context.Background()) +} diff --git a/pkg/cmd/grafana-cli/runner/runner.go b/pkg/cmd/grafana-cli/runner/runner.go index d74b9668043..6fcb7f68ab0 100644 --- a/pkg/cmd/grafana-cli/runner/runner.go +++ b/pkg/cmd/grafana-cli/runner/runner.go @@ -3,6 +3,7 @@ package runner import ( "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" @@ -15,16 +16,20 @@ type Runner struct { Features featuremgmt.FeatureToggles EncryptionService encryption.Internal SecretsService *manager.SecretsService + SecretsMigrator secrets.Migrator } func New(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, settingsProvider setting.Provider, - encryptionService encryption.Internal, features featuremgmt.FeatureToggles, secretsService *manager.SecretsService) Runner { + encryptionService encryption.Internal, features featuremgmt.FeatureToggles, + secretsService *manager.SecretsService, secretsMigrator secrets.Migrator, +) Runner { return Runner{ Cfg: cfg, SQLStore: sqlStore, SettingsProvider: settingsProvider, EncryptionService: encryptionService, SecretsService: secretsService, + SecretsMigrator: secretsMigrator, Features: features, } } diff --git a/pkg/cmd/grafana-cli/runner/wire.go b/pkg/cmd/grafana-cli/runner/wire.go index 23807b31885..d23f46d6369 100644 --- a/pkg/cmd/grafana-cli/runner/wire.go +++ b/pkg/cmd/grafana-cli/runner/wire.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/services/secrets" secretsDatabase "github.com/grafana/grafana/pkg/services/secrets/database" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" + secretsMigrator "github.com/grafana/grafana/pkg/services/secrets/migrator" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" @@ -37,6 +38,8 @@ var wireSet = wire.NewSet( wire.Bind(new(secrets.Store), new(*secretsDatabase.SecretsStoreImpl)), secretsManager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*secretsManager.SecretsService)), + secretsMigrator.ProvideSecretsMigrator, + wire.Bind(new(secrets.Migrator), new(*secretsMigrator.SecretsMigrator)), hooks.ProvideService, ) diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index c6195b78093..72371bdab40 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -8,6 +8,8 @@ import ( _ "github.com/Azure/go-autorest/autorest" _ "github.com/Azure/go-autorest/autorest/adal" _ "github.com/beevik/etree" + _ "github.com/blugelabs/bluge" + _ "github.com/blugelabs/bluge_segment_api" _ "github.com/cortexproject/cortex/pkg/util" _ "github.com/crewjam/saml" _ "github.com/gobwas/glob" diff --git a/pkg/infra/filestorage/api.go b/pkg/infra/filestorage/api.go index 645ae462cf9..e298d6ce768 100644 --- a/pkg/infra/filestorage/api.go +++ b/pkg/infra/filestorage/api.go @@ -27,7 +27,7 @@ var ( ) func ValidatePath(path string) error { - if !filepath.IsAbs(path) { + if !strings.HasPrefix(path, Delimiter) { return ErrRelativePath } @@ -39,7 +39,8 @@ func ValidatePath(path string) error { return ErrPathEndsWithDelimiter } - if filepath.Clean(path) != path { + // apply `ToSlash` to replace OS-specific separators introduced by the Clean() function + if filepath.ToSlash(filepath.Clean(path)) != path { return ErrNonCanonicalPath } diff --git a/pkg/infra/filestorage/api_test.go b/pkg/infra/filestorage/api_test.go index f22393bdd36..4cbf2906219 100644 --- a/pkg/infra/filestorage/api_test.go +++ b/pkg/infra/filestorage/api_test.go @@ -100,6 +100,9 @@ func TestFilestorageApi_ValidatePath(t *testing.T) { { path: "/myFile/file.jpg", }, + { + path: "/file.jpg", + }, } for _, tt := range tests { if tt.expectedError == nil { diff --git a/pkg/infra/usagestats/statscollector/service.go b/pkg/infra/usagestats/statscollector/service.go index 603c19d022f..49acd8eb0e8 100644 --- a/pkg/infra/usagestats/statscollector/service.go +++ b/pkg/infra/usagestats/statscollector/service.go @@ -261,14 +261,12 @@ func (s *Service) collectElasticStats(ctx context.Context) (map[string]interface s.log.Error("Failed to get elasticsearch json data", "error", err) return nil, err } - for _, data := range esDataSourcesQuery.Result { - esVersion, err := data.JsonData.Get("esVersion").Int() + esVersion, err := data.JsonData.Get("esVersion").String() if err != nil { continue } - - statName := fmt.Sprintf("stats.ds.elasticsearch.v%d.count", esVersion) + statName := fmt.Sprintf("stats.ds.elasticsearch.v%s.count", strings.ReplaceAll(esVersion, ".", "_")) count, _ := m[statName].(int64) diff --git a/pkg/infra/usagestats/statscollector/service_test.go b/pkg/infra/usagestats/statscollector/service_test.go index 1550fb265a6..73e60b87b73 100644 --- a/pkg/infra/usagestats/statscollector/service_test.go +++ b/pkg/infra/usagestats/statscollector/service_test.go @@ -121,6 +121,24 @@ func TestFeatureUsageStats(t *testing.T) { func TestCollectingUsageStats(t *testing.T) { sqlStore := mockstore.NewSQLStoreMock() + sqlStore.ExpectedDataSources = []*datasources.DataSource{ + { + JsonData: simplejson.NewFromAny(map[string]interface{}{ + "esVersion": "2.0.0", + }), + }, + { + JsonData: simplejson.NewFromAny(map[string]interface{}{ + "esVersion": "2.0.0", + }), + }, + { + JsonData: simplejson.NewFromAny(map[string]interface{}{ + "esVersion": "70.1.1", + }), + }, + } + s := createService(t, &setting.Cfg{ ReportingEnabled: true, BuildVersion: "5.0.0", @@ -130,7 +148,8 @@ func TestCollectingUsageStats(t *testing.T) { AuthProxyEnabled: true, Packaging: "deb", ReportingDistributor: "hosted-grafana", - }, sqlStore) + }, sqlStore, + withDatasources(mockDatasourceService{datasources: sqlStore.ExpectedDataSources})) s.startTime = time.Now().Add(-1 * time.Minute) @@ -182,6 +201,45 @@ func TestCollectingUsageStats(t *testing.T) { assert.InDelta(t, int64(65), metrics["stats.uptime"], 6) } +func TestElasticStats(t *testing.T) { + sqlStore := mockstore.NewSQLStoreMock() + + s := createService(t, &setting.Cfg{ + ReportingEnabled: true, + BuildVersion: "5.0.0", + AnonymousEnabled: true, + BasicAuthEnabled: true, + LDAPEnabled: true, + AuthProxyEnabled: true, + Packaging: "deb", + ReportingDistributor: "hosted-grafana", + }, sqlStore, + withDatasources(mockDatasourceService{datasources: sqlStore.ExpectedDataSources})) + + sqlStore.ExpectedDataSources = []*datasources.DataSource{ + { + JsonData: simplejson.NewFromAny(map[string]interface{}{ + "esVersion": "2.0.0", + }), + }, + { + JsonData: simplejson.NewFromAny(map[string]interface{}{ + "esVersion": "2.0.0", + }), + }, + { + JsonData: simplejson.NewFromAny(map[string]interface{}{ + "esVersion": "70.1.1", + }), + }, + } + + metrics, err := s.collectElasticStats(context.Background()) + require.NoError(t, err) + + assert.EqualValues(t, 2, metrics["stats.ds."+datasources.DS_ES+".v2_0_0.count"]) + assert.EqualValues(t, 1, metrics["stats.ds."+datasources.DS_ES+".v70_1_1.count"]) +} func TestDatasourceStats(t *testing.T) { sqlStore := mockstore.NewSQLStoreMock() s := createService(t, &setting.Cfg{}, sqlStore) diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index b95a2c46f74..a4d903ef73b 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -192,18 +192,18 @@ func shouldForceLogin(c *models.ReqContext) bool { return forceLogin } -func OrgAdminFolderAdminOrTeamAdmin(ss sqlstore.Store, ds dashboards.DashboardService) func(c *models.ReqContext) { +func OrgAdminDashOrFolderAdminOrTeamAdmin(ss sqlstore.Store, ds dashboards.DashboardService) func(c *models.ReqContext) { return func(c *models.ReqContext) { if c.OrgRole == models.ROLE_ADMIN { return } - hasAdminPermissionInFoldersQuery := models.HasAdminPermissionInFoldersQuery{SignedInUser: c.SignedInUser} - if err := ds.HasAdminPermissionInFolders(c.Req.Context(), &hasAdminPermissionInFoldersQuery); err != nil { + hasAdminPermissionInDashOrFoldersQuery := models.HasAdminPermissionInDashboardsOrFoldersQuery{SignedInUser: c.SignedInUser} + if err := ds.HasAdminPermissionInDashboardsOrFolders(c.Req.Context(), &hasAdminPermissionInDashOrFoldersQuery); err != nil { c.JsonApiErr(500, "Failed to check if user is a folder admin", err) } - if hasAdminPermissionInFoldersQuery.Result { + if hasAdminPermissionInDashOrFoldersQuery.Result { return } diff --git a/pkg/models/dashboard_queries.go b/pkg/models/dashboard_queries.go index 0490071b116..7f27011e73c 100644 --- a/pkg/models/dashboard_queries.go +++ b/pkg/models/dashboard_queries.go @@ -4,7 +4,23 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" ) -func GetQueriesFromDashboard(dashboard *simplejson.Json) map[int64][]*simplejson.Json { +func GetUniqueDashboardDatasourceUids(dashboard *simplejson.Json) []string { + var datasourceUids []string + exists := map[string]bool{} + + for _, panelObj := range dashboard.Get("panels").MustArray() { + panel := simplejson.NewFromAny(panelObj) + uid := panel.Get("datasource").Get("uid").MustString() + if _, ok := exists[uid]; !ok { + datasourceUids = append(datasourceUids, uid) + exists[uid] = true + } + } + + return datasourceUids +} + +func GroupQueriesByPanelId(dashboard *simplejson.Json) map[int64][]*simplejson.Json { result := make(map[int64][]*simplejson.Json) for _, panelObj := range dashboard.Get("panels").MustArray() { diff --git a/pkg/models/dashboard_queries_test.go b/pkg/models/dashboard_queries_test.go index 95daa3bae22..ed02d11c88b 100644 --- a/pkg/models/dashboard_queries_test.go +++ b/pkg/models/dashboard_queries_test.go @@ -56,6 +56,79 @@ const ( "schemaVersion": 35 }` + dashboardWithDuplicateDatasources = ` +{ + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "abc123" + }, + "id": 1, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "abc123" + }, + "exemplar": true, + "expr": "go_goroutines{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Panel Title", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "_yxMP8Ynk" + }, + "id": 2, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "_yxMP8Ynk" + }, + "exemplar": true, + "expr": "go_goroutines{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Panel Title", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "_yxMP8Ynk" + }, + "id": 3, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "_yxMP8Ynk" + }, + "exemplar": true, + "expr": "go_goroutines{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Panel Title", + "type": "timeseries" + } + ], + "schemaVersion": 35 +}` + oldStyleDashboard = ` { "panels": [ @@ -79,12 +152,32 @@ const ( }` ) -func TestGetQueriesFromDashboard(t *testing.T) { +func TestGetUniqueDashboardDatasourceUids(t *testing.T) { + t.Run("can get unique datasource ids from dashboard", func(t *testing.T) { + json, err := simplejson.NewJson([]byte(dashboardWithDuplicateDatasources)) + require.NoError(t, err) + + uids := GetUniqueDashboardDatasourceUids(json) + require.Len(t, uids, 2) + require.Equal(t, "abc123", uids[0]) + require.Equal(t, "_yxMP8Ynk", uids[1]) + }) + + t.Run("can get no datasource uids from empty dashboard", func(t *testing.T) { + json, err := simplejson.NewJson([]byte(`{"panels": {}}`)) + require.NoError(t, err) + + uids := GetUniqueDashboardDatasourceUids(json) + require.Len(t, uids, 0) + }) +} + +func TestGroupQueriesByPanelId(t *testing.T) { t.Run("can extract no queries from empty dashboard", func(t *testing.T) { json, err := simplejson.NewJson([]byte(`{"panels": {}}`)) require.NoError(t, err) - queries := GetQueriesFromDashboard(json) + queries := GroupQueriesByPanelId(json) require.Len(t, queries, 0) }) @@ -92,7 +185,7 @@ func TestGetQueriesFromDashboard(t *testing.T) { json, err := simplejson.NewJson([]byte(dashboardWithNoQueries)) require.NoError(t, err) - queries := GetQueriesFromDashboard(json) + queries := GroupQueriesByPanelId(json) require.Len(t, queries, 1) require.Contains(t, queries, int64(2)) require.Len(t, queries[2], 0) @@ -102,7 +195,7 @@ func TestGetQueriesFromDashboard(t *testing.T) { json, err := simplejson.NewJson([]byte(dashboardWithQueries)) require.NoError(t, err) - queries := GetQueriesFromDashboard(json) + queries := GroupQueriesByPanelId(json) require.Len(t, queries, 1) require.Contains(t, queries, int64(2)) require.Len(t, queries[2], 2) @@ -138,7 +231,7 @@ func TestGetQueriesFromDashboard(t *testing.T) { json, err := simplejson.NewJson([]byte(oldStyleDashboard)) require.NoError(t, err) - queries := GetQueriesFromDashboard(json) + queries := GroupQueriesByPanelId(json) require.Len(t, queries, 1) require.Contains(t, queries, int64(2)) require.Len(t, queries[2], 1) diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index c10fd39e22c..a289d2c9a72 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -10,173 +10,10 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" ) const RootFolderName = "General" -// Typed errors -var ( - ErrDashboardNotFound = DashboardErr{ - Reason: "Dashboard not found", - StatusCode: 404, - Status: "not-found", - } - ErrDashboardCorrupt = DashboardErr{ - Reason: "Dashboard data is missing or corrupt", - StatusCode: 500, - Status: "not-found", - } - ErrDashboardPanelNotFound = DashboardErr{ - Reason: "Dashboard panel not found", - StatusCode: 404, - Status: "not-found", - } - ErrDashboardFolderNotFound = DashboardErr{ - Reason: "Folder not found", - StatusCode: 404, - } - ErrDashboardSnapshotNotFound = DashboardErr{ - Reason: "Dashboard snapshot not found", - StatusCode: 404, - } - ErrDashboardWithSameUIDExists = DashboardErr{ - Reason: "A dashboard with the same uid already exists", - StatusCode: 400, - } - ErrDashboardWithSameNameInFolderExists = DashboardErr{ - Reason: "A dashboard with the same name in the folder already exists", - StatusCode: 412, - Status: "name-exists", - } - ErrDashboardVersionMismatch = DashboardErr{ - Reason: "The dashboard has been changed by someone else", - StatusCode: 412, - Status: "version-mismatch", - } - ErrDashboardTitleEmpty = DashboardErr{ - Reason: "Dashboard title cannot be empty", - StatusCode: 400, - Status: "empty-name", - } - ErrDashboardFolderCannotHaveParent = DashboardErr{ - Reason: "A Dashboard Folder cannot be added to another folder", - StatusCode: 400, - } - ErrDashboardsWithSameSlugExists = DashboardErr{ - Reason: "Multiple dashboards with the same slug exists", - StatusCode: 412, - } - ErrDashboardFailedGenerateUniqueUid = DashboardErr{ - Reason: "Failed to generate unique dashboard id", - StatusCode: 500, - } - ErrDashboardTypeMismatch = DashboardErr{ - Reason: "Dashboard cannot be changed to a folder", - StatusCode: 400, - } - ErrDashboardFolderWithSameNameAsDashboard = DashboardErr{ - Reason: "Folder name cannot be the same as one of its dashboards", - StatusCode: 400, - } - ErrDashboardWithSameNameAsFolder = DashboardErr{ - Reason: "Dashboard name cannot be the same as folder", - StatusCode: 400, - Status: "name-match", - } - ErrDashboardFolderNameExists = DashboardErr{ - Reason: "A folder with that name already exists", - StatusCode: 400, - } - ErrDashboardUpdateAccessDenied = DashboardErr{ - Reason: "Access denied to save dashboard", - StatusCode: 403, - } - ErrDashboardInvalidUid = DashboardErr{ - Reason: "uid contains illegal characters", - StatusCode: 400, - } - ErrDashboardUidTooLong = DashboardErr{ - Reason: "uid too long, max 40 characters", - StatusCode: 400, - } - ErrDashboardCannotSaveProvisionedDashboard = DashboardErr{ - Reason: "Cannot save provisioned dashboard", - StatusCode: 400, - } - ErrDashboardRefreshIntervalTooShort = DashboardErr{ - Reason: "Dashboard refresh interval is too low", - StatusCode: 400, - } - ErrDashboardCannotDeleteProvisionedDashboard = DashboardErr{ - Reason: "provisioned dashboard cannot be deleted", - StatusCode: 400, - } - ErrDashboardIdentifierNotSet = DashboardErr{ - Reason: "Unique identifier needed to be able to get a dashboard", - StatusCode: 400, - } - ErrDashboardIdentifierInvalid = DashboardErr{ - Reason: "Dashboard ID not a number", - StatusCode: 400, - } - ErrDashboardPanelIdentifierInvalid = DashboardErr{ - Reason: "Dashboard panel ID not a number", - StatusCode: 400, - } - ErrDashboardOrPanelIdentifierNotSet = DashboardErr{ - Reason: "Unique identifier needed to be able to get a dashboard panel", - StatusCode: 400, - } - ErrProvisionedDashboardNotFound = DashboardErr{ - Reason: "Dashboard is not provisioned", - StatusCode: 404, - Status: "not-found", - } - ErrDashboardThumbnailNotFound = DashboardErr{ - Reason: "Dashboard thumbnail not found", - StatusCode: 404, - Status: "not-found", - } -) - -// DashboardErr represents a dashboard error. -type DashboardErr struct { - StatusCode int - Status string - Reason string -} - -// Equal returns whether equal to another DashboardErr. -func (e DashboardErr) Equal(o DashboardErr) bool { - return o.StatusCode == e.StatusCode && o.Status == e.Status && o.Reason == e.Reason -} - -// Error returns the error message. -func (e DashboardErr) Error() string { - if e.Reason != "" { - return e.Reason - } - return "Dashboard Error" -} - -// Body returns the error's response body, if applicable. -func (e DashboardErr) Body() util.DynMap { - if e.Status == "" { - return nil - } - - return util.DynMap{"status": e.Status, "message": e.Error()} -} - -type UpdatePluginDashboardError struct { - PluginId string -} - -func (d UpdatePluginDashboardError) Error() string { - return "Dashboard belongs to plugin" -} - const ( DashTypeDB = "db" DashTypeSnapshot = "snapshot" diff --git a/pkg/models/folders.go b/pkg/models/folders.go index 895cfea2a5c..63ac8b58bcc 100644 --- a/pkg/models/folders.go +++ b/pkg/models/folders.go @@ -1,24 +1,10 @@ package models import ( - "errors" "strings" "time" ) -// Typed errors -var ( - ErrFolderNotFound = errors.New("folder not found") - ErrFolderVersionMismatch = errors.New("the folder has been changed by someone else") - ErrFolderTitleEmpty = errors.New("folder title cannot be empty") - ErrFolderWithSameUIDExists = errors.New("a folder/dashboard with the same uid already exists") - ErrFolderInvalidUID = errors.New("invalid uid for folder provided") - ErrFolderSameNameExists = errors.New("a folder or dashboard in the general folder with the same name already exists") - ErrFolderFailedGenerateUniqueUid = errors.New("failed to generate unique folder ID") - ErrFolderAccessDenied = errors.New("access denied to folder") - ErrFolderContainsAlertRules = errors.New("folder contains alert rules") -) - type Folder struct { Id int64 Uid string @@ -109,7 +95,7 @@ type HasEditPermissionInFoldersQuery struct { Result bool } -type HasAdminPermissionInFoldersQuery struct { +type HasAdminPermissionInDashboardsOrFoldersQuery struct { SignedInUser *SignedInUser Result bool } diff --git a/pkg/models/org_user.go b/pkg/models/org_user.go index 0b91f064956..b92a165dc78 100644 --- a/pkg/models/org_user.go +++ b/pkg/models/org_user.go @@ -102,6 +102,9 @@ type AddOrgUserCommand struct { OrgId int64 `json:"-"` UserId int64 `json:"-"` + + // internal use: avoid adding service accounts to orgs via user routes + AllowAddingServiceAccount bool `json:"-"` } type UpdateOrgUserCommand struct { diff --git a/pkg/plugins/backendplugin/pluginextensionv2/generate.sh b/pkg/plugins/backendplugin/pluginextensionv2/generate.sh index dade69745c5..c7e2379cf79 100755 --- a/pkg/plugins/backendplugin/pluginextensionv2/generate.sh +++ b/pkg/plugins/backendplugin/pluginextensionv2/generate.sh @@ -13,4 +13,4 @@ DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" cd "$DIR" -protoc -I ./ rendererv2.proto --go_out=plugins=grpc:./ \ No newline at end of file +protoc -I ./ *.proto --go_out=plugins=grpc:./ diff --git a/pkg/plugins/backendplugin/pluginextensionv2/renderer_grpc_plugin.go b/pkg/plugins/backendplugin/pluginextensionv2/renderer_grpc_plugin.go index 6aef8a1ecd0..6dcc8e2e261 100644 --- a/pkg/plugins/backendplugin/pluginextensionv2/renderer_grpc_plugin.go +++ b/pkg/plugins/backendplugin/pluginextensionv2/renderer_grpc_plugin.go @@ -9,6 +9,7 @@ import ( type RendererPlugin interface { RendererClient + SanitizerClient } type RendererGRPCPlugin struct { @@ -20,11 +21,16 @@ func (p *RendererGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Serve } func (p *RendererGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { - return &RendererGRPCClient{NewRendererClient(c)}, nil + return &RendererGRPCClient{NewRendererClient(c), NewSanitizerClient(c)}, nil } type RendererGRPCClient struct { RendererClient + SanitizerClient +} + +func (m *RendererGRPCClient) Sanitize(ctx context.Context, req *SanitizeRequest, opts ...grpc.CallOption) (*SanitizeResponse, error) { + return m.SanitizerClient.Sanitize(ctx, req, opts...) } func (m *RendererGRPCClient) Render(ctx context.Context, req *RenderRequest, opts ...grpc.CallOption) (*RenderResponse, error) { @@ -32,4 +38,5 @@ func (m *RendererGRPCClient) Render(ctx context.Context, req *RenderRequest, opt } var _ RendererClient = &RendererGRPCClient{} +var _ SanitizerClient = &RendererGRPCClient{} var _ plugin.GRPCPlugin = &RendererGRPCPlugin{} diff --git a/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go b/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go index 75e5f4e4a31..9194ea8dcb1 100644 --- a/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go +++ b/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go @@ -1,21 +1,20 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 -// protoc v3.15.8 +// protoc-gen-go v1.28.0 +// protoc v3.19.4 // source: rendererv2.proto package pluginextensionv2 import ( context "context" - reflect "reflect" - sync "sync" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) const ( diff --git a/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.pb.go b/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.pb.go new file mode 100644 index 00000000000..b4cd30b7ab1 --- /dev/null +++ b/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.pb.go @@ -0,0 +1,337 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.19.4 +// source: sanitizer.proto + +package pluginextensionv2 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SanitizeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Filename string `protobuf:"bytes,1,opt,name=filename,proto3" json:"filename,omitempty"` + Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` + ConfigType string `protobuf:"bytes,3,opt,name=configType,proto3" json:"configType,omitempty"` // DOMPurify, ... + Config []byte `protobuf:"bytes,4,opt,name=config,proto3" json:"config,omitempty"` +} + +func (x *SanitizeRequest) Reset() { + *x = SanitizeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_sanitizer_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SanitizeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SanitizeRequest) ProtoMessage() {} + +func (x *SanitizeRequest) ProtoReflect() protoreflect.Message { + mi := &file_sanitizer_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SanitizeRequest.ProtoReflect.Descriptor instead. +func (*SanitizeRequest) Descriptor() ([]byte, []int) { + return file_sanitizer_proto_rawDescGZIP(), []int{0} +} + +func (x *SanitizeRequest) GetFilename() string { + if x != nil { + return x.Filename + } + return "" +} + +func (x *SanitizeRequest) GetContent() []byte { + if x != nil { + return x.Content + } + return nil +} + +func (x *SanitizeRequest) GetConfigType() string { + if x != nil { + return x.ConfigType + } + return "" +} + +func (x *SanitizeRequest) GetConfig() []byte { + if x != nil { + return x.Config + } + return nil +} + +type SanitizeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + Sanitized []byte `protobuf:"bytes,2,opt,name=sanitized,proto3" json:"sanitized,omitempty"` +} + +func (x *SanitizeResponse) Reset() { + *x = SanitizeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_sanitizer_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SanitizeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SanitizeResponse) ProtoMessage() {} + +func (x *SanitizeResponse) ProtoReflect() protoreflect.Message { + mi := &file_sanitizer_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SanitizeResponse.ProtoReflect.Descriptor instead. +func (*SanitizeResponse) Descriptor() ([]byte, []int) { + return file_sanitizer_proto_rawDescGZIP(), []int{1} +} + +func (x *SanitizeResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *SanitizeResponse) GetSanitized() []byte { + if x != nil { + return x.Sanitized + } + return nil +} + +var File_sanitizer_proto protoreflect.FileDescriptor + +var file_sanitizer_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x11, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, + 0x6f, 0x6e, 0x76, 0x32, 0x22, 0x7f, 0x0a, 0x0f, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x0a, + 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x46, 0x0a, 0x10, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x32, 0x60, 0x0a, + 0x09, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x53, 0x0a, 0x08, 0x53, 0x61, + 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x76, 0x32, 0x2e, 0x53, 0x61, 0x6e, 0x69, 0x74, + 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x76, 0x32, 0x2e, 0x53, + 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x15, 0x5a, 0x13, 0x2e, 0x3b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_sanitizer_proto_rawDescOnce sync.Once + file_sanitizer_proto_rawDescData = file_sanitizer_proto_rawDesc +) + +func file_sanitizer_proto_rawDescGZIP() []byte { + file_sanitizer_proto_rawDescOnce.Do(func() { + file_sanitizer_proto_rawDescData = protoimpl.X.CompressGZIP(file_sanitizer_proto_rawDescData) + }) + return file_sanitizer_proto_rawDescData +} + +var file_sanitizer_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_sanitizer_proto_goTypes = []interface{}{ + (*SanitizeRequest)(nil), // 0: pluginextensionv2.SanitizeRequest + (*SanitizeResponse)(nil), // 1: pluginextensionv2.SanitizeResponse +} +var file_sanitizer_proto_depIdxs = []int32{ + 0, // 0: pluginextensionv2.Sanitizer.Sanitize:input_type -> pluginextensionv2.SanitizeRequest + 1, // 1: pluginextensionv2.Sanitizer.Sanitize:output_type -> pluginextensionv2.SanitizeResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_sanitizer_proto_init() } +func file_sanitizer_proto_init() { + if File_sanitizer_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_sanitizer_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SanitizeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sanitizer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SanitizeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_sanitizer_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_sanitizer_proto_goTypes, + DependencyIndexes: file_sanitizer_proto_depIdxs, + MessageInfos: file_sanitizer_proto_msgTypes, + }.Build() + File_sanitizer_proto = out.File + file_sanitizer_proto_rawDesc = nil + file_sanitizer_proto_goTypes = nil + file_sanitizer_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// SanitizerClient is the client API for Sanitizer service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type SanitizerClient interface { + Sanitize(ctx context.Context, in *SanitizeRequest, opts ...grpc.CallOption) (*SanitizeResponse, error) +} + +type sanitizerClient struct { + cc grpc.ClientConnInterface +} + +func NewSanitizerClient(cc grpc.ClientConnInterface) SanitizerClient { + return &sanitizerClient{cc} +} + +func (c *sanitizerClient) Sanitize(ctx context.Context, in *SanitizeRequest, opts ...grpc.CallOption) (*SanitizeResponse, error) { + out := new(SanitizeResponse) + err := c.cc.Invoke(ctx, "/pluginextensionv2.Sanitizer/Sanitize", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SanitizerServer is the server API for Sanitizer service. +type SanitizerServer interface { + Sanitize(context.Context, *SanitizeRequest) (*SanitizeResponse, error) +} + +// UnimplementedSanitizerServer can be embedded to have forward compatible implementations. +type UnimplementedSanitizerServer struct { +} + +func (*UnimplementedSanitizerServer) Sanitize(context.Context, *SanitizeRequest) (*SanitizeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Sanitize not implemented") +} + +func RegisterSanitizerServer(s *grpc.Server, srv SanitizerServer) { + s.RegisterService(&_Sanitizer_serviceDesc, srv) +} + +func _Sanitizer_Sanitize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SanitizeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SanitizerServer).Sanitize(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pluginextensionv2.Sanitizer/Sanitize", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SanitizerServer).Sanitize(ctx, req.(*SanitizeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Sanitizer_serviceDesc = grpc.ServiceDesc{ + ServiceName: "pluginextensionv2.Sanitizer", + HandlerType: (*SanitizerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Sanitize", + Handler: _Sanitizer_Sanitize_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "sanitizer.proto", +} diff --git a/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.proto b/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.proto new file mode 100644 index 00000000000..fc2606dab7c --- /dev/null +++ b/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package pluginextensionv2; + +option go_package = ".;pluginextensionv2"; + +message SanitizeRequest { + string filename = 1; + bytes content = 2; + string configType = 3; // DOMPurify, ... + bytes config = 4; +} + +message SanitizeResponse { + string error = 1; + bytes sanitized = 2; +} + +service Sanitizer { + rpc Sanitize(SanitizeRequest) returns (SanitizeResponse); +} diff --git a/pkg/plugins/manager/loader/loader_test.go b/pkg/plugins/manager/loader/loader_test.go index b7f399c06f3..76eca25cd26 100644 --- a/pkg/plugins/manager/loader/loader_test.go +++ b/pkg/plugins/manager/loader/loader_test.go @@ -531,8 +531,8 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) { }, }, pluginErrors: map[string]*plugins.Error{ - "test": { - PluginID: "test", + "test-panel": { + PluginID: "test-panel", ErrorCode: "signatureMissing", }, }, @@ -556,6 +556,11 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) { if !cmp.Equal(got, tt.want, compareOpts) { t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts)) } + pluginErrs := l.PluginErrors() + require.Equal(t, len(tt.pluginErrors), len(pluginErrs)) + for _, pluginErr := range pluginErrs { + require.Equal(t, tt.pluginErrors[pluginErr.PluginID], pluginErr) + } }) } }) diff --git a/pkg/server/backgroundsvcs/background_services.go b/pkg/server/backgroundsvcs/background_services.go index c6970da787b..9fa9558f050 100644 --- a/pkg/server/backgroundsvcs/background_services.go +++ b/pkg/server/backgroundsvcs/background_services.go @@ -25,6 +25,7 @@ import ( secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/store" + "github.com/grafana/grafana/pkg/services/store/sanitizer" "github.com/grafana/grafana/pkg/services/thumbs" "github.com/grafana/grafana/pkg/services/updatechecker" ) @@ -41,7 +42,7 @@ func ProvideBackgroundServiceRegistry( // Need to make sure these are initialized, is there a better place to put them? _ dashboardsnapshots.Service, _ *alerting.AlertNotificationService, _ serviceaccounts.Service, _ *guardian.Provider, - _ *plugindashboardsservice.DashboardUpdater, + _ *plugindashboardsservice.DashboardUpdater, _ *sanitizer.Provider, ) *BackgroundServiceRegistry { return NewBackgroundServiceRegistry( httpServer, diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 6da86c06297..08ebbd15c0f 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -6,6 +6,7 @@ package server import ( "github.com/google/wire" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/services/store/sanitizer" "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/avatar" @@ -77,6 +78,10 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsettings" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service" "github.com/grafana/grafana/pkg/services/preference/prefimpl" + "github.com/grafana/grafana/pkg/services/publicdashboards" + publicdashboardsApi "github.com/grafana/grafana/pkg/services/publicdashboards/api" + publicdashboardsStore "github.com/grafana/grafana/pkg/services/publicdashboards/database" + publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service" "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/queryhistory" "github.com/grafana/grafana/pkg/services/quota" @@ -87,6 +92,7 @@ import ( secretsDatabase "github.com/grafana/grafana/pkg/services/secrets/database" secretsStore "github.com/grafana/grafana/pkg/services/secrets/kvstore" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" + secretsMigrator "github.com/grafana/grafana/pkg/services/secrets/migrator" "github.com/grafana/grafana/pkg/services/serviceaccounts" serviceaccountsmanager "github.com/grafana/grafana/pkg/services/serviceaccounts/manager" "github.com/grafana/grafana/pkg/services/shorturls" @@ -220,6 +226,8 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(secrets.Service), new(*secretsManager.SecretsService)), secretsDatabase.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*secretsDatabase.SecretsStoreImpl)), + secretsMigrator.ProvideSecretsMigrator, + wire.Bind(new(secrets.Migrator), new(*secretsMigrator.SecretsMigrator)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*dashsnapstore.DashboardSnapshotStore)), dashsnapstore.ProvideStore, @@ -256,6 +264,7 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(alerting.DashAlertExtractor), new(*alerting.DashAlertExtractorService)), comments.ProvideService, guardian.ProvideService, + sanitizer.ProvideService, secretsStore.ProvideService, avatar.ProvideAvatarCacheServer, authproxy.ProvideAuthProxy, @@ -272,6 +281,11 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), starimpl.ProvideService, dashverimpl.ProvideService, + publicdashboardsService.ProvideService, + wire.Bind(new(publicdashboards.Service), new(*publicdashboardsService.PublicDashboardServiceImpl)), + publicdashboardsStore.ProvideStore, + wire.Bind(new(publicdashboards.Store), new(*publicdashboardsStore.PublicDashboardStoreImpl)), + publicdashboardsApi.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, ) diff --git a/pkg/services/accesscontrol/database/database.go b/pkg/services/accesscontrol/database/database.go index c89b0801341..9e4d02f4b92 100644 --- a/pkg/services/accesscontrol/database/database.go +++ b/pkg/services/accesscontrol/database/database.go @@ -2,6 +2,7 @@ package database import ( "context" + "strconv" "strings" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -110,3 +111,46 @@ func deletePermissions(sess *sqlstore.DBSession, ids []int64) error { return nil } + +func (s *AccessControlStore) DeleteUserPermissions(ctx context.Context, userID int64) error { + err := s.sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + // Delete user role assignments + if _, err := sess.Exec("DELETE FROM user_role WHERE user_id = ?", userID); err != nil { + return err + } + + // Delete permissions that are scoped to user + if _, err := sess.Exec("DELETE FROM permission WHERE scope = ?", accesscontrol.Scope("users", "id", strconv.FormatInt(userID, 10))); err != nil { + return err + } + + var roleIDs []int64 + if err := sess.SQL("SELECT id FROM role WHERE name = ?", accesscontrol.ManagedUserRoleName(userID)).Find(&roleIDs); err != nil { + return err + } + + if len(roleIDs) == 0 { + return nil + } + + query := "DELETE FROM permission WHERE role_id IN(? " + strings.Repeat(",?", len(roleIDs)-1) + ")" + args := make([]interface{}, 0, len(roleIDs)+1) + args = append(args, query) + for _, id := range roleIDs { + args = append(args, id) + } + + // Delete managed user permissions + if _, err := sess.Exec(args...); err != nil { + return err + } + + // Delete managed user roles + if _, err := sess.Exec("DELETE FROM role WHERE name = ?", accesscontrol.ManagedUserRoleName(userID)); err != nil { + return err + } + + return nil + }) + return err +} diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go index 094f27fd01c..1531523dc67 100644 --- a/pkg/services/accesscontrol/database/database_test.go +++ b/pkg/services/accesscontrol/database/database_test.go @@ -131,6 +131,31 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { } } +func TestAccessControlStore_DeleteUserPermissions(t *testing.T) { + store, sql := setupTestEnv(t) + + user, _ := createUserAndTeam(t, sql, 1) + + _, err := store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, types.SetResourcePermissionCommand{ + Actions: []string{"dashboards:write"}, + Resource: "dashboards", + ResourceID: "1", + }, nil) + require.NoError(t, err) + + err = store.DeleteUserPermissions(context.Background(), user.ID) + require.NoError(t, err) + + permissions, err := store.GetUserPermissions(context.Background(), accesscontrol.GetUserPermissionsQuery{ + OrgID: 1, + UserID: user.ID, + Roles: []string{"Admin"}, + Actions: []string{"dashboards:write"}, + }) + require.NoError(t, err) + assert.Len(t, permissions, 0) +} + func createUserAndTeam(t *testing.T, sql *sqlstore.SQLStore, orgID int64) (*user.User, models.Team) { t.Helper() diff --git a/pkg/services/accesscontrol/database/resource_permissions.go b/pkg/services/accesscontrol/database/resource_permissions.go index ab34e1c63f3..0a39bb2663a 100644 --- a/pkg/services/accesscontrol/database/resource_permissions.go +++ b/pkg/services/accesscontrol/database/resource_permissions.go @@ -9,23 +9,25 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types" + "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/sqlstore" ) type flatResourcePermission struct { - ID int64 `xorm:"id"` - RoleName string - Action string - Scope string - UserId int64 - UserLogin string - UserEmail string - TeamId int64 - TeamEmail string - Team string - BuiltInRole string - Created time.Time - Updated time.Time + ID int64 `xorm:"id"` + RoleName string + Action string + Scope string + UserId int64 + UserLogin string + UserEmail string + UserIsServiceAccount bool + TeamId int64 + TeamEmail string + Team string + BuiltInRole string + Created time.Time + Updated time.Time } func (p *flatResourcePermission) IsManaged(scope string) bool { @@ -292,6 +294,7 @@ func (s *AccessControlStore) getResourcePermissions(sess *sqlstore.DBSession, or ur.user_id AS user_id, u.login AS user_login, u.email AS user_email, + u.is_service_account AS user_is_service_account, 0 AS team_id, '' AS team, '' AS team_email, @@ -302,6 +305,7 @@ func (s *AccessControlStore) getResourcePermissions(sess *sqlstore.DBSession, or 0 AS user_id, '' AS user_login, '' AS user_email, + false AS user_is_service_account, tr.team_id AS team_id, t.name AS team, t.email AS team_email, @@ -312,6 +316,7 @@ func (s *AccessControlStore) getResourcePermissions(sess *sqlstore.DBSession, or 0 AS user_id, '' AS user_login, '' AS user_email, + false AS user_is_service_account, 0 as team_id, '' AS team, '' AS team_email, @@ -370,8 +375,13 @@ func (s *AccessControlStore) getResourcePermissions(sess *sqlstore.DBSession, or if err != nil { return nil, err } - user := userSelect + userFrom + where + " AND " + userFilter.Where + serviceAccountFilter, err := accesscontrol.Filter(query.User, "u.id", "serviceaccounts:id:", serviceaccounts.ActionRead) + if err != nil { + return nil, err + } + user := userSelect + userFrom + where + " AND (" + userFilter.Where + " OR " + serviceAccountFilter.Where + ") " args = append(args, userFilter.Args...) + args = append(args, serviceAccountFilter.Args...) teamFilter, err := accesscontrol.Filter(query.User, "t.id", "teams:id:", accesscontrol.ActionTeamsRead) if err != nil { @@ -457,20 +467,21 @@ func flatPermissionsToResourcePermission(scope string, permissions []flatResourc first := permissions[0] return &accesscontrol.ResourcePermission{ - ID: first.ID, - RoleName: first.RoleName, - Actions: actions, - Scope: first.Scope, - UserId: first.UserId, - UserLogin: first.UserLogin, - UserEmail: first.UserEmail, - TeamId: first.TeamId, - TeamEmail: first.TeamEmail, - Team: first.Team, - BuiltInRole: first.BuiltInRole, - Created: first.Created, - Updated: first.Updated, - IsManaged: first.IsManaged(scope), + ID: first.ID, + RoleName: first.RoleName, + Actions: actions, + Scope: first.Scope, + UserId: first.UserId, + UserLogin: first.UserLogin, + UserEmail: first.UserEmail, + UserIsServiceAccount: first.UserIsServiceAccount, + TeamId: first.TeamId, + TeamEmail: first.TeamEmail, + Team: first.Team, + BuiltInRole: first.BuiltInRole, + Created: first.Created, + Updated: first.Updated, + IsManaged: first.IsManaged(scope), } } @@ -581,6 +592,7 @@ func (s *AccessControlStore) getResourcePermissionsByIds(sess *sqlstore.DBSessio ur.user_id AS user_id, u.login AS user_login, u.email AS user_email, + u.is_service_account AS user_is_service_account, tr.team_id AS team_id, t.name AS team, t.email AS team_email, diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index 0317ea73b52..85ef4f1188b 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -222,20 +222,21 @@ type ScopeParams struct { // ResourcePermission is structure that holds all actions that either a team / user / builtin-role // can perform against specific resource. type ResourcePermission struct { - ID int64 - RoleName string - Actions []string - Scope string - UserId int64 - UserLogin string - UserEmail string - TeamId int64 - TeamEmail string - Team string - BuiltInRole string - IsManaged bool - Created time.Time - Updated time.Time + ID int64 + RoleName string + Actions []string + Scope string + UserId int64 + UserLogin string + UserEmail string + UserIsServiceAccount bool + TeamId int64 + TeamEmail string + Team string + BuiltInRole string + IsManaged bool + Created time.Time + Updated time.Time } func (p *ResourcePermission) Contains(targetActions []string) bool { diff --git a/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go b/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go index a271c3bbd6f..de8da4c50b4 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go @@ -58,9 +58,10 @@ func ProvideTeamPermissions( return nil }, Assignments: resourcepermissions.Assignments{ - Users: true, - Teams: false, - BuiltInRoles: false, + Users: true, + Teams: false, + BuiltInRoles: false, + ServiceAccounts: true, }, PermissionsToActions: map[string][]string{ "Member": TeamMemberActions, @@ -149,9 +150,10 @@ func ProvideDashboardPermissions( return []string{}, nil }, Assignments: resourcepermissions.Assignments{ - Users: true, - Teams: true, - BuiltInRoles: true, + Users: true, + Teams: true, + BuiltInRoles: true, + ServiceAccounts: false, }, PermissionsToActions: map[string][]string{ "View": DashboardViewActions, @@ -206,9 +208,10 @@ func ProvideFolderPermissions( return nil }, Assignments: resourcepermissions.Assignments{ - Users: true, - Teams: true, - BuiltInRoles: true, + Users: true, + Teams: true, + BuiltInRoles: true, + ServiceAccounts: false, }, PermissionsToActions: map[string][]string{ "View": append(DashboardViewActions, FolderViewActions...), diff --git a/pkg/services/accesscontrol/resourcepermissions/api.go b/pkg/services/accesscontrol/resourcepermissions/api.go index a1892130cfd..c4d8d963ef8 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api.go +++ b/pkg/services/accesscontrol/resourcepermissions/api.go @@ -40,7 +40,7 @@ func (a *api) registerEndpoints() { scope := accesscontrol.Scope(a.service.options.Resource, a.service.options.ResourceAttribute, accesscontrol.Parameter(":resourceID")) r.Get("/description", auth(disable, accesscontrol.EvalPermission(actionRead)), routing.Wrap(a.getDescription)) r.Get("/:resourceID", inheritanceSolver, auth(disable, accesscontrol.EvalPermission(actionRead, scope)), routing.Wrap(a.getPermissions)) - if a.service.options.Assignments.Users { + if a.service.options.Assignments.Users || a.service.options.Assignments.ServiceAccounts { r.Post("/:resourceID/users/:userID", inheritanceSolver, auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission)) } if a.service.options.Assignments.Teams { @@ -53,9 +53,10 @@ func (a *api) registerEndpoints() { } type Assignments struct { - Users bool `json:"users"` - Teams bool `json:"teams"` - BuiltInRoles bool `json:"builtInRoles"` + Users bool `json:"users"` + Teams bool `json:"teams"` + BuiltInRoles bool `json:"builtInRoles"` + ServiceAccounts bool `json:"serviceAccounts"` } type Description struct { @@ -71,18 +72,19 @@ func (a *api) getDescription(c *models.ReqContext) response.Response { } type resourcePermissionDTO struct { - ID int64 `json:"id"` - RoleName string `json:"roleName"` - IsManaged bool `json:"isManaged"` - UserID int64 `json:"userId,omitempty"` - UserLogin string `json:"userLogin,omitempty"` - UserAvatarUrl string `json:"userAvatarUrl,omitempty"` - Team string `json:"team,omitempty"` - TeamID int64 `json:"teamId,omitempty"` - TeamAvatarUrl string `json:"teamAvatarUrl,omitempty"` - BuiltInRole string `json:"builtInRole,omitempty"` - Actions []string `json:"actions"` - Permission string `json:"permission"` + ID int64 `json:"id"` + RoleName string `json:"roleName"` + IsManaged bool `json:"isManaged"` + UserID int64 `json:"userId,omitempty"` + UserLogin string `json:"userLogin,omitempty"` + UserAvatarUrl string `json:"userAvatarUrl,omitempty"` + UserIsServiceAccount bool `json:"userIsServiceAccount,omitempty"` + Team string `json:"team,omitempty"` + TeamID int64 `json:"teamId,omitempty"` + TeamAvatarUrl string `json:"teamAvatarUrl,omitempty"` + BuiltInRole string `json:"builtInRole,omitempty"` + Actions []string `json:"actions"` + Permission string `json:"permission"` } func (a *api) getPermissions(c *models.ReqContext) response.Response { @@ -110,18 +112,19 @@ func (a *api) getPermissions(c *models.ReqContext) response.Response { } dto = append(dto, resourcePermissionDTO{ - ID: p.ID, - RoleName: p.RoleName, - UserID: p.UserId, - UserLogin: p.UserLogin, - UserAvatarUrl: dtos.GetGravatarUrl(p.UserEmail), - Team: p.Team, - TeamID: p.TeamId, - TeamAvatarUrl: teamAvatarUrl, - BuiltInRole: p.BuiltInRole, - Actions: p.Actions, - Permission: permission, - IsManaged: p.IsManaged, + ID: p.ID, + RoleName: p.RoleName, + UserID: p.UserId, + UserLogin: p.UserLogin, + UserAvatarUrl: dtos.GetGravatarUrl(p.UserEmail), + UserIsServiceAccount: p.UserIsServiceAccount, + Team: p.Team, + TeamID: p.TeamId, + TeamAvatarUrl: teamAvatarUrl, + BuiltInRole: p.BuiltInRole, + Actions: p.Actions, + Permission: permission, + IsManaged: p.IsManaged, }) } } diff --git a/pkg/services/accesscontrol/resourcepermissions/api_test.go b/pkg/services/accesscontrol/resourcepermissions/api_test.go index 2f32ae41b81..2178aa8d0fe 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/api_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" + "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -159,12 +160,12 @@ func TestApi_getPermissions(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, sql := setupTestEnvironment(t, tt.permissions, testOptions) + service, sql := setupTestEnvironment(t, tt.permissions, testOptionsDashboards) server := setupTestServer(t, &models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) seedPermissions(t, tt.resourceID, sql, service) - permissions, recorder := getPermission(t, server, testOptions.Resource, tt.resourceID) + permissions, recorder := getPermission(t, server, testOptionsDashboards.Resource, tt.resourceID) assert.Equal(t, tt.expectedStatus, recorder.Code) if tt.expectedStatus == http.StatusOK { @@ -236,14 +237,14 @@ func TestApi_setBuiltinRolePermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, _ := setupTestEnvironment(t, tt.permissions, testOptions) + service, _ := setupTestEnvironment(t, tt.permissions, testOptionsDashboards) server := setupTestServer(t, &models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) - recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "builtInRoles", tt.builtInRole) + recorder := setPermission(t, server, testOptionsDashboards.Resource, tt.resourceID, tt.permission, "builtInRoles", tt.builtInRole) assert.Equal(t, tt.expectedStatus, recorder.Code) if tt.expectedStatus == http.StatusOK { - permissions, _ := getPermission(t, server, testOptions.Resource, tt.resourceID) + permissions, _ := getPermission(t, server, testOptionsDashboards.Resource, tt.resourceID) require.Len(t, permissions, 1) assert.Equal(t, tt.permission, permissions[0].Permission) assert.Equal(t, tt.builtInRole, permissions[0].BuiltInRole) @@ -314,19 +315,19 @@ func TestApi_setTeamPermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, sql := setupTestEnvironment(t, tt.permissions, testOptions) + service, sql := setupTestEnvironment(t, tt.permissions, testOptionsDashboards) server := setupTestServer(t, &models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) // seed team _, err := sql.CreateTeam("test", "test@test.com", 1) require.NoError(t, err) - recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "teams", strconv.Itoa(int(tt.teamID))) + recorder := setPermission(t, server, testOptionsDashboards.Resource, tt.resourceID, tt.permission, "teams", strconv.Itoa(int(tt.teamID))) assert.Equal(t, tt.expectedStatus, recorder.Code) assert.Equal(t, tt.expectedStatus, recorder.Code) if tt.expectedStatus == http.StatusOK { - permissions, _ := getPermission(t, server, testOptions.Resource, tt.resourceID) + permissions, _ := getPermission(t, server, testOptionsDashboards.Resource, tt.resourceID) require.Len(t, permissions, 1) assert.Equal(t, tt.permission, permissions[0].Permission) assert.Equal(t, tt.teamID, permissions[0].TeamID) @@ -397,19 +398,18 @@ func TestApi_setUserPermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, sql := setupTestEnvironment(t, tt.permissions, testOptions) + service, sql := setupTestEnvironment(t, tt.permissions, testOptionsDashboards) server := setupTestServer(t, &models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) // seed user _, err := sql.CreateUser(context.Background(), user.CreateUserCommand{Login: "test", OrgID: 1}) require.NoError(t, err) - recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "users", strconv.Itoa(int(tt.userID))) + recorder := setPermission(t, server, testOptionsDashboards.Resource, tt.resourceID, tt.permission, "users", strconv.Itoa(int(tt.userID))) assert.Equal(t, tt.expectedStatus, recorder.Code) - assert.Equal(t, tt.expectedStatus, recorder.Code) if tt.expectedStatus == http.StatusOK { - permissions, _ := getPermission(t, server, testOptions.Resource, tt.resourceID) + permissions, _ := getPermission(t, server, testOptionsDashboards.Resource, tt.resourceID) require.Len(t, permissions, 1) assert.Equal(t, tt.permission, permissions[0].Permission) assert.Equal(t, tt.userID, permissions[0].UserID) @@ -418,6 +418,92 @@ func TestApi_setUserPermission(t *testing.T) { } } +type setServiceAccountPermissionTestCase struct { + desc string + serviceaccountID int64 + resourceID string + expectedStatus int + permission string + permissions []accesscontrol.Permission +} + +func TestApi_setServiceAccountPermission(t *testing.T) { + tests := []setServiceAccountPermissionTestCase{ + { + desc: "should set Edit permission for serviceaccount 1", + serviceaccountID: 1, + resourceID: "1", + expectedStatus: 200, + permission: "Edit", + permissions: []accesscontrol.Permission{ + {Action: "teams.permissions:read", Scope: "teams:id:1"}, + {Action: "teams.permissions:write", Scope: "teams:id:1"}, + {Action: accesscontrol.ActionTeamsRead, Scope: accesscontrol.ScopeTeamsAll}, + {Action: serviceaccounts.ActionRead, Scope: serviceaccounts.ScopeAll}, + }, + }, + { + desc: "should set View permission for serviceaccount 1", + serviceaccountID: 1, + resourceID: "1", + expectedStatus: 200, + permission: "View", + permissions: []accesscontrol.Permission{ + {Action: "teams.permissions:read", Scope: "teams:id:1"}, + {Action: "teams.permissions:write", Scope: "teams:id:1"}, + {Action: accesscontrol.ActionTeamsRead, Scope: accesscontrol.ScopeTeamsAll}, + {Action: serviceaccounts.ActionRead, Scope: serviceaccounts.ScopeAll}, + }, + }, + { + desc: "should set return http 400 when serviceaccount does not exist", + serviceaccountID: 2, + resourceID: "1", + expectedStatus: http.StatusBadRequest, + permission: "View", + permissions: []accesscontrol.Permission{ + {Action: "teams.permissions:read", Scope: "teams:id:1"}, + {Action: "teams.permissions:write", Scope: "teams:id:1"}, + {Action: accesscontrol.ActionTeamsRead, Scope: accesscontrol.ScopeTeamsAll}, + {Action: serviceaccounts.ActionRead, Scope: serviceaccounts.ScopeAll}, + }, + }, + { + desc: "should return http 403 when missing permissions", + serviceaccountID: 1, + resourceID: "1", + expectedStatus: http.StatusForbidden, + permission: "View", + permissions: []accesscontrol.Permission{ + {Action: "teams.permissions:read", Scope: "teams:id:1"}, + {Action: accesscontrol.ActionTeamsRead, Scope: accesscontrol.ScopeTeamsAll}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + service, sql := setupTestEnvironment(t, tt.permissions, testOptionsTeams) + server := setupTestServer(t, &models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) + + // seed serviceaccount + _, err := sql.CreateUser(context.Background(), user.CreateUserCommand{Login: "test", OrgID: 1, IsServiceAccount: true}) + require.NoError(t, err) + + recorder := setPermission(t, server, testOptionsTeams.Resource, tt.resourceID, tt.permission, "users", strconv.Itoa(int(tt.serviceaccountID))) + assert.Equal(t, tt.expectedStatus, recorder.Code) + + if tt.expectedStatus == http.StatusOK { + permissions, _ := getPermission(t, server, testOptionsTeams.Resource, tt.resourceID) + require.Len(t, permissions, 1) + assert.Equal(t, tt.permission, permissions[0].Permission) + assert.Equal(t, tt.serviceaccountID, permissions[0].UserID) + assert.Equal(t, true, permissions[0].UserIsServiceAccount) + } + }) + } +} + func setupTestServer(t *testing.T, user *models.SignedInUser, service *Service) *web.Mux { server := web.New() server.UseMiddleware(web.Renderer(path.Join(setting.StaticRootPath, "views"), "[[", "]]")) @@ -444,13 +530,14 @@ func contextProvider(tc *testContext) web.Handler { } } -var testOptions = Options{ +var testOptionsDashboards = Options{ Resource: "dashboards", ResourceAttribute: "id", Assignments: Assignments{ - Users: true, - Teams: true, - BuiltInRoles: true, + Users: true, + Teams: true, + BuiltInRoles: true, + ServiceAccounts: true, }, PermissionsToActions: map[string][]string{ "View": {"dashboards:read"}, @@ -458,6 +545,21 @@ var testOptions = Options{ }, } +var testOptionsTeams = Options{ + Resource: "teams", + ResourceAttribute: "id", + Assignments: Assignments{ + Users: true, + Teams: true, + BuiltInRoles: true, + ServiceAccounts: true, + }, + PermissionsToActions: map[string][]string{ + "View": {"teams:read"}, + "Edit": {"teams:read", "teams:write", "teams:delete"}, + }, +} + func getPermission(t *testing.T, server *web.Mux, resource, resourceID string) ([]resourcePermissionDTO, *httptest.ResponseRecorder) { req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/access-control/%s/%s", resource, resourceID), nil) require.NoError(t, err) diff --git a/pkg/services/accesscontrol/resourcepermissions/service.go b/pkg/services/accesscontrol/resourcepermissions/service.go index d8ae4903963..2722dba149a 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service.go +++ b/pkg/services/accesscontrol/resourcepermissions/service.go @@ -275,7 +275,7 @@ func (s *Service) validateResource(ctx context.Context, orgID int64, resourceID } func (s *Service) validateUser(ctx context.Context, orgID, userID int64) error { - if !s.options.Assignments.Users { + if !(s.options.Assignments.Users || s.options.Assignments.ServiceAccounts) { return ErrInvalidAssignment } diff --git a/pkg/services/accesscontrol/resourcepermissions/service_test.go b/pkg/services/accesscontrol/resourcepermissions/service_test.go index 8775502ab99..be8a18c5db8 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/service_test.go @@ -38,7 +38,7 @@ func TestService_SetUserPermission(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { service, sql := setupTestEnvironment(t, []accesscontrol.Permission{}, Options{ Resource: "dashboards", - Assignments: Assignments{Users: true}, + Assignments: Assignments{Users: true, ServiceAccounts: true}, PermissionsToActions: nil, }) @@ -159,9 +159,10 @@ func TestService_SetPermissions(t *testing.T) { options: Options{ Resource: "dashboards", Assignments: Assignments{ - Users: true, - Teams: true, - BuiltInRoles: true, + Users: true, + Teams: true, + BuiltInRoles: true, + ServiceAccounts: true, }, PermissionsToActions: map[string][]string{ "View": {"dashboards:read"}, @@ -178,9 +179,10 @@ func TestService_SetPermissions(t *testing.T) { options: Options{ Resource: "dashboards", Assignments: Assignments{ - Users: true, - Teams: true, - BuiltInRoles: true, + Users: true, + Teams: true, + BuiltInRoles: true, + ServiceAccounts: true, }, PermissionsToActions: map[string][]string{ "View": {"dashboards:read"}, diff --git a/pkg/services/alerting/notifier_test.go b/pkg/services/alerting/notifier_test.go index b55e15870cc..29858fb65fa 100644 --- a/pkg/services/alerting/notifier_test.go +++ b/pkg/services/alerting/notifier_test.go @@ -339,6 +339,10 @@ type testRenderService struct { renderErrorImageProvider func(error error) (*rendering.RenderResult, error) } +func (s *testRenderService) SanitizeSVG(ctx context.Context, req *rendering.SanitizeSVGRequest) (*rendering.SanitizeSVGResponse, error) { + return &rendering.SanitizeSVGResponse{Sanitized: req.Content}, nil +} + func (s *testRenderService) HasCapability(feature rendering.CapabilityName) (rendering.CapabilitySupportRequestResult, error) { return rendering.CapabilitySupportRequestResult{}, nil } diff --git a/pkg/services/dashboards/accesscontrol_test.go b/pkg/services/dashboards/accesscontrol_test.go index dc8f2ba5589..d9e57e44657 100644 --- a/pkg/services/dashboards/accesscontrol_test.go +++ b/pkg/services/dashboards/accesscontrol_test.go @@ -64,12 +64,12 @@ func TestNewFolderNameScopeResolver(t *testing.T) { _, resolver := NewFolderNameScopeResolver(dashboardStore) orgId := rand.Int63() - dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrDashboardNotFound).Once() + dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once() scope := "folders:name:" + util.GenerateShortUID() resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) - require.ErrorIs(t, err, models.ErrDashboardNotFound) + require.ErrorIs(t, err, ErrDashboardNotFound) require.Nil(t, resolvedScopes) }) } @@ -136,11 +136,11 @@ func TestNewFolderIDScopeResolver(t *testing.T) { _, resolver := NewFolderIDScopeResolver(dashboardStore) orgId := rand.Int63() - dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrDashboardNotFound).Once() + dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once() scope := "folders:id:10" resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) - require.ErrorIs(t, err, models.ErrDashboardNotFound) + require.ErrorIs(t, err, ErrDashboardNotFound) require.Nil(t, resolvedScopes) }) } diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index 9ccdc716346..b3a1b3cffe3 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -3,14 +3,12 @@ package dashboards import ( "context" - "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/models" ) //go:generate mockery --name DashboardService --structname FakeDashboardService --inpackage --filename dashboard_service_mock.go // DashboardService is a service for operating on dashboards. type DashboardService interface { - BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error FindDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) @@ -19,14 +17,11 @@ type DashboardService interface { GetDashboards(ctx context.Context, query *models.GetDashboardsQuery) error GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error GetDashboardUIDById(ctx context.Context, query *models.GetDashboardRefByIdQuery) error - GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error) - GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) - HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error + HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*models.Dashboard, error) MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*models.Dashboard, error) - SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error } @@ -65,18 +60,13 @@ type Store interface { GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) GetProvisionedDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error) GetProvisionedDataByDashboardUID(orgID int64, dashboardUID string) (*models.DashboardProvisioning, error) - GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) - GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) - GenerateNewPublicDashboardUid(ctx context.Context) (string, error) - HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error + HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error // SaveAlerts saves dashboard alerts. SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error) SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) - SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error) UnprovisionDashboard(ctx context.Context, id int64) error - UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error // ValidateDashboardBeforeSave validates a dashboard before save. ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) diff --git a/pkg/services/dashboards/dashboard_service_mock.go b/pkg/services/dashboards/dashboard_service_mock.go index e486c1126d1..8e42d531998 100644 --- a/pkg/services/dashboards/dashboard_service_mock.go +++ b/pkg/services/dashboards/dashboard_service_mock.go @@ -1,14 +1,12 @@ -// Code generated by mockery v2.12.1. DO NOT EDIT. +// Code generated by mockery v2.12.2. DO NOT EDIT. package dashboards import ( context "context" - dtos "github.com/grafana/grafana/pkg/api/dtos" - mock "github.com/stretchr/testify/mock" - models "github.com/grafana/grafana/pkg/models" + mock "github.com/stretchr/testify/mock" testing "testing" ) @@ -18,27 +16,6 @@ type FakeDashboardService struct { mock.Mock } -// BuildPublicDashboardMetricRequest provides a mock function with given fields: ctx, dashboard, publicDashboard, panelId -func (_m *FakeDashboardService) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) { - ret := _m.Called(ctx, dashboard, publicDashboard, panelId) - - var r0 dtos.MetricRequest - if rf, ok := ret.Get(0).(func(context.Context, *models.Dashboard, *models.PublicDashboard, int64) dtos.MetricRequest); ok { - r0 = rf(ctx, dashboard, publicDashboard, panelId) - } else { - r0 = ret.Get(0).(dtos.MetricRequest) - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *models.Dashboard, *models.PublicDashboard, int64) error); ok { - r1 = rf(ctx, dashboard, publicDashboard, panelId) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // BuildSaveDashboardCommand provides a mock function with given fields: ctx, dto, shouldValidateAlerts, validateProvisionedDashboard func (_m *FakeDashboardService) BuildSaveDashboardCommand(ctx context.Context, dto *SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*models.SaveDashboardCommand, error) { ret := _m.Called(ctx, dto, shouldValidateAlerts, validateProvisionedDashboard) @@ -169,58 +146,12 @@ func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *models return r0 } -// GetPublicDashboard provides a mock function with given fields: ctx, accessToken -func (_m *FakeDashboardService) GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error) { - ret := _m.Called(ctx, accessToken) - - var r0 *models.Dashboard - if rf, ok := ret.Get(0).(func(context.Context, string) *models.Dashboard); ok { - r0 = rf(ctx, accessToken) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*models.Dashboard) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, accessToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid -func (_m *FakeDashboardService) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { - ret := _m.Called(ctx, orgId, dashboardUid) - - var r0 *models.PublicDashboard - if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboard); ok { - r0 = rf(ctx, orgId, dashboardUid) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*models.PublicDashboard) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok { - r1 = rf(ctx, orgId, dashboardUid) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// HasAdminPermissionInFolders provides a mock function with given fields: ctx, query -func (_m *FakeDashboardService) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error { +// HasAdminPermissionInDashboardsOrFolders provides a mock function with given fields: ctx, query +func (_m *FakeDashboardService) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error { ret := _m.Called(ctx, query) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *models.HasAdminPermissionInFoldersQuery) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *models.HasAdminPermissionInDashboardsOrFoldersQuery) error); ok { r0 = rf(ctx, query) } else { r0 = ret.Error(0) @@ -303,29 +234,6 @@ func (_m *FakeDashboardService) SaveDashboard(ctx context.Context, dto *SaveDash return r0, r1 } -// SavePublicDashboardConfig provides a mock function with given fields: ctx, dto -func (_m *FakeDashboardService) SavePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) { - ret := _m.Called(ctx, dto) - - var r0 *models.PublicDashboard - if rf, ok := ret.Get(0).(func(context.Context, *SavePublicDashboardConfigDTO) *models.PublicDashboard); ok { - r0 = rf(ctx, dto) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*models.PublicDashboard) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *SavePublicDashboardConfigDTO) error); ok { - r1 = rf(ctx, dto) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // SearchDashboards provides a mock function with given fields: ctx, query func (_m *FakeDashboardService) SearchDashboards(ctx context.Context, query *models.FindPersistedDashboardsQuery) error { ret := _m.Called(ctx, query) diff --git a/pkg/services/dashboards/database/acl.go b/pkg/services/dashboards/database/acl.go index 940a2ab7aad..32f98bc6f6b 100644 --- a/pkg/services/dashboards/database/acl.go +++ b/pkg/services/dashboards/database/acl.go @@ -123,7 +123,7 @@ func (d *DashboardStore) HasEditPermissionInFolders(ctx context.Context, query * }) } -func (d *DashboardStore) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error { +func (d *DashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error { return d.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { if query.SignedInUser.HasRole(models.ROLE_ADMIN) { query.Result = true @@ -131,7 +131,7 @@ func (d *DashboardStore) HasAdminPermissionInFolders(ctx context.Context, query } builder := &sqlstore.SQLBuilder{} - builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, d.dialect.BooleanStr(true)) + builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ?", query.SignedInUser.OrgId) builder.WriteDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_ADMIN) type folderCount struct { diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 847654c416c..41b3fb271bf 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -60,7 +60,7 @@ func (d *DashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*models.Folder, error) { if title == "" { - return nil, models.ErrFolderTitleEmpty + return nil, dashboards.ErrFolderTitleEmpty } // there is a unique constraint on org_id, folder_id, title @@ -72,7 +72,7 @@ func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, titl return err } if !has { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } dashboard.SetId(dashboard.Id) dashboard.SetUid(dashboard.Uid) @@ -89,7 +89,7 @@ func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int6 return err } if !has { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } dashboard.SetId(dashboard.Id) dashboard.SetUid(dashboard.Uid) @@ -103,7 +103,7 @@ func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int6 func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*models.Folder, error) { if uid == "" { - return nil, models.ErrDashboardIdentifierNotSet + return nil, dashboards.ErrDashboardIdentifierNotSet } dashboard := models.Dashboard{OrgId: orgID, FolderId: 0, Uid: uid} @@ -113,7 +113,7 @@ func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid st return err } if !has { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } dashboard.SetId(dashboard.Id) dashboard.SetUid(dashboard.Uid) @@ -147,15 +147,14 @@ func (d *DashboardStore) GetProvisionedDataByDashboardUID(orgID int64, dashboard return err } if !exists { - return models. - ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } exists, err = sess.Where("dashboard_id = ?", dashboard.Id).Get(&provisionedDashboard) if err != nil { return err } if !exists { - return models.ErrProvisionedDashboardNotFound + return dashboards.ErrProvisionedDashboardNotFound } return nil }) @@ -267,7 +266,7 @@ func (d *DashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context for _, deleteDashCommand := range result { err := d.DeleteDashboard(ctx, &models.DeleteDashboardCommand{Id: deleteDashCommand.DashboardId}) - if err != nil && !errors.Is(err, models.ErrDashboardNotFound) { + if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) { return err } } @@ -289,7 +288,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode } if !dashWithIdExists { - return false, models.ErrDashboardNotFound + return false, dashboards.ErrDashboardNotFound } if dash.Uid == "" { @@ -317,7 +316,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode } if !folderExists { - return false, models.ErrDashboardFolderNotFound + return false, dashboards.ErrDashboardFolderNotFound } } @@ -326,7 +325,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode } if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id { - return false, models.ErrDashboardWithSameUIDExists + return false, dashboards.ErrDashboardWithSameUIDExists } existing := existingById @@ -339,7 +338,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode if (existing.IsFolder && !dash.IsFolder) || (!existing.IsFolder && dash.IsFolder) { - return isParentFolderChanged, models.ErrDashboardTypeMismatch + return isParentFolderChanged, dashboards.ErrDashboardTypeMismatch } if !dash.IsFolder && dash.FolderId != existing.FolderId { @@ -351,13 +350,13 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode if overwrite { dash.SetVersion(existing.Version) } else { - return isParentFolderChanged, models.ErrDashboardVersionMismatch + return isParentFolderChanged, dashboards.ErrDashboardVersionMismatch } } // do not allow plugin dashboard updates without overwrite flag if existing.PluginId != "" && !overwrite { - return isParentFolderChanged, models.UpdatePluginDashboardError{PluginId: existing.PluginId} + return isParentFolderChanged, dashboards.UpdatePluginDashboardError{PluginId: existing.PluginId} } return isParentFolderChanged, nil @@ -374,11 +373,11 @@ func getExistingDashboardByTitleAndFolder(sess *sqlstore.DBSession, dash *models if exists && dash.Id != existing.Id { if existing.IsFolder && !dash.IsFolder { - return isParentFolderChanged, models.ErrDashboardWithSameNameAsFolder + return isParentFolderChanged, dashboards.ErrDashboardWithSameNameAsFolder } if !existing.IsFolder && dash.IsFolder { - return isParentFolderChanged, models.ErrDashboardFolderWithSameNameAsDashboard + return isParentFolderChanged, dashboards.ErrDashboardFolderWithSameNameAsDashboard } if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) { @@ -390,7 +389,7 @@ func getExistingDashboardByTitleAndFolder(sess *sqlstore.DBSession, dash *models dash.SetUid(existing.Uid) dash.SetVersion(existing.Version) } else { - return isParentFolderChanged, models.ErrDashboardWithSameNameInFolderExists + return isParentFolderChanged, dashboards.ErrDashboardWithSameNameInFolderExists } } @@ -413,7 +412,7 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e return err } if !dashWithIdExists { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } // check for is someone else has written in between @@ -421,13 +420,13 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e if cmd.Overwrite { dash.SetVersion(existing.Version) } else { - return models.ErrDashboardVersionMismatch + return dashboards.ErrDashboardVersionMismatch } } // do not allow plugin dashboard updates without overwrite flag if existing.PluginId != "" && !cmd.Overwrite { - return models.UpdatePluginDashboardError{PluginId: existing.PluginId} + return dashboards.UpdatePluginDashboardError{PluginId: existing.PluginId} } } @@ -470,7 +469,7 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e } if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } dashVersion := &dashver.DashboardVersion{ @@ -488,7 +487,7 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e if affectedRows, err = sess.Insert(dashVersion); err != nil { return err } else if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } // delete existing tags @@ -525,7 +524,7 @@ func generateNewDashboardUid(sess *sqlstore.DBSession, orgId int64) (string, err } } - return "", models.ErrDashboardFailedGenerateUniqueUid + return "", dashboards.ErrDashboardFailedGenerateUniqueUid } func saveProvisionedData(sess *sqlstore.DBSession, provisioning *models.DashboardProvisioning, dashboard *models.Dashboard) error { @@ -709,7 +708,7 @@ func (d *DashboardStore) deleteDashboard(cmd *models.DeleteDashboardCommand, ses if err != nil { return err } else if !has { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } deletes := []string{ @@ -779,7 +778,7 @@ func (d *DashboardStore) deleteDashboard(cmd *models.DeleteDashboardCommand, ses } if exists { if !cmd.ForceDeleteFolderRules { - return fmt.Errorf("folder cannot be deleted: %w", models.ErrFolderContainsAlertRules) + return fmt.Errorf("folder cannot be deleted: %w", dashboards.ErrFolderContainsAlertRules) } // Delete all rules under this folder. @@ -836,7 +835,7 @@ func (d *DashboardStore) deleteAlertDefinition(dashboardId int64, sess *sqlstore func (d *DashboardStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) (*models.Dashboard, error) { err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { if query.Id == 0 && len(query.Slug) == 0 && len(query.Uid) == 0 { - return models.ErrDashboardIdentifierNotSet + return dashboards.ErrDashboardIdentifierNotSet } dashboard := models.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid} @@ -845,7 +844,7 @@ func (d *DashboardStore) GetDashboard(ctx context.Context, query *models.GetDash if err != nil { return err } else if !has { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } dashboard.SetId(dashboard.Id) @@ -865,7 +864,7 @@ func (d *DashboardStore) GetDashboardUIDById(ctx context.Context, query *models. if err != nil { return err } else if !exists { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } query.Result = us return nil diff --git a/pkg/services/dashboards/database/database_dashboard_public.go b/pkg/services/dashboards/database/database_dashboard_public.go deleted file mode 100644 index 0c5ff91aa70..00000000000 --- a/pkg/services/dashboards/database/database_dashboard_public.go +++ /dev/null @@ -1,136 +0,0 @@ -package database - -import ( - "context" - - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/util" -) - -// retrieves public dashboard configuration -func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) { - if accessToken == "" { - return nil, nil, models.ErrPublicDashboardIdentifierNotSet - } - - // get public dashboard - pdRes := &models.PublicDashboard{AccessToken: accessToken} - err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - has, err := sess.Get(pdRes) - if err != nil { - return err - } - if !has { - return models.ErrPublicDashboardNotFound - } - return nil - }) - - if err != nil { - return nil, nil, err - } - - // find dashboard - dashRes := &models.Dashboard{OrgId: pdRes.OrgId, Uid: pdRes.DashboardUid} - err = d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - has, err := sess.Get(dashRes) - if err != nil { - return err - } - if !has { - return models.ErrPublicDashboardNotFound - } - return nil - }) - - if err != nil { - return nil, nil, err - } - - return pdRes, dashRes, err -} - -// generates a new unique uid to retrieve a public dashboard -func (d *DashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (string, error) { - var uid string - - err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { - for i := 0; i < 3; i++ { - uid = util.GenerateShortUID() - - exists, err := sess.Get(&models.PublicDashboard{Uid: uid}) - if err != nil { - return err - } - - if !exists { - return nil - } - } - - return models.ErrPublicDashboardFailedGenerateUniqueUid - }) - - if err != nil { - return "", err - } - - return uid, nil -} - -// retrieves public dashboard configuration -func (d *DashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { - if dashboardUid == "" { - return nil, models.ErrDashboardIdentifierNotSet - } - - pdRes := &models.PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid} - err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - // publicDashboard - _, err := sess.Get(pdRes) - if err != nil { - return err - } - - return nil - }) - - if err != nil { - return nil, err - } - - return pdRes, err -} - -// persists public dashboard configuration -func (d *DashboardStore) SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error) { - err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - _, err := sess.UseBool("is_enabled").Insert(&cmd.PublicDashboard) - if err != nil { - return err - } - - return nil - }) - - if err != nil { - return nil, err - } - - return &cmd.PublicDashboard, nil -} - -// updates existing public dashboard configuration -func (d *DashboardStore) UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error { - err := d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - _, err := sess.ID(cmd.PublicDashboard.Uid).UseBool("is_enabled").Update(&cmd.PublicDashboard) - if err != nil { - return err - } - - return nil - }) - - return err -} diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index 5d1378d65f1..a967863eec8 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/user" ) @@ -321,10 +322,10 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) t.Run("should have admin permission in folders", func(t *testing.T) { - query := &models.HasAdminPermissionInFoldersQuery{ + query := &models.HasAdminPermissionInDashboardsOrFoldersQuery{ SignedInUser: &models.SignedInUser{UserId: adminUser.ID, OrgId: 1, OrgRole: models.ROLE_ADMIN}, } - err := dashboardStore.HasAdminPermissionInFolders(context.Background(), query) + err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) require.NoError(t, err) require.True(t, query.Result) }) @@ -369,10 +370,10 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) t.Run("should not have admin permission in folders", func(t *testing.T) { - query := &models.HasAdminPermissionInFoldersQuery{ + query := &models.HasAdminPermissionInDashboardsOrFoldersQuery{ SignedInUser: &models.SignedInUser{UserId: adminUser.ID, OrgId: 1, OrgRole: models.ROLE_EDITOR}, } - err := dashboardStore.HasAdminPermissionInFolders(context.Background(), query) + err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) require.NoError(t, err) require.False(t, query.Result) }) @@ -417,10 +418,10 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) t.Run("should not have admin permission in folders", func(t *testing.T) { - query := &models.HasAdminPermissionInFoldersQuery{ + query := &models.HasAdminPermissionInDashboardsOrFoldersQuery{ SignedInUser: &models.SignedInUser{UserId: adminUser.ID, OrgId: 1, OrgRole: models.ROLE_VIEWER}, } - err := dashboardStore.HasAdminPermissionInFolders(context.Background(), query) + err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) require.NoError(t, err) require.False(t, query.Result) }) @@ -492,12 +493,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("should not find dashboard", func(t *testing.T) { d, err := dashboardStore.GetFolderByUID(context.Background(), orgId, dash.Uid) require.Nil(t, d) - require.ErrorIs(t, err, models.ErrFolderNotFound) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) }) t.Run("should search in organization", func(t *testing.T) { d, err := dashboardStore.GetFolderByUID(context.Background(), orgId+1, folder.Uid) require.Nil(t, d) - require.ErrorIs(t, err, models.ErrFolderNotFound) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) }) }) @@ -516,12 +517,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("should not find dashboard", func(t *testing.T) { d, err := dashboardStore.GetFolderByID(context.Background(), orgId, dash.Id) require.Nil(t, d) - require.ErrorIs(t, err, models.ErrFolderNotFound) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) }) t.Run("should search in organization", func(t *testing.T) { d, err := dashboardStore.GetFolderByID(context.Background(), orgId+1, folder.Id) require.Nil(t, d) - require.ErrorIs(t, err, models.ErrFolderNotFound) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) }) }) }) diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 3f692f26f45..d0332cebc99 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -124,7 +124,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { } _, err := dashboardStore.GetDashboard(context.Background(), &query) - require.Equal(t, err, models.ErrDashboardIdentifierNotSet) + require.Equal(t, err, dashboards.ErrDashboardIdentifierNotSet) }) t.Run("Should be able to get dashboards by IDs & UIDs", func(t *testing.T) { @@ -227,7 +227,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { setup() deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: false} err := dashboardStore.DeleteDashboard(context.Background(), deleteCmd) - require.True(t, errors.Is(err, models.ErrFolderContainsAlertRules)) + require.True(t, errors.Is(err, dashboards.ErrFolderContainsAlertRules)) }) t.Run("Should be able to delete a dashboard folder and its children if force delete rules is enabled", func(t *testing.T) { @@ -274,7 +274,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { } _, err := dashboardStore.SaveDashboard(cmd) - require.Equal(t, err, models.ErrDashboardNotFound) + require.Equal(t, err, dashboards.ErrDashboardNotFound) }) t.Run("Should not return error if no dashboard is found for update when dashboard id is zero", func(t *testing.T) { diff --git a/pkg/services/dashboards/errors.go b/pkg/services/dashboards/errors.go new file mode 100644 index 00000000000..4ee627bcd74 --- /dev/null +++ b/pkg/services/dashboards/errors.go @@ -0,0 +1,197 @@ +package dashboards + +import ( + "errors" + + "github.com/grafana/grafana/pkg/util" +) + +// Typed errors +var ( + ErrDashboardNotFound = DashboardErr{ + Reason: "Dashboard not found", + StatusCode: 404, + Status: "not-found", + } + ErrDashboardCorrupt = DashboardErr{ + Reason: "Dashboard data is missing or corrupt", + StatusCode: 500, + Status: "not-found", + } + ErrDashboardPanelNotFound = DashboardErr{ + Reason: "Dashboard panel not found", + StatusCode: 404, + Status: "not-found", + } + ErrDashboardFolderNotFound = DashboardErr{ + Reason: "Folder not found", + StatusCode: 404, + } + ErrDashboardWithSameUIDExists = DashboardErr{ + Reason: "A dashboard with the same uid already exists", + StatusCode: 400, + } + ErrDashboardWithSameNameInFolderExists = DashboardErr{ + Reason: "A dashboard with the same name in the folder already exists", + StatusCode: 412, + Status: "name-exists", + } + ErrDashboardVersionMismatch = DashboardErr{ + Reason: "The dashboard has been changed by someone else", + StatusCode: 412, + Status: "version-mismatch", + } + ErrDashboardTitleEmpty = DashboardErr{ + Reason: "Dashboard title cannot be empty", + StatusCode: 400, + Status: "empty-name", + } + ErrDashboardFolderCannotHaveParent = DashboardErr{ + Reason: "A Dashboard Folder cannot be added to another folder", + StatusCode: 400, + } + ErrDashboardsWithSameSlugExists = DashboardErr{ + Reason: "Multiple dashboards with the same slug exists", + StatusCode: 412, + } + ErrDashboardFailedGenerateUniqueUid = DashboardErr{ + Reason: "Failed to generate unique dashboard id", + StatusCode: 500, + } + ErrDashboardTypeMismatch = DashboardErr{ + Reason: "Dashboard cannot be changed to a folder", + StatusCode: 400, + } + ErrDashboardFolderWithSameNameAsDashboard = DashboardErr{ + Reason: "Folder name cannot be the same as one of its dashboards", + StatusCode: 400, + } + ErrDashboardWithSameNameAsFolder = DashboardErr{ + Reason: "Dashboard name cannot be the same as folder", + StatusCode: 400, + Status: "name-match", + } + ErrDashboardFolderNameExists = DashboardErr{ + Reason: "A folder with that name already exists", + StatusCode: 400, + } + ErrDashboardUpdateAccessDenied = DashboardErr{ + Reason: "Access denied to save dashboard", + StatusCode: 403, + } + ErrDashboardInvalidUid = DashboardErr{ + Reason: "uid contains illegal characters", + StatusCode: 400, + } + ErrDashboardUidTooLong = DashboardErr{ + Reason: "uid too long, max 40 characters", + StatusCode: 400, + } + ErrDashboardCannotSaveProvisionedDashboard = DashboardErr{ + Reason: "Cannot save provisioned dashboard", + StatusCode: 400, + } + ErrDashboardRefreshIntervalTooShort = DashboardErr{ + Reason: "Dashboard refresh interval is too low", + StatusCode: 400, + } + ErrDashboardCannotDeleteProvisionedDashboard = DashboardErr{ + Reason: "provisioned dashboard cannot be deleted", + StatusCode: 400, + } + ErrDashboardIdentifierNotSet = DashboardErr{ + Reason: "Unique identifier needed to be able to get a dashboard", + StatusCode: 400, + } + ErrDashboardIdentifierInvalid = DashboardErr{ + Reason: "Dashboard ID not a number", + StatusCode: 400, + } + ErrDashboardPanelIdentifierInvalid = DashboardErr{ + Reason: "Dashboard panel ID not a number", + StatusCode: 400, + } + ErrDashboardOrPanelIdentifierNotSet = DashboardErr{ + Reason: "Unique identifier needed to be able to get a dashboard panel", + StatusCode: 400, + } + ErrProvisionedDashboardNotFound = DashboardErr{ + Reason: "Dashboard is not provisioned", + StatusCode: 404, + Status: "not-found", + } + ErrDashboardThumbnailNotFound = DashboardErr{ + Reason: "Dashboard thumbnail not found", + StatusCode: 404, + Status: "not-found", + } + ErrPublicDashboardFailedGenerateUniqueUid = DashboardErr{ + Reason: "Failed to generate unique public dashboard id", + StatusCode: 500, + } + ErrPublicDashboardFailedGenerateAccesstoken = DashboardErr{ + Reason: "Failed to public dashboard access token", + StatusCode: 500, + } + ErrPublicDashboardNotFound = DashboardErr{ + Reason: "Public dashboard not found", + StatusCode: 404, + Status: "not-found", + } + ErrPublicDashboardPanelNotFound = DashboardErr{ + Reason: "Panel not found in dashboard", + StatusCode: 404, + Status: "not-found", + } + ErrPublicDashboardIdentifierNotSet = DashboardErr{ + Reason: "No Uid for public dashboard specified", + StatusCode: 400, + } + + ErrFolderNotFound = errors.New("folder not found") + ErrFolderVersionMismatch = errors.New("the folder has been changed by someone else") + ErrFolderTitleEmpty = errors.New("folder title cannot be empty") + ErrFolderWithSameUIDExists = errors.New("a folder/dashboard with the same uid already exists") + ErrFolderInvalidUID = errors.New("invalid uid for folder provided") + ErrFolderSameNameExists = errors.New("a folder or dashboard in the general folder with the same name already exists") + ErrFolderFailedGenerateUniqueUid = errors.New("failed to generate unique folder ID") + ErrFolderAccessDenied = errors.New("access denied to folder") + ErrFolderContainsAlertRules = errors.New("folder contains alert rules") +) + +// DashboardErr represents a dashboard error. +type DashboardErr struct { + StatusCode int + Status string + Reason string +} + +// Equal returns whether equal to another DashboardErr. +func (e DashboardErr) Equal(o DashboardErr) bool { + return o.StatusCode == e.StatusCode && o.Status == e.Status && o.Reason == e.Reason +} + +// Error returns the error message. +func (e DashboardErr) Error() string { + if e.Reason != "" { + return e.Reason + } + return "Dashboard Error" +} + +// Body returns the error's response body, if applicable. +func (e DashboardErr) Body() util.DynMap { + if e.Status == "" { + return nil + } + + return util.DynMap{"status": e.Status, "message": e.Error()} +} + +type UpdatePluginDashboardError struct { + PluginId string +} + +func (d UpdatePluginDashboardError) Error() string { + return "Dashboard belongs to plugin" +} diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index 2fedc4abf01..f2f3db9c2ac 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -15,13 +15,6 @@ type SaveDashboardDTO struct { Dashboard *models.Dashboard } -type SavePublicDashboardConfigDTO struct { - DashboardUid string - OrgId int64 - UserId int64 - PublicDashboard *models.PublicDashboard -} - type DashboardSearchProjection struct { ID int64 `xorm:"id"` UID string `xorm:"uid"` diff --git a/pkg/services/dashboards/service/dashboard_public.go b/pkg/services/dashboards/service/dashboard_public.go deleted file mode 100644 index a0e5c790abe..00000000000 --- a/pkg/services/dashboards/service/dashboard_public.go +++ /dev/null @@ -1,154 +0,0 @@ -package service - -import ( - "context" - "fmt" - "time" - - "github.com/gofrs/uuid" - "github.com/grafana/grafana/pkg/api/dtos" - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/dashboards" -) - -// Gets public dashboard via access token -func (dr *DashboardServiceImpl) GetPublicDashboard(ctx context.Context, accessToken string) (*models.Dashboard, error) { - pubdash, d, err := dr.dashboardStore.GetPublicDashboard(ctx, accessToken) - - if err != nil { - return nil, err - } - - if pubdash == nil || d == nil { - return nil, models.ErrPublicDashboardNotFound - } - - if !pubdash.IsEnabled { - return nil, models.ErrPublicDashboardNotFound - } - - ts := pubdash.BuildTimeSettings(d) - d.Data.SetPath([]string{"time", "from"}, ts.From) - d.Data.SetPath([]string{"time", "to"}, ts.To) - - return d, nil -} - -// GetPublicDashboardConfig is a helper method to retrieve the public dashboard configuration for a given dashboard from the database -func (dr *DashboardServiceImpl) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { - pdc, err := dr.dashboardStore.GetPublicDashboardConfig(ctx, orgId, dashboardUid) - if err != nil { - return nil, err - } - - return pdc, nil -} - -// SavePublicDashboardConfig is a helper method to persist the sharing config -// to the database. It handles validations for sharing config and persistence -func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) { - if len(dto.DashboardUid) == 0 { - return nil, models.ErrDashboardIdentifierNotSet - } - - // set default value for time settings - if dto.PublicDashboard.TimeSettings == nil { - json, err := simplejson.NewJson([]byte("{}")) - if err != nil { - return nil, err - } - dto.PublicDashboard.TimeSettings = json - } - - if dto.PublicDashboard.Uid == "" { - return dr.savePublicDashboardConfig(ctx, dto) - } - - return dr.updatePublicDashboardConfig(ctx, dto) -} - -func (dr *DashboardServiceImpl) savePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) { - uid, err := dr.dashboardStore.GenerateNewPublicDashboardUid(ctx) - if err != nil { - return nil, err - } - - accessToken, err := GenerateAccessToken() - if err != nil { - return nil, err - } - - cmd := models.SavePublicDashboardConfigCommand{ - DashboardUid: dto.DashboardUid, - OrgId: dto.OrgId, - PublicDashboard: models.PublicDashboard{ - Uid: uid, - DashboardUid: dto.DashboardUid, - OrgId: dto.OrgId, - IsEnabled: dto.PublicDashboard.IsEnabled, - TimeSettings: dto.PublicDashboard.TimeSettings, - CreatedBy: dto.UserId, - CreatedAt: time.Now(), - AccessToken: accessToken, - }, - } - - return dr.dashboardStore.SavePublicDashboardConfig(ctx, cmd) -} - -func (dr *DashboardServiceImpl) updatePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) { - cmd := models.SavePublicDashboardConfigCommand{ - PublicDashboard: models.PublicDashboard{ - Uid: dto.PublicDashboard.Uid, - IsEnabled: dto.PublicDashboard.IsEnabled, - TimeSettings: dto.PublicDashboard.TimeSettings, - UpdatedBy: dto.UserId, - UpdatedAt: time.Now(), - }, - } - - err := dr.dashboardStore.UpdatePublicDashboardConfig(ctx, cmd) - if err != nil { - return nil, err - } - - publicDashboard, err := dr.dashboardStore.GetPublicDashboardConfig(ctx, dto.OrgId, dto.DashboardUid) - if err != nil { - return nil, err - } - - return publicDashboard, nil -} - -// BuildPublicDashboardMetricRequest merges public dashboard parameters with -// dashboard and returns a metrics request to be sent to query backend -func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) { - if !publicDashboard.IsEnabled { - return dtos.MetricRequest{}, models.ErrPublicDashboardNotFound - } - - queriesByPanel := models.GetQueriesFromDashboard(dashboard.Data) - - if _, ok := queriesByPanel[panelId]; !ok { - return dtos.MetricRequest{}, models.ErrPublicDashboardPanelNotFound - } - - ts := publicDashboard.BuildTimeSettings(dashboard) - - return dtos.MetricRequest{ - From: ts.From, - To: ts.To, - Queries: queriesByPanel[panelId], - }, nil -} - -// generates a uuid formatted without dashes to use as access token -func GenerateAccessToken() (string, error) { - token, err := uuid.NewV4() - if err != nil { - return "", err - } - - return fmt.Sprintf("%x", token), nil -} diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index fc193ca4888..cda6ed922b0 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -83,21 +83,21 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d dash.SetUid(strings.TrimSpace(dash.Uid)) if dash.Title == "" { - return nil, models.ErrDashboardTitleEmpty + return nil, dashboards.ErrDashboardTitleEmpty } if dash.IsFolder && dash.FolderId > 0 { - return nil, models.ErrDashboardFolderCannotHaveParent + return nil, dashboards.ErrDashboardFolderCannotHaveParent } if dash.IsFolder && strings.EqualFold(dash.Title, models.RootFolderName) { - return nil, models.ErrDashboardFolderNameExists + return nil, dashboards.ErrDashboardFolderNameExists } if !util.IsValidShortUID(dash.Uid) { - return nil, models.ErrDashboardInvalidUid + return nil, dashboards.ErrDashboardInvalidUid } else if util.IsShortUIDTooLong(dash.Uid) { - return nil, models.ErrDashboardUidTooLong + return nil, dashboards.ErrDashboardUidTooLong } if err := validateDashboardRefreshInterval(dash); err != nil { @@ -123,7 +123,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d if err != nil { return nil, err } - return nil, models.ErrDashboardUpdateAccessDenied + return nil, dashboards.ErrDashboardUpdateAccessDenied } } @@ -134,7 +134,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d } if provisionedData != nil { - return nil, models.ErrDashboardCannotSaveProvisionedDashboard + return nil, dashboards.ErrDashboardCannotSaveProvisionedDashboard } } @@ -144,14 +144,14 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d if err != nil { return nil, err } - return nil, models.ErrDashboardUpdateAccessDenied + return nil, dashboards.ErrDashboardUpdateAccessDenied } } else { if canSave, err := guard.CanSave(); err != nil || !canSave { if err != nil { return nil, err } - return nil, models.ErrDashboardUpdateAccessDenied + return nil, dashboards.ErrDashboardUpdateAccessDenied } } @@ -202,7 +202,7 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error { } if d < minRefreshInterval { - return models.ErrDashboardRefreshIntervalTooShort + return dashboards.ErrDashboardRefreshIntervalTooShort } return nil @@ -414,7 +414,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId } if provisionedData != nil { - return models.ErrDashboardCannotDeleteProvisionedDashboard + return dashboards.ErrDashboardCannotDeleteProvisionedDashboard } } cmd := &models.DeleteDashboardCommand{OrgId: orgId, Id: dashboardId} @@ -573,8 +573,8 @@ func (dr *DashboardServiceImpl) GetDashboardAclInfoList(ctx context.Context, que return dr.dashboardStore.GetDashboardAclInfoList(ctx, query) } -func (dr *DashboardServiceImpl) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error { - return dr.dashboardStore.HasAdminPermissionInFolders(ctx, query) +func (dr *DashboardServiceImpl) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error { + return dr.dashboardStore.HasAdminPermissionInDashboardsOrFolders(ctx, query) } func (dr *DashboardServiceImpl) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error { diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 0cf92da3839..dad3576988e 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/models" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/alerting" - dashbboardservice "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/guardian" @@ -39,7 +39,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardNotFound, err) + assert.Equal(t, dashboards.ErrDashboardNotFound, err) }) // Given other organization @@ -59,7 +59,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardNotFound, err) + assert.Equal(t, dashboards.ErrDashboardNotFound, err) }) permissionScenario(t, "When creating a dashboard with same uid as dashboard in organization A, it should create a new dashboard in org B", @@ -101,7 +101,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sqlStore) - assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, int64(0), sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -121,7 +121,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.otherSavedFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -141,7 +141,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -162,7 +162,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -183,7 +183,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -204,7 +204,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -225,7 +225,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -246,7 +246,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -267,7 +267,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -288,7 +288,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) + require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -429,7 +429,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardFolderNotFound, err) + assert.Equal(t, dashboards.ErrDashboardFolderNotFound, err) }) permissionScenario(t, "When updating an existing dashboard by id without current version", canSave, @@ -445,7 +445,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardVersionMismatch, err) + assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err) }) permissionScenario(t, "When updating an existing dashboard by id with current version", canSave, @@ -485,7 +485,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardVersionMismatch, err) + assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err) }) permissionScenario(t, "When updating an existing dashboard by uid with current version", canSave, @@ -524,7 +524,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err) + assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err) }) permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder", @@ -540,7 +540,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err) + assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err) }) permissionScenario(t, "When creating a folder with same name as existing folder", canSave, @@ -556,7 +556,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err) + assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err) }) }) @@ -644,7 +644,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardWithSameUIDExists, err) + assert.Equal(t, dashboards.ErrDashboardWithSameUIDExists, err) }) permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder", canSave, @@ -708,7 +708,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardTypeMismatch, err) + assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) }) permissionScenario(t, "When updating existing dashboard to a folder using id", canSave, @@ -724,7 +724,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardTypeMismatch, err) + assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) }) permissionScenario(t, "When updating existing folder to a dashboard using uid", canSave, @@ -740,7 +740,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardTypeMismatch, err) + assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) }) permissionScenario(t, "When updating existing dashboard to a folder using uid", canSave, @@ -756,7 +756,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardTypeMismatch, err) + assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) }) permissionScenario(t, "When updating existing folder to a dashboard using title", canSave, @@ -771,7 +771,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardWithSameNameAsFolder, err) + assert.Equal(t, dashboards.ErrDashboardWithSameNameAsFolder, err) }) permissionScenario(t, "When updating existing dashboard to a folder using title", canSave, @@ -786,7 +786,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardFolderWithSameNameAsDashboard, err) + assert.Equal(t, dashboards.ErrDashboardFolderWithSameNameAsDashboard, err) }) }) }) @@ -796,7 +796,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { type permissionScenarioContext struct { dashboardGuardianMock *guardian.FakeDashboardGuardian sqlStore *sqlstore.SQLStore - dashboardStore dashbboardservice.Store + dashboardStore dashboards.Store savedFolder *models.Dashboard savedDashInFolder *models.Dashboard otherSavedFolder *models.Dashboard @@ -913,7 +913,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto }), } - dto := dashbboardservice.SaveDashboardDTO{ + dto := dashboards.SaveDashboardDTO{ OrgId: orgID, Dashboard: cmd.GetDashboardModel(), User: &models.SignedInUser{ @@ -950,7 +950,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore. }), } - dto := dashbboardservice.SaveDashboardDTO{ + dto := dashboards.SaveDashboardDTO{ OrgId: orgID, Dashboard: cmd.GetDashboardModel(), User: &models.SignedInUser{ @@ -975,10 +975,10 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore. return res } -func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashbboardservice.SaveDashboardDTO { +func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO { dash := (&cmd).GetDashboardModel() - return dashbboardservice.SaveDashboardDTO{ + return dashboards.SaveDashboardDTO{ Dashboard: dash, Message: cmd.Message, OrgId: cmd.OrgId, diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index dc9dc3cfefc..652d245f823 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - m "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/setting" ) @@ -21,7 +21,7 @@ func TestIntegrationDashboardService(t *testing.T) { t.Skip("skipping integration test") } t.Run("Dashboard service tests", func(t *testing.T) { - fakeStore := m.FakeDashboardStore{} + fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ log: log.New("test.logger"), @@ -34,7 +34,7 @@ func TestIntegrationDashboardService(t *testing.T) { guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) t.Run("Save dashboard validation", func(t *testing.T) { - dto := &m.SaveDashboardDTO{} + dto := &dashboards.SaveDashboardDTO{} t.Run("When saving a dashboard with empty title it should return error", func(t *testing.T) { titles := []string{"", " ", " \t "} @@ -42,7 +42,7 @@ func TestIntegrationDashboardService(t *testing.T) { for _, title := range titles { dto.Dashboard = models.NewDashboard(title) _, err := service.SaveDashboard(context.Background(), dto, false) - require.Equal(t, err, models.ErrDashboardTitleEmpty) + require.Equal(t, err, dashboards.ErrDashboardTitleEmpty) } }) @@ -50,13 +50,13 @@ func TestIntegrationDashboardService(t *testing.T) { dto.Dashboard = models.NewDashboardFolder("Folder") dto.Dashboard.FolderId = 1 _, err := service.SaveDashboard(context.Background(), dto, false) - require.Equal(t, err, models.ErrDashboardFolderCannotHaveParent) + require.Equal(t, err, dashboards.ErrDashboardFolderCannotHaveParent) }) t.Run("Should return validation error if folder is named General", func(t *testing.T) { dto.Dashboard = models.NewDashboardFolder("General") _, err := service.SaveDashboard(context.Background(), dto, false) - require.Equal(t, err, models.ErrDashboardFolderNameExists) + require.Equal(t, err, dashboards.ErrDashboardFolderNameExists) }) t.Run("When saving a dashboard should validate uid", func(t *testing.T) { @@ -68,9 +68,9 @@ func TestIntegrationDashboardService(t *testing.T) { {Uid: " ", Error: nil}, {Uid: " \t ", Error: nil}, {Uid: "asdf90_-", Error: nil}, - {Uid: "asdf/90", Error: models.ErrDashboardInvalidUid}, + {Uid: "asdf/90", Error: dashboards.ErrDashboardInvalidUid}, {Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil}, - {Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidTooLong}, + {Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: dashboards.ErrDashboardUidTooLong}, } for _, tc := range testCases { @@ -94,7 +94,7 @@ func TestIntegrationDashboardService(t *testing.T) { dto.Dashboard.SetId(3) dto.User = &models.SignedInUser{UserId: 1} _, err := service.SaveDashboard(context.Background(), dto, false) - require.Equal(t, err, models.ErrDashboardCannotSaveProvisionedDashboard) + require.Equal(t, err, dashboards.ErrDashboardCannotSaveProvisionedDashboard) }) t.Run("Should not return validation error if dashboard is provisioned but UI updates allowed", func(t *testing.T) { @@ -123,7 +123,7 @@ func TestIntegrationDashboardService(t *testing.T) { }) t.Run("Save provisioned dashboard validation", func(t *testing.T) { - dto := &m.SaveDashboardDTO{} + dto := &dashboards.SaveDashboardDTO{} t.Run("Should not return validation error if dashboard is provisioned", func(t *testing.T) { fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once() @@ -157,7 +157,7 @@ func TestIntegrationDashboardService(t *testing.T) { }) t.Run("Import dashboard validation", func(t *testing.T) { - dto := &m.SaveDashboardDTO{} + dto := &dashboards.SaveDashboardDTO{} t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) { fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once() @@ -167,7 +167,7 @@ func TestIntegrationDashboardService(t *testing.T) { dto.Dashboard.SetId(3) dto.User = &models.SignedInUser{UserId: 1} _, err := service.ImportDashboard(context.Background(), dto) - require.Equal(t, err, models.ErrDashboardCannotSaveProvisionedDashboard) + require.Equal(t, err, dashboards.ErrDashboardCannotSaveProvisionedDashboard) }) }) @@ -182,7 +182,7 @@ func TestIntegrationDashboardService(t *testing.T) { t.Run("DeleteDashboard should fail to delete it when provisioning information is missing", func(t *testing.T) { fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{}, nil).Once() err := service.DeleteDashboard(context.Background(), 1, 1) - require.Equal(t, err, models.ErrDashboardCannotDeleteProvisionedDashboard) + require.Equal(t, err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) }) }) diff --git a/pkg/services/dashboards/service/folder_service.go b/pkg/services/dashboards/service/folder_service.go index c48c5bf7655..de4f3016821 100644 --- a/pkg/services/dashboards/service/folder_service.go +++ b/pkg/services/dashboards/service/folder_service.go @@ -94,7 +94,7 @@ func (f *FolderServiceImpl) GetFolderByID(ctx context.Context, user *models.Sign if err != nil { return nil, toFolderError(err) } - return nil, models.ErrFolderAccessDenied + return nil, dashboards.ErrFolderAccessDenied } return dashFolder, nil @@ -111,7 +111,7 @@ func (f *FolderServiceImpl) GetFolderByUID(ctx context.Context, user *models.Sig if err != nil { return nil, toFolderError(err) } - return nil, models.ErrFolderAccessDenied + return nil, dashboards.ErrFolderAccessDenied } return dashFolder, nil @@ -128,7 +128,7 @@ func (f *FolderServiceImpl) GetFolderByTitle(ctx context.Context, user *models.S if err != nil { return nil, toFolderError(err) } - return nil, models.ErrFolderAccessDenied + return nil, dashboards.ErrFolderAccessDenied } return dashFolder, nil @@ -140,7 +140,7 @@ func (f *FolderServiceImpl) CreateFolder(ctx context.Context, user *models.Signe trimmedUID := strings.TrimSpace(uid) if trimmedUID == accesscontrol.GeneralFolderUID { - return nil, models.ErrFolderInvalidUID + return nil, dashboards.ErrFolderInvalidUID } dashFolder.SetUid(trimmedUID) @@ -201,7 +201,7 @@ func (f *FolderServiceImpl) UpdateFolder(ctx context.Context, user *models.Signe dashFolder := query.Result if !dashFolder.IsFolder { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } cmd.UpdateDashboardModel(dashFolder, orgID, user.UserId) @@ -254,7 +254,7 @@ func (f *FolderServiceImpl) DeleteFolder(ctx context.Context, user *models.Signe if err != nil { return nil, toFolderError(err) } - return nil, models.ErrFolderAccessDenied + return nil, dashboards.ErrFolderAccessDenied } deleteCmd := models.DeleteDashboardCommand{OrgId: orgID, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules} @@ -271,32 +271,32 @@ func (f *FolderServiceImpl) MakeUserAdmin(ctx context.Context, orgID int64, user } func toFolderError(err error) error { - if errors.Is(err, models.ErrDashboardTitleEmpty) { - return models.ErrFolderTitleEmpty + if errors.Is(err, dashboards.ErrDashboardTitleEmpty) { + return dashboards.ErrFolderTitleEmpty } - if errors.Is(err, models.ErrDashboardUpdateAccessDenied) { - return models.ErrFolderAccessDenied + if errors.Is(err, dashboards.ErrDashboardUpdateAccessDenied) { + return dashboards.ErrFolderAccessDenied } - if errors.Is(err, models.ErrDashboardWithSameNameInFolderExists) { - return models.ErrFolderSameNameExists + if errors.Is(err, dashboards.ErrDashboardWithSameNameInFolderExists) { + return dashboards.ErrFolderSameNameExists } - if errors.Is(err, models.ErrDashboardWithSameUIDExists) { - return models.ErrFolderWithSameUIDExists + if errors.Is(err, dashboards.ErrDashboardWithSameUIDExists) { + return dashboards.ErrFolderWithSameUIDExists } - if errors.Is(err, models.ErrDashboardVersionMismatch) { - return models.ErrFolderVersionMismatch + if errors.Is(err, dashboards.ErrDashboardVersionMismatch) { + return dashboards.ErrFolderVersionMismatch } - if errors.Is(err, models.ErrDashboardNotFound) { - return models.ErrFolderNotFound + if errors.Is(err, dashboards.ErrDashboardNotFound) { + return dashboards.ErrFolderNotFound } - if errors.Is(err, models.ErrDashboardFailedGenerateUniqueUid) { - err = models.ErrFolderFailedGenerateUniqueUid + if errors.Is(err, dashboards.ErrDashboardFailedGenerateUniqueUid) { + err = dashboards.ErrFolderFailedGenerateUniqueUid } return err diff --git a/pkg/services/dashboards/service/folder_service_test.go b/pkg/services/dashboards/service/folder_service_test.go index 99cf9168ab7..46c03f6830f 100644 --- a/pkg/services/dashboards/service/folder_service_test.go +++ b/pkg/services/dashboards/service/folder_service_test.go @@ -77,7 +77,7 @@ func TestIntegrationFolderService(t *testing.T) { t.Run("When get folder by id should return access denied error", func(t *testing.T) { _, err := service.GetFolderByID(context.Background(), user, folderId, orgID) - require.Equal(t, err, models.ErrFolderAccessDenied) + require.Equal(t, err, dashboards.ErrFolderAccessDenied) }) t.Run("When get folder by id, with id = 0 should return default folder", func(t *testing.T) { @@ -88,13 +88,13 @@ func TestIntegrationFolderService(t *testing.T) { t.Run("When get folder by uid should return access denied error", func(t *testing.T) { _, err := service.GetFolderByUID(context.Background(), user, orgID, folderUID) - require.Equal(t, err, models.ErrFolderAccessDenied) + require.Equal(t, err, dashboards.ErrFolderAccessDenied) }) t.Run("When creating folder should return access denied error", func(t *testing.T) { store.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Times(2) _, err := service.CreateFolder(context.Background(), user, orgID, folder.Title, folderUID) - require.Equal(t, err, models.ErrFolderAccessDenied) + require.Equal(t, err, dashboards.ErrFolderAccessDenied) }) t.Run("When updating folder should return access denied error", func(t *testing.T) { @@ -107,13 +107,13 @@ func TestIntegrationFolderService(t *testing.T) { Uid: folderUID, Title: "Folder-TEST", }) - require.Equal(t, err, models.ErrFolderAccessDenied) + require.Equal(t, err, dashboards.ErrFolderAccessDenied) }) t.Run("When deleting folder by uid should return access denied error", func(t *testing.T) { _, err := service.DeleteFolder(context.Background(), user, orgID, folderUID, false) require.Error(t, err) - require.Equal(t, err, models.ErrFolderAccessDenied) + require.Equal(t, err, dashboards.ErrFolderAccessDenied) }) t.Cleanup(func() { @@ -144,7 +144,7 @@ func TestIntegrationFolderService(t *testing.T) { dash.Id = rand.Int63() _, err := service.CreateFolder(context.Background(), user, orgID, dash.Title, "general") - require.ErrorIs(t, err, models.ErrFolderInvalidUID) + require.ErrorIs(t, err, dashboards.ErrFolderInvalidUID) }) t.Run("When updating folder should not return access denied error", func(t *testing.T) { @@ -238,14 +238,14 @@ func TestIntegrationFolderService(t *testing.T) { ActualError error ExpectedError error }{ - {ActualError: models.ErrDashboardTitleEmpty, ExpectedError: models.ErrFolderTitleEmpty}, - {ActualError: models.ErrDashboardUpdateAccessDenied, ExpectedError: models.ErrFolderAccessDenied}, - {ActualError: models.ErrDashboardWithSameNameInFolderExists, ExpectedError: models.ErrFolderSameNameExists}, - {ActualError: models.ErrDashboardWithSameUIDExists, ExpectedError: models.ErrFolderWithSameUIDExists}, - {ActualError: models.ErrDashboardVersionMismatch, ExpectedError: models.ErrFolderVersionMismatch}, - {ActualError: models.ErrDashboardNotFound, ExpectedError: models.ErrFolderNotFound}, - {ActualError: models.ErrDashboardFailedGenerateUniqueUid, ExpectedError: models.ErrFolderFailedGenerateUniqueUid}, - {ActualError: models.ErrDashboardInvalidUid, ExpectedError: models.ErrDashboardInvalidUid}, + {ActualError: dashboards.ErrDashboardTitleEmpty, ExpectedError: dashboards.ErrFolderTitleEmpty}, + {ActualError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedError: dashboards.ErrFolderAccessDenied}, + {ActualError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedError: dashboards.ErrFolderSameNameExists}, + {ActualError: dashboards.ErrDashboardWithSameUIDExists, ExpectedError: dashboards.ErrFolderWithSameUIDExists}, + {ActualError: dashboards.ErrDashboardVersionMismatch, ExpectedError: dashboards.ErrFolderVersionMismatch}, + {ActualError: dashboards.ErrDashboardNotFound, ExpectedError: dashboards.ErrFolderNotFound}, + {ActualError: dashboards.ErrDashboardFailedGenerateUniqueUid, ExpectedError: dashboards.ErrFolderFailedGenerateUniqueUid}, + {ActualError: dashboards.ErrDashboardInvalidUid, ExpectedError: dashboards.ErrDashboardInvalidUid}, } for _, tc := range testCases { diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index 20ce1c3e75d..ddf976aa679 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.12.1. DO NOT EDIT. +// Code generated by mockery v2.12.2. DO NOT EDIT. package dashboards @@ -67,27 +67,6 @@ func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *models. return r0, r1 } -// GenerateNewPublicDashboardUid provides a mock function with given fields: ctx -func (_m *FakeDashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (string, error) { - ret := _m.Called(ctx) - - var r0 string - if rf, ok := ret.Get(0).(func(context.Context) string); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(string) - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetDashboard provides a mock function with given fields: ctx, query func (_m *FakeDashboardStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) (*models.Dashboard, error) { ret := _m.Called(ctx, query) @@ -319,67 +298,12 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(orgID int64, dash return r0, r1 } -// GetPublicDashboard provides a mock function with given fields: ctx, accessToken -func (_m *FakeDashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) { - ret := _m.Called(ctx, accessToken) - - var r0 *models.PublicDashboard - if rf, ok := ret.Get(0).(func(context.Context, string) *models.PublicDashboard); ok { - r0 = rf(ctx, accessToken) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*models.PublicDashboard) - } - } - - var r1 *models.Dashboard - if rf, ok := ret.Get(1).(func(context.Context, string) *models.Dashboard); ok { - r1 = rf(ctx, accessToken) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(*models.Dashboard) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(context.Context, string) error); ok { - r2 = rf(ctx, accessToken) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetPublicDashboardConfig provides a mock function with given fields: ctx, orgId, dashboardUid -func (_m *FakeDashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { - ret := _m.Called(ctx, orgId, dashboardUid) - - var r0 *models.PublicDashboard - if rf, ok := ret.Get(0).(func(context.Context, int64, string) *models.PublicDashboard); ok { - r0 = rf(ctx, orgId, dashboardUid) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*models.PublicDashboard) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok { - r1 = rf(ctx, orgId, dashboardUid) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// HasAdminPermissionInFolders provides a mock function with given fields: ctx, query -func (_m *FakeDashboardStore) HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error { +// HasAdminPermissionInDashboardsOrFolders provides a mock function with given fields: ctx, query +func (_m *FakeDashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error { ret := _m.Called(ctx, query) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *models.HasAdminPermissionInFoldersQuery) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *models.HasAdminPermissionInDashboardsOrFoldersQuery) error); ok { r0 = rf(ctx, query) } else { r0 = ret.Error(0) @@ -462,29 +386,6 @@ func (_m *FakeDashboardStore) SaveProvisionedDashboard(cmd models.SaveDashboardC return r0, r1 } -// SavePublicDashboardConfig provides a mock function with given fields: ctx, cmd -func (_m *FakeDashboardStore) SavePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) (*models.PublicDashboard, error) { - ret := _m.Called(ctx, cmd) - - var r0 *models.PublicDashboard - if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardConfigCommand) *models.PublicDashboard); ok { - r0 = rf(ctx, cmd) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*models.PublicDashboard) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, models.SavePublicDashboardConfigCommand) error); ok { - r1 = rf(ctx, cmd) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // UnprovisionDashboard provides a mock function with given fields: ctx, id func (_m *FakeDashboardStore) UnprovisionDashboard(ctx context.Context, id int64) error { ret := _m.Called(ctx, id) @@ -513,20 +414,6 @@ func (_m *FakeDashboardStore) UpdateDashboardACL(ctx context.Context, uid int64, return r0 } -// UpdatePublicDashboardConfig provides a mock function with given fields: ctx, cmd -func (_m *FakeDashboardStore) UpdatePublicDashboardConfig(ctx context.Context, cmd models.SavePublicDashboardConfigCommand) error { - ret := _m.Called(ctx, cmd) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, models.SavePublicDashboardConfigCommand) error); ok { - r0 = rf(ctx, cmd) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // ValidateDashboardBeforeSave provides a mock function with given fields: dashboard, overwrite func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error) { ret := _m.Called(dashboard, overwrite) diff --git a/pkg/services/dashboardsnapshots/database/database.go b/pkg/services/dashboardsnapshots/database/database.go index 8732802d737..fc0b956ff9e 100644 --- a/pkg/services/dashboardsnapshots/database/database.go +++ b/pkg/services/dashboardsnapshots/database/database.go @@ -91,7 +91,7 @@ func (d *DashboardSnapshotStore) GetDashboardSnapshot(ctx context.Context, query if err != nil { return err } else if !has { - return models.ErrDashboardSnapshotNotFound + return dashboardsnapshots.ErrDashboardSnapshotNotFound } query.Result = &snapshot diff --git a/pkg/services/dashboardsnapshots/errors.go b/pkg/services/dashboardsnapshots/errors.go new file mode 100644 index 00000000000..268532c833b --- /dev/null +++ b/pkg/services/dashboardsnapshots/errors.go @@ -0,0 +1,8 @@ +package dashboardsnapshots + +import "github.com/grafana/grafana/pkg/services/dashboards" + +var ErrDashboardSnapshotNotFound = dashboards.DashboardErr{ + Reason: "Dashboard snapshot not found", + StatusCode: 404, +} diff --git a/pkg/services/dashboardversion/dashverimpl/store_test.go b/pkg/services/dashboardversion/dashverimpl/store_test.go index de11a60ca8a..d696ef4e2a0 100644 --- a/pkg/services/dashboardversion/dashverimpl/store_test.go +++ b/pkg/services/dashboardversion/dashverimpl/store_test.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/util" @@ -134,7 +135,7 @@ func getDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, dashboard *models.D if err != nil { return err } else if !has { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } dashboard.SetId(dashboard.Id) @@ -188,7 +189,7 @@ func insertTestDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, title string if affectedRows, err := sess.Insert(dashVersion); err != nil { return err } else if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } return nil @@ -250,7 +251,7 @@ func updateTestDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, dashboard *m if affectedRows, err := sess.Insert(dashVersion); err != nil { return err } else if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } return nil diff --git a/pkg/services/datasources/models.go b/pkg/services/datasources/models.go index 9e6253b86ed..173fed2e7e9 100644 --- a/pkg/services/datasources/models.go +++ b/pkg/services/datasources/models.go @@ -47,9 +47,9 @@ type CorrelationDTO struct { } type DataSource struct { - Id int64 `json:"id"` - OrgId int64 `json:"orgId"` - Version int `json:"version"` + Id int64 `json:"id,omitempty"` + OrgId int64 `json:"orgId,omitempty"` + Version int `json:"version,omitempty"` Name string `json:"name"` Type string `json:"type"` @@ -71,8 +71,8 @@ type DataSource struct { ReadOnly bool `json:"readOnly"` Uid string `json:"uid"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` + Created time.Time `json:"created,omitempty"` + Updated time.Time `json:"updated,omitempty"` } // AllowedCookies parses the jsondata.keepCookies and returns a list of diff --git a/pkg/services/export/commit_helper.go b/pkg/services/export/commit_helper.go new file mode 100644 index 00000000000..b19749c6a03 --- /dev/null +++ b/pkg/services/export/commit_helper.go @@ -0,0 +1,153 @@ +package export + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path" + "strings" + "time" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/services/sqlstore" + jsoniter "github.com/json-iterator/go" +) + +type commitHelper struct { + ctx context.Context + repo *git.Repository + work *git.Worktree + orgDir string // includes the orgID + workDir string // same as the worktree root + orgID int64 + users map[int64]*userInfo +} + +type commitBody struct { + fpath string // absolute + body []byte + frame *data.Frame +} + +type commitOptions struct { + body []commitBody + when time.Time + userID int64 + comment string +} + +func (ch *commitHelper) initOrg(sql *sqlstore.SQLStore, orgID int64) error { + return sql.WithDbSession(ch.ctx, func(sess *sqlstore.DBSession) error { + sess.Table("user"). + Join("inner", "org_user", "user.id = org_user.user_id"). + Cols("user.*", "org_user.role"). + Where("org_user.org_id = ?", orgID). + Asc("user.id") + + rows := make([]*userInfo, 0) + err := sess.Find(&rows) + if err != nil { + return err + } + + lookup := make(map[int64]*userInfo, len(rows)) + for _, row := range rows { + lookup[row.ID] = row + } + ch.users = lookup + ch.orgID = orgID + return err + }) +} + +func (ch *commitHelper) add(opts commitOptions) error { + for _, b := range opts.body { + if !strings.HasPrefix(b.fpath, ch.orgDir) { + return fmt.Errorf("invalid path, must be within the root folder") + } + + // make sure the parent exists + err := os.MkdirAll(path.Dir(b.fpath), 0750) + if err != nil { + return err + } + + body := b.body + if b.frame != nil { + body, err = jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent(b.frame, "", " ") + if err != nil { + return err + } + } + + err = ioutil.WriteFile(b.fpath, body, 0644) + if err != nil { + return err + } + + sub := b.fpath[len(ch.workDir)+1:] + _, err = ch.work.Add(sub) + if err != nil { + status, e2 := ch.work.Status() + if e2 != nil { + return fmt.Errorf("error adding: %s (invalud work status: %s)", sub, e2.Error()) + } + fmt.Printf("STATUS: %+v\n", status) + return fmt.Errorf("unable to add file: %s (%d)", sub, len(b.body)) + } + } + + user, ok := ch.users[opts.userID] + if !ok { + user = &userInfo{ + Name: "admin", + Email: "admin@unknown.org", + } + } + sig := user.getAuthor() + if opts.when.Unix() > 10 { + sig.When = opts.when + } + + copts := &git.CommitOptions{ + Author: &sig, + } + + _, err := ch.work.Commit(opts.comment, copts) + return err +} + +type userInfo struct { + ID int64 `json:"-" xorm:"id"` + Login string `json:"login"` + Email string `json:"email"` + Name string `json:"name"` + Password string `json:"password"` + Salt string `json:"salt"` + Role string `json:"org_role"` // org role + Theme string `json:"-"` // managed in preferences + Created time.Time `json:"-"` // managed in git or external source + Updated time.Time `json:"-"` // managed in git or external source + IsDisabled bool `json:"disabled" xorm:"is_disabled"` + IsServiceAccount bool `json:"serviceAccount" xorm:"is_service_account"` + LastSeenAt time.Time `json:"-" xorm:"last_seen_at"` +} + +func (u *userInfo) getAuthor() object.Signature { + return object.Signature{ + Name: firstRealStringX(u.Name, u.Login, u.Email, "?"), + Email: firstRealStringX(u.Email, u.Login, u.Name, "?"), + } +} + +func firstRealStringX(vals ...string) string { + for _, v := range vals { + if v != "" { + return v + } + } + return "?" +} diff --git a/pkg/services/export/dummy_job.go b/pkg/services/export/dummy_job.go index 029bc8edabb..f892b1cea6b 100644 --- a/pkg/services/export/dummy_job.go +++ b/pkg/services/export/dummy_job.go @@ -1,7 +1,6 @@ package export import ( - "errors" "fmt" "math" "math/rand" @@ -23,10 +22,6 @@ type dummyExportJob struct { } func startDummyExportJob(cfg ExportConfig, broadcaster statusBroadcaster) (Job, error) { - if cfg.Format != "git" { - return nil, errors.New("only git format is supported") - } - job := &dummyExportJob{ logger: log.New("dummy_export_job"), cfg: cfg, diff --git a/pkg/services/export/export_anno.go b/pkg/services/export/export_anno.go new file mode 100644 index 00000000000..4f3d0f96684 --- /dev/null +++ b/pkg/services/export/export_anno.go @@ -0,0 +1,129 @@ +package export + +import ( + "encoding/json" + "fmt" + "path/filepath" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/services/sqlstore" + jsoniter "github.com/json-iterator/go" +) + +func exportAnnotations(helper *commitHelper, job *gitExportJob) error { + return job.sql.WithDbSession(helper.ctx, func(sess *sqlstore.DBSession) error { + type annoResult struct { + ID int64 `xorm:"id"` + DashboardID int64 `xorm:"dashboard_id"` + PanelID int64 `xorm:"panel_id"` + UserID int64 `xorm:"user_id"` + Text string `xorm:"text"` + Epoch int64 `xorm:"epoch"` + EpochEnd int64 `xorm:"epoch_end"` + Created int64 `xorm:"created"` // not used + Tags string `xorm:"tags"` // JSON Array + } + + type annoEvent struct { + PanelID int64 `json:"panel"` + Text string `json:"text"` + Epoch int64 `json:"epoch"` // dashboard/start+end is really the UID + EpochEnd int64 `json:"epoch_end,omitempty"` + Tags []string + } + + rows := make([]*annoResult, 0) + + sess.Table("annotation"). + Where("org_id = ? AND alert_id = 0", helper.orgID).Asc("epoch") + + err := sess.Find(&rows) + if err != nil { + return err + } + + count := len(rows) + f_ID := data.NewFieldFromFieldType(data.FieldTypeInt64, count) + f_DashboardID := data.NewFieldFromFieldType(data.FieldTypeInt64, count) + f_PanelID := data.NewFieldFromFieldType(data.FieldTypeInt64, count) + f_Epoch := data.NewFieldFromFieldType(data.FieldTypeTime, count) + f_EpochEnd := data.NewFieldFromFieldType(data.FieldTypeNullableTime, count) + f_Text := data.NewFieldFromFieldType(data.FieldTypeString, count) + f_Tags := data.NewFieldFromFieldType(data.FieldTypeJSON, count) + + f_ID.Name = "ID" + f_DashboardID.Name = "DashboardID" + f_PanelID.Name = "PanelID" + f_Epoch.Name = "Epoch" + f_EpochEnd.Name = "EpochEnd" + f_Text.Name = "Text" + f_Tags.Name = "Tags" + + for id, row := range rows { + f_ID.Set(id, row.ID) + f_DashboardID.Set(id, row.DashboardID) + f_PanelID.Set(id, row.PanelID) + f_Epoch.Set(id, time.UnixMilli(row.Epoch)) + if row.Epoch != row.EpochEnd { + f_EpochEnd.SetConcrete(id, time.UnixMilli(row.EpochEnd)) + } + f_Text.Set(id, row.Text) + f_Tags.Set(id, json.RawMessage(row.Tags)) + + // Save a file for each + event := &annoEvent{ + PanelID: row.PanelID, + Text: row.Text, + } + err = json.Unmarshal([]byte(row.Tags), &event.Tags) + if err != nil { + return err + } + fname := fmt.Sprintf("%d", row.Epoch) + if row.Epoch != row.EpochEnd { + fname += "-" + fmt.Sprintf("%d", row.EpochEnd) + } + + err = helper.add(commitOptions{ + body: []commitBody{ + { + fpath: filepath.Join(helper.orgDir, + "annotations", + "dashboard", + fmt.Sprintf("id-%d", row.DashboardID), + fname+".json"), + body: prettyJSON(event), + }, + }, + when: time.UnixMilli(row.Epoch), + comment: fmt.Sprintf("Added annotation (%d)", row.ID), + userID: row.UserID, + }) + if err != nil { + return err + } + } + + frame := data.NewFrame("", f_ID, f_DashboardID, f_PanelID, f_Epoch, f_EpochEnd, f_Text, f_Tags) + js, err := jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent(frame, "", " ") + if err != nil { + return err + } + + err = helper.add(commitOptions{ + body: []commitBody{ + { + fpath: filepath.Join(helper.orgDir, "annotations", "annotations.json"), + body: js, // TODO, pretty? + }, + }, + when: time.Now(), + comment: "Exported annotations", + }) + if err != nil { + return err + } + return err + }) +} diff --git a/pkg/services/export/export_auth.go b/pkg/services/export/export_auth.go new file mode 100644 index 00000000000..37da1a92d63 --- /dev/null +++ b/pkg/services/export/export_auth.go @@ -0,0 +1,73 @@ +package export + +import ( + "fmt" + "path" + + "github.com/grafana/grafana/pkg/services/sqlstore" +) + +func dumpAuthTables(helper *commitHelper, job *gitExportJob) error { + return job.sql.WithDbSession(helper.ctx, func(sess *sqlstore.DBSession) error { + commit := commitOptions{ + comment: "auth tables dump", + } + + tables := []string{ + "user", // joined with "org_user" to get the role + "user_role", + "builtin_role", + "api_key", + "team", "team_group", "team_role", "team_member", + "role", + "temp_user", + "user_auth_token", // no org_id... is it temporary? + "permission", + } + + for _, table := range tables { + switch table { + case "permission": + sess.Table(table). + Join("left", "role", "permission.role_id = role.id"). + Cols("permission.*"). + Where("org_id = ?", helper.orgID). + Asc("permission.id") + case "user": + sess.Table(table). + Join("inner", "org_user", "user.id = org_user.user_id"). + Cols("user.*", "org_user.role"). + Where("org_user.org_id = ?", helper.orgID). + Asc("user.id") + case "user_auth_token": + sess.Table(table). + Join("inner", "org_user", "user_auth_token.id = org_user.user_id"). + Cols("user_auth_token.*"). + Where("org_user.org_id = ?", helper.orgID). + Asc("user_auth_token.id") + default: + sess.Table(table).Where("org_id = ?", helper.orgID).Asc("id") + } + + raw, err := sess.QueryInterface() + if err != nil { + return fmt.Errorf("unable to read: %s // %s", table, err.Error()) + } + if len(raw) < 1 { + continue // don't write empty files + } + frame, err := queryResultToDataFrame(raw, frameOpts{ + skip: []string{"org_id", "version", "help_flags1", "theme"}, + }) + if err != nil { + return err + } + frame.Name = table + commit.body = append(commit.body, commitBody{ + fpath: path.Join(helper.orgDir, "auth", "sql.dump", table+".json"), + frame: frame, + }) + } + return helper.add(commit) + }) +} diff --git a/pkg/services/export/export_dashboards.go b/pkg/services/export/export_dashboards.go new file mode 100644 index 00000000000..212c31c1a38 --- /dev/null +++ b/pkg/services/export/export_dashboards.go @@ -0,0 +1,229 @@ +package export + +import ( + "bytes" + "encoding/json" + "fmt" + "path" + "path/filepath" + "strings" + "time" + + "github.com/google/uuid" + "github.com/grafana/grafana/pkg/infra/filestorage" + "github.com/grafana/grafana/pkg/services/searchV2/extract" + "github.com/grafana/grafana/pkg/services/sqlstore" +) + +func exportDashboards(helper *commitHelper, job *gitExportJob, lookup dsLookup) error { + alias := make(map[string]string, 100) + ids := make(map[int64]string, 100) + folders := make(map[int64]string, 100) + + // Should root files be at the root or in a subfolder called "general"? + if true { + folders[0] = "general" + } + + rootDir := path.Join(helper.orgDir, "root") + folderStructure := commitOptions{ + when: time.Now(), + comment: "Exported folder structure", + } + + err := job.sql.WithDbSession(helper.ctx, func(sess *sqlstore.DBSession) error { + type dashDataQueryResult struct { + Id int64 + UID string `xorm:"uid"` + IsFolder bool `xorm:"is_folder"` + FolderID int64 `xorm:"folder_id"` + Slug string `xorm:"slug"` + Data []byte + Created time.Time + Updated time.Time + } + + rows := make([]*dashDataQueryResult, 0) + + sess.Table("dashboard"). + Where("org_id = ?", helper.orgID). + Cols("id", "is_folder", "folder_id", "data", "slug", "created", "updated", "uid") + + err := sess.Find(&rows) + if err != nil { + return err + } + + // Process all folders + for _, row := range rows { + if !row.IsFolder { + continue + } + dash, err := extract.ReadDashboard(bytes.NewReader(row.Data), lookup) + if err != nil { + return err + } + + dash.UID = row.UID + slug := cleanFileName(dash.Title) + folder := map[string]string{ + "title": dash.Title, + } + + folderStructure.body = append(folderStructure.body, commitBody{ + fpath: path.Join(rootDir, slug, "__folder.json"), + body: prettyJSON(folder), + }) + + alias[dash.UID] = slug + folders[row.Id] = slug + + if row.Created.Before(folderStructure.when) { + folderStructure.when = row.Created + } + } + + // Now process the dashboards in each folder + for _, row := range rows { + if row.IsFolder { + continue + } + fname := row.Slug + "-dash.json" + fpath, ok := folders[row.FolderID] + if ok { + fpath = path.Join(fpath, fname) + } else { + fpath = fname + } + + alias[row.UID] = fpath + ids[row.Id] = fpath + } + + return err + }) + if err != nil { + return err + } + + err = helper.add(folderStructure) + if err != nil { + return err + } + + err = helper.add(commitOptions{ + body: []commitBody{ + { + fpath: filepath.Join(helper.orgDir, "root-alias.json"), + body: prettyJSON(alias), + }, + { + fpath: filepath.Join(helper.orgDir, "root-ids.json"), + body: prettyJSON(ids), + }, + }, + when: folderStructure.when, + comment: "adding UID alias structure", + }) + if err != nil { + return err + } + + // Now walk the history + err = job.sql.WithDbSession(helper.ctx, func(sess *sqlstore.DBSession) error { + type dashVersionResult struct { + DashId int64 `xorm:"dashboard_id"` + Version int64 `xorm:"version"` + Created time.Time `xorm:"created"` + CreatedBy int64 `xorm:"created_by"` + Message string `xorm:"message"` + Data []byte + } + + rows := make([]*dashVersionResult, 0, len(ids)) + + sess.Table("dashboard_version"). + Join("INNER", "dashboard", "dashboard.id = dashboard_version.dashboard_id"). + Where("org_id = ?", job.orgID). + Cols("dashboard_version.dashboard_id", + "dashboard_version.version", + "dashboard_version.created", + "dashboard_version.created_by", + "dashboard_version.message", + "dashboard_version.data"). + Asc("dashboard_version.created") + + err := sess.Find(&rows) + if err != nil { + return err + } + + count := int64(0) + + // Process all folders (only one level deep!!!) + for _, row := range rows { + fpath, ok := ids[row.DashId] + if !ok { + continue + } + + msg := row.Message + if msg == "" { + msg = fmt.Sprintf("Version: %d", row.Version) + } + + err = helper.add(commitOptions{ + body: []commitBody{ + { + fpath: filepath.Join(rootDir, fpath), + body: cleanDashboardJSON(row.Data), + }, + }, + userID: row.CreatedBy, + when: row.Created, + comment: msg, + }) + if err != nil { + return err + } + + count++ + fmt.Printf("COMMIT: %d // %s (%d)\n", count, fpath, row.Version) + + job.status.Current = count + job.status.Last = fpath + job.status.Changed = time.Now().UnixMilli() + job.broadcaster(job.status) + } + + return nil + }) + + return err +} + +func cleanDashboardJSON(data []byte) []byte { + var dash map[string]interface{} + err := json.Unmarshal(data, &dash) + if err != nil { + return nil + } + delete(dash, "id") + delete(dash, "uid") + delete(dash, "version") + + clean, _ := json.MarshalIndent(dash, "", " ") + return clean +} + +// replace any unsafe file name characters... TODO, but be a standard way to do this cleanly!!! +func cleanFileName(name string) string { + name = strings.ReplaceAll(name, "/", "-") + name = strings.ReplaceAll(name, "\\", "-") + name = strings.ReplaceAll(name, ":", "-") + if err := filestorage.ValidatePath(filestorage.Delimiter + name); err != nil { + randomName, _ := uuid.NewRandom() + return randomName.String() + } + return name +} diff --git a/pkg/services/export/export_ds.go b/pkg/services/export/export_ds.go new file mode 100644 index 00000000000..4dbe53c5553 --- /dev/null +++ b/pkg/services/export/export_ds.go @@ -0,0 +1,72 @@ +package export + +import ( + "fmt" + "path/filepath" + "sort" + + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/searchV2/extract" +) + +type dsLookup func(ref *extract.DataSourceRef) *extract.DataSourceRef + +func exportDataSources(helper *commitHelper, job *gitExportJob) (dsLookup, error) { + cmd := &datasources.GetDataSourcesQuery{ + OrgId: job.orgID, + } + err := job.sql.GetDataSources(helper.ctx, cmd) + if err != nil { + return nil, err + } + + sort.SliceStable(cmd.Result, func(i, j int) bool { + return cmd.Result[i].Created.After(cmd.Result[j].Created) + }) + + byUID := make(map[string]*extract.DataSourceRef, len(cmd.Result)) + byName := make(map[string]*extract.DataSourceRef, len(cmd.Result)) + for _, ds := range cmd.Result { + ref := &extract.DataSourceRef{ + UID: ds.Uid, + Type: ds.Type, + } + byUID[ds.Uid] = ref + byName[ds.Name] = ref + ds.OrgId = 0 + ds.Version = 0 + + err := helper.add(commitOptions{ + body: []commitBody{ + { + fpath: filepath.Join(helper.orgDir, "datasources", fmt.Sprintf("%s-ds.json", ds.Uid)), + body: prettyJSON(ds), + }, + }, + when: ds.Created, + comment: fmt.Sprintf("Add datasource: %s", ds.Name), + }) + if err != nil { + return nil, err + } + } + + // Return the lookup function + return func(ref *extract.DataSourceRef) *extract.DataSourceRef { + if ref == nil || ref.UID == "" { + return &extract.DataSourceRef{ + UID: "default.uid", + Type: "default.type", + } + } + v, ok := byUID[ref.UID] + if ok { + return v + } + v, ok = byName[ref.UID] + if ok { + return v + } + return nil + }, nil +} diff --git a/pkg/services/export/export_snapshots.go b/pkg/services/export/export_snapshots.go new file mode 100644 index 00000000000..e9390cc8b79 --- /dev/null +++ b/pkg/services/export/export_snapshots.go @@ -0,0 +1,43 @@ +package export + +import ( + "fmt" + "path/filepath" + "time" + + "github.com/grafana/grafana/pkg/services/dashboardsnapshots" +) + +func exportSnapshots(helper *commitHelper, job *gitExportJob) error { + cmd := &dashboardsnapshots.GetDashboardSnapshotsQuery{ + OrgId: job.orgID, + Limit: 500000, + SignedInUser: nil, + } + if cmd.SignedInUser == nil { + return fmt.Errorf("snapshots requires an admin user") + } + + err := job.dashboardsnapshotsService.SearchDashboardSnapshots(helper.ctx, cmd) + if err != nil { + return err + } + + if len(cmd.Result) < 1 { + return nil // nothing + } + + gitcmd := commitOptions{ + when: time.Now(), + comment: "Export playlists", + } + + for _, snapshot := range cmd.Result { + gitcmd.body = append(gitcmd.body, commitBody{ + fpath: filepath.Join(helper.orgDir, "snapshot", fmt.Sprintf("%d-snapshot.json", snapshot.Id)), + body: prettyJSON(snapshot), + }) + } + + return helper.add(gitcmd) +} diff --git a/pkg/services/export/export_sys_playlists.go b/pkg/services/export/export_sys_playlists.go new file mode 100644 index 00000000000..83943db9563 --- /dev/null +++ b/pkg/services/export/export_sys_playlists.go @@ -0,0 +1,40 @@ +package export + +import ( + "fmt" + "path/filepath" + "time" + + "github.com/grafana/grafana/pkg/models" +) + +func exportSystemPlaylists(helper *commitHelper, job *gitExportJob) error { + cmd := &models.GetPlaylistsQuery{ + OrgId: job.orgID, + Limit: 500000, + } + err := job.sql.SearchPlaylists(helper.ctx, cmd) + if err != nil { + return err + } + + if len(cmd.Result) < 1 { + return nil // nothing + } + + gitcmd := commitOptions{ + when: time.Now(), + comment: "Export playlists", + } + + for _, playlist := range cmd.Result { + // TODO: fix the playlist API so it returns the json we need :) + + gitcmd.body = append(gitcmd.body, commitBody{ + fpath: filepath.Join(helper.orgDir, "system", "playlists", fmt.Sprintf("%s-playlist.json", playlist.UID)), + body: prettyJSON(playlist), + }) + } + + return helper.add(gitcmd) +} diff --git a/pkg/services/export/export_sys_preferences.go b/pkg/services/export/export_sys_preferences.go new file mode 100644 index 00000000000..926240acc04 --- /dev/null +++ b/pkg/services/export/export_sys_preferences.go @@ -0,0 +1,128 @@ +package export + +import ( + "fmt" + "path" + "path/filepath" + "time" + + "github.com/grafana/grafana/pkg/services/sqlstore" +) + +func exportSystemPreferences(helper *commitHelper, job *gitExportJob) error { + type preferences struct { + UserID int64 `json:"-" xorm:"user_id"` + TeamID int64 `json:"-" xorm:"team_id"` + HomeDashboardID int64 `json:"-" xorm:"home_dashboard_id"` + Updated time.Time `json:"-" xorm:"updated"` + JSONData map[string]interface{} `json:"-" xorm:"json_data"` + + Theme string `json:"theme"` + Locale string `json:"locale"` + Timezone string `json:"timezone"` + WeekStart string `json:"week_start,omitempty"` + HomeDashboard string `json:"home,omitempty" xorm:"uid"` // dashboard + NavBar interface{} `json:"navbar,omitempty"` + QueryHistory interface{} `json:"queryHistory,omitempty"` + } + + prefsDir := path.Join(helper.orgDir, "system", "preferences") + users := make(map[int64]*userInfo, len(helper.users)) + for _, user := range helper.users { + users[user.ID] = user + } + + return job.sql.WithDbSession(helper.ctx, func(sess *sqlstore.DBSession) error { + rows := make([]*preferences, 0) + + sess.Table("preferences"). + Join("LEFT", "dashboard", "dashboard.id = preferences.home_dashboard_id"). + Cols("preferences.*", "dashboard.uid"). + Where("preferences.org_id = ?", helper.orgID) + + err := sess.Find(&rows) + if err != nil { + return err + } + + var comment string + var fpath string + for _, row := range rows { + if row.TeamID > 0 { + fpath = filepath.Join(prefsDir, "team", fmt.Sprintf("%d.json", row.TeamID)) + comment = fmt.Sprintf("Team preferences: %d", row.TeamID) + } else if row.UserID == 0 { + fpath = filepath.Join(prefsDir, "default.json") + comment = "Default preferences" + } else { + user, ok := users[row.UserID] + if ok { + delete(users, row.UserID) + } else { + user = &userInfo{ + Login: fmt.Sprintf("__%d__", row.UserID), + } + } + fpath = filepath.Join(prefsDir, "user", fmt.Sprintf("%s.json", user.Login)) + comment = fmt.Sprintf("User preferences: %s", user.getAuthor().Name) + } + + if row.JSONData != nil { + v, ok := row.JSONData["locale"] + if ok && row.Locale == "" { + s, ok := v.(string) + if ok { + row.Locale = s + } + } + + v, ok = row.JSONData["navbar"] + if ok && row.NavBar == nil { + row.NavBar = v + } + + v, ok = row.JSONData["queryHistory"] + if ok && row.QueryHistory == nil { + row.QueryHistory = v + } + } + + err := helper.add(commitOptions{ + body: []commitBody{ + { + fpath: fpath, + body: prettyJSON(row), + }, + }, + when: row.Updated, + comment: comment, + userID: row.UserID, + }) + if err != nil { + return err + } + } + + // add a file for all useres that may not be in the system + for _, user := range users { + row := preferences{ + Theme: user.Theme, // never set? + } + err := helper.add(commitOptions{ + body: []commitBody{ + { + fpath: filepath.Join(prefsDir, "user", fmt.Sprintf("%s.json", user.Login)), + body: prettyJSON(row), + }, + }, + when: user.Updated, + comment: "user preferences", + userID: row.UserID, + }) + if err != nil { + return err + } + } + return err + }) +} diff --git a/pkg/services/export/export_sys_stars.go b/pkg/services/export/export_sys_stars.go new file mode 100644 index 00000000000..53fd6fea0b9 --- /dev/null +++ b/pkg/services/export/export_sys_stars.go @@ -0,0 +1,65 @@ +package export + +import ( + "fmt" + "path/filepath" + + "github.com/grafana/grafana/pkg/services/sqlstore" +) + +func exportSystemStars(helper *commitHelper, job *gitExportJob) error { + byUser := make(map[int64][]string, 50) + + err := job.sql.WithDbSession(helper.ctx, func(sess *sqlstore.DBSession) error { + type starResult struct { + User int64 `xorm:"user_id"` + UID string `xorm:"uid"` + } + + rows := make([]*starResult, 0) + + sess.Table("star"). + Join("INNER", "dashboard", "dashboard.id = star.dashboard_id"). + Cols("star.user_id", "dashboard.uid"). + Where("dashboard.org_id = ?", helper.orgID) + + err := sess.Find(&rows) + if err != nil { + return err + } + + for _, row := range rows { + stars := append(byUser[row.User], fmt.Sprintf("dashboard/%s", row.UID)) + byUser[row.User] = stars + } + return err + }) + if err != nil { + return err + } + + for userID, stars := range byUser { + user, ok := helper.users[userID] + if !ok { + user = &userInfo{ + Login: fmt.Sprintf("__unknown_%d", userID), + } + } + + err := helper.add(commitOptions{ + body: []commitBody{ + { + fpath: filepath.Join(helper.orgDir, "system", "stars", fmt.Sprintf("%s.json", user.Login)), + body: prettyJSON(stars), + }, + }, + when: user.Updated, + comment: "user preferences", + userID: userID, + }) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/services/export/frame_helper.go b/pkg/services/export/frame_helper.go new file mode 100644 index 00000000000..1f7a291d8e5 --- /dev/null +++ b/pkg/services/export/frame_helper.go @@ -0,0 +1,138 @@ +package export + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +type fieldInfo struct { + Name string + Conv data.FieldConverter +} + +type frameOpts struct { + schema []fieldInfo + skip []string +} + +func prettyJSON(v interface{}) []byte { + b, _ := json.MarshalIndent(v, "", " ") + return b +} + +func queryResultToDataFrame(rows []map[string]interface{}, opts frameOpts) (*data.Frame, error) { + count := len(rows) + if count < 1 { + return nil, nil // empty frame + } + + schema := opts.schema + if len(schema) < 1 { + skip := make(map[string]bool, len(opts.skip)) + for _, k := range opts.skip { + skip[k] = true + } + + for k, v := range rows[0] { + if skip[k] { + continue + } + field := fieldInfo{ + Name: k, + Conv: data.FieldConverter{ + OutputFieldType: data.FieldTypeFor(v), + }, + } + if field.Conv.OutputFieldType == data.FieldTypeUnknown { + fmt.Printf("UNKNOWN type: %s / %v\n", k, v) + continue + } + + // Don't write passwords to disk for now!!!! + if k == "password" || k == "salt" { + field.Conv.Converter = func(v interface{}) (interface{}, error) { + return fmt.Sprintf("<%s>", k), nil + } + } + + schema = append(schema, field) + } + } + + fields := make([]*data.Field, len(schema)) + for i, s := range schema { + fields[i] = data.NewFieldFromFieldType(s.Conv.OutputFieldType, count) + fields[i].Name = s.Name + } + + var err error + for i, row := range rows { + for j, s := range schema { + v, ok := row[s.Name] + if ok && v != nil { + if s.Conv.Converter != nil { + v, err = s.Conv.Converter(v) + if err != nil { + return nil, fmt.Errorf("converting field: %s // %s", s.Name, err.Error()) + } + } + fields[j].Set(i, v) + } + } + } + + // Fields are in random order + if len(opts.schema) < 1 { + last := []*data.Field{} + frame := data.NewFrame("") + lookup := make(map[string]*data.Field, len(fields)) + for _, f := range fields { + if f.Name == "id" { + frame.Fields = append(frame.Fields, f) // first + continue + } + lookup[f.Name] = f + } + + // First items + for _, name := range []string{"name", "login", "email", "role", "description", "uid"} { + f, ok := lookup[name] + if ok { + frame.Fields = append(frame.Fields, f) // first + delete(lookup, name) + } + } + + // IDs + for k, f := range lookup { + if strings.HasSuffix(k, "_id") { + frame.Fields = append(frame.Fields, f) // first + delete(lookup, k) + } else if strings.HasPrefix(k, "is_") { + last = append(last, f) // first + delete(lookup, k) + } + } + + // Last items + for _, name := range []string{"created", "updated"} { + f, ok := lookup[name] + if ok { + last = append(last, f) // first + delete(lookup, name) + } + } + + // Rest + for _, f := range lookup { + frame.Fields = append(frame.Fields, f) + } + + frame.Fields = append(frame.Fields, last...) + return frame, nil + } + return data.NewFrame("", fields...), nil +} diff --git a/pkg/services/export/git_export_job.go b/pkg/services/export/git_export_job.go new file mode 100644 index 00000000000..21243e05c52 --- /dev/null +++ b/pkg/services/export/git_export_job.go @@ -0,0 +1,205 @@ +package export + +import ( + "context" + "fmt" + "path" + "sync" + "time" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboardsnapshots" + "github.com/grafana/grafana/pkg/services/sqlstore" +) + +var _ Job = new(gitExportJob) + +type gitExportJob struct { + logger log.Logger + sql *sqlstore.SQLStore + dashboardsnapshotsService dashboardsnapshots.Service + orgID int64 + rootDir string + + statusMu sync.Mutex + status ExportStatus + cfg ExportConfig + broadcaster statusBroadcaster +} + +type simpleExporter = func(helper *commitHelper, job *gitExportJob) error + +func startGitExportJob(cfg ExportConfig, sql *sqlstore.SQLStore, dashboardsnapshotsService dashboardsnapshots.Service, rootDir string, orgID int64, broadcaster statusBroadcaster) (Job, error) { + job := &gitExportJob{ + logger: log.New("git_export_job"), + cfg: cfg, + sql: sql, + dashboardsnapshotsService: dashboardsnapshotsService, + orgID: orgID, + rootDir: rootDir, + broadcaster: broadcaster, + status: ExportStatus{ + Running: true, + Target: "git export", + Started: time.Now().UnixMilli(), + Current: 0, + }, + } + + broadcaster(job.status) + go job.start() + return job, nil +} + +func (e *gitExportJob) getStatus() ExportStatus { + e.statusMu.Lock() + defer e.statusMu.Unlock() + + return e.status +} + +func (e *gitExportJob) getConfig() ExportConfig { + e.statusMu.Lock() + defer e.statusMu.Unlock() + + return e.cfg +} + +// Utility function to export dashboards +func (e *gitExportJob) start() { + defer func() { + e.logger.Info("Finished git export job") + + e.statusMu.Lock() + defer e.statusMu.Unlock() + s := e.status + if err := recover(); err != nil { + e.logger.Error("export panic", "error", err) + s.Status = fmt.Sprintf("ERROR: %v", err) + } + // Make sure it finishes OK + if s.Finished < 10 { + s.Finished = time.Now().UnixMilli() + } + s.Running = false + if s.Status == "" { + s.Status = "done" + } + s.Target = e.rootDir + e.status = s + e.broadcaster(s) + }() + + err := e.doExportWithHistory() + if err != nil { + e.logger.Error("ERROR", "e", err) + e.status.Status = "ERROR" + e.status.Last = err.Error() + e.broadcaster(e.status) + } +} + +func (e *gitExportJob) doExportWithHistory() error { + r, err := git.PlainInit(e.rootDir, false) + if err != nil { + return err + } + // default to "main" branch + h := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.ReferenceName("refs/heads/main")) + err = r.Storer.SetReference(h) + if err != nil { + return err + } + + w, err := r.Worktree() + if err != nil { + return err + } + helper := &commitHelper{ + repo: r, + work: w, + ctx: context.Background(), + workDir: e.rootDir, + orgDir: e.rootDir, + } + + cmd := &models.SearchOrgsQuery{} + err = e.sql.SearchOrgs(helper.ctx, cmd) + if err != nil { + return err + } + + // Export each org + for _, org := range cmd.Result { + if len(cmd.Result) > 1 { + helper.orgDir = path.Join(e.rootDir, fmt.Sprintf("org_%d", org.Id)) + } + err = helper.initOrg(e.sql, org.Id) + if err != nil { + return err + } + + err = e.doOrgExportWithHistory(helper) + if err != nil { + return err + } + } + + // cleanup the folder + e.status.Target = "pruning..." + e.broadcaster(e.status) + err = r.Prune(git.PruneOptions{}) + + // TODO + // git gc --prune=now --aggressive + + return err +} + +func (e *gitExportJob) doOrgExportWithHistory(helper *commitHelper) error { + lookup, err := exportDataSources(helper, e) + if err != nil { + return err + } + + if true { + err = exportDashboards(helper, e, lookup) + if err != nil { + return err + } + } + + // Run all the simple exporters + exporters := []simpleExporter{ + dumpAuthTables, + exportSystemPreferences, + exportSystemStars, + exportSystemPlaylists, + exportAnnotations, + } + + // This needs a real admin user to use the interfaces (and decrypt) + if false { + exporters = append(exporters, exportSnapshots) + } + + for _, fn := range exporters { + err = fn(helper, e) + if err != nil { + return err + } + } + return err +} + +/** + +git remote add origin git@github.com:ryantxu/test-dash-repo.git +git branch -M main +git push -u origin main + +**/ diff --git a/pkg/services/export/service.go b/pkg/services/export/service.go index ee4f1b040ea..6551775a065 100644 --- a/pkg/services/export/service.go +++ b/pkg/services/export/service.go @@ -2,15 +2,21 @@ package export import ( "encoding/json" + "fmt" "net/http" + "os" + "path/filepath" "sync" + "time" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/setting" ) type ExportService interface { @@ -22,25 +28,31 @@ type ExportService interface { } type StandardExport struct { - logger log.Logger - sql *sqlstore.SQLStore - glive *live.GrafanaLive - mutex sync.Mutex + logger log.Logger + glive *live.GrafanaLive + mutex sync.Mutex + dataDir string + + // Services + sql *sqlstore.SQLStore + dashboardsnapshotsService dashboardsnapshots.Service // updated with mutex exportJob Job } -func ProvideService(sql *sqlstore.SQLStore, features featuremgmt.FeatureToggles, gl *live.GrafanaLive) ExportService { +func ProvideService(sql *sqlstore.SQLStore, features featuremgmt.FeatureToggles, gl *live.GrafanaLive, cfg *setting.Cfg, dashboardsnapshotsService dashboardsnapshots.Service) ExportService { if !features.IsEnabled(featuremgmt.FlagExport) { return &StubExport{} } return &StandardExport{ - sql: sql, - glive: gl, - logger: log.New("export_service"), - exportJob: &stoppedJob{}, + sql: sql, + glive: gl, + logger: log.New("export_service"), + dashboardsnapshotsService: dashboardsnapshotsService, + exportJob: &stoppedJob{}, + dataDir: cfg.DataPath, } } @@ -67,9 +79,23 @@ func (ex *StandardExport) HandleRequestExport(c *models.ReqContext) response.Res return response.Error(http.StatusLocked, "export already running", nil) } - job, err := startDummyExportJob(cfg, func(s ExportStatus) { + var job Job + broadcast := func(s ExportStatus) { ex.broadcastStatus(c.OrgId, s) - }) + } + switch cfg.Format { + case "dummy": + job, err = startDummyExportJob(cfg, broadcast) + case "git": + dir := filepath.Join(ex.dataDir, "export_git", fmt.Sprintf("git_%d", time.Now().Unix())) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return response.Error(http.StatusBadRequest, "Error creating export folder", nil) + } + job, err = startGitExportJob(cfg, ex.sql, ex.dashboardsnapshotsService, dir, c.OrgId, broadcast) + default: + return response.Error(http.StatusBadRequest, "Unsupported job format", nil) + } + if err != nil { ex.logger.Error("failed to start export job", "err", err) return response.Error(http.StatusBadRequest, "failed to start export job", err) diff --git a/pkg/services/featuremgmt/manager.go b/pkg/services/featuremgmt/manager.go index 8593e6635cf..8c3ff596e06 100644 --- a/pkg/services/featuremgmt/manager.go +++ b/pkg/services/featuremgmt/manager.go @@ -162,7 +162,8 @@ func (fm *FeatureManager) HandleGetSettings(c *models.ReqContext) { } // WithFeatures is used to define feature toggles for testing. -// The arguments are a list of strings that are optionally followed by a boolean value +// The arguments are a list of strings that are optionally followed by a boolean value for example: +// WithFeatures([]interface{}{"my_feature", "other_feature"}) or WithFeatures([]interface{}{"my_feature", true}) func WithFeatures(spec ...interface{}) *FeatureManager { count := len(spec) enabled := make(map[string]bool, count) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 153383c3cc3..81c39c2bace 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -97,10 +97,9 @@ var ( RequiresDevMode: true, }, { - Name: "publicDashboards", - Description: "enables public access to dashboards", - State: FeatureStateAlpha, - RequiresDevMode: true, + Name: "publicDashboards", + Description: "enables public access to dashboards", + State: FeatureStateAlpha, }, { Name: "lokiLive", @@ -228,6 +227,12 @@ var ( State: FeatureStateAlpha, FrontendOnly: true, }, + { + Name: "scenes", + Description: "Experimental framework to build interactive dashboards", + State: FeatureStateAlpha, + FrontendOnly: true, + }, { Name: "useLegacyHeatmapPanel", Description: "Continue to use the angular/flot based heatmap panel", diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index d9ff3c122ae..d95e7241994 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -167,6 +167,10 @@ const ( // Allow elements nesting FlagCanvasPanelNesting = "canvasPanelNesting" + // FlagScenes + // Experimental framework to build interactive dashboards + FlagScenes = "scenes" + // FlagUseLegacyHeatmapPanel // Continue to use the angular/flot based heatmap panel FlagUseLegacyHeatmapPanel = "useLegacyHeatmapPanel" diff --git a/pkg/services/kmsproviders/osskmsproviders/osskmsproviders.go b/pkg/services/kmsproviders/osskmsproviders/osskmsproviders.go index 4292ed16a58..511e7dc7091 100644 --- a/pkg/services/kmsproviders/osskmsproviders/osskmsproviders.go +++ b/pkg/services/kmsproviders/osskmsproviders/osskmsproviders.go @@ -24,10 +24,6 @@ func ProvideService(enc encryption.Internal, settings setting.Provider, features } func (s Service) Provide() (map[secrets.ProviderID]secrets.Provider, error) { - if s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { - return nil, nil - } - return map[secrets.ProviderID]secrets.Provider{ kmsproviders.Default: grafana.New(s.settings, s.enc), }, nil diff --git a/pkg/services/libraryelements/api.go b/pkg/services/libraryelements/api.go index d78cc817fb3..8781d0cd0b6 100644 --- a/pkg/services/libraryelements/api.go +++ b/pkg/services/libraryelements/api.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/web" ) @@ -173,11 +174,11 @@ func toLibraryElementError(err error, message string) response.Response { if errors.Is(err, errLibraryElementVersionMismatch) { return response.Error(412, errLibraryElementVersionMismatch.Error(), err) } - if errors.Is(err, models.ErrFolderNotFound) { - return response.Error(404, models.ErrFolderNotFound.Error(), err) + if errors.Is(err, dashboards.ErrFolderNotFound) { + return response.Error(404, dashboards.ErrFolderNotFound.Error(), err) } - if errors.Is(err, models.ErrFolderAccessDenied) { - return response.Error(403, models.ErrFolderAccessDenied.Error(), err) + if errors.Is(err, dashboards.ErrFolderAccessDenied) { + return response.Error(403, dashboards.ErrFolderAccessDenied.Error(), err) } if errors.Is(err, errLibraryElementHasConnections) { return response.Error(403, errLibraryElementHasConnections.Error(), err) diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index e5b0cb5c1c2..674799c0e4a 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" @@ -707,7 +708,7 @@ func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Conte } if len(folderUIDs) == 0 { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } if len(folderUIDs) != 1 { diff --git a/pkg/services/libraryelements/guard.go b/pkg/services/libraryelements/guard.go index 3f7d776f6f7..7795a5f58cf 100644 --- a/pkg/services/libraryelements/guard.go +++ b/pkg/services/libraryelements/guard.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/guardian" ) @@ -29,7 +30,7 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte } if isGeneralFolder(folderID) && user.HasRole(models.ROLE_VIEWER) { - return models.ErrFolderAccessDenied + return dashboards.ErrFolderAccessDenied } folder, err := l.folderService.GetFolderByID(ctx, user, folderID, user.OrgId) if err != nil { @@ -43,7 +44,7 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte return err } if !canEdit { - return models.ErrFolderAccessDenied + return dashboards.ErrFolderAccessDenied } return nil @@ -66,7 +67,7 @@ func (l *LibraryElementService) requireViewPermissionsOnFolder(ctx context.Conte return err } if !canView { - return models.ErrFolderAccessDenied + return dashboards.ErrFolderAccessDenied } return nil @@ -80,7 +81,7 @@ func (l *LibraryElementService) requireEditPermissionsOnDashboard(ctx context.Co return err } if !canEdit { - return models.ErrDashboardUpdateAccessDenied + return dashboards.ErrDashboardUpdateAccessDenied } return nil diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index d0463b59da7..00753e31fed 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -77,7 +77,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) { scenarioWithPanel(t, "When an admin tries to delete a folder uid that doesn't exist, it should fail", func(t *testing.T, sc scenarioContext) { err := sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.Uid+"xxxx") - require.EqualError(t, err, models.ErrFolderNotFound.Error()) + require.EqualError(t, err, dashboards.ErrFolderNotFound.Error()) }) scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too", diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index d1a6a1f1d59..2569d256b9f 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -54,6 +54,7 @@ type AlertRuleService interface { CreateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error) UpdateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error) DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance alerting_models.Provenance) error + GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (definitions.AlertRuleGroup, error) UpdateRuleGroup(ctx context.Context, orgID int64, folderUID, rulegroup string, interval int64) error } @@ -284,7 +285,18 @@ func (srv *ProvisioningSrv) RouteDeleteAlertRule(c *models.ReqContext, UID strin return response.JSON(http.StatusNoContent, "") } -func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag definitions.AlertRuleGroup, folderUID string, group string) response.Response { +func (srv *ProvisioningSrv) RouteGetAlertRuleGroup(c *models.ReqContext, folder string, group string) response.Response { + g, err := srv.alertRules.GetRuleGroup(c.Req.Context(), c.OrgId, folder, group) + if err != nil { + if errors.Is(err, store.ErrAlertRuleGroupNotFound) { + return ErrResp(http.StatusNotFound, err, "") + } + return ErrResp(http.StatusInternalServerError, err, "") + } + return response.JSON(http.StatusOK, g) +} + +func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag definitions.AlertRuleGroupMetadata, folderUID string, group string) response.Response { err := srv.alertRules.UpdateRuleGroup(c.Req.Context(), c.OrgId, folderUID, group, ag.Interval) if err != nil { if errors.Is(err, store.ErrOptimisticLock) { diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index 118dd0edb27..88574902f5f 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -239,6 +239,28 @@ func TestProvisioningApi(t *testing.T) { require.Equal(t, 404, response.Status()) }) }) + + t.Run("alert rule groups", func(t *testing.T) { + t.Run("are present, GET returns 200", func(t *testing.T) { + sut := createProvisioningSrvSut(t) + rc := createTestRequestCtx() + insertRule(t, sut, createTestAlertRule("rule", 1)) + + response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "my-cool-group") + + require.Equal(t, 200, response.Status()) + }) + + t.Run("are missing, GET returns 404", func(t *testing.T) { + sut := createProvisioningSrvSut(t) + rc := createTestRequestCtx() + insertRule(t, sut, createTestAlertRule("rule", 1)) + + response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "does not exist") + + require.Equal(t, 404, response.Status()) + }) + }) } func createProvisioningSrvSut(t *testing.T) ProvisioningSrv { @@ -382,6 +404,7 @@ func createTestAlertRule(title string, orgID int64) definitions.AlertRule { }, }, RuleGroup: "my-cool-group", + FolderUID: "folder-uid", For: time.Second * 60, NoDataState: models.OK, ExecErrState: models.OkErrState, diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 6dd093c66d4..acf6ebf16a7 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -9,6 +9,7 @@ import ( "time" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" @@ -483,8 +484,9 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64, provena Provenance: provenance, }, } + forDuration := model.Duration(r.For) gettableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{ - For: model.Duration(r.For), + For: &forDuration, Annotations: r.Annotations, Labels: r.Labels, } @@ -495,7 +497,7 @@ func toNamespaceErrorResponse(err error) response.Response { if errors.Is(err, ngmodels.ErrCannotEditNamespace) { return ErrResp(http.StatusForbidden, err, err.Error()) } - if errors.Is(err, models.ErrDashboardIdentifierNotSet) { + if errors.Is(err, dashboards.ErrDashboardIdentifierNotSet) { return ErrResp(http.StatusBadRequest, err, err.Error()) } return apierrors.ToFolderErrorResponse(err) diff --git a/pkg/services/ngalert/api/api_ruler_validation.go b/pkg/services/ngalert/api/api_ruler_validation.go index 0ba65ee2bd1..1c69a8d16a2 100644 --- a/pkg/services/ngalert/api/api_ruler_validation.go +++ b/pkg/services/ngalert/api/api_ruler_validation.go @@ -110,8 +110,13 @@ func validateRuleNode( ExecErrState: errorState, } + var err error + newAlertRule.For, err = validateForInterval(ruleNode) + if err != nil { + return nil, err + } + if ruleNode.ApiRuleNode != nil { - newAlertRule.For = time.Duration(ruleNode.ApiRuleNode.For) newAlertRule.Annotations = ruleNode.ApiRuleNode.Annotations newAlertRule.Labels = ruleNode.ApiRuleNode.Labels @@ -131,10 +136,24 @@ func validateRuleNode( newAlertRule.PanelID = &panelIDValue } } - return &newAlertRule, nil } +// validateForInterval validates ApiRuleNode.For and converts it to time.Duration. If the field is not specified returns 0 if GrafanaManagedAlert.UID is empty and -1 if it is not. +func validateForInterval(ruleNode *apimodels.PostableExtendedRuleNode) (time.Duration, error) { + if ruleNode.ApiRuleNode == nil || ruleNode.ApiRuleNode.For == nil { + if ruleNode.GrafanaManagedAlert.UID != "" { + return -1, nil // will be patched later with the real value of the current version of the rule + } + return 0, nil // if it's a new rule, use the 0 as the default + } + duration := time.Duration(*ruleNode.ApiRuleNode.For) + if duration < 0 { + return 0, fmt.Errorf("field `for` cannot be negative [%v]. 0 or any positive duration are allowed", *ruleNode.ApiRuleNode.For) + } + return duration, nil +} + // validateRuleGroup validates API model (definitions.PostableRuleGroupConfig) and converts it to a collection of models.AlertRule. // Returns a slice that contains all rules described by API model or error if either group specification or an alert definition is not valid. func validateRuleGroup( diff --git a/pkg/services/ngalert/api/api_ruler_validation_test.go b/pkg/services/ngalert/api/api_ruler_validation_test.go index b2fe8116509..9bf26b8fcda 100644 --- a/pkg/services/ngalert/api/api_ruler_validation_test.go +++ b/pkg/services/ngalert/api/api_ruler_validation_test.go @@ -42,9 +42,10 @@ func config(t *testing.T) *setting.UnifiedAlertingSettings { } func validRule() apimodels.PostableExtendedRuleNode { + forDuration := model.Duration(rand.Int63n(1000)) return apimodels.PostableExtendedRuleNode{ ApiRuleNode: &apimodels.ApiRuleNode{ - For: model.Duration(rand.Int63n(1000)), + For: &forDuration, Labels: map[string]string{ "test-label": "data", }, @@ -240,7 +241,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) { require.Equal(t, name, alert.RuleGroup) require.Equal(t, models.NoDataState(api.GrafanaManagedAlert.NoDataState), alert.NoDataState) require.Equal(t, models.ExecutionErrorState(api.GrafanaManagedAlert.ExecErrState), alert.ExecErrState) - require.Equal(t, time.Duration(api.ApiRuleNode.For), alert.For) + require.Equal(t, time.Duration(*api.ApiRuleNode.For), alert.For) require.Equal(t, api.ApiRuleNode.Annotations, alert.Annotations) require.Equal(t, api.ApiRuleNode.Labels, alert.Labels) }, diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go index a2ebca1fb1f..49f2ad16cd2 100644 --- a/pkg/services/ngalert/api/authorization.go +++ b/pkg/services/ngalert/api/authorization.go @@ -185,7 +185,8 @@ func (api *API) authorize(method, path string) web.Handler { http.MethodGet + "/api/v1/provisioning/templates/{name}", http.MethodGet + "/api/v1/provisioning/mute-timings", http.MethodGet + "/api/v1/provisioning/mute-timings/{name}", - http.MethodGet + "/api/v1/provisioning/alert-rules/{UID}": + http.MethodGet + "/api/v1/provisioning/alert-rules/{UID}", + http.MethodGet + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": fallback = middleware.ReqOrgAdmin eval = ac.EvalPermission(ac.ActionAlertingProvisioningRead) // organization scope diff --git a/pkg/services/ngalert/api/forked_provisioning.go b/pkg/services/ngalert/api/forked_provisioning.go index 6446ef47bb5..3339ab10d1f 100644 --- a/pkg/services/ngalert/api/forked_provisioning.go +++ b/pkg/services/ngalert/api/forked_provisioning.go @@ -95,6 +95,10 @@ func (f *ForkedProvisioningApi) forkRouteDeleteAlertRule(ctx *models.ReqContext, return f.svc.RouteDeleteAlertRule(ctx, UID) } -func (f *ForkedProvisioningApi) forkRoutePutAlertRuleGroup(ctx *models.ReqContext, ag apimodels.AlertRuleGroup, folder, group string) response.Response { +func (f *ForkedProvisioningApi) forkRouteGetAlertRuleGroup(ctx *models.ReqContext, folder, group string) response.Response { + return f.svc.RouteGetAlertRuleGroup(ctx, folder, group) +} + +func (f *ForkedProvisioningApi) forkRoutePutAlertRuleGroup(ctx *models.ReqContext, ag apimodels.AlertRuleGroupMetadata, folder, group string) response.Response { return f.svc.RoutePutAlertRuleGroup(ctx, ag, folder, group) } diff --git a/pkg/services/ngalert/api/generated_base_api_provisioning.go b/pkg/services/ngalert/api/generated_base_api_provisioning.go index b449a168658..8fb66c89cc1 100644 --- a/pkg/services/ngalert/api/generated_base_api_provisioning.go +++ b/pkg/services/ngalert/api/generated_base_api_provisioning.go @@ -24,6 +24,7 @@ type ProvisioningApiForkingService interface { RouteDeleteMuteTiming(*models.ReqContext) response.Response RouteDeleteTemplate(*models.ReqContext) response.Response RouteGetAlertRule(*models.ReqContext) response.Response + RouteGetAlertRuleGroup(*models.ReqContext) response.Response RouteGetContactpoints(*models.ReqContext) response.Response RouteGetMuteTiming(*models.ReqContext) response.Response RouteGetMuteTimings(*models.ReqContext) response.Response @@ -61,6 +62,11 @@ func (f *ForkedProvisioningApi) RouteGetAlertRule(ctx *models.ReqContext) respon uIDParam := web.Params(ctx.Req)[":UID"] return f.forkRouteGetAlertRule(ctx, uIDParam) } +func (f *ForkedProvisioningApi) RouteGetAlertRuleGroup(ctx *models.ReqContext) response.Response { + folderUIDParam := web.Params(ctx.Req)[":FolderUID"] + groupParam := web.Params(ctx.Req)[":Group"] + return f.forkRouteGetAlertRuleGroup(ctx, folderUIDParam, groupParam) +} func (f *ForkedProvisioningApi) RouteGetContactpoints(ctx *models.ReqContext) response.Response { return f.forkRouteGetContactpoints(ctx) } @@ -113,7 +119,7 @@ func (f *ForkedProvisioningApi) RoutePutAlertRule(ctx *models.ReqContext) respon func (f *ForkedProvisioningApi) RoutePutAlertRuleGroup(ctx *models.ReqContext) response.Response { folderUIDParam := web.Params(ctx.Req)[":FolderUID"] groupParam := web.Params(ctx.Req)[":Group"] - conf := apimodels.AlertRuleGroup{} + conf := apimodels.AlertRuleGroupMetadata{} if err := web.Bind(ctx.Req, &conf); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } @@ -203,6 +209,16 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApiForkingServi m, ), ) + group.Get( + toMacaronPath("/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}"), + api.authorize(http.MethodGet, "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}"), + metrics.Instrument( + http.MethodGet, + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}", + srv.RouteGetAlertRuleGroup, + m, + ), + ) group.Get( toMacaronPath("/api/v1/provisioning/contact-points"), api.authorize(http.MethodGet, "/api/v1/provisioning/contact-points"), diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index e976a0da3fd..60645a449f3 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -308,7 +308,7 @@ ], "type": "object" }, - "AlertRuleGroup": { + "AlertRuleGroupMetadata": { "properties": { "interval": { "format": "int64", @@ -470,30 +470,6 @@ }, "type": "array" }, - "DashboardAclUpdateItem": { - "properties": { - "permission": { - "$ref": "#/definitions/PermissionType" - }, - "role": { - "enum": [ - "Viewer", - "Editor", - "Admin" - ], - "type": "string" - }, - "teamId": { - "format": "int64", - "type": "integer" - }, - "userId": { - "format": "int64", - "type": "integer" - } - }, - "type": "object" - }, "DateTime": { "description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.", "format": "date-time", @@ -1336,48 +1312,6 @@ }, "type": "array" }, - "MetricRequest": { - "properties": { - "debug": { - "type": "boolean" - }, - "from": { - "description": "From Start time in epoch timestamps in milliseconds or relative using Grafana time units.", - "example": "now-1h", - "type": "string" - }, - "queries": { - "description": "queries.refId – Specifies an identifier of the query. Is optional and default to “A”.\nqueries.datasourceId – Specifies the data source to be queried. Each query in the request must have an unique datasourceId.\nqueries.maxDataPoints - Species maximum amount of data points that dashboard panel can render. Is optional and default to 100.\nqueries.intervalMs - Specifies the time interval in milliseconds of time series. Is optional and defaults to 1000.", - "example": [ - { - "datasource": { - "uid": "PD8C576611E62080A" - }, - "format": "table", - "intervalMs": 86400000, - "maxDataPoints": 1092, - "rawSql": "SELECT 1 as valueOne, 2 as valueTwo", - "refId": "A" - } - ], - "items": { - "$ref": "#/definitions/Json" - }, - "type": "array" - }, - "to": { - "description": "To End time in epoch timestamps in milliseconds or relative using Grafana time units.", - "example": "now", - "type": "string" - } - }, - "required": [ - "from", - "to", - "queries" - ], - "type": "object" - }, "MonthRange": { "properties": { "Begin": { @@ -1425,34 +1359,6 @@ }, "type": "object" }, - "NavLink": { - "properties": { - "id": { - "type": "string" - }, - "target": { - "type": "string" - }, - "text": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "type": "object" - }, - "NavbarPreference": { - "properties": { - "savedItems": { - "items": { - "$ref": "#/definitions/NavLink" - }, - "type": "array" - } - }, - "type": "object" - }, "NotFound": { "type": "object" }, @@ -1668,53 +1574,9 @@ }, "type": "object" }, - "PatchPrefsCmd": { - "properties": { - "homeDashboardId": { - "default": 0, - "description": "The numerical :id of a favorited dashboard", - "format": "int64", - "type": "integer" - }, - "homeDashboardUID": { - "type": "string" - }, - "locale": { - "type": "string" - }, - "navbar": { - "$ref": "#/definitions/NavbarPreference" - }, - "queryHistory": { - "$ref": "#/definitions/QueryHistoryPreference" - }, - "theme": { - "enum": [ - "light", - "dark" - ], - "type": "string" - }, - "timezone": { - "enum": [ - "utc", - "browser" - ], - "type": "string" - }, - "weekStart": { - "type": "string" - } - }, - "type": "object" - }, "PermissionDenied": { "type": "object" }, - "PermissionType": { - "format": "int64", - "type": "integer" - }, "Point": { "properties": { "T": { @@ -2036,14 +1898,6 @@ }, "type": "object" }, - "QueryHistoryPreference": { - "properties": { - "homeTab": { - "type": "string" - } - }, - "type": "object" - }, "Receiver": { "properties": { "email_configs": { @@ -2738,6 +2592,7 @@ "type": "object" }, "URL": { + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", "properties": { "ForceQuery": { "type": "boolean" @@ -2770,58 +2625,7 @@ "$ref": "#/definitions/Userinfo" } }, - "title": "URL is a custom URL type that allows validation at configuration load time.", - "type": "object" - }, - "UpdateDashboardAclCommand": { - "properties": { - "items": { - "items": { - "$ref": "#/definitions/DashboardAclUpdateItem" - }, - "type": "array" - } - }, - "type": "object" - }, - "UpdatePrefsCmd": { - "properties": { - "homeDashboardId": { - "default": 0, - "description": "The numerical :id of a favorited dashboard", - "format": "int64", - "type": "integer" - }, - "homeDashboardUID": { - "type": "string" - }, - "locale": { - "type": "string" - }, - "navbar": { - "$ref": "#/definitions/NavbarPreference" - }, - "queryHistory": { - "$ref": "#/definitions/QueryHistoryPreference" - }, - "theme": { - "enum": [ - "light", - "dark" - ], - "type": "string" - }, - "timezone": { - "enum": [ - "utc", - "browser" - ], - "type": "string" - }, - "weekStart": { - "type": "string" - } - }, + "title": "A URL represents a parsed URL (technically, a URI reference).", "type": "object" }, "Userinfo": { @@ -2991,7 +2795,6 @@ "type": "object" }, "alertGroup": { - "description": "AlertGroup alert group", "properties": { "alerts": { "description": "alerts", @@ -3015,7 +2818,6 @@ "type": "object" }, "alertGroups": { - "description": "AlertGroups alert groups", "items": { "$ref": "#/definitions/alertGroup" }, @@ -3123,6 +2925,7 @@ "$ref": "#/definitions/Duration" }, "gettableAlert": { + "description": "GettableAlert gettable alert", "properties": { "annotations": { "$ref": "#/definitions/labelSet" @@ -3184,7 +2987,6 @@ "type": "array" }, "gettableSilence": { - "description": "GettableSilence gettable silence", "properties": { "comment": { "description": "comment", @@ -3344,7 +3146,6 @@ "type": "array" }, "postableSilence": { - "description": "PostableSilence postable silence", "properties": { "comment": { "description": "comment", @@ -3382,6 +3183,7 @@ "type": "object" }, "receiver": { + "description": "Receiver receiver", "properties": { "name": { "description": "name", @@ -3730,6 +3532,35 @@ } }, "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": { + "get": { + "operationId": "RouteGetAlertRuleGroup", + "parameters": [ + { + "in": "path", + "name": "FolderUID", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "Group", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "$ref": "#/responses/AlertRuleGroup" + }, + "404": { + "description": " Not found." + } + }, + "summary": "Get a rule group.", + "tags": [ + "provisioning" + ] + }, "put": { "consumes": [ "application/json" @@ -3752,15 +3583,15 @@ "in": "body", "name": "Body", "schema": { - "$ref": "#/definitions/AlertRuleGroup" + "$ref": "#/definitions/AlertRuleGroupMetadata" } } ], "responses": { "200": { - "description": "AlertRuleGroup", + "description": "AlertRuleGroupMetadata", "schema": { - "$ref": "#/definitions/AlertRuleGroup" + "$ref": "#/definitions/AlertRuleGroupMetadata" } }, "400": { diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_validation.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager_validation.go index 5b6365ea412..20338b4fc5a 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager_validation.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager_validation.go @@ -101,6 +101,34 @@ func (r *Route) Validate() error { return r.validateChild() } +func (r *Route) ValidateReceivers(receivers map[string]struct{}) error { + if _, exists := receivers[r.Receiver]; !exists { + return fmt.Errorf("receiver '%s' does not exist", r.Receiver) + } + for _, children := range r.Routes { + err := children.ValidateReceivers(receivers) + if err != nil { + return err + } + } + return nil +} + +func (r *Route) ValidateMuteTimes(muteTimes map[string]struct{}) error { + for _, name := range r.MuteTimeIntervals { + if _, exists := muteTimes[name]; !exists { + return fmt.Errorf("mute time interval '%s' does not exist", name) + } + } + for _, child := range r.Routes { + err := child.ValidateMuteTimes(muteTimes) + if err != nil { + return err + } + } + return nil +} + func (mt *MuteTimeInterval) Validate() error { s, err := yaml.Marshal(mt.MuteTimeInterval) if err != nil { diff --git a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go index 3c93ef4f635..8cdd3e1ae1d 100644 --- a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go +++ b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go @@ -257,7 +257,7 @@ type ApiRuleNode struct { Record string `yaml:"record,omitempty" json:"record,omitempty"` Alert string `yaml:"alert,omitempty" json:"alert,omitempty"` Expr string `yaml:"expr" json:"expr"` - For model.Duration `yaml:"for,omitempty" json:"for,omitempty"` + For *model.Duration `yaml:"for,omitempty" json:"for,omitempty"` Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` Annotations map[string]string `yaml:"annotations,omitempty" json:"annotations,omitempty"` } diff --git a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go index 364e36a5997..781d02dc8b1 100644 --- a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go @@ -50,7 +50,7 @@ func Test_Rule_Marshaling(t *testing.T) { desc: "grafana with for, annotation and label properties", input: PostableExtendedRuleNode{ ApiRuleNode: &ApiRuleNode{ - For: dur, + For: &dur, Annotations: map[string]string{"foo": "bar"}, Labels: map[string]string{"label1": "val1"}}, GrafanaManagedAlert: &PostableGrafanaRule{}, @@ -136,7 +136,7 @@ func Test_Rule_Group_Marshaling(t *testing.T) { Rules: []PostableExtendedRuleNode{ { ApiRuleNode: &ApiRuleNode{ - For: dur, + For: &dur, Annotations: map[string]string{"foo": "bar"}, Labels: map[string]string{"label1": "val1"}, }, diff --git a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go index 44a7df7491e..d54f27090bd 100644 --- a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go +++ b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go @@ -135,6 +135,14 @@ func NewAlertRule(rule models.AlertRule, provenance models.Provenance) AlertRule } } +// swagger:route GET /api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RouteGetAlertRuleGroup +// +// Get a rule group. +// +// Responses: +// 200: AlertRuleGroup +// 404: description: Not found. + // swagger:route PUT /api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RoutePutAlertRuleGroup // // Update the interval of a rule group. @@ -143,16 +151,16 @@ func NewAlertRule(rule models.AlertRule, provenance models.Provenance) AlertRule // - application/json // // Responses: -// 200: AlertRuleGroup +// 200: AlertRuleGroupMetadata // 400: ValidationError -// swagger:parameters RoutePutAlertRuleGroup +// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup type FolderUIDPathParam struct { // in:path FolderUID string `json:"FolderUID"` } -// swagger:parameters RoutePutAlertRuleGroup +// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup type RuleGroupPathParam struct { // in:path Group string `json:"Group"` @@ -161,9 +169,16 @@ type RuleGroupPathParam struct { // swagger:parameters RoutePutAlertRuleGroup type AlertRuleGroupPayload struct { // in:body - Body AlertRuleGroup + Body AlertRuleGroupMetadata +} + +type AlertRuleGroupMetadata struct { + Interval int64 `json:"interval"` } type AlertRuleGroup struct { - Interval int64 `json:"interval"` + Title string `json:"title"` + FolderUID string `json:"folderUid"` + Interval int64 `json:"interval"` + Rules []models.AlertRule `json:"rules"` } diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index 5f1dc277dda..05c7dbeb931 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -308,7 +308,7 @@ ], "type": "object" }, - "AlertRuleGroup": { + "AlertRuleGroupMetadata": { "properties": { "interval": { "format": "int64", @@ -2592,6 +2592,7 @@ "type": "object" }, "URL": { + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", "properties": { "ForceQuery": { "type": "boolean" @@ -2624,7 +2625,7 @@ "$ref": "#/definitions/Userinfo" } }, - "title": "URL is a custom URL type that allows validation at configuration load time.", + "title": "A URL represents a parsed URL (technically, a URI reference).", "type": "object" }, "Userinfo": { @@ -2794,6 +2795,7 @@ "type": "object" }, "alertGroup": { + "description": "AlertGroup alert group", "properties": { "alerts": { "description": "alerts", @@ -2988,6 +2990,7 @@ "type": "array" }, "gettableSilence": { + "description": "GettableSilence gettable silence", "properties": { "comment": { "description": "comment", @@ -3146,6 +3149,7 @@ "type": "array" }, "postableSilence": { + "description": "PostableSilence postable silence", "properties": { "comment": { "description": "comment", @@ -5157,6 +5161,35 @@ } }, "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": { + "get": { + "operationId": "RouteGetAlertRuleGroup", + "parameters": [ + { + "in": "path", + "name": "FolderUID", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "Group", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "$ref": "#/responses/AlertRuleGroup" + }, + "404": { + "description": " Not found." + } + }, + "summary": "Get a rule group.", + "tags": [ + "provisioning" + ] + }, "put": { "consumes": [ "application/json" @@ -5179,15 +5212,15 @@ "in": "body", "name": "Body", "schema": { - "$ref": "#/definitions/AlertRuleGroup" + "$ref": "#/definitions/AlertRuleGroupMetadata" } } ], "responses": { "200": { - "description": "AlertRuleGroup", + "description": "AlertRuleGroupMetadata", "schema": { - "$ref": "#/definitions/AlertRuleGroup" + "$ref": "#/definitions/AlertRuleGroupMetadata" } }, "400": { diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 61d42c493db..6878508f9b7 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -1893,6 +1893,36 @@ } }, "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": { + "get": { + "tags": [ + "provisioning", + "stable" + ], + "summary": "Get a rule group.", + "operationId": "RouteGetAlertRuleGroup", + "parameters": [ + { + "type": "string", + "name": "FolderUID", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "Group", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/AlertRuleGroup" + }, + "404": { + "description": " Not found." + } + } + }, "put": { "consumes": [ "application/json" @@ -1920,15 +1950,15 @@ "name": "Body", "in": "body", "schema": { - "$ref": "#/definitions/AlertRuleGroup" + "$ref": "#/definitions/AlertRuleGroupMetadata" } } ], "responses": { "200": { - "description": "AlertRuleGroup", + "description": "AlertRuleGroupMetadata", "schema": { - "$ref": "#/definitions/AlertRuleGroup" + "$ref": "#/definitions/AlertRuleGroupMetadata" } }, "400": { @@ -2629,7 +2659,7 @@ } } }, - "AlertRuleGroup": { + "AlertRuleGroupMetadata": { "type": "object", "properties": { "interval": { @@ -4917,8 +4947,9 @@ } }, "URL": { + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", "type": "object", - "title": "URL is a custom URL type that allows validation at configuration load time.", + "title": "A URL represents a parsed URL (technically, a URI reference).", "properties": { "ForceQuery": { "type": "boolean" @@ -5119,6 +5150,7 @@ } }, "alertGroup": { + "description": "AlertGroup alert group", "type": "object", "required": [ "alerts", @@ -5143,7 +5175,6 @@ "$ref": "#/definitions/alertGroup" }, "alertGroups": { - "description": "AlertGroups alert groups", "type": "array", "items": { "$ref": "#/definitions/alertGroup" @@ -5252,7 +5283,6 @@ "$ref": "#/definitions/Duration" }, "gettableAlert": { - "description": "GettableAlert gettable alert", "type": "object", "required": [ "labels", @@ -5309,7 +5339,6 @@ "$ref": "#/definitions/gettableAlert" }, "gettableAlerts": { - "description": "GettableAlerts gettable alerts", "type": "array", "items": { "$ref": "#/definitions/gettableAlert" @@ -5515,6 +5544,7 @@ "$ref": "#/definitions/postableSilence" }, "receiver": { + "description": "Receiver receiver", "type": "object", "required": [ "name" diff --git a/pkg/services/ngalert/image/service.go b/pkg/services/ngalert/image/service.go index 68111528372..0d5b656086a 100644 --- a/pkg/services/ngalert/image/service.go +++ b/pkg/services/ngalert/image/service.go @@ -9,7 +9,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/grafana/grafana/pkg/components/imguploader" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/dashboards" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" @@ -66,8 +65,8 @@ func NewScreenshotImageServiceFromCfg(cfg *setting.Cfg, metrics prometheus.Regis }, nil } - s := screenshot.NewBrowserScreenshotService(ds, rs) // Image uploading is an optional feature of screenshots + s := screenshot.NewRemoteRenderScreenshotService(ds, rs) if cfg.UnifiedAlerting.Screenshots.UploadExternalImageStorage { u, err := imguploader.NewImageUploader() if err != nil { @@ -111,7 +110,7 @@ func (s *ScreenshotImageService) NewImage(ctx context.Context, r *ngmodels.Alert if err != nil { // TODO: Check for screenshot upload failures. These images should still be // stored because we have a local disk path that could be useful. - if errors.Is(err, models.ErrDashboardNotFound) { + if errors.Is(err, dashboards.ErrDashboardNotFound) { return nil, ErrNoDashboard } return nil, err diff --git a/pkg/services/ngalert/models/alert_query.go b/pkg/services/ngalert/models/alert_query.go index a57ceff1737..503f349362d 100644 --- a/pkg/services/ngalert/models/alert_query.go +++ b/pkg/services/ngalert/models/alert_query.go @@ -40,11 +40,29 @@ func (d *Duration) UnmarshalJSON(b []byte) error { } } +func (d Duration) MarshalYAML() (interface{}, error) { + return time.Duration(d).Seconds(), nil +} + +func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error { + var v interface{} + if err := unmarshal(&v); err != nil { + return err + } + switch value := v.(type) { + case int: + *d = Duration(time.Duration(value) * time.Second) + return nil + default: + return fmt.Errorf("invalid duration %v", v) + } +} + // RelativeTimeRange is the per query start and end time // for requests. type RelativeTimeRange struct { - From Duration `json:"from"` - To Duration `json:"to"` + From Duration `json:"from" yaml:"from"` + To Duration `json:"to" yaml:"to"` } // isValid checks that From duration is greater than To duration. diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index c3161282602..92d02b1a0e6 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -87,7 +87,7 @@ const ( // This isn't a hard-coded secret token, hence the nolint. //nolint:gosec - ScreenshotTokenAnnotation = "__alertScreenshotToken__" + ImageTokenAnnotation = "__alertImageToken__" // GrafanaReservedLabelPrefix contains the prefix for Grafana reserved labels. These differ from "__
    diff --git a/public/app/angular/angular_wrappers.ts b/public/app/angular/angular_wrappers.ts index 15159553cd7..30a63a5af02 100644 --- a/public/app/angular/angular_wrappers.ts +++ b/public/app/angular/angular_wrappers.ts @@ -17,7 +17,7 @@ import { QueryEditor as CloudMonitoringQueryEditor } from 'app/plugins/datasourc import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA'; import { Footer } from '../core/components/Footer/Footer'; -import PageHeader from '../core/components/PageHeader/PageHeader'; +import { PageHeader } from '../core/components/PageHeader/PageHeader'; import { MetricSelect } from '../core/components/Select/MetricSelect'; import { TagFilter } from '../core/components/TagFilter/TagFilter'; import { HelpModal } from '../core/components/help/HelpModal'; diff --git a/public/app/angular/services/nav_model_srv.ts b/public/app/angular/services/nav_model_srv.ts index 73506625130..bf8790e1b3b 100644 --- a/public/app/angular/services/nav_model_srv.ts +++ b/public/app/angular/services/nav_model_srv.ts @@ -71,7 +71,6 @@ export function getWarningNav(text: string, subTitle?: string): NavModel { icon: 'exclamation-triangle', }; return { - breadcrumbs: [node], node: node, main: node, }; diff --git a/public/app/core/components/AccessControl/AddPermission.tsx b/public/app/core/components/AccessControl/AddPermission.tsx index 8c17862ec7a..9785b75776a 100644 --- a/public/app/core/components/AccessControl/AddPermission.tsx +++ b/public/app/core/components/AccessControl/AddPermission.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { Button, Form, HorizontalGroup, Select } from '@grafana/ui'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; +import { ServiceAccountPicker } from 'app/core/components/Select/ServiceAccountPicker'; import { TeamPicker } from 'app/core/components/Select/TeamPicker'; import { UserPicker } from 'app/core/components/Select/UserPicker'; import { OrgRole } from 'app/types/acl'; @@ -28,6 +29,9 @@ export const AddPermission = ({ title = 'Add Permission For', permissions, assig if (assignments.users) { options.push({ value: PermissionTarget.User, label: 'User' }); } + if (assignments.serviceAccounts) { + options.push({ value: PermissionTarget.ServiceAccount, label: 'Service Account' }); + } if (assignments.teams) { options.push({ value: PermissionTarget.Team, label: 'Team' }); } @@ -46,6 +50,7 @@ export const AddPermission = ({ title = 'Add Permission For', permissions, assig const isValid = () => (target === PermissionTarget.Team && teamId > 0) || (target === PermissionTarget.User && userId > 0) || + (target === PermissionTarget.ServiceAccount && userId > 0) || (PermissionTarget.BuiltInRole && OrgRole.hasOwnProperty(builtInRole)); return ( @@ -72,6 +77,10 @@ export const AddPermission = ({ title = 'Add Permission For', permissions, assig setUserId(u.value || 0)} className={'width-20'} /> )} + {target === PermissionTarget.ServiceAccount && ( + setUserId(s.value?.id || 0)} className={'width-20'} /> + )} + {target === PermissionTarget.Team && ( setTeamId(t.value?.id || 0)} className={'width-20'} /> )} diff --git a/public/app/core/components/AccessControl/Permissions.tsx b/public/app/core/components/AccessControl/Permissions.tsx index 39446dedd47..0e8b9955ef1 100644 --- a/public/app/core/components/AccessControl/Permissions.tsx +++ b/public/app/core/components/AccessControl/Permissions.tsx @@ -16,6 +16,7 @@ const INITIAL_DESCRIPTION: Description = { assignments: { teams: false, users: false, + serviceAccounts: false, builtInRoles: false, }, }; @@ -57,7 +58,7 @@ export const Permissions = ({ const onAdd = (state: SetPermission) => { let promise: Promise | null = null; - if (state.target === PermissionTarget.User) { + if (state.target === PermissionTarget.User || state.target === PermissionTarget.ServiceAccount) { promise = setUserPermission(resource, resourceId, state.userId!, state.permission); } else if (state.target === PermissionTarget.Team) { promise = setTeamPermission(resource, resourceId, state.teamId!, state.permission); @@ -109,7 +110,15 @@ export const Permissions = ({ const users = useMemo( () => sortBy( - items.filter((i) => i.userId), + items.filter((i) => i.userId && !i.userIsServiceAccount), + ['userLogin'] + ), + [items] + ); + const serviceAccounts = useMemo( + () => + sortBy( + items.filter((i) => i.userId && i.userIsServiceAccount), ['userLogin'] ), [items] @@ -161,6 +170,14 @@ export const Permissions = ({ onRemove={onRemove} canSet={canSetPermissions} /> + { - /** This is nav tree id provided by route. - * It's not enough for item navigation. For that pages will need provide an item nav model as well via TopNavUpdate - */ - navId?: string; -} +export interface Props extends PropsWithChildren<{}> {} -export function TopNavPage({ children, navId }: Props) { +export function AppChrome({ children }: Props) { const styles = useStyles2(getStyles); const [searchBarHidden, toggleSearchBar] = useToggle(false); // repace with local storage - const props = useObservable(topNavUpdates, topNavDefaultProps); - const navModel = useSelector(createSelector(getNavIndex, (navIndex) => getNavModel(navIndex, navId ?? 'home'))); + const [megaMenuOpen, setMegaMenuOpen] = useState(false); + const state = appChromeService.useState(); + + if (state.chromeless || !config.featureToggles.topnav) { + return
    {children}
    ; + } return ( -
    +
    {!searchBarHidden && } setMegaMenuOpen(!megaMenuOpen)} />
    {children}
    -
    + {megaMenuOpen && setMegaMenuOpen(false)} />} + ); } -function getNavIndex(store: StoreState) { - return store.navIndex; -} - const getStyles = (theme: GrafanaTheme2) => { const shadow = theme.isDark ? `0 0.6px 1.5px rgb(0 0 0), 0 2px 4px rgb(0 0 0 / 40%), 0 5px 10px rgb(0 0 0 / 23%)` : '0 0.6px 1.5px rgb(0 0 0 / 8%), 0 2px 4px rgb(0 0 0 / 6%), 0 5px 10px rgb(0 0 0 / 5%)'; return { - viewport: css({ + content: css({ display: 'flex', + flexDirection: 'column', + paddingTop: TOP_BAR_LEVEL_HEIGHT * 2, flexGrow: 1, height: '100%', }), - content: css({ - display: 'flex', - paddingTop: TOP_BAR_LEVEL_HEIGHT * 2 + 16, - flexGrow: 1, - }), contentNoSearchBar: css({ - paddingTop: TOP_BAR_LEVEL_HEIGHT + 16, + paddingTop: TOP_BAR_LEVEL_HEIGHT, }), topNav: css({ display: 'flex', diff --git a/public/app/core/components/AppChrome/AppChromeService.tsx b/public/app/core/components/AppChrome/AppChromeService.tsx new file mode 100644 index 00000000000..0c113f7701d --- /dev/null +++ b/public/app/core/components/AppChrome/AppChromeService.tsx @@ -0,0 +1,51 @@ +import { useObservable } from 'react-use'; +import { BehaviorSubject } from 'rxjs'; + +import { NavModelItem } from '@grafana/data'; +import { isShallowEqual } from 'app/core/utils/isShallowEqual'; + +import { RouteDescriptor } from '../../navigation/types'; + +export interface AppChromeState { + chromeless: boolean; + sectionNav: NavModelItem; + pageNav?: NavModelItem; + actions?: React.ReactNode; +} + +const defaultSection: NavModelItem = { text: 'Grafana' }; + +export class AppChromeService { + readonly state = new BehaviorSubject({ + chromeless: true, // start out hidden to not flash it on pages without chrome + sectionNav: defaultSection, + }); + + routeMounted(route: RouteDescriptor) { + this.update({ + chromeless: route.chromeless === true, + sectionNav: defaultSection, + pageNav: undefined, + actions: undefined, + }); + } + + update(state: Partial) { + const current = this.state.getValue(); + const newState: AppChromeState = { + ...current, + ...state, + }; + + if (!isShallowEqual(current, newState)) { + this.state.next(newState); + } + } + + useState() { + // eslint-disable-next-line react-hooks/rules-of-hooks + return useObservable(this.state, this.state.getValue()); + } +} + +export const appChromeService = new AppChromeService(); diff --git a/public/app/core/components/AppChrome/AppChromeUpdate.tsx b/public/app/core/components/AppChrome/AppChromeUpdate.tsx new file mode 100644 index 00000000000..38545bfce8f --- /dev/null +++ b/public/app/core/components/AppChrome/AppChromeUpdate.tsx @@ -0,0 +1,22 @@ +import React, { useEffect } from 'react'; + +import { NavModelItem } from '@grafana/data'; + +import { appChromeService } from './AppChromeService'; + +export interface AppChromeUpdateProps { + pageNav?: NavModelItem; + actions?: React.ReactNode; +} +/** + * This needs to be moved to @grafana/ui or runtime. + * This is the way core pages and plugins update the breadcrumbs and page toolbar actions + */ +export const AppChromeUpdate = React.memo(({ pageNav, actions }: AppChromeUpdateProps) => { + useEffect(() => { + appChromeService.update({ pageNav, actions }); + }); + return null; +}); + +AppChromeUpdate.displayName = 'TopNavUpdate'; diff --git a/public/app/core/components/TopNav/Breadcrumbs.tsx b/public/app/core/components/AppChrome/Breadcrumbs.tsx similarity index 86% rename from public/app/core/components/TopNav/Breadcrumbs.tsx rename to public/app/core/components/AppChrome/Breadcrumbs.tsx index bdfad00315e..c7bc80ec1d8 100644 --- a/public/app/core/components/TopNav/Breadcrumbs.tsx +++ b/public/app/core/components/AppChrome/Breadcrumbs.tsx @@ -4,11 +4,9 @@ import React from 'react'; import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { useStyles2, Icon, IconName } from '@grafana/ui'; -import { TopNavProps } from './TopNavUpdate'; - -export interface Props extends TopNavProps { +export interface Props { sectionNav: NavModelItem; - subNav?: NavModelItem; + pageNav?: NavModelItem; } export interface Breadcrumb { @@ -17,9 +15,9 @@ export interface Breadcrumb { href?: string; } -export function Breadcrumbs({ sectionNav, subNav }: Props) { +export function Breadcrumbs({ sectionNav, pageNav }: Props) { const styles = useStyles2(getStyles); - const crumbs: Breadcrumb[] = [{ icon: 'home', href: '/' }]; + const crumbs: Breadcrumb[] = [{ icon: 'home-alt', href: '/' }]; function addCrumbs(node: NavModelItem) { if (node.parentItem) { @@ -31,8 +29,8 @@ export function Breadcrumbs({ sectionNav, subNav }: Props) { addCrumbs(sectionNav); - if (subNav) { - addCrumbs(subNav); + if (pageNav) { + addCrumbs(pageNav); } return ( diff --git a/public/app/core/components/TopNav/NavToolbar.tsx b/public/app/core/components/AppChrome/NavToolbar.tsx similarity index 80% rename from public/app/core/components/TopNav/NavToolbar.tsx rename to public/app/core/components/AppChrome/NavToolbar.tsx index afe8474d3d1..d0ca6b346d6 100644 --- a/public/app/core/components/TopNav/NavToolbar.tsx +++ b/public/app/core/components/AppChrome/NavToolbar.tsx @@ -5,25 +5,33 @@ import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { IconButton, ToolbarButton, useStyles2 } from '@grafana/ui'; import { Breadcrumbs } from './Breadcrumbs'; -import { TopNavProps } from './TopNavUpdate'; import { TOP_BAR_LEVEL_HEIGHT } from './types'; -export interface Props extends TopNavProps { +export interface Props { onToggleSearchBar(): void; + onToggleMegaMenu(): void; searchBarHidden?: boolean; sectionNav: NavModelItem; - subNav?: NavModelItem; + pageNav?: NavModelItem; + actions: React.ReactNode; } -export function NavToolbar({ actions, onToggleSearchBar, searchBarHidden, sectionNav, subNav }: Props) { +export function NavToolbar({ + actions, + searchBarHidden, + sectionNav, + pageNav, + onToggleMegaMenu, + onToggleSearchBar, +}: Props) { const styles = useStyles2(getStyles); return (
    - {}} /> +
    - +
    {actions} diff --git a/public/app/core/components/TopNav/TopSearchBar.tsx b/public/app/core/components/AppChrome/TopSearchBar.tsx similarity index 100% rename from public/app/core/components/TopNav/TopSearchBar.tsx rename to public/app/core/components/AppChrome/TopSearchBar.tsx diff --git a/public/app/core/components/AppChrome/types.ts b/public/app/core/components/AppChrome/types.ts new file mode 100644 index 00000000000..654334fdea6 --- /dev/null +++ b/public/app/core/components/AppChrome/types.ts @@ -0,0 +1,8 @@ +import { NavModelItem } from '@grafana/data'; + +export const TOP_BAR_LEVEL_HEIGHT = 40; + +export interface ToolbarUpdateProps { + pageNav?: NavModelItem; + actions?: React.ReactNode; +} diff --git a/public/app/core/components/ErrorPage/ErrorPage.tsx b/public/app/core/components/ErrorPage/ErrorPage.tsx index 8fe3e4c645e..25e77912f6c 100644 --- a/public/app/core/components/ErrorPage/ErrorPage.tsx +++ b/public/app/core/components/ErrorPage/ErrorPage.tsx @@ -7,7 +7,7 @@ import { Icon } from '@grafana/ui'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; -import Page from '../Page/Page'; +import { Page } from '../Page/Page'; interface ConnectedProps { navModel: NavModel; diff --git a/public/app/core/components/MegaMenu/MegaMenu.test.tsx b/public/app/core/components/MegaMenu/MegaMenu.test.tsx new file mode 100644 index 00000000000..04283424de5 --- /dev/null +++ b/public/app/core/components/MegaMenu/MegaMenu.test.tsx @@ -0,0 +1,61 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router-dom'; + +import { NavModelItem, NavSection } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { configureStore } from 'app/store/configureStore'; + +import TestProvider from '../../../../test/helpers/TestProvider'; + +import { MegaMenu } from './MegaMenu'; + +const setup = () => { + const navBarTree: NavModelItem[] = [ + { + text: 'Section name', + section: NavSection.Core, + id: 'section', + url: 'section', + children: [ + { text: 'Child1', id: 'child1', url: 'section/child1' }, + { text: 'Child2', id: 'child2', url: 'section/child2' }, + ], + }, + { + text: 'Profile', + id: 'profile', + section: NavSection.Config, + url: 'profile', + }, + ]; + + const store = configureStore({ navBarTree }); + + return render( + + + + {}} /> + + + + ); +}; + +describe('MegaMenu', () => { + it('should render component', async () => { + setup(); + + expect(await screen.findByTestId('navbarmenu')).toBeInTheDocument(); + expect(await screen.findByLabelText('Home')).toBeInTheDocument(); + expect(screen.queryAllByLabelText('Section name').length).toBe(2); + }); + + it('should filter out profile', async () => { + setup(); + + expect(screen.queryByLabelText('Profile')).not.toBeInTheDocument(); + }); +}); diff --git a/public/app/core/components/MegaMenu/MegaMenu.tsx b/public/app/core/components/MegaMenu/MegaMenu.tsx new file mode 100644 index 00000000000..894949df2c4 --- /dev/null +++ b/public/app/core/components/MegaMenu/MegaMenu.tsx @@ -0,0 +1,80 @@ +import { css } from '@emotion/css'; +import { cloneDeep } from 'lodash'; +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; + +import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { useTheme2 } from '@grafana/ui'; +import { StoreState } from 'app/types'; + +import { enrichConfigItems, enrichWithInteractionTracking, getActiveItem } from '../NavBar/utils'; + +import { NavBarMenu } from './NavBarMenu'; + +export interface Props { + onClose: () => void; + searchBarHidden?: boolean; +} + +export const MegaMenu = React.memo(({ onClose, searchBarHidden }) => { + const navBarTree = useSelector((state: StoreState) => state.navBarTree); + const theme = useTheme2(); + const styles = getStyles(theme); + const location = useLocation(); + const [showSwitcherModal, setShowSwitcherModal] = useState(false); + + const toggleSwitcherModal = () => { + setShowSwitcherModal(!showSwitcherModal); + }; + + const homeItem: NavModelItem = enrichWithInteractionTracking( + { + id: 'home', + text: 'Home', + url: config.appSubUrl || '/', + icon: 'home-alt', + }, + true + ); + + const navTree = cloneDeep(navBarTree); + + const coreItems = navTree + .filter((item) => item.section === NavSection.Core) + .map((item) => enrichWithInteractionTracking(item, true)); + const pluginItems = navTree + .filter((item) => item.section === NavSection.Plugin) + .map((item) => enrichWithInteractionTracking(item, true)); + const configItems = enrichConfigItems( + navTree.filter((item) => item.section === NavSection.Config && item && item.id !== 'help' && item.id !== 'profile'), + location, + toggleSwitcherModal + ).map((item) => enrichWithInteractionTracking(item, true)); + + const activeItem = getActiveItem(navTree, location.pathname); + + return ( +
    + +
    + ); +}); + +MegaMenu.displayName = 'MegaMenu'; + +const getStyles = (theme: GrafanaTheme2) => ({ + menuWrapper: css({ + position: 'fixed', + display: 'grid', + gridAutoFlow: 'column', + height: '100%', + zIndex: theme.zIndex.sidemenu, + }), +}); diff --git a/public/app/core/components/MegaMenu/NavBarMenu.tsx b/public/app/core/components/MegaMenu/NavBarMenu.tsx new file mode 100644 index 00000000000..2b2df363de4 --- /dev/null +++ b/public/app/core/components/MegaMenu/NavBarMenu.tsx @@ -0,0 +1,211 @@ +import { css } from '@emotion/css'; +import { useDialog } from '@react-aria/dialog'; +import { FocusScope } from '@react-aria/focus'; +import { OverlayContainer, useOverlay } from '@react-aria/overlays'; +import React, { useRef } from 'react'; +import CSSTransition from 'react-transition-group/CSSTransition'; + +import { GrafanaTheme2, NavModelItem } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; +import { CustomScrollbar, Icon, IconButton, useTheme2 } from '@grafana/ui'; + +import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types'; +import { NavItem } from '../NavBar/NavBarMenu'; +import { NavBarToggle } from '../NavBar/NavBarToggle'; + +const MENU_WIDTH = '350px'; + +export interface Props { + activeItem?: NavModelItem; + navItems: NavModelItem[]; + searchBarHidden?: boolean; + onClose: () => void; +} + +export function NavBarMenu({ activeItem, navItems, searchBarHidden, onClose }: Props) { + const theme = useTheme2(); + const styles = getStyles(theme, searchBarHidden); + const animationSpeed = theme.transitions.duration.shortest; + const animStyles = getAnimStyles(theme, animationSpeed); + const ref = useRef(null); + const { dialogProps } = useDialog({}, ref); + + const { overlayProps, underlayProps } = useOverlay( + { + isDismissable: true, + isOpen: true, + onClose, + }, + ref + ); + + return ( + + + +
    +
    + + +
    + { + reportInteraction('grafana_navigation_collapsed'); + onClose(); + }} + /> + +
    +
    + +
    + + + + ); +} + +NavBarMenu.displayName = 'NavBarMenu'; + +const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => { + const topPosition = (searchBarHidden ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2) + 1; + + return { + backdrop: css({ + backdropFilter: 'blur(1px)', + backgroundColor: theme.components.overlay.background, + bottom: 0, + left: 0, + position: 'fixed', + right: 0, + top: topPosition, + zIndex: theme.zIndex.navbarFixed - 2, + }), + container: css({ + display: 'flex', + bottom: 0, + flexDirection: 'column', + left: 0, + paddingTop: theme.spacing(1), + marginRight: theme.spacing(1.5), + right: 0, + // Needs to below navbar should we change the navbarFixed? add add a new level? + zIndex: theme.zIndex.navbarFixed - 1, + position: 'fixed', + top: topPosition, + boxSizing: 'content-box', + [theme.breakpoints.up('md')]: { + borderRight: `1px solid ${theme.colors.border.weak}`, + right: 'unset', + }, + }), + content: css({ + display: 'flex', + flexDirection: 'column', + overflow: 'auto', + }), + mobileHeader: css({ + borderBottom: `1px solid ${theme.colors.border.weak}`, + display: 'flex', + justifyContent: 'space-between', + padding: theme.spacing(1, 2, 2), + [theme.breakpoints.up('md')]: { + display: 'none', + }, + }), + itemList: css({ + display: 'grid', + gridAutoRows: `minmax(${theme.spacing(6)}, auto)`, + minWidth: MENU_WIDTH, + }), + menuCollapseIcon: css({ + position: 'absolute', + top: '43px', + right: '0px', + transform: `translateX(50%)`, + }), + }; +}; + +const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => { + const commonTransition = { + transitionDuration: `${animationDuration}ms`, + transitionTimingFunction: theme.transitions.easing.easeInOut, + [theme.breakpoints.down('md')]: { + overflow: 'hidden', + }, + }; + + const overlayTransition = { + ...commonTransition, + transitionProperty: 'background-color, box-shadow, width', + // this is needed to prevent a horizontal scrollbar during the animation on firefox + '.scrollbar-view': { + overflow: 'hidden !important', + }, + }; + + const backdropTransition = { + ...commonTransition, + transitionProperty: 'opacity', + }; + + const overlayOpen = { + backgroundColor: theme.colors.background.primary, + boxShadow: theme.shadows.z3, + width: '100%', + [theme.breakpoints.up('md')]: { + width: MENU_WIDTH, + }, + }; + + const overlayClosed = { + boxShadow: 'none', + width: 0, + [theme.breakpoints.up('md')]: { + backgroundColor: theme.colors.background.primary, + width: theme.spacing(7), + }, + }; + + const backdropOpen = { + opacity: 1, + }; + + const backdropClosed = { + opacity: 0, + }; + + return { + backdrop: { + appear: css(backdropClosed), + appearActive: css(backdropTransition, backdropOpen), + appearDone: css(backdropOpen), + exit: css(backdropOpen), + exitActive: css(backdropTransition, backdropClosed), + }, + overlay: { + appear: css(overlayClosed), + appearActive: css(overlayTransition, overlayOpen), + appearDone: css(overlayOpen), + exit: css(overlayOpen), + exitActive: css(overlayTransition, overlayClosed), + }, + }; +}; diff --git a/public/app/core/components/NavBar/NavBarMenu.tsx b/public/app/core/components/NavBar/NavBarMenu.tsx index e549cd222b7..53ac531b05a 100644 --- a/public/app/core/components/NavBar/NavBarMenu.tsx +++ b/public/app/core/components/NavBar/NavBarMenu.tsx @@ -220,7 +220,7 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => { }; }; -function NavItem({ +export function NavItem({ link, activeItem, onClose, @@ -458,7 +458,7 @@ function linkHasChildren(link: NavModelItem): link is NavModelItem & { children: } function getLinkIcon(link: NavModelItem) { - if (link.id === 'home') { + if (link.icon === 'grafana') { return ; } else if (link.icon) { return ; diff --git a/public/app/core/components/Page/Page.test.tsx b/public/app/core/components/Page/Page.test.tsx new file mode 100644 index 00000000000..3ac2e7edcff --- /dev/null +++ b/public/app/core/components/Page/Page.test.tsx @@ -0,0 +1,67 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { NavModelItem } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { configureStore } from 'app/store/configureStore'; + +import { Page } from './Page'; +import { PageProps } from './types'; + +const pageNav: NavModelItem = { + text: 'Main title', + children: [ + { text: 'Child1', url: '1', active: true }, + { text: 'Child2', url: '2' }, + ], +}; + +const setup = (props: Partial) => { + config.bootData.navTree = [ + { + text: 'Section name', + id: 'section', + url: 'section', + children: [ + { text: 'Child1', id: 'child1', url: 'section/child1' }, + { text: 'Child2', id: 'child2', url: 'section/child2' }, + ], + }, + ]; + + const store = configureStore(); + + return render( + + +
    Children
    +
    +
    + ); +}; + +describe('Render', () => { + it('should render component with emtpy Page container', async () => { + setup({}); + const children = await screen.findByTestId('page-children'); + expect(children).toBeInTheDocument(); + + const pageHeader = screen.queryByRole('heading'); + expect(pageHeader).not.toBeInTheDocument(); + }); + + it('should render header when pageNav supplied', async () => { + setup({ pageNav }); + + expect(screen.getByRole('heading', { name: 'Main title' })).toBeInTheDocument(); + expect(screen.getAllByRole('tab').length).toBe(2); + }); + + it('should get header nav model from redux navIndex', async () => { + setup({ navId: 'child1' }); + + expect(screen.getByRole('heading', { name: 'Section name' })).toBeInTheDocument(); + expect(screen.getAllByRole('tab').length).toBe(2); + }); +}); diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 121b9ba1548..643c9ee281e 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -1,45 +1,33 @@ // Libraries import { css, cx } from '@emotion/css'; -import React, { FC, HTMLAttributes, useEffect } from 'react'; +import React from 'react'; -import { GrafanaTheme2, NavModel } from '@grafana/data'; +import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { CustomScrollbar, useStyles2 } from '@grafana/ui'; -import { getTitleFromNavModel } from 'app/core/selectors/navModel'; -// Components -import { Branding } from '../Branding/Branding'; import { Footer } from '../Footer/Footer'; -import PageHeader from '../PageHeader/PageHeader'; +import { PageHeader } from '../PageHeader/PageHeader'; +import { Page as NewPage } from '../PageNew/Page'; import { PageContents } from './PageContents'; +import { PageType } from './types'; +import { usePageNav } from './usePageNav'; +import { usePageTitle } from './usePageTitle'; -interface Props extends HTMLAttributes { - children: React.ReactNode; - navModel?: NavModel; -} - -export interface PageType extends FC { - Header: typeof PageHeader; - Contents: typeof PageContents; -} - -export const Page: PageType = ({ navModel, children, className, ...otherProps }) => { +export const OldPage: PageType = ({ navId, navModel: oldNavProp, pageNav, children, className, ...otherProps }) => { const styles = useStyles2(getStyles); + const navModel = usePageNav(navId, oldNavProp); - useEffect(() => { - if (navModel) { - const title = getTitleFromNavModel(navModel); - document.title = title ? `${title} - ${Branding.AppTitle}` : Branding.AppTitle; - } else { - document.title = Branding.AppTitle; - } - }, [navModel]); + usePageTitle(navModel, pageNav); + + const pageHeaderNav = pageNav ?? navModel?.main; return (
    - {navModel && } + {pageHeaderNav && } {children}
    @@ -48,12 +36,12 @@ export const Page: PageType = ({ navModel, children, className, ...otherProps }) ); }; -Page.Header = PageHeader; -Page.Contents = PageContents; +OldPage.Header = PageHeader; +OldPage.Contents = PageContents; -export default Page; +export const Page: PageType = config.featureToggles.topnav ? NewPage : OldPage; -const getStyles = (theme: GrafanaTheme2) => ({ +const getStyles = (_: GrafanaTheme2) => ({ wrapper: css` width: 100%; flex-grow: 1; diff --git a/public/app/core/components/Page/types.ts b/public/app/core/components/Page/types.ts new file mode 100644 index 00000000000..bc8340f22fc --- /dev/null +++ b/public/app/core/components/Page/types.ts @@ -0,0 +1,19 @@ +import { FC, HTMLAttributes } from 'react'; + +import { NavModel, NavModelItem } from '@grafana/data'; + +import { PageHeader } from '../PageHeader/PageHeader'; + +import { PageContents } from './PageContents'; + +export interface PageProps extends HTMLAttributes { + children: React.ReactNode; + navId?: string; + navModel?: NavModel; + pageNav?: NavModelItem; +} + +export interface PageType extends FC { + Header: typeof PageHeader; + Contents: typeof PageContents; +} diff --git a/public/app/core/components/Page/usePageNav.ts b/public/app/core/components/Page/usePageNav.ts new file mode 100644 index 00000000000..e78a5d17aa6 --- /dev/null +++ b/public/app/core/components/Page/usePageNav.ts @@ -0,0 +1,29 @@ +import { useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; + +import { NavModel } from '@grafana/data'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { store } from 'app/store/store'; +import { StoreState } from 'app/types'; + +export function usePageNav(navId?: string, oldProp?: NavModel): NavModel | undefined { + if (oldProp) { + return oldProp; + } + + if (!navId) { + return; + } + + // Page component is used in so many tests, this simplifies not having to initialize a full redux store + if (!store) { + return; + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + return useSelector(createSelector(getNavIndex, (navIndex) => getNavModel(navIndex, navId ?? 'home'))); +} + +function getNavIndex(store: StoreState) { + return store.navIndex; +} diff --git a/public/app/core/components/Page/usePageTitle.ts b/public/app/core/components/Page/usePageTitle.ts new file mode 100644 index 00000000000..b7512bb7b46 --- /dev/null +++ b/public/app/core/components/Page/usePageTitle.ts @@ -0,0 +1,32 @@ +import { useEffect } from 'react'; + +import { NavModel, NavModelItem } from '@grafana/data'; + +import { Branding } from '../Branding/Branding'; + +export function usePageTitle(navModel?: NavModel, pageNav?: NavModelItem) { + useEffect(() => { + const parts: string[] = []; + + if (pageNav) { + if (pageNav.children) { + const activePage = pageNav.children.find((x) => x.active); + if (activePage) { + parts.push(activePage.text); + } + } + parts.push(pageNav.text); + } + + if (navModel) { + if (navModel.node !== navModel.main) { + parts.push(navModel.node.text); + } + parts.push(navModel.main.text); + } + + parts.push(Branding.AppTitle); + + document.title = parts.join(' - '); + }, [navModel, pageNav]); +} diff --git a/public/app/core/components/PageHeader/PageHeader.test.tsx b/public/app/core/components/PageHeader/PageHeader.test.tsx index 2965d007142..3e5af5ee1f9 100644 --- a/public/app/core/components/PageHeader/PageHeader.test.tsx +++ b/public/app/core/components/PageHeader/PageHeader.test.tsx @@ -1,23 +1,20 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import PageHeader from './PageHeader'; +import { PageHeader } from './PageHeader'; describe('PageHeader', () => { describe('when the nav tree has a node with a title', () => { it('should render the title', async () => { const nav = { - main: { - icon: 'folder-open', - id: 'node', - subTitle: 'node subtitle', - url: '', - text: 'node', - }, - node: {}, + icon: 'folder-open', + id: 'node', + subTitle: 'node subtitle', + url: '', + text: 'node', }; - render(); + render(); expect(screen.getByRole('heading', { name: 'node' })).toBeInTheDocument(); }); @@ -26,18 +23,15 @@ describe('PageHeader', () => { describe('when the nav tree has a node with breadcrumbs and a title', () => { it('should render the title with breadcrumbs first and then title last', async () => { const nav = { - main: { - icon: 'folder-open', - id: 'child', - subTitle: 'child subtitle', - url: '', - text: 'child', - breadcrumbs: [{ title: 'Parent', url: 'parentUrl' }], - }, - node: {}, + icon: 'folder-open', + id: 'child', + subTitle: 'child subtitle', + url: '', + text: 'child', + breadcrumbs: [{ title: 'Parent', url: 'parentUrl' }], }; - render(); + render(); expect(screen.getByRole('heading', { name: 'Parent / child' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'Parent' })).toBeInTheDocument(); diff --git a/public/app/core/components/PageHeader/PageHeader.tsx b/public/app/core/components/PageHeader/PageHeader.tsx index 116ff8186dd..45fe58f1474 100644 --- a/public/app/core/components/PageHeader/PageHeader.tsx +++ b/public/app/core/components/PageHeader/PageHeader.tsx @@ -1,14 +1,14 @@ import { css } from '@emotion/css'; import React, { FC } from 'react'; -import { NavModel, NavModelItem, NavModelBreadcrumb, GrafanaTheme2 } from '@grafana/data'; +import { NavModelItem, NavModelBreadcrumb, GrafanaTheme2 } from '@grafana/data'; import { Tab, TabsBar, Icon, IconName, useStyles2 } from '@grafana/ui'; import { PanelHeaderMenuItem } from 'app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem'; import { ProBadge } from '../Upgrade/ProBadge'; export interface Props { - model: NavModel; + navItem: NavModelItem; } const SelectNav = ({ children, customCss }: { children: NavModelItem[]; customCss: string }) => { @@ -75,21 +75,19 @@ const Navigation = ({ children }: { children: NavModelItem[] }) => { ); }; -export const PageHeader: FC = ({ model }) => { +export const PageHeader: FC = ({ navItem: model }) => { const styles = useStyles2(getStyles); if (!model) { return null; } - const main = model.main; - const children = main.children; return (
    - {renderHeaderTitle(main)} - {children && children.length && {children}} + {renderHeaderTitle(model)} + {model.children && model.children.length > 0 && {model.children}}
    @@ -157,5 +155,3 @@ const getStyles = (theme: GrafanaTheme2) => ({ background: ${theme.colors.background.canvas}; `, }); - -export default PageHeader; diff --git a/public/app/core/components/PageNew/Page.test.tsx b/public/app/core/components/PageNew/Page.test.tsx new file mode 100644 index 00000000000..a28ecec2b7d --- /dev/null +++ b/public/app/core/components/PageNew/Page.test.tsx @@ -0,0 +1,79 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { NavModelItem } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { configureStore } from 'app/store/configureStore'; + +import { PageProps } from '../Page/types'; + +import { Page } from './Page'; + +const pageNav: NavModelItem = { + text: 'pageNav title', + children: [ + { text: 'pageNav child1', url: '1', active: true }, + { text: 'pageNav child2', url: '2' }, + ], +}; + +const setup = (props: Partial) => { + config.bootData.navTree = [ + { + text: 'Section name', + id: 'section', + url: 'section', + children: [ + { text: 'Child1', id: 'child1', url: 'section/child1' }, + { text: 'Child2', id: 'child2', url: 'section/child2' }, + ], + }, + ]; + + const store = configureStore(); + + return render( + + +
    Children
    +
    +
    + ); +}; + +describe('Render', () => { + it('should render component with emtpy Page container', async () => { + setup({}); + const children = await screen.findByTestId('page-children'); + expect(children).toBeInTheDocument(); + + const pageHeader = screen.queryByRole('heading'); + expect(pageHeader).not.toBeInTheDocument(); + }); + + it('should render header when pageNav supplied', async () => { + setup({ pageNav }); + + expect(screen.getByRole('heading', { name: 'pageNav title' })).toBeInTheDocument(); + expect(screen.getAllByRole('tab').length).toBe(2); + }); + + it('should render section nav model based on navId', async () => { + setup({ navId: 'child1' }); + + expect(screen.getByRole('heading', { name: 'Section name' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Child1' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Tab Child1' })).toBeInTheDocument(); + expect(screen.getAllByRole('tab').length).toBe(2); + }); + + it('should render section nav model based on navId and item page nav', async () => { + setup({ navId: 'child1', pageNav }); + + expect(screen.getByRole('heading', { name: 'Section name' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'pageNav title' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Tab Child1' })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: 'Tab pageNav child1' })).toBeInTheDocument(); + }); +}); diff --git a/public/app/core/components/PageNew/Page.tsx b/public/app/core/components/PageNew/Page.tsx new file mode 100644 index 00000000000..72bf56d951a --- /dev/null +++ b/public/app/core/components/PageNew/Page.tsx @@ -0,0 +1,93 @@ +// Libraries +import { css, cx } from '@emotion/css'; +import React, { useEffect } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { CustomScrollbar, useStyles2 } from '@grafana/ui'; + +// Components +import { appChromeService } from '../AppChrome/AppChromeService'; +import { Footer } from '../Footer/Footer'; +import { PageType } from '../Page/types'; +import { usePageNav } from '../Page/usePageNav'; +import { usePageTitle } from '../Page/usePageTitle'; + +import { PageContents } from './PageContents'; +import { PageHeader } from './PageHeader'; +import { PageTabs } from './PageTabs'; +import { SectionNav } from './SectionNav'; + +export const Page: PageType = ({ navId, navModel: oldNavProp, pageNav, children, className, ...otherProps }) => { + const styles = useStyles2(getStyles); + const navModel = usePageNav(navId, oldNavProp); + + usePageTitle(navModel, pageNav); + + const pageHeaderNav = pageNav ?? navModel?.node; + + useEffect(() => { + if (navModel || pageNav) { + appChromeService.update({ sectionNav: navModel?.node, pageNav }); + } + }, [navModel, pageNav]); + + return ( +
    +
    + {navModel && navModel.main.children && } +
    + +
    + {pageHeaderNav && } + {pageNav && pageNav.children && } + {children} +
    +
    + +
    +
    +
    + ); +}; + +Page.Header = PageHeader; +Page.Contents = PageContents; + +const getStyles = (theme: GrafanaTheme2) => { + const shadow = theme.isDark + ? `0 0.6px 1.5px -1px rgb(0 0 0),0 2px 4px -1px rgb(0 0 0 / 40%),0 5px 10px -1px rgb(0 0 0 / 23%)` + : '0 0.6px 1.5px -1px rgb(0 0 0 / 8%),0 2px 4px rgb(0 0 0 / 6%),0 5px 10px -1px rgb(0 0 0 / 5%)'; + + return { + wrapper: css` + height: 100%; + display: flex; + flex: 1 1 0; + flex-direction: column; + min-height: 0; + `, + panes: css({ + display: 'flex', + height: '100%', + width: '100%', + flexGrow: 1, + minHeight: 0, + flexDirection: 'column', + [theme.breakpoints.up('md')]: { + flexDirection: 'row', + }, + }), + pageContent: css({ + flexGrow: 1, + }), + pageInner: css({ + padding: theme.spacing(3), + boxShadow: shadow, + background: theme.colors.background.primary, + margin: theme.spacing(2, 2, 2, 1), + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + }), + }; +}; diff --git a/public/app/core/components/PageNew/PageContents.tsx b/public/app/core/components/PageNew/PageContents.tsx new file mode 100644 index 00000000000..85a04fa295a --- /dev/null +++ b/public/app/core/components/PageNew/PageContents.tsx @@ -0,0 +1,14 @@ +// Libraries +import React, { FC } from 'react'; + +import PageLoader from '../PageLoader/PageLoader'; + +interface Props { + isLoading?: boolean; + children: React.ReactNode; + className?: string; +} + +export const PageContents: FC = ({ isLoading, children }) => { + return <>{isLoading ? : children}; +}; diff --git a/public/app/core/components/PageNew/PageHeader.tsx b/public/app/core/components/PageNew/PageHeader.tsx new file mode 100644 index 00000000000..1879563c90e --- /dev/null +++ b/public/app/core/components/PageNew/PageHeader.tsx @@ -0,0 +1,43 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { NavModelItem, GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +export interface Props { + navItem: NavModelItem; +} + +export function PageHeader({ navItem }: Props) { + const styles = useStyles2(getStyles); + + return ( + <> +

    + {navItem.img && {`logo} + {navItem.text} +

    + {navItem.subTitle &&
    {navItem.subTitle}
    } + + ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + pageTitle: css({ + display: 'flex', + marginBottom: theme.spacing(3), + }), + pageSubTitle: css({ + marginBottom: theme.spacing(2), + position: 'relative', + top: theme.spacing(-1), + color: theme.colors.text.secondary, + }), + pageImg: css({ + width: '32px', + height: '32px', + marginRight: theme.spacing(2), + }), + }; +}; diff --git a/public/app/core/components/PageNew/PageTabs.tsx b/public/app/core/components/PageNew/PageTabs.tsx new file mode 100644 index 00000000000..d3fb3a73c9b --- /dev/null +++ b/public/app/core/components/PageNew/PageTabs.tsx @@ -0,0 +1,42 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { NavModelItem, GrafanaTheme2 } from '@grafana/data'; +import { IconName, useStyles2, TabsBar, Tab } from '@grafana/ui'; + +export interface Props { + navItem: NavModelItem; +} + +export function PageTabs({ navItem }: Props) { + const styles = useStyles2(getStyles); + + return ( +
    + + {navItem.children!.map((child, index) => { + return ( + !child.hideFromTabs && ( + + ) + ); + })} + +
    + ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + tabsWrapper: css({ + paddingBottom: theme.spacing(3), + }), + }; +}; diff --git a/public/app/core/components/PageNew/SectionNav.tsx b/public/app/core/components/PageNew/SectionNav.tsx new file mode 100644 index 00000000000..3570de4294f --- /dev/null +++ b/public/app/core/components/PageNew/SectionNav.tsx @@ -0,0 +1,92 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { NavModel, GrafanaTheme2 } from '@grafana/data'; +import { IconName, useStyles2, Icon, VerticalTab } from '@grafana/ui'; + +export interface Props { + model: NavModel; +} + +export function SectionNav(props: Props) { + const styles = useStyles2(getStyles); + + const main = props.model.main; + const directChildren = props.model.main.children!.filter((x) => !x.hideFromTabs && !x.children); + const nestedItems = props.model.main.children!.filter((x) => x.children && x.children.length); + + return ( + + ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + nav: css({ + display: 'flex', + flexDirection: 'column', + background: theme.colors.background.canvas, + padding: theme.spacing(3, 2), + flexShrink: 0, + [theme.breakpoints.up('md')]: { + width: '250px', + }, + }), + sectionName: css({ + display: 'flex', + gap: theme.spacing(1), + padding: theme.spacing(0.5, 0, 3, 0.25), + fontSize: theme.typography.h4.fontSize, + margin: 0, + }), + items: css({ + // paddingLeft: '9px', + }), + subSection: css({ + padding: theme.spacing(3, 0, 1, 1), + fontWeight: 500, + fontSize: '16px', + }), + }; +}; diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx index b046539d99f..2efdd6a2729 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx @@ -1,56 +1,44 @@ -import { mount, shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { QueryOperationRow } from './QueryOperationRow'; +import { QueryOperationRow, QueryOperationRowProps } from './QueryOperationRow'; + +const setup = (propOverrides?: Partial) => { + const props: QueryOperationRowProps = { + title: 'test-title', + headerElement: '', + index: 0, + id: 'test-id', + children:
    children
    , + ...propOverrides, + }; + return render(); +}; describe('QueryOperationRow', () => { - it('renders', () => { - expect(() => - shallow( - -
    Test
    -
    - ) - ).not.toThrow(); + it('renders without exploding', () => { + expect(() => setup()).not.toThrow(); + }); + + it('renders the component content', () => { + setup(); + expect(screen.getByText(/^test-title$/)).toBeInTheDocument(); }); describe('callbacks', () => { - it('should not call onOpen when component is shallowed', async () => { - const onOpenSpy = jest.fn(); - // @ts-ignore strict null error, you shouldn't use promise like approach with act but I don't know what the intention is here - await act(async () => { - shallow( - -
    Test
    -
    - ); - }); - expect(onOpenSpy).not.toBeCalled(); - }); - it('should call onOpen when row is opened and onClose when row is collapsed', async () => { const onOpenSpy = jest.fn(); const onCloseSpy = jest.fn(); - const wrapper = mount( - -
    Test
    -
    - ); - const titleEl = wrapper.find({ 'aria-label': 'Query operation row title' }); - expect(titleEl).toHaveLength(1); + setup({ isOpen: false, onOpen: onOpenSpy, onClose: onCloseSpy }); - // @ts-ignore strict null error, you shouldn't use promise like approach with act but I don't know what the intention is here - await act(async () => { - // open - titleEl.first().simulate('click'); - }); + const queryRow = screen.getByText(/^test-title$/); + expect(queryRow).toBeInTheDocument(); - // @ts-ignore strict null error, you shouldn't use promise like approach with act but I don't know what the intention is here - await act(async () => { - // close - titleEl.first().simulate('click'); - }); + // open row on click + await userEvent.click(queryRow); + // close row on click + await userEvent.click(queryRow); expect(onOpenSpy).toBeCalledTimes(1); expect(onCloseSpy).toBeCalledTimes(1); @@ -59,40 +47,26 @@ describe('QueryOperationRow', () => { describe('headerElement rendering', () => { it('should render headerElement provided as element', () => { - const title =
    Test
    ; - const wrapper = mount( - -
    Test
    -
    - ); + const title =
    test-header-element
    ; + setup({ headerElement: title, id: 'test-id', index: 0 }); - const titleEl = wrapper.find({ 'aria-label': 'test title' }); - expect(titleEl).toHaveLength(1); + expect(screen.getByText(/^test-header-element$/)).toBeInTheDocument(); }); it('should render headerElement provided as function', () => { - const title = () =>
    Test
    ; - const wrapper = mount( - -
    Test
    -
    - ); + const title = () =>
    test-function-header
    ; + setup({ headerElement: title, id: 'test-id', index: 0 }); - const titleEl = wrapper.find({ 'aria-label': 'test title' }); - expect(titleEl).toHaveLength(1); + expect(screen.getByText(/^test-function-header$/)).toBeInTheDocument(); }); it('should expose api to headerElement rendered as function', () => { const propsSpy = jest.fn(); - const title = (props: any) => { + const title = (props: Partial) => { propsSpy(props); return
    Test
    ; }; - shallow( - -
    Test
    -
    - ); + setup({ headerElement: title, id: 'test-id', index: 0 }); expect(Object.keys(propsSpy.mock.calls[0][0])).toContain('isOpen'); }); @@ -100,40 +74,27 @@ describe('QueryOperationRow', () => { describe('actions rendering', () => { it('should render actions provided as element', () => { - const actions =
    Test
    ; - const wrapper = mount( - -
    Test
    -
    - ); + const actions =
    test-actions
    ; + setup({ actions: actions, id: 'test-id', index: 0 }); - const actionsEl = wrapper.find({ 'aria-label': 'test actions' }); - expect(actionsEl).toHaveLength(1); + expect(screen.getByText(/^test-actions$/)).toBeInTheDocument(); }); it('should render actions provided as function', () => { - const actions = () =>
    Test
    ; - const wrapper = mount( - -
    Test
    -
    - ); + const actions = () =>
    test-actions
    ; + setup({ actions: actions, id: 'test-id', index: 0 }); - const actionsEl = wrapper.find({ 'aria-label': 'test actions' }); - expect(actionsEl).toHaveLength(1); + expect(screen.getByText(/^test-actions$/)).toBeInTheDocument(); }); it('should expose api to title rendered as function', () => { const propsSpy = jest.fn(); - const actions = (props: any) => { + const actions = (props: Partial) => { propsSpy(props); - return
    Test
    ; + return
    test-actions
    ; }; - shallow( - -
    Test
    -
    - ); + setup({ actions: actions, id: 'test-id', index: 0 }); + expect(screen.getByText(/^test-actions$/)).toBeInTheDocument(); expect(Object.keys(propsSpy.mock.calls[0][0])).toEqual(['isOpen', 'onOpen', 'onClose']); }); }); diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx index 331e189e976..5fd86d4267d 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx @@ -9,7 +9,7 @@ import { ReactUtils, stylesFactory, useTheme } from '@grafana/ui'; import { QueryOperationRowHeader } from './QueryOperationRowHeader'; -interface QueryOperationRowProps { +export interface QueryOperationRowProps { index: number; id: string; title?: string; diff --git a/public/app/core/components/Select/ServiceAccountPicker.tsx b/public/app/core/components/Select/ServiceAccountPicker.tsx new file mode 100644 index 00000000000..954f1ddc425 --- /dev/null +++ b/public/app/core/components/Select/ServiceAccountPicker.tsx @@ -0,0 +1,69 @@ +import { debounce, isNil } from 'lodash'; +import React, { useEffect, useState } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; +import { AsyncSelect } from '@grafana/ui'; +import { ServiceAccount } from 'app/types'; + +export interface Props { + onSelected: (serviceAccount: SelectableValue) => void; + className?: string; + inputId?: string; + autoFocus?: boolean; +} + +export function ServiceAccountPicker({ onSelected, className, inputId, autoFocus }: Props) { + const [loadingServiceAccounts, setLoadingServiceAccounts] = useState(false); + + // For whatever reason the autoFocus prop doesn't seem to work + // with AsyncSelect, hence this workaround. Maybe fixed in a later version? + useEffect(() => { + if (autoFocus && inputId) { + document.getElementById(inputId)?.focus(); + } + }, [autoFocus, inputId]); + + let search = (query?: string) => { + setLoadingServiceAccounts(true); + + if (isNil(query)) { + query = ''; + } + + return getBackendSrv() + .get(`/api/serviceaccounts/search?query=${query}`) + .then((result: { serviceAccounts: ServiceAccount[] }) => { + const serviceAccounts: Array> = result.serviceAccounts.map((serviceAccount) => { + return { + id: serviceAccount.id, + value: serviceAccount, + label: serviceAccount.login, + imgUrl: serviceAccount.avatarUrl, + login: serviceAccount.login, + }; + }); + setLoadingServiceAccounts(false); + return serviceAccounts; + }); + }; + + const debouncedSearch = debounce(search, 300, { + leading: true, + trailing: true, + }); + + return ( + + ); +} diff --git a/public/app/core/components/TopNav/TopNavUpdate.tsx b/public/app/core/components/TopNav/TopNavUpdate.tsx deleted file mode 100644 index c16ba7a2755..00000000000 --- a/public/app/core/components/TopNav/TopNavUpdate.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useEffect } from 'react'; -import { Subject } from 'rxjs'; - -import { NavModelItem } from '@grafana/data'; - -export interface TopNavProps { - subNav?: NavModelItem; - actions?: React.ReactNode; -} - -export const topNavUpdates = new Subject(); -export const topNavDefaultProps: TopNavProps = {}; - -/** - * This needs to be moved to @grafana/ui or runtime. - * This is the way core pages and plugins update the breadcrumbs and page toolbar actions - */ -export function TopNavUpdate(props: TopNavProps) { - useEffect(() => { - topNavUpdates.next(props); - }); - return null; -} diff --git a/public/app/core/components/TopNav/types.ts b/public/app/core/components/TopNav/types.ts deleted file mode 100644 index 9171a881c48..00000000000 --- a/public/app/core/components/TopNav/types.ts +++ /dev/null @@ -1 +0,0 @@ -export const TOP_BAR_LEVEL_HEIGHT = 40; diff --git a/public/app/core/logsModel.ts b/public/app/core/logsModel.ts index 48facc8143f..eae01687679 100644 --- a/public/app/core/logsModel.ts +++ b/public/app/core/logsModel.ts @@ -1,5 +1,5 @@ import { size } from 'lodash'; -import { Observable } from 'rxjs'; +import { Observable, from, isObservable } from 'rxjs'; import { AbsoluteTimeRange, @@ -8,6 +8,7 @@ import { DataQueryRequest, DataQueryResponse, DataSourceApi, + DataSourceJsonData, dateTimeFormat, dateTimeFormatTimeAgo, FieldCache, @@ -106,13 +107,20 @@ export function filterLogLevels(logRows: LogRowModel[], hiddenLogLevels: Set; + target: LogLevel; + color: string; +} + export function makeDataFramesForLogs(sortedRows: LogRowModel[], bucketSize: number): DataFrame[] { // currently interval is rangeMs / resolution, which is too low for showing series as bars. // Should be solved higher up the chain when executing queries & interval calculated and not here but this is a temporary fix. // Graph time series by log level - const seriesByLevel: any = {}; - const seriesList: any[] = []; + const seriesByLevel: Record = {}; + const seriesList: Series[] = []; for (const row of sortedRows) { let series = seriesByLevel[row.logLevel]; @@ -459,12 +467,13 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi } const limits = logSeries.filter((series) => series.meta && series.meta.limit); - const limitValue = Object.values( - limits.reduce((acc: any, elem: any) => { - acc[elem.refId] = elem.meta.limit; - return acc; - }, {}) - ).reduce((acc: number, elem: any) => (acc += elem), 0) as number; + const lastLimitPerRef = limits.reduce>((acc, elem) => { + acc[elem.refId ?? ''] = elem.meta?.limit ?? 0; + + return acc; + }, {}); + + const limitValue = Object.values(lastLimitPerRef).reduce((acc, elem) => (acc += elem), 0); if (limitValue > 0) { meta.push({ @@ -598,6 +607,7 @@ export function aggregateRawLogsVolume( extractLevel: (dataFrame: DataFrame) => LogLevel ): DataFrame[] { const logsVolumeByLevelMap: Partial> = {}; + rawLogsVolume.forEach((dataFrame) => { const level = extractLevel(dataFrame); if (!logsVolumeByLevelMap[level]) { @@ -661,10 +671,10 @@ type LogsVolumeQueryOptions = { /** * Creates an observable, which makes requests to get logs volume and aggregates results. */ -export function queryLogsVolume( - datasource: DataSourceApi, - logsVolumeRequest: DataQueryRequest, - options: LogsVolumeQueryOptions +export function queryLogsVolume( + datasource: DataSourceApi, + logsVolumeRequest: DataQueryRequest, + options: LogsVolumeQueryOptions ): Observable { const timespan = options.range.to.valueOf() - options.range.from.valueOf(); const intervalInfo = getIntervalInfo(logsVolumeRequest.scopedVars, timespan); @@ -683,7 +693,10 @@ export function queryLogsVolume( data: [], }); - const subscription = (datasource.query(logsVolumeRequest) as Observable).subscribe({ + const queryResponse = datasource.query(logsVolumeRequest); + const queryObservable = isObservable(queryResponse) ? queryResponse : from(queryResponse); + + const subscription = queryObservable.subscribe({ complete: () => { const aggregatedLogsVolume = aggregateRawLogsVolume(rawLogsVolume, options.extractLevel); if (aggregatedLogsVolume[0]) { diff --git a/public/app/core/navigation/GrafanaRoute.tsx b/public/app/core/navigation/GrafanaRoute.tsx index 7b2e7445b0d..17a2e93d3b9 100644 --- a/public/app/core/navigation/GrafanaRoute.tsx +++ b/public/app/core/navigation/GrafanaRoute.tsx @@ -2,9 +2,9 @@ import React from 'react'; // @ts-ignore import Drop from 'tether-drop'; -import { config, locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime'; +import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime'; -import { TopNavPage } from '../components/TopNav/TopNavPage'; +import { appChromeService } from '../components/AppChrome/AppChromeService'; import { keybindingSrv } from '../services/keybindingSrv'; import { GrafanaRouteComponentProps } from './types'; @@ -13,6 +13,8 @@ export interface Props extends Omit { export class GrafanaRoute extends React.Component { componentDidMount() { + appChromeService.routeMounted(this.props.route); + this.updateBodyClassNames(); this.cleanupDOM(); // unbinds all and re-bind global keybindins @@ -71,12 +73,6 @@ export class GrafanaRoute extends React.Component { navigationLogger('GrafanaRoute', false, 'Rendered', props.route); const RouteComponent = props.route.component; - const routeElement = ; - - if (config.featureToggles.topnav && !props.route.navHidden) { - return {routeElement}; - } - - return routeElement; + return ; } } diff --git a/public/app/core/navigation/types.ts b/public/app/core/navigation/types.ts index d34089aca8f..cffda8ede63 100644 --- a/public/app/core/navigation/types.ts +++ b/public/app/core/navigation/types.ts @@ -17,7 +17,6 @@ export interface RouteDescriptor { pageClass?: string; /** Can be used like an id for the route if the same component is used by many routes */ routeName?: string; - navHidden?: boolean; + chromeless?: boolean; exact?: boolean; - navId?: string; } diff --git a/public/app/core/reducers/navBarTree.ts b/public/app/core/reducers/navBarTree.ts index b05979ac042..6907d7af6b9 100644 --- a/public/app/core/reducers/navBarTree.ts +++ b/public/app/core/reducers/navBarTree.ts @@ -1,7 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { NavModelItem } from '@grafana/data'; -import config from 'app/core/config'; +import { config } from '@grafana/runtime'; export const initialState: NavModelItem[] = config.bootData?.navTree ?? []; diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts index bdb256bfe1a..6be4673248d 100644 --- a/public/app/core/reducers/navModel.ts +++ b/public/app/core/reducers/navModel.ts @@ -32,7 +32,6 @@ function buildWarningNav(text: string, subTitle?: string): NavModel { icon: 'exclamation-triangle', }; return { - breadcrumbs: [node], node: node, main: node, }; diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 8d726b03d84..d0c67af38ab 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -398,19 +398,19 @@ export class BackendSrv implements BackendService { return await this.request({ method: 'GET', url, params, requestId }); } - async delete(url: string, data?: any) { + async delete(url: string, data?: any): Promise { return await this.request({ method: 'DELETE', url, data }); } - async post(url: string, data?: any) { + async post(url: string, data?: any): Promise { return await this.request({ method: 'POST', url, data }); } - async patch(url: string, data: any) { + async patch(url: string, data: any): Promise { return await this.request({ method: 'PATCH', url, data }); } - async put(url: string, data: any) { + async put(url: string, data: any): Promise { return await this.request({ method: 'PUT', url, data }); } @@ -425,6 +425,7 @@ export class BackendSrv implements BackendService { return this.request({ url: '/api/login/ping', method: 'GET', retry: 1 }); } + /** @deprecated */ search(query: any): Promise { return this.get('/api/search', query); } diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts index a5030a3c22a..b6111211610 100644 --- a/public/app/core/services/search_srv.ts +++ b/public/app/core/services/search_srv.ts @@ -13,6 +13,7 @@ interface Sections { [key: string]: Partial; } +/** @deprecated */ export class SearchSrv { private getRecentDashboards(sections: DashboardSection[] | any) { return this.queryForRecentDashboards().then((result: any[]) => { diff --git a/public/app/core/utils/isShallowEqual.ts b/public/app/core/utils/isShallowEqual.ts new file mode 100644 index 00000000000..8d772c8af5b --- /dev/null +++ b/public/app/core/utils/isShallowEqual.ts @@ -0,0 +1,29 @@ +// From https://github.com/streamich/fast-shallow-equal + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isShallowEqual(a: any, b: any) { + if (a === b) { + return true; + } + + if (!(a instanceof Object) || !(b instanceof Object)) { + return false; + } + + var keys = Object.keys(a); + var length = keys.length; + + for (let i = 0; i < length; i++) { + if (!(keys[i] in b)) { + return false; + } + } + + for (let i = 0; i < length; i++) { + if (a[keys[i]] !== b[keys[i]]) { + return false; + } + } + + return length === Object.keys(b).length; +} diff --git a/public/app/features/admin/AdminEditOrgPage.tsx b/public/app/features/admin/AdminEditOrgPage.tsx index 44bc10e0134..8ab6d856d77 100644 --- a/public/app/features/admin/AdminEditOrgPage.tsx +++ b/public/app/features/admin/AdminEditOrgPage.tsx @@ -6,7 +6,7 @@ import { useAsyncFn } from 'react-use'; import { UrlQueryValue } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { Form, Field, Input, Button, Legend, Alert } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/admin/AdminListOrgsPage.tsx b/public/app/features/admin/AdminListOrgsPage.tsx index 6fff515aa09..94757ff3715 100644 --- a/public/app/features/admin/AdminListOrgsPage.tsx +++ b/public/app/features/admin/AdminListOrgsPage.tsx @@ -4,7 +4,7 @@ import useAsyncFn from 'react-use/lib/useAsyncFn'; import { getBackendSrv } from '@grafana/runtime'; import { LinkButton } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; diff --git a/public/app/features/admin/AdminSettings.tsx b/public/app/features/admin/AdminSettings.tsx index e2e2a0c7ecb..f0b90e6e2b8 100644 --- a/public/app/features/admin/AdminSettings.tsx +++ b/public/app/features/admin/AdminSettings.tsx @@ -4,7 +4,7 @@ import { useAsync } from 'react-use'; import { NavModel } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; diff --git a/public/app/features/admin/ExportStartButton.tsx b/public/app/features/admin/ExportStartButton.tsx deleted file mode 100644 index f94d4f5b081..00000000000 --- a/public/app/features/admin/ExportStartButton.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { css } from '@emotion/css'; -import React, { useState } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { getBackendSrv } from '@grafana/runtime'; -import { Button, CodeEditor, Modal, useTheme2 } from '@grafana/ui'; - -export const ExportStartButton = () => { - const styles = getStyles(useTheme2()); - const [open, setOpen] = useState(false); - const [body, setBody] = useState({ - format: 'git', - git: {}, - }); - const onDismiss = () => setOpen(false); - const doStart = () => { - getBackendSrv() - .post('/api/admin/export', body) - .then((v) => { - console.log('GOT', v); - onDismiss(); - }); - }; - - return ( - <> - -
    - { - setBody(JSON.parse(text)); // force JSON? - }} - /> -
    - - - - -
    - - - - ); -}; - -const getStyles = (theme: GrafanaTheme2) => { - return { - wrap: css` - border: 2px solid #111; - `, - }; -}; diff --git a/public/app/features/admin/ExportStatus.tsx b/public/app/features/admin/ExportStatus.tsx deleted file mode 100644 index 6b638b8f358..00000000000 --- a/public/app/features/admin/ExportStatus.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { css } from '@emotion/css'; -import React, { useEffect, useState } from 'react'; - -import { GrafanaTheme2, isLiveChannelMessageEvent, isLiveChannelStatusEvent, LiveChannelScope } from '@grafana/data'; -import { getBackendSrv, getGrafanaLiveSrv } from '@grafana/runtime'; -import { Button, useTheme2 } from '@grafana/ui'; - -import { ExportStartButton } from './ExportStartButton'; - -interface ExportStatusMessage { - running: boolean; - target: string; - started: number; - finished: number; - update: number; - count: number; - current: number; - last: string; - status: string; -} - -export const ExportStatus = () => { - const styles = getStyles(useTheme2()); - const [status, setStatus] = useState(); - - useEffect(() => { - const subscription = getGrafanaLiveSrv() - .getStream({ - scope: LiveChannelScope.Grafana, - namespace: 'broadcast', - path: 'export', - }) - .subscribe({ - next: (evt) => { - if (isLiveChannelMessageEvent(evt)) { - setStatus(evt.message); - } else if (isLiveChannelStatusEvent(evt)) { - setStatus(evt.message); - } - }, - }); - return () => { - subscription.unsubscribe(); - }; - }, []); - - if (!status) { - return ( -
    - -
    - ); - } - - return ( -
    -
    {JSON.stringify(status, null, 2)}
    - {Boolean(!status.running) && } - {Boolean(status.running) && ( - - )} -
    - ); -}; - -const getStyles = (theme: GrafanaTheme2) => { - return { - wrap: css` - border: 4px solid red; - `, - running: css` - border: 4px solid green; - `, - }; -}; diff --git a/public/app/features/admin/ServerStats.tsx b/public/app/features/admin/ServerStats.tsx index 1b3cf7799d6..672d1ba2746 100644 --- a/public/app/features/admin/ServerStats.tsx +++ b/public/app/features/admin/ServerStats.tsx @@ -10,7 +10,6 @@ import { contextSrv } from '../../core/services/context_srv'; import { Loader } from '../plugins/admin/components/Loader'; import { CrawlerStatus } from './CrawlerStatus'; -import { ExportStatus } from './ExportStatus'; import { getServerStats, ServerStat } from './state/apis'; export const ServerStats = () => { @@ -99,7 +98,6 @@ export const ServerStats = () => { )} {config.featureToggles.dashboardPreviews && config.featureToggles.dashboardPreviewsAdmin && } - {config.featureToggles.export && } ); }; diff --git a/public/app/features/admin/UpgradePage.tsx b/public/app/features/admin/UpgradePage.tsx index 3678cb244c6..07b93187d28 100644 --- a/public/app/features/admin/UpgradePage.tsx +++ b/public/app/features/admin/UpgradePage.tsx @@ -4,8 +4,8 @@ import { connect } from 'react-redux'; import { GrafanaTheme2, NavModel } from '@grafana/data'; import { LinkButton, useStyles2 } from '@grafana/ui'; +import { Page } from 'app/core/components/Page/Page'; -import Page from '../../core/components/Page/Page'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from '../../types'; diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx index 264e9ce943a..14865464d1e 100644 --- a/public/app/features/admin/UserAdminPage.tsx +++ b/public/app/features/admin/UserAdminPage.tsx @@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { NavModel } from '@grafana/data'; import { featureEnabled } from '@grafana/runtime'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/admin/UserCreatePage.tsx b/public/app/features/admin/UserCreatePage.tsx index b98e82a3e6a..e68a20eeebf 100644 --- a/public/app/features/admin/UserCreatePage.tsx +++ b/public/app/features/admin/UserCreatePage.tsx @@ -5,7 +5,7 @@ import { useHistory } from 'react-router-dom'; import { NavModel } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { Form, Button, Input, Field } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from '../../types'; diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx index 7fa5809ae4f..f7435d397df 100644 --- a/public/app/features/admin/UserListAdminPage.tsx +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -13,7 +13,7 @@ import { useStyles2, FilterInput, } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { contextSrv } from 'app/core/core'; diff --git a/public/app/features/admin/ldap/LdapPage.tsx b/public/app/features/admin/ldap/LdapPage.tsx index 28adbdc13ad..f0959e6f70e 100644 --- a/public/app/features/admin/ldap/LdapPage.tsx +++ b/public/app/features/admin/ldap/LdapPage.tsx @@ -5,7 +5,7 @@ import { NavModel } from '@grafana/data'; import { featureEnabled } from '@grafana/runtime'; import { Alert, Button, LegacyForms } from '@grafana/ui'; const { FormField } = LegacyForms; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 10de691e571..a19d2ab7bbd 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -5,7 +5,7 @@ import { SelectableValue } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; import { Button, FilterInput, LinkButton, Select, VerticalGroup } from '@grafana/ui'; import appEvents from 'app/core/app_events'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { AlertRule, StoreState } from 'app/types'; diff --git a/public/app/features/alerting/EditNotificationChannelPage.tsx b/public/app/features/alerting/EditNotificationChannelPage.tsx index 45aba248222..7303f7ccfd3 100644 --- a/public/app/features/alerting/EditNotificationChannelPage.tsx +++ b/public/app/features/alerting/EditNotificationChannelPage.tsx @@ -4,7 +4,7 @@ import { MapDispatchToProps, MapStateToProps } from 'react-redux'; import { NavModel } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Form, Spinner } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { connectWithCleanUp } from 'app/core/components/connectWithCleanUp'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; diff --git a/public/app/features/alerting/FeatureTogglePage.tsx b/public/app/features/alerting/FeatureTogglePage.tsx index 63f62796f94..e6def5ec3f5 100644 --- a/public/app/features/alerting/FeatureTogglePage.tsx +++ b/public/app/features/alerting/FeatureTogglePage.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; export default function FeatureTogglePage() { diff --git a/public/app/features/alerting/NewNotificationChannelPage.tsx b/public/app/features/alerting/NewNotificationChannelPage.tsx index 418029fb2e5..93d6ca43865 100644 --- a/public/app/features/alerting/NewNotificationChannelPage.tsx +++ b/public/app/features/alerting/NewNotificationChannelPage.tsx @@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { config } from '@grafana/runtime'; import { Form } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { NotificationChannelDTO, StoreState } from '../../types'; diff --git a/public/app/features/alerting/NotificationsListPage.tsx b/public/app/features/alerting/NotificationsListPage.tsx index 9a5e5b3f4eb..6f11e260aac 100644 --- a/public/app/features/alerting/NotificationsListPage.tsx +++ b/public/app/features/alerting/NotificationsListPage.tsx @@ -4,7 +4,7 @@ import { useAsyncFn } from 'react-use'; import { getBackendSrv } from '@grafana/runtime'; import { HorizontalGroup, Button, LinkButton } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { appEvents } from 'app/core/core'; import { useNavModel } from 'app/core/hooks/useNavModel'; import { AlertNotification } from 'app/types/alerting'; diff --git a/public/app/features/alerting/state/alertDef.ts b/public/app/features/alerting/state/alertDef.ts index de4f61cf7a6..41f0d3e9660 100644 --- a/public/app/features/alerting/state/alertDef.ts +++ b/public/app/features/alerting/state/alertDef.ts @@ -81,8 +81,14 @@ function createReducerPart(model: any) { return new QueryPart(model, def); } +// state can also contain a "Reason", ie. "Alerting (NoData)" which indicates that the actual state is "Alerting" but +// the reason it is set to "Alerting" is "NoData"; a lack of data points to evaluate. +function normalizeAlertState(state: string) { + return state.toLowerCase().replace(/_/g, '').split(' ')[0]; +} + function getStateDisplayModel(state: string) { - const normalizedState = state.toLowerCase().replace(/_/g, ''); + const normalizedState = normalizeAlertState(state); switch (normalizedState) { case 'normal': @@ -121,13 +127,6 @@ function getStateDisplayModel(state: string) { stateClass: 'alert-state-warning', }; } - case 'unknown': { - return { - text: 'UNKNOWN', - iconClass: 'question-circle', - stateClass: '.alert-state-paused', - }; - } case 'firing': { return { @@ -152,9 +151,16 @@ function getStateDisplayModel(state: string) { stateClass: 'alert-state-critical', }; } - } - throw { message: 'Unknown alert state' }; + case 'unknown': + default: { + return { + text: 'UNKNOWN', + iconClass: 'question-circle', + stateClass: '.alert-state-paused', + }; + } + } } function joinEvalMatches(matches: any, separator: string) { diff --git a/public/app/features/alerting/unified/RuleEditor.tsx b/public/app/features/alerting/unified/RuleEditor.tsx index 57d66fc319f..e550148abbe 100644 --- a/public/app/features/alerting/unified/RuleEditor.tsx +++ b/public/app/features/alerting/unified/RuleEditor.tsx @@ -5,7 +5,7 @@ import { useAsync } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { Alert, LinkButton, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { RuleIdentifier } from 'app/types/unified-alerting'; diff --git a/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx b/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx index 21a7826139e..bb87e4a7a4e 100644 --- a/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx +++ b/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react'; import { useSelector } from 'react-redux'; -import Page from 'app/core/components/Page/Page'; +import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types/store'; diff --git a/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx b/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx index 5c193fd1053..0666eb1e4dc 100644 --- a/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx +++ b/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx @@ -7,6 +7,7 @@ import { Tooltip, useStyles } from '@grafana/ui'; import { Annotation, annotationLabels } from '../utils/constants'; import { DetailsField } from './DetailsField'; +import { Tokenize } from './Tokenize'; import { Well } from './Well'; const wellableAnnotationKeys = ['message', 'description']; @@ -38,8 +39,10 @@ const AnnotationValue: FC = ({ annotationKey, value }) => { const needsWell = wellableAnnotationKeys.includes(annotationKey); const needsLink = value && value.startsWith('http'); + const tokenizeValue = ; + if (needsWell) { - return {value}; + return {tokenizeValue}; } if (needsLink) { @@ -50,7 +53,7 @@ const AnnotationValue: FC = ({ annotationKey, value }) => { ); } - return <>{value}; + return <>{tokenizeValue}; }; export const getStyles = (theme: GrafanaTheme) => ({ diff --git a/public/app/features/alerting/unified/components/HoverCard.tsx b/public/app/features/alerting/unified/components/HoverCard.tsx new file mode 100644 index 00000000000..baea3a6e36f --- /dev/null +++ b/public/app/features/alerting/unified/components/HoverCard.tsx @@ -0,0 +1,62 @@ +import { css } from '@emotion/css'; +import { Placement } from '@popperjs/core'; +import classnames from 'classnames'; +import React, { FC, ReactElement, useRef } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Popover as GrafanaPopover, PopoverController, useStyles2 } from '@grafana/ui'; + +export interface HoverCardProps { + children: ReactElement; + content: ReactElement; + wrapperClassName?: string; + placement?: Placement; + disabled?: boolean; +} + +export const HoverCard: FC = ({ children, content, wrapperClassName, disabled = false, ...rest }) => { + const popoverRef = useRef(null); + const styles = useStyles2(getStyles); + + if (disabled) { + return children; + } + + return ( + + {(showPopper, hidePopper, popperProps) => { + return ( + <> + {popoverRef.current && ( + + )} + + {React.cloneElement(children, { + ref: popoverRef, + onMouseEnter: showPopper, + onMouseLeave: hidePopper, + })} + + ); + }} + + ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + popover: css` + border-radius: ${theme.shape.borderRadius()}; + box-shadow: ${theme.shadows.z3}; + background: ${theme.colors.background.primary}; + border: 1px solid ${theme.colors.border.medium}; + + padding: ${theme.spacing(1)}; + `, +}); diff --git a/public/app/features/alerting/unified/components/Tokenize.tsx b/public/app/features/alerting/unified/components/Tokenize.tsx new file mode 100644 index 00000000000..77b217e6b9a --- /dev/null +++ b/public/app/features/alerting/unified/components/Tokenize.tsx @@ -0,0 +1,149 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Badge, useStyles2 } from '@grafana/ui'; + +import { HoverCard } from './HoverCard'; +import { keywords as KEYWORDS, builtinFunctions as FUNCTIONS } from './receivers/editor/language'; + +const VARIABLES = ['$', '.', '"']; + +interface TokenizerProps { + input: string; + delimiter?: [string, string]; +} + +function Tokenize({ input, delimiter = ['{{', '}}'] }: TokenizerProps) { + const styles = useStyles2(getStyles); + + const [open, close] = delimiter; + const normalizedIput = normalizeInput(input); + + /** + * This RegExp uses 2 named capture groups, text that comes before the token and the token itself + * + * open close + * ───────── ── ─────────── ── + * Some text {{ $labels.foo }} + */ + const regex = new RegExp(`(?.*?)(${open}(?.*?)${close}|$)`, 'gm'); + + const matches = Array.from(normalizedIput.matchAll(regex)); + + const output: React.ReactElement[] = []; + + matches.forEach((match, index) => { + const before = match.groups?.before; + const token = match.groups?.token?.trim(); + + if (before) { + output.push({before}); + } + + if (token) { + const type = tokenType(token); + const description = type === TokenType.Variable ? token : ''; + const tokenContent = `${open} ${token} ${close}`; + + output.push(); + } + }); + + return {output}; +} + +enum TokenType { + Variable = 'variable', + Function = 'function', + Keyword = 'keyword', + Unknown = 'unknown', +} + +interface TokenProps { + content: string; + type?: TokenType; + description?: string; +} + +function Token({ content, description, type }: TokenProps) { + const styles = useStyles2(getStyles); + const varName = content.trim(); + + const disableCard = Boolean(type) === false; + + return ( + + {type}} color={'blue'} /> {description && {description}} +
    + } + > + + + + + ); +} + +function normalizeInput(input: string) { + return input.replace(/\s+/g, ' ').trim(); +} + +function isVariable(input: string) { + return VARIABLES.some((character) => input.startsWith(character)); +} + +function isKeyword(input: string) { + return KEYWORDS.some((keyword) => input.startsWith(keyword)); +} + +function isFunction(input: string) { + return FUNCTIONS.some((functionName) => input.startsWith(functionName)); +} + +function tokenType(input: string) { + let tokenType; + if (isVariable(input)) { + tokenType = TokenType.Variable; + } else if (isKeyword(input)) { + tokenType = TokenType.Keyword; + } else if (isFunction(input)) { + tokenType = TokenType.Function; + } else { + tokenType = TokenType.Unknown; + } + + return tokenType; +} + +const getStyles = (theme: GrafanaTheme2) => ({ + wrapper: css` + display: inline-flex; + align-items: center; + white-space: pre; + `, + token: css` + cursor: default; + font-family: ${theme.typography.fontFamilyMonospace}; + `, + popover: css` + border-radius: ${theme.shape.borderRadius()}; + box-shadow: ${theme.shadows.z3}; + background: ${theme.colors.background.primary}; + border: 1px solid ${theme.colors.border.medium}; + + padding: ${theme.spacing(1)}; + `, + hoverTokenItem: css` + display: flex; + flex-direction: row; + align-items: center; + gap: ${theme.spacing(1)}; + `, +}); + +export { Tokenize, Token }; diff --git a/public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx b/public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx new file mode 100644 index 00000000000..556b29b2ac5 --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx @@ -0,0 +1,53 @@ +/** + * This file contains the template editor we'll be using for alertmanager templates. + * + * It includes auto-complete for template data and syntax highlighting + */ +import { editor } from 'monaco-editor'; +import React, { FC } from 'react'; + +import { CodeEditor } from '@grafana/ui'; +import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types'; + +import goTemplateLanguageDefinition, { GO_TEMPLATE_LANGUAGE_ID } from './editor/definition'; +import { registerLanguage } from './editor/register'; + +const getSuggestions = () => { + return []; +}; + +type TemplateEditorProps = Omit & { + autoHeight?: boolean; +}; + +const TemplateEditor: FC = (props) => { + const shouldAutoHeight = Boolean(props.autoHeight); + + const onEditorDidMount = (editor: editor.IStandaloneCodeEditor) => { + if (shouldAutoHeight) { + const contentHeight = editor.getContentHeight(); + + try { + // we're passing NaN in to the width because the type definition wants a number (NaN is a number, go figure) + // but the width could be defined as a string "auto", passing NaN seems to just ignore our width update here + editor.layout({ height: contentHeight, width: NaN }); + } catch (err) {} + } + }; + + return ( + { + registerLanguage(monaco, goTemplateLanguageDefinition); + }} + language={GO_TEMPLATE_LANGUAGE_ID} + /> + ); +}; + +export { TemplateEditor }; diff --git a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx index 3348b3c3fce..2eba93cb61e 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx @@ -2,9 +2,10 @@ import { css } from '@emotion/css'; import React, { FC } from 'react'; import { useForm, Validate } from 'react-hook-form'; import { useDispatch } from 'react-redux'; +import AutoSizer from 'react-virtualized-auto-sizer'; import { GrafanaTheme2 } from '@grafana/data'; -import { Alert, Button, Field, FieldSet, Input, LinkButton, TextArea, useStyles2 } from '@grafana/ui'; +import { Alert, Button, Field, FieldSet, Input, LinkButton, useStyles2 } from '@grafana/ui'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; @@ -14,6 +15,8 @@ import { makeAMLink } from '../../utils/misc'; import { ensureDefine } from '../../utils/templates'; import { ProvisionedResource, ProvisioningAlert } from '../Provisioning'; +import { TemplateEditor } from './TemplateEditor'; + interface Values { name: string; content: string; @@ -83,6 +86,8 @@ export const TemplateForm: FC = ({ existing, alertManagerSourceName, conf handleSubmit, register, formState: { errors }, + getValues, + setValue, } = useForm({ mode: 'onSubmit', defaultValues: existing ?? defaults, @@ -143,12 +148,18 @@ export const TemplateForm: FC = ({ existing, alertManagerSourceName, conf invalid={!!errors.content?.message} required > -