diff --git a/.betterer.results b/.betterer.results index 90befadff48..841f032c0ae 100644 --- a/.betterer.results +++ b/.betterer.results @@ -11,17 +11,14 @@ exports[`no enzyme tests`] = { "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.tsx:793800575": [ [14, 35, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.tsx:596989456": [ - [14, 35, 13, "RegExp match", "2409514259"] - ], "packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.tsx:3266788928": [ [14, 56, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js:989353473": [ - [15, 17, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.tsx:1220382119": [ + [15, 31, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js:4192868446": [ - [15, 17, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.tsx:3450948735": [ + [15, 31, 13, "RegExp match", "2409514259"] ], "packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js:551014442": [ [13, 26, 13, "RegExp match", "2409514259"] diff --git a/devenv/docker/blocks/auth/authentik/README.md b/devenv/docker/blocks/auth/authentik/README.md index 4567ed3d4dd..a1fda99ef52 100644 --- a/devenv/docker/blocks/auth/authentik/README.md +++ b/devenv/docker/blocks/auth/authentik/README.md @@ -7,7 +7,7 @@ Useful for testing a second implementation ## Currently configured in DB and instructions - [x] LDAP -- [ ] SAML +- [x] SAML - [ ] OAuth ## Deployment @@ -38,6 +38,40 @@ sync_cron = "* * * * 1" active_sync_enabled = true ``` +## SAML Setup + +**Warning:** SLO + +Grafana expects SLO support to be communicated in the metadata + +Single Logout is not supported by authentik. +https://github.com/goauthentik/authentik/issues/3321 + +**Warning** Client signature validation + +Grafana expects the idP to retrieve the client's public key from the metadata. +Authentik does not seem to support this and therefore client signature verification is set +as optional. + +```ini +[auth.saml] +enabled = true +certificate_path = devenv/docker/blocks/auth/authentik/cert.crt +private_key_path = devenv/docker/blocks/auth/authentik/key.pem +idp_metadata_url = http://localhost:9000/api/v3/providers/saml/2/metadata/?download +assertion_attribute_name = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name +assertion_attribute_login = http://schemas.goauthentik.io/2021/02/saml/username +assertion_attribute_email = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress +assertion_attribute_groups = http://schemas.xmlsoap.org/claims/Group +assertion_attribute_org = http://schemas.xmlsoap.org/claims/Group +allow_sign_up = true +single_logout = false # not supported by authentik +signature_algorithm = rsa-sha256 +allow_idp_initiated = true +org_mapping = admin:1:Admin, editor:1:Editor, viewer:1:Viewer +``` + + ## Available users and groups *authentik admin*: diff --git a/devenv/docker/blocks/auth/authentik/cert.crt b/devenv/docker/blocks/auth/authentik/cert.crt new file mode 100644 index 00000000000..d3f7df68581 --- /dev/null +++ b/devenv/docker/blocks/auth/authentik/cert.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFmTCCA4GgAwIBAgIURahpP+wtYqe6MePw3wj3fKPtEkQwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3By +aW5nZmllbGQxDDAKBgNVBAoMA0RpczEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29t +MB4XDTIyMDcwODA3MDYwOFoXDTIzMDcwODA3MDYwOFowXDELMAkGA1UEBhMCVVMx +DzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3ByaW5nZmllbGQxDDAKBgNVBAoM +A0RpczEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAzkEB7WRDIMv1iX+4KmDJKPFCCJhZK+UNULCEFmpV/9+I +0vHdj55eVj2fuxahuemWdC+GogXL+ybAPhLvJwnl5OlojH9uco7IuQI/D8C14IwK +/qtvBouhCkbwl/+s3GGDqy7dmFk3O9OmllSADlIpFRSioEc6l/8sshfelsyXXv+5 +kKu87CRYDTLdC/Mhxa5dA1zqUZnvvrfeuc41Z8Aj3kFNSXeRbMKA+ejm0kA+Bc+M +XXhP4T4gBuk1gR0Bo0y2sEDb0mlJ1H/FGkJZ6n12gj3IBYMfrfJwj00PVUvXAq/8 +rtfieKgZvcrbwR7fJ7upaWROuxmknjGJPh11Hzm4d653M34BkDNYMr/e49Kktuv7 +vDG8KOHx7f16r3Ze8CL1NYy4DXicoplZo/pdyPYIrdt1bPMZ4fwqufgPORVZm3/z +2JDpeHY2DtqP14jFDW5fYzdJ4Zt8YZgtAzuBbZB4QMFoOK+9GkfZMz/8LtProEKt +dhEDksygIbPmyfuyI7tJYtJ4Y28Ybw2nE8XJ3SR1zyhvjCTgQo063xBgPiADVD1V +cvpnt3n1/5UwgtLSJzVLu8az+Hj4iq7RjeYdmwGFHeccRZv/u8dNmjqMkt9URGcn +cps8s4pN3VJeqQX4/G6n24CoBy3Nzu2sGgSnG6UOkVGc9WobjPRrKzdpnxm+ercC +AwEAAaNTMFEwHQYDVR0OBBYEFA7V5G5JiS51iEZLL1cxgJdt0y2EMB8GA1UdIwQY +MBaAFA7V5G5JiS51iEZLL1cxgJdt0y2EMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggIBADDcBZSlux/RR1o/hnr9dxqYghVXdoM1Mnjuq0A4go+GbzE9 +t4/ihdyzIhtDgIwZJUBma6OzoZgzH/0W+iOFKZLpGg3i5LizbN8Ry0ndRq4ZbjZ2 +8nwwSTAh2RkO8XZglrKrYyHm4nT2WPg17x2u1cQTy/t/jtE/EdULY9Pu8pUuw2MS +J4B0zU2r7N/+PmwzkC7RD+9zhQKY/tHMvwOdFgU6aeunzNtUAE4YOjhhM0UF1njt +RAhXjPEi39wbE7RYurcCoUZD21B5YMq9j8DG5emgiEa2ygcIxKJAzJ9ESzaXCehS +FNSp/LEI8PQLpN/nDh4lBZZFvVPmm+/BEonLtR2qr7fuWNZX/pQIv+hLRrXy9AF0 +8/fAG4+Te07Kz7Zoz24Dsyth66ReX4hodjAvJkYzbTFiuBcxMZSXrdgBWIpGXboY +4hxZjem3qUo7IVsbEToptEh4rF8xs7uTk8+xtnjgQY62mDWaDCudpVCfaL33SnX0 +FRyzz9TI5meOrNRAhKpuaDLit+v3C2NAKPo3Yi9VLTY0eunYPlgNDrKZ2JNNlTxw +dk1vM2/mq1hhGgbzixmRZpvc7g/H7t6mOVqKg+U0XC78tCjZO0UY3TGwjMmCw8i/ +qG0nX8quZ/hYyf6dAMDFw4Rw9BTW6vkanRJz0/v7mxuyJIoJ6B6jRvLWTzbw +-----END CERTIFICATE----- diff --git a/devenv/docker/blocks/auth/authentik/cloak.sql b/devenv/docker/blocks/auth/authentik/cloak.sql index 42f7eb842f6..ca0e30a0860 100644 --- a/devenv/docker/blocks/auth/authentik/cloak.sql +++ b/devenv/docker/blocks/auth/authentik/cloak.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 12.12 --- Dumped by pg_dump version 12.12 +-- Dumped from database version 12.13 +-- Dumped by pg_dump version 12.13 SET statement_timeout = 0; SET lock_timeout = 0; @@ -2497,6 +2497,7 @@ COPY public.authentik_blueprints_blueprintinstance (created, last_updated, manag COPY public.authentik_core_application (policybindingmodel_ptr_id, name, slug, meta_launch_url, meta_description, meta_publisher, provider_id, meta_icon, "group", open_in_new_tab) FROM stdin; 77d294b5-8a50-4d6d-8edd-5b15e5871826 Grafana grafana 1 t +506b1180-4069-4dda-9405-e185f355c34d Grafana SAML grafana-saml 2 t \. @@ -2505,13 +2506,8 @@ COPY public.authentik_core_application (policybindingmodel_ptr_id, name, slug, m -- COPY public.authentik_core_authenticatedsession (expires, expiring, uuid, session_key, last_ip, last_user_agent, last_used, user_id) FROM stdin; -2022-11-08 09:17:04.214445+00 t 5e425054-99e2-42a5-825b-6f91340c588d c62hjlwggmigklbysnjtp27ngqnnpbet 192.168.48.1 Mozilla/5.0 (X11; Linux x86_64; rv:106.0) Gecko/20100101 Firefox/106.0 2022-10-25 09:17:04.214738+00 1 -2022-11-08 09:53:30.259637+00 t 0b7411fe-871e-4936-84ec-03cd5955dc71 jeye9zabh725eoyreourgd4exswni4hz 192.168.48.1 goauthentik.io/outpost/2022.10.0 2022-10-25 09:53:30.259801+00 6 -2022-11-08 09:53:37.37657+00 t 959492a1-cf7e-40d3-8619-793981ce2226 8uc0fdpimytzcilq3tb53nsjpzjfp6x6 192.168.48.1 goauthentik.io/outpost/2022.10.0 2022-10-25 09:53:37.376711+00 6 -2022-11-08 09:53:47.038795+00 t 030b257a-ac48-4695-8d31-05ab564cc005 hevohjgfg5iptplcsylpfxlrifwn3l7q 192.168.48.1 goauthentik.io/outpost/2022.10.0 2022-10-25 09:53:47.03893+00 6 -2022-11-08 09:53:47.787505+00 t 6d327213-04a9-4ff3-8117-5f29157020fa dp93085lk6qggac9wo1qw4k7p8iih95g 192.168.48.1 goauthentik.io/outpost/2022.10.0 2022-10-25 09:53:47.787698+00 6 -2022-11-08 09:54:00.199533+00 t 90cb55c3-56d4-4310-8516-549284316c46 02oy1th515jtb7atyhocmfusdjx5oczl 192.168.48.1 goauthentik.io/outpost/2022.10.0 2022-10-25 09:54:00.199672+00 6 -2022-11-08 09:54:00.960305+00 t a203616f-cf71-4b30-88fb-440cb75d18eb wqs1feozyr605vl93efmaiaqt999abiy 192.168.48.1 goauthentik.io/outpost/2022.10.0 2022-10-25 09:54:00.960442+00 5 +2023-01-16 14:16:55.806987+00 t b7327320-c234-46b6-a1ee-05ca2d8e6871 zhvx6kegjga3td4qm1baw0nl6v6vidqd 172.24.0.1 Mozilla/5.0 (X11; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 2023-01-02 14:16:55.807308+00 1 +2023-01-16 16:38:22.075322+00 t f785c0cb-0a2c-4b03-8f0d-5577684f3e8e av8uhyv2bz1v0g09wti608pzujwin3fh 172.24.0.1 Mozilla/5.0 (X11; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0 2023-01-02 16:38:22.07557+00 5 \. @@ -2525,6 +2521,7 @@ a9c6327c-cfca-4d5b-a0af-421645e43c31 ldapsearch {} \N f 17bb354e-cea2-48db-b322-9f9c8b53b8bb admin {} \N f 7e216c1b-2a0f-4772-ac3d-7bdaf7511a2e editor {} \N f 10d9a55e-195b-41a0-9fe6-63382d5b8208 viewer {} \N f +ca14281c-e025-4691-b5a2-3ee415920e80 extra-group {} \N f \. @@ -2561,6 +2558,7 @@ e8809202-4ff0-4086-b078-196e6152e826 authentik default OpenLDAP Mapping: cn retu COPY public.authentik_core_provider (id, authorization_flow_id, name) FROM stdin; 1 87ca9599-5323-451c-8603-2f5bee7a0c12 grafana-ldap +2 a7ef54bb-7959-4559-979d-9345c022e086 grafana-saml \. @@ -2569,6 +2567,13 @@ COPY public.authentik_core_provider (id, authorization_flow_id, name) FROM stdin -- COPY public.authentik_core_provider_property_mappings (id, provider_id, propertymapping_id) FROM stdin; +1 2 05712cf6-0557-4ae7-95e7-3a0538962b67 +2 2 38ac59c3-e754-44e7-9790-5108a2cd027c +3 2 ef027c1a-728f-44e5-9072-6942d3e8d15e +4 2 29fab284-b6db-433a-9c3c-7575aba47b2b +5 2 c5d93ce6-8cda-42fa-9c19-3837ba67788a +6 2 765b25ee-0e8b-4394-a56c-e27abf68f5ce +7 2 b3fa1ee7-9507-4869-a29a-3c17c2482839 \. @@ -2606,12 +2611,12 @@ COPY public.authentik_core_token (token_uuid, expires, expiring, description, us COPY public.authentik_core_user (id, password, last_login, username, first_name, last_name, email, is_active, date_joined, uuid, name, password_change_date, attributes, path) FROM stdin; 2 !n7aLj0jKk3WIaSvfbaqM1nyy6fWejHsM54lRFljX \N AnonymousUser t 2022-10-25 09:01:03.467789+00 fe23113c-e658-40b0-872b-d522b6d84e95 2022-10-25 09:01:03.468042+00 {} users 7 pbkdf2_sha256$390000$s67BmqSPGYWQPDnZ01phY4$ky7qvH0SBsnsqeEM19AB0B4Ki8zIcmfshf7/r7aiodE= \N authentik-viewer authentik-viewer@localhost t 2022-10-25 09:56:58.979268+00 ee7aa163-4303-4c2d-99c7-293ffd9d08dd authentik-viewer 2022-10-25 09:57:13.55852+00 {} users -1 pbkdf2_sha256$390000$c4McOkDtg2Hps9uyXGdezW$8g7sh3praXa6Jo1AHfrng1dsf8dktric2hQYlUyAGyY= 2022-10-25 09:17:04.22378+00 akadmin admin@localhost t 2022-10-25 09:00:53.707692+00 d719cb26-008d-4bff-8f5c-13d1bf53de25 authentik Default Admin 2022-10-25 09:09:42.841946+00 {} users 8 pbkdf2_sha256$390000$g9w4rydmb2SLrzWYyInNZZ$W5G+Y2iayJp1KIp9wHuiozJ79Mdibo3zFx+Zxvofm5s= \N authentik-editor authentik-editor@localhost t 2022-10-25 09:57:43.64112+00 3678963f-023e-48b3-97a1-0910b4fbbfea authentik-editor 2022-10-25 09:57:54.40015+00 {} users -4 !yrI1uXKCdF0svOmz83ldwXJnWMOUY8oY52dzYNT1 \N ak-outpost-efe635b92ce74de4977f9f25b9f36d97 t 2022-10-25 09:13:37.843347+00 a7dc29a9-11b7-4d35-aec1-260ae1ea62f5 Outpost ldap-outpost Service-Account 2022-10-25 09:13:37.843557+00 {"goauthentik.io/user/override-ips": true, "goauthentik.io/user/service-account": true} goauthentik.io/outposts -3 !8j4wiqLzHmjS9fUEU0e0DsXx4z8Cj8W6lXnOCbo3 \N ak-outpost-0c24aadcf97e4720a70f72a190b0cafc t 2022-10-25 09:01:07.984838+00 b9835bf9-2711-41ef-86d2-b07124b9fef9 Outpost authentik Embedded Outpost Service-Account 2022-10-25 09:01:07.98507+00 {"goauthentik.io/user/override-ips": true, "goauthentik.io/user/service-account": true} goauthentik.io/outposts 6 pbkdf2_sha256$390000$k0Ykknoutfj56D2AaJFxWJ$bmAmMoNISoYnUn+DQnepRd39Mp1pnY74yfAoiHwRYRc= 2022-10-25 09:54:00.207978+00 ldapservice ldapservice@localhost t 2022-10-25 09:49:18.218809+00 94fafa12-9abd-4567-9627-3c6998f38cca ldapservice 2022-10-25 09:49:50.76013+00 {} users -5 pbkdf2_sha256$390000$GmvsVEytoLZGHoKB8LdomP$Xi06ESYoLZ/ijL0FZDT964KM9C9WoPhQtv6Rfl3b6Nc= 2022-10-25 09:54:00.969255+00 authentik-admin authentik-admin@localhost t 2022-10-25 09:37:30.689793+00 a7d3e4ed-8b75-456f-b3aa-f53f45afd3a4 authentik-admin 2022-10-25 09:37:49.480346+00 {} users +1 pbkdf2_sha256$390000$c4McOkDtg2Hps9uyXGdezW$8g7sh3praXa6Jo1AHfrng1dsf8dktric2hQYlUyAGyY= 2023-01-02 14:16:55.816269+00 akadmin admin@localhost t 2022-10-25 09:00:53.707692+00 d719cb26-008d-4bff-8f5c-13d1bf53de25 authentik Default Admin 2022-10-25 09:09:42.841946+00 {} users +5 pbkdf2_sha256$390000$GmvsVEytoLZGHoKB8LdomP$Xi06ESYoLZ/ijL0FZDT964KM9C9WoPhQtv6Rfl3b6Nc= 2023-01-02 16:38:22.084529+00 authentik-admin authentik-admin@localhost t 2022-10-25 09:37:30.689793+00 a7d3e4ed-8b75-456f-b3aa-f53f45afd3a4 authentik-admin 2022-10-25 09:37:49.480346+00 {} users +3 !8j4wiqLzHmjS9fUEU0e0DsXx4z8Cj8W6lXnOCbo3 \N ak-outpost-0c24aadcf97e4720a70f72a190b0cafc t 2022-10-25 09:01:07.984838+00 b9835bf9-2711-41ef-86d2-b07124b9fef9 Outpost authentik Embedded Outpost Service-Account 2022-10-25 09:01:07.98507+00 {"goauthentik.io/user/override-ips": true, "goauthentik.io/user/service-account": true} goauthentik.io/outposts +4 !yrI1uXKCdF0svOmz83ldwXJnWMOUY8oY52dzYNT1 \N ak-outpost-efe635b92ce74de4977f9f25b9f36d97 t 2022-10-25 09:13:37.843347+00 a7dc29a9-11b7-4d35-aec1-260ae1ea62f5 Outpost ldap-outpost Service-Account 2022-10-25 09:13:37.843557+00 {"goauthentik.io/user/override-ips": true, "goauthentik.io/user/service-account": true} goauthentik.io/outposts \. @@ -2625,6 +2630,8 @@ COPY public.authentik_core_user_ak_groups (id, user_id, group_id) FROM stdin; 3 5 17bb354e-cea2-48db-b322-9f9c8b53b8bb 4 8 7e216c1b-2a0f-4772-ac3d-7bdaf7511a2e 5 7 10d9a55e-195b-41a0-9fe6-63382d5b8208 +6 8 ca14281c-e025-4691-b5a2-3ee415920e80 +7 5 ca14281c-e025-4691-b5a2-3ee415920e80 \. @@ -2641,10 +2648,10 @@ COPY public.authentik_core_user_groups (id, user_id, group_id) FROM stdin; -- COPY public.authentik_core_user_user_permissions (id, user_id, permission_id) FROM stdin; -2 3 21 -28 4 21 -29 4 321 -30 4 348 +31 4 21 +32 4 321 +33 4 348 +34 3 21 \. @@ -2701,6 +2708,7 @@ a4594d02-c239-4607-b95b-3043d3d3b565 login_failed authentik.events.signals {"sta ede37431-b8b6-4b88-baaf-78a58802305c login_failed authentik.events.signals {"stage": {"pk": "02b4a829f9ab45d0adcc11e3c165f1c4", "app": "authentik_stages_password", "name": "default-authentication-password", "model_name": "passwordstage"}, "password": "********************", "username": "akadmin", "http_request": {"args": {"query": "goauthentik.io%2Foutpost%2Fldap=true"}, "path": "/api/v3/flows/executor/default-authentication-flow/", "method": "POST"}} 192.168.48.1 2022-10-25 09:42:31.137414+00 {"pk": 2, "email": "", "username": "AnonymousUser"} 2023-10-25 09:42:31.132685+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} 8d8dab83-750e-44f3-b5f9-e33b85e2f8b2 model_updated authentik.events.signals {"model": {"pk": 1, "app": "authentik_providers_ldap", "name": "grafana-ldap", "model_name": "ldapprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/ldap/1/", "method": "PUT"}} 192.168.48.1 2022-10-25 09:43:27.594287+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:43:27.59388+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} dacab0a4-45f4-43be-8471-44d99f3b8f4f login_failed authentik.events.signals {"stage": {"pk": "02b4a829f9ab45d0adcc11e3c165f1c4", "app": "authentik_stages_password", "name": "default-authentication-password", "model_name": "passwordstage"}, "password": "********************", "username": "akadmin", "http_request": {"args": {"query": "goauthentik.io%2Foutpost%2Fldap=true"}, "path": "/api/v3/flows/executor/default-authentication-flow/", "method": "POST"}} 192.168.48.1 2022-10-25 09:43:30.63453+00 {"pk": 2, "email": "", "username": "AnonymousUser"} 2023-10-25 09:43:30.629991+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +12d75a37-20ee-44f4-9f1b-9f99d6dfdeea update_available authentik.admin.tasks {"message": "Changelog: https://goauthentik.io/docs/releases/2022.12#fixed-in-2022121", "new_version": "2022.12.1"} \N 2023-01-02 14:17:08.672486+00 {} 2024-01-02 14:17:08.671057+00 t {"pk": "3fe5e3c1df6f42e1a4a934178957c3b6", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} 63133b06-3f6d-4b91-8dab-3de201e71ab5 model_updated authentik.events.signals {"model": {"pk": 1, "app": "authentik_providers_ldap", "name": "grafana-ldap", "model_name": "ldapprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/ldap/1/", "method": "PUT"}} 192.168.48.1 2022-10-25 09:44:12.026999+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:44:12.026611+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} 8b1c946c-4713-47ff-a104-9053e97e4a70 model_created authentik.events.signals {"model": {"pk": "f4abcb7d5f9248908760afb9effda665", "app": "authentik_stages_identification", "name": "ldap-identification-stage", "model_name": "identificationstage"}, "http_request": {"args": {}, "path": "/api/v3/stages/identification/", "method": "POST"}} 192.168.48.1 2022-10-25 09:45:15.819069+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:45:15.81859+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} bcd2fae1-ef0b-4f9c-807d-67a66817d063 model_created authentik.events.signals {"model": {"pk": "95715d75025b4e5e9d2396826479906d", "app": "authentik_stages_password", "name": "ldap-authentication-password", "model_name": "passwordstage"}, "http_request": {"args": {}, "path": "/api/v3/stages/password/", "method": "POST"}} 192.168.48.1 2022-10-25 09:45:43.056738+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:45:43.056256+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} @@ -2715,6 +2723,7 @@ dfab21a9-ad29-4980-a762-80a1f0c42ab5 model_created authentik.events.signals {"mo d273c74c-ec69-445b-87aa-0a7869c359b6 password_set authentik.events.signals {} 255.255.255.255 2022-10-25 09:49:44.127166+00 {"pk": 6, "email": "ldapservice@localhost", "username": "ldapservice"} 2023-10-25 09:49:44.12686+00 t {"pk": "124e241c825640d9b416596b2daab8b3", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} 9969bf93-4d3f-4325-97db-4037c6e9ed59 model_updated authentik.events.signals {"model": {"pk": 6, "app": "authentik_core", "name": "ldapservice", "model_name": "user"}, "http_request": {"args": {}, "path": "/api/v3/core/users/6/set_password/", "method": "POST"}} 192.168.48.1 2022-10-25 09:49:44.253791+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:49:44.253274+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} f8e8270f-871a-41bd-b02e-f3c29e9897f8 password_set authentik.events.signals {} 255.255.255.255 2022-10-25 09:49:45.195476+00 {"pk": 6, "email": "ldapservice@localhost", "username": "ldapservice"} 2023-10-25 09:49:45.195113+00 t {"pk": "669e359eaf174055957065109edc72a8", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} +a36d1d0c-a444-4dcd-b27c-297ecfce47fc authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": "SAMLRequest=nJJBj9MwEIX%2FiuV7aicNSWttIpWtEJUWqLaFA7epM9lacuzgmQD771GzRVok1MMebc97n%2BfN3BEMfjSbic%2FhEX9MSCx%2BDz6QuTw0ckrBRCBHJsCAZNiaw%2BbTgykW2gARJnYxyFeS8bZmTJGjjV6K3baRrstWuu7zWndlX%2BaAuF6eynVV9brP39WnWq9sUeVVibUU3zCRi6GRxUJLsSOacBeIIXAjC10sM51nujjmlVnmZrlerMr6uxRbJHYBeFaemUejlI8W%2FDkSm7XWWsE4emfnEnVpQT0l6CFANh%2BIojq50LnwpBJ2LqFlJcXmb%2FP3MdA0YDpg%2Buksfn18%2BA9neeHMdmBJiv01hfcvvrcju8LJfDwe99n%2By%2BEo23lsZs4giQ8xDcC3TS43rsv6udRgYMfPsr3xzwEZOmC4U69Q7XVdPsOAu%2B0%2Bemef34DnBIEcBpZi4338dZ8QGBvJaUKp2hfkv0vZ%2FgkAAP%2F%2F&RelayState=TUVNeGt5N3FINDA5UGVEM1JuSHA1dU82TFBWUUZPSzVEcFR2U2RqQ1hrb2lOdmdKMEp8U2RLaHBycFZ6&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=w3j4ku9na9TCOBB0lF%2FHA5VTsVQb%2Fa0kuSgILzVcbLTq%2BcAcPRNmEcpVqZPHcWtIfJPw%2F%2FP0HsQhKiPm70JbrVBbZPsZO3ysTe%2Fa9bhpot1yf9HHbkFWShcT%2BA5hgap1DEvJMSZD8N5%2Fkq%2BO47s9%2F0ngZeN%2FErO41mXDrvEGXFEJfHCgU18BOAwbKN9eGj09idnldNiUJ4mm1HEbAHI935KS6buBDnLgM0dYIYZzEIBKewt7j8poD98hRyV9GnCBrN%2FH2D7yOf3IGwKBc4hdMhYbeqBKcj4MTywBEQIc1uY9nUdtRI4N1Nrfmwd2KOpribVB%2FE6musXjb%2Fxo5%2BRmvyMASQGb%2Foo1QIfDZ87lRbRNKffw8KTI7dTusJTl%2BH6mYEtWU5rd%2B6bsiSLFiLl%2F7wpFOHlynTaqte2J1Cg7RAaJt8o0V7vb3%2BpS9CxaFY%2BF8J7MLc3drOTLy9EReBgV2vmdkwS2lE92EYkOzm987nusuUGP2f8ET5oUr1udHw1w2UPdyoJHdTYiHwLUufCM1%2BgaJsoZALfzgW0vKe8ghMUrHl03M9mddynTF%2B%2F9y%2FtIp3INVoWeS6WRHhX0bwlkwBXpL675KkW7q%2F0rVYJ3m6BOt9JkzXPv3l42yQPYs8KY3TXvbwIf2m0L%2FTORX5Jnhs09rWhQrNv%2BrONgD1mUrsA%3D"}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:31:40.277118+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:31:40.276431+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} c07b9726-c9a6-41d9-8750-298dc6c02bb9 model_updated authentik.events.signals {"model": {"pk": 6, "app": "authentik_core", "name": "ldapservice", "model_name": "user"}, "http_request": {"args": {}, "path": "/api/v3/core/users/6/set_password/", "method": "POST"}} 192.168.48.1 2022-10-25 09:49:45.334164+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:49:45.333246+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} 0e5513c2-7946-4809-b7ad-e8ee39eb06f7 password_set authentik.events.signals {} 255.255.255.255 2022-10-25 09:49:50.747043+00 {"pk": 6, "email": "ldapservice@localhost", "username": "ldapservice"} 2023-10-25 09:49:50.746711+00 t {"pk": "669e359eaf174055957065109edc72a8", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} 3c0fb3f6-a496-4f75-8748-065b52ecef2d model_updated authentik.events.signals {"model": {"pk": 6, "app": "authentik_core", "name": "ldapservice", "model_name": "user"}, "http_request": {"args": {}, "path": "/api/v3/core/users/6/set_password/", "method": "POST"}} 192.168.48.1 2022-10-25 09:49:50.876408+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:49:50.87598+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} @@ -2744,6 +2753,33 @@ a8885c27-5608-4eb5-a0d1-1f19dea20bda model_created authentik.events.signals {"mo 3454a52b-238c-4cb6-8db5-64c8303e339c model_updated authentik.events.signals {"model": {"pk": "17bb354ecea248dbb3229f9c8b53b8bb", "app": "authentik_core", "name": "admin", "model_name": "group"}, "http_request": {"args": {}, "path": "/api/v3/core/groups/17bb354e-cea2-48db-b322-9f9c8b53b8bb/", "method": "PUT"}} 192.168.48.1 2022-10-25 09:59:21.181345+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:59:21.180942+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} fdf81360-a8a8-4c8f-988d-2ed85c613a49 model_updated authentik.events.signals {"model": {"pk": "7e216c1b2a0f4772ac3d7bdaf7511a2e", "app": "authentik_core", "name": "editor", "model_name": "group"}, "http_request": {"args": {}, "path": "/api/v3/core/groups/7e216c1b-2a0f-4772-ac3d-7bdaf7511a2e/", "method": "PUT"}} 192.168.48.1 2022-10-25 09:59:30.4796+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:59:30.478673+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} a3998ed2-5beb-4a26-b395-f5b579d632ca model_updated authentik.events.signals {"model": {"pk": "10d9a55e195b41a09fe663382d5b8208", "app": "authentik_core", "name": "viewer", "model_name": "group"}, "http_request": {"args": {}, "path": "/api/v3/core/groups/10d9a55e-195b-41a0-9fe6-63382d5b8208/", "method": "PUT"}} 192.168.48.1 2022-10-25 09:59:42.963339+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2023-10-25 09:59:42.962914+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +2b3c7de7-f3cb-4e4d-bbcd-36ff4d1add8e login authentik.events.signals {"auth_method": "password", "http_request": {"args": {"query": "next=%2F"}, "path": "/api/v3/flows/executor/default-authentication-flow/", "method": "GET"}, "auth_method_args": {}} 172.24.0.1 2023-01-02 14:16:55.819839+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 14:16:55.819267+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +eeb1be95-4ade-4812-93b0-2c87db29716c update_available authentik.admin.tasks {"message": "Changelog: https://goauthentik.io/docs/releases/2022.12#fixed-in-2022121", "new_version": "2022.12.1"} \N 2023-01-02 14:17:08.645256+00 {} 2024-01-02 14:17:08.643976+00 t {"pk": "3fe5e3c1df6f42e1a4a934178957c3b6", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} +fa558ccb-705b-43b8-9a2e-86d4f1d8d0a0 system_task_exception authentik.events.monitored_tasks {"message": "Task notification_transport encountered an error: Traceback (most recent call last):\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/trace.py\\", line 451, in trace_task\\n R = retval = fun(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/integrations/celery.py\\", line 207, in _inner\\n reraise(*exc_info)\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/_compat.py\\", line 56, in reraise\\n raise value\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/integrations/celery.py\\", line 202, in _inner\\n return f(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/trace.py\\", line 734, in __protected_call__\\n return self.run(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/autoretry.py\\", line 54, in run\\n ret = task.retry(exc=exc, **retry_kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/task.py\\", line 717, in retry\\n raise_with_context(exc)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/autoretry.py\\", line 34, in run\\n return task._orig_run(*args, **kwargs)\\n File \\"/authentik/events/tasks.py\\", line 130, in notification_transport\\n raise exc\\n File \\"/authentik/events/tasks.py\\", line 126, in notification_transport\\n transport.send(notification)\\n File \\"/authentik/events/models.py\\", line 341, in send\\n return self.send_email(notification)\\n File \\"/authentik/events/models.py\\", line 463, in send_email\\n raise NotificationTransportError from exc\\nauthentik.events.models.NotificationTransportError: "} \N 2023-01-02 14:17:13.924879+00 {} 2024-01-02 14:17:13.924102+00 t {"pk": "3fe5e3c1df6f42e1a4a934178957c3b6", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} +c1d32b85-3711-44c1-bd8b-918eba2599e2 system_task_exception authentik.events.monitored_tasks {"message": "Task notification_transport encountered an error: Traceback (most recent call last):\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/trace.py\\", line 451, in trace_task\\n R = retval = fun(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/integrations/celery.py\\", line 207, in _inner\\n reraise(*exc_info)\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/_compat.py\\", line 56, in reraise\\n raise value\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/integrations/celery.py\\", line 202, in _inner\\n return f(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/trace.py\\", line 734, in __protected_call__\\n return self.run(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/autoretry.py\\", line 54, in run\\n ret = task.retry(exc=exc, **retry_kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/task.py\\", line 717, in retry\\n raise_with_context(exc)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/autoretry.py\\", line 34, in run\\n return task._orig_run(*args, **kwargs)\\n File \\"/authentik/events/tasks.py\\", line 130, in notification_transport\\n raise exc\\n File \\"/authentik/events/tasks.py\\", line 126, in notification_transport\\n transport.send(notification)\\n File \\"/authentik/events/models.py\\", line 341, in send\\n return self.send_email(notification)\\n File \\"/authentik/events/models.py\\", line 463, in send_email\\n raise NotificationTransportError from exc\\nauthentik.events.models.NotificationTransportError: "} \N 2023-01-02 14:17:14.593626+00 {} 2024-01-02 14:17:14.592229+00 t {"pk": "3fe5e3c1df6f42e1a4a934178957c3b6", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} +521e9674-7fa5-4d70-b266-9b8048b640b0 model_created authentik.events.signals {"model": {"pk": 2, "app": "authentik_providers_saml", "name": "grafana-saml", "model_name": "samlprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/saml/", "method": "POST"}} 172.24.0.1 2023-01-02 14:20:39.41267+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 14:20:39.41213+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +8a5a4f65-cbd8-4617-a52a-515f7e008164 model_created authentik.events.signals {"model": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}, "http_request": {"args": {}, "path": "/api/v3/core/applications/", "method": "POST"}} 172.24.0.1 2023-01-02 14:21:07.723593+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 14:21:07.722493+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +7ee95403-f95f-4a94-be59-0eb1210a3b55 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": ""}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 14:21:13.310418+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 14:21:13.30943+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +2e67f976-24c8-4f39-9fb8-160c8df5fc48 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": ""}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 14:21:43.965265+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 14:21:43.964033+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +a2a95f65-9f92-496e-b577-55c844b90891 model_updated authentik.events.signals {"model": {"pk": 2, "app": "authentik_providers_saml", "name": "grafana-saml", "model_name": "samlprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/saml/2/", "method": "PUT"}} 172.24.0.1 2023-01-02 14:22:28.185263+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 14:22:28.184795+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +95797e9f-e818-4f5d-9fdb-2d499833ae4c model_updated authentik.events.signals {"model": {"pk": 2, "app": "authentik_providers_saml", "name": "grafana-saml", "model_name": "samlprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/saml/2/", "method": "PUT"}} 172.24.0.1 2023-01-02 14:23:24.663957+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 14:23:24.663405+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +aa49c5e8-b508-4046-907e-cfdca67edf75 configuration_error authentik.providers.saml.views.sso {"message": "Failed to verify signature", "provider": {"pk": 2, "app": "authentik_providers_saml", "name": "grafana-saml", "model_name": "samlprovider"}} \N 2023-01-02 16:20:27.103437+00 {} 2024-01-02 16:20:27.102874+00 t {"pk": "69cc7a86348f4144973b0cbaaea44e3f", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} +9aac6d1c-02e9-4e51-b705-85a86f1919df system_task_exception authentik.events.monitored_tasks {"message": "Task notification_transport encountered an error: Traceback (most recent call last):\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/trace.py\\", line 451, in trace_task\\n R = retval = fun(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/integrations/celery.py\\", line 207, in _inner\\n reraise(*exc_info)\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/_compat.py\\", line 56, in reraise\\n raise value\\n File \\"/usr/local/lib/python3.10/site-packages/sentry_sdk/integrations/celery.py\\", line 202, in _inner\\n return f(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/trace.py\\", line 734, in __protected_call__\\n return self.run(*args, **kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/autoretry.py\\", line 54, in run\\n ret = task.retry(exc=exc, **retry_kwargs)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/task.py\\", line 717, in retry\\n raise_with_context(exc)\\n File \\"/usr/local/lib/python3.10/site-packages/celery/app/autoretry.py\\", line 34, in run\\n return task._orig_run(*args, **kwargs)\\n File \\"/authentik/events/tasks.py\\", line 130, in notification_transport\\n raise exc\\n File \\"/authentik/events/tasks.py\\", line 126, in notification_transport\\n transport.send(notification)\\n File \\"/authentik/events/models.py\\", line 341, in send\\n return self.send_email(notification)\\n File \\"/authentik/events/models.py\\", line 463, in send_email\\n raise NotificationTransportError from exc\\nauthentik.events.models.NotificationTransportError: "} \N 2023-01-02 16:20:32.525925+00 {} 2024-01-02 16:20:32.524994+00 t {"pk": "3fe5e3c1df6f42e1a4a934178957c3b6", "app": "authentik_tenants", "name": "Tenant fallback", "model_name": "tenant"} +d93d9f43-d318-4f57-8fc5-58859aafd0a6 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": ""}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:20:39.35962+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:20:39.358773+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +9a1859e1-089a-46c9-a2a9-5ca91e1a14da model_updated authentik.events.signals {"model": {"pk": 2, "app": "authentik_providers_saml", "name": "grafana-saml", "model_name": "samlprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/saml/2/", "method": "PUT"}} 172.24.0.1 2023-01-02 16:21:00.718418+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:21:00.717578+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +58277880-d969-4e13-ba98-dfca29dc2815 model_updated authentik.events.signals {"model": {"pk": 2, "app": "authentik_providers_saml", "name": "grafana-saml", "model_name": "samlprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/saml/2/", "method": "PUT"}} 172.24.0.1 2023-01-02 16:21:20.595211+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:21:20.594713+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +9c9fc310-2115-4aee-a0f0-d91abc0dae03 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": ""}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:21:26.679637+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:21:26.67829+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +2f1ac464-0e79-47ea-b3df-bf70f90d22c7 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": ""}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:21:47.199639+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:21:47.198789+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +d4549f57-3afb-4e6f-8726-be968bbc902f model_updated authentik.events.signals {"model": {"pk": 2, "app": "authentik_providers_saml", "name": "grafana-saml", "model_name": "samlprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/saml/2/", "method": "PUT"}} 172.24.0.1 2023-01-02 16:22:24.896791+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:22:24.895837+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +8d6e7456-f3f6-4806-a1ae-286cd50c6e54 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": ""}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:22:37.062381+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:22:37.061613+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +7203b3e8-4475-498f-b837-5775a0501e86 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": ""}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:26:08.685203+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:26:08.684412+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +7384e1a4-157e-47dd-ad30-f0c4030a1b54 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": "SAMLRequest=nJJBj9MwEIX%2FiuV76sRRmtbaRCpbISotUG0LB25TZ7q15NjBMwH236Nmi1QklANH2%2FPe53kzDwS9H8xm5Et4xu8jEotfvQ9krg%2BNHFMwEciRCdAjGbbmsPn4ZPQiN0CEiV0M8k4yzGuGFDna6KXYbRvpumxZl%2BeyrivbrWoN5akDrOtTpYuyWq3XS1utKnvSxVmKr5jIxdBIvcil2BGNuAvEELiROtdllhdZro%2FF0uiVKcpFWa%2B%2FSbFFYheAJ%2BWFeTBK%2BWjBXyKxWed5rmAYvLNTibq2oF4SnCFANh2Iojq50LnwohJ2LqFlJcXmT%2FOPMdDYYzpg%2BuEsfnl%2B%2BgenvHImO7Akxf6Wwrs33%2FnIbnAyH47Hfbb%2FfDjKdhqbmTJI4n1MPfC8yfXGddl5KjUY2PGrbGf%2B2SNDBwwP6g7V3tblE%2FS42%2B6jd%2Fb1P%2FCcIJDDwFJsvI8%2FHxMCYyM5jShV%2B4b8eynb3wEAAP%2F%2F&RelayState=MVFtczE2eEVENFo0amhyUEtoUFkySDJ5Wm5CRkRQWXJYaW42eTBMNTIyYkZNRENhS0Z8WkV6SHRydFZ6&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=Uds21tE5ZR3SmvxFqy9VrYyL8ujqShawaGcz0V77dayLPpeykS6JN36ZoTD4eHSPUqmHqvTziWpLC8O2IlbfANmm8%2F58AOWsxSaKjdqwlKgWeLSQUrqhDXLOdOfabzMXPFkcBg9jS7P%2FXRl4rGSyjS%2BCYTJdazdrM8w5JOBvO%2B%2B2NGfxbo1hcHbGprQTt7ScDsxt7WPcEE0GRVhEbOZAym%2Fp6ZwCGB%2FkxC%2BGN68hdbXKD%2B51X0SIdF8Apy%2BYw4nshrIqteXb6jcoTwfNQdNY25wnuJ3J4LXaE5ZqajjjIIfPFYjBsnRFuzFUed3372rahiX5ax0SWp7Fj3uxyG6%2FQil9MYSvCX1Kw0rQf5zKP%2FFohivJ3vtbgHsVa6mqgWPR%2BFyiX5xZbDi4DaHGof%2F7NM5f9g3CgcaQr5v8%2BK8eHn%2Fl1EjDlJSDr2BSJ3NX68581527VAcWZ2OjXRIeodh5DKBrQ9F9%2BTW4AEuTlRBPUWdL15209HG6MDcN5p6M0OgYh%2B6STW5FY0aqgSFBjxjXAGk%2BlKQTq%2BrpJnLQXxgJlby3%2BYkyO92Ick34NsxoaDr5ySYYtM6M%2FuZP9443t8B9xun407wdaja3CNWP8iXuM2%2FhS1JCDud%2Fb8oCq14l0sITUqUK4R%2BCEd7odJDUuSNdDjHiM%2BEW1mf3MyuB68M1dhE%3D"}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:28:13.791097+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:28:13.789745+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +b4315108-6c82-4ff3-aeea-56a31d071961 model_updated authentik.events.signals {"model": {"pk": 2, "app": "authentik_providers_saml", "name": "grafana-saml", "model_name": "samlprovider"}, "http_request": {"args": {}, "path": "/api/v3/providers/saml/2/", "method": "PUT"}} 172.24.0.1 2023-01-02 16:30:58.798901+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:30:58.797248+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +439439e2-e8e7-4af6-b3ef-e563b36f674a model_created authentik.events.signals {"model": {"pk": "ca14281ce0254691b5a23ee415920e80", "app": "authentik_core", "name": "extra-group", "model_name": "group"}, "http_request": {"args": {}, "path": "/api/v3/core/groups/", "method": "POST"}} 172.24.0.1 2023-01-02 16:33:50.270344+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:33:50.269707+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +c5efd86a-6999-4a7b-8fd9-305b26d96bc6 login authentik.events.signals {"auth_method": "password", "http_request": {"args": {"query": "next=%2Fapplication%2Fsaml%2Fgrafana-saml%2Fsso%2Fbinding%2Fredirect%2F%3FSAMLRequest%3DnJJBb9swDIX%252FiqC7I1t2U0%252BoDWQNhgXotqDJdtiNlelGgCx5Ir2t%252F36ImwEZMPjQoyS%252B94mPvCMY%252FGg2E5%252FCI%252F6YkFj8Hnwgc35o5JSCiUCOTIABybA1h82nB6NXuQEiTOxikFeScVkzpsjRRi%252FFbttI12VY6luLsF5bjbaobxHqQvc3T1VVV7qsir4rboq%252BRim%252BYSIXQyP1KpdiRzThLhBD4EbqXJdZXmS5PhZrU9am0Kt19V2KLRK7ADwLT8yjUcpHC%252F4Uic27PM8VjKN3di5R5w7Uc4IeAmTzgSiqJxc6F55Vws4ltKyk2Pzt%252FT4GmgZMB0w%252FncWvjw%252F%252F4ZRnzmwHlqTYX0J4%252F%252Bq7nNgFTubj8bjP9l8OR9nOUzNzBEl8iGkAXjY537gu6%252BdSg4Edv8h24Z8DMnTAcKeuUO1lWz7DgLvtPnpnX96A5wSBHAaWYuN9%252FHWfEBgbyWlCqdpX5L872f4JAAD%252F%252Fw%253D%253D%26RelayState%3DR3cwMXZJMU9xZUVaY1RZVmxVMDkwVk1DSEVkQzYzR2hBU3NNUHNkVlZxSEt1SDdrUDJ8Z2pPbHA5cFZ6%26SigAlg%3Dhttp%253A%252F%252Fwww.w3.org%252F2001%252F04%252Fxmldsig-more%2523rsa-sha256%26Signature%3DBX5F2GCFaeadEWb17AxG2mVisyVfBGrNRczpBwqe0TwTQZKySlVpaED8Vqak2hSyOC9HdKSkRmYGBJqrKPImbGzdeptgxXqlr8usnoBmxy1UF8GKrOM2ptPOqMsXargtigpS7mLatQN1spTkxyDUnfQfCknvH%252BgjQymkGDjtMtOQLEP2y3webPtuBiwv43Wl4YzojxSXALALITtANMgCqdsE8omJ%252FtuZZG2iu31j%252BJk3OTDWhZn6QBNNrV%252FMEPZOVD5CpnxagNhPC4J74rmWP2JA6Yu%252BcqFaCBTlty0do7D%252FQVvdzjHgrnAaosxltAtUkDWS47%252Fo1Ed9Jp2jTzsLzOh2%252F2EXZB7UrPIiQL28THjChuMv9ohoVrwHi6Z77SSJzJJYQmUKPC6IUg4PeoUY%252FpghlrtqxTDeghePUcLuMTusLu4wf%252BdMEvhwi20Eaz6ABr7F0TYAxnKfdEwsG%252FEIkBHa109%252Fv3YqDKKbKUuJKyUeu%252F%252Bo9YwbZG5eRDqKoBoNRbAyDOBVqXMgkTNUEhs5qtLDbOlgcim%252F5Tum5usf921zEn0Z01vGTO2bjrjdRbdJiAfAPhOj%252F69n7rJn%252FklU8NtXdiNHYZn0MLAdOKpBChjnfjehCp08n%252BG4ZcCXMe62gnD2Art0HJf30z2N76vJsoyrXMypc%252BN6WpUQSpsgb6w%253D"}, "path": "/api/v3/flows/executor/default-authentication-flow/", "method": "GET"}, "auth_method_args": {}} 172.24.0.1 2023-01-02 16:38:22.087813+00 {"pk": 5, "email": "authentik-admin@localhost", "username": "authentik-admin"} 2024-01-02 16:38:22.087203+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +0ffd3d0e-31f3-4b55-bdf7-d00160afcfdc authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": "SAMLRequest=nJJBb9swDIX%2FiqC7I1t2U0%2BoDWQNhgXotqDJdtiNlelGgCx5Ir2t%2F36ImwEZMPjQoyS%2B94mPvCMY%2FGg2E5%2FCI%2F6YkFj8Hnwgc35o5JSCiUCOTIABybA1h82nB6NXuQEiTOxikFeScVkzpsjRRi%2FFbttI12VY6luLsF5bjbaobxHqQvc3T1VVV7qsir4rboq%2BRim%2BYSIXQyP1KpdiRzThLhBD4EbqXJdZXmS5PhZrU9am0Kt19V2KLRK7ADwLT8yjUcpHC%2F4Uic27PM8VjKN3di5R5w7Uc4IeAmTzgSiqJxc6F55Vws4ltKyk2Pzt%2FT4GmgZMB0w%2FncWvjw%2F%2F4ZRnzmwHlqTYX0J4%2F%2Bq7nNgFTubj8bjP9l8OR9nOUzNzBEl8iGkAXjY537gu6%2BdSg4Edv8h24Z8DMnTAcKeuUO1lWz7DgLvtPnpnX96A5wSBHAaWYuN9%2FHWfEBgbyWlCqdpX5L872f4JAAD%2F%2Fw%3D%3D&RelayState=R3cwMXZJMU9xZUVaY1RZVmxVMDkwVk1DSEVkQzYzR2hBU3NNUHNkVlZxSEt1SDdrUDJ8Z2pPbHA5cFZ6&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=BX5F2GCFaeadEWb17AxG2mVisyVfBGrNRczpBwqe0TwTQZKySlVpaED8Vqak2hSyOC9HdKSkRmYGBJqrKPImbGzdeptgxXqlr8usnoBmxy1UF8GKrOM2ptPOqMsXargtigpS7mLatQN1spTkxyDUnfQfCknvH%2BgjQymkGDjtMtOQLEP2y3webPtuBiwv43Wl4YzojxSXALALITtANMgCqdsE8omJ%2FtuZZG2iu31j%2BJk3OTDWhZn6QBNNrV%2FMEPZOVD5CpnxagNhPC4J74rmWP2JA6Yu%2BcqFaCBTlty0do7D%2FQVvdzjHgrnAaosxltAtUkDWS47%2Fo1Ed9Jp2jTzsLzOh2%2F2EXZB7UrPIiQL28THjChuMv9ohoVrwHi6Z77SSJzJJYQmUKPC6IUg4PeoUY%2FpghlrtqxTDeghePUcLuMTusLu4wf%2BdMEvhwi20Eaz6ABr7F0TYAxnKfdEwsG%2FEIkBHa109%2Fv3YqDKKbKUuJKyUeu%2F%2Bo9YwbZG5eRDqKoBoNRbAyDOBVqXMgkTNUEhs5qtLDbOlgcim%2F5Tum5usf921zEn0Z01vGTO2bjrjdRbdJiAfAPhOj%2F69n7rJn%2FklU8NtXdiNHYZn0MLAdOKpBChjnfjehCp08n%2BG4ZcCXMe62gnD2Art0HJf30z2N76vJsoyrXMypc%2BN6WpUQSpsgb6w%3D"}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:38:22.508665+00 {"pk": 5, "email": "authentik-admin@localhost", "username": "authentik-admin"} 2024-01-02 16:38:22.508022+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +025e97e0-ee2c-4178-a410-2b832340a35a authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": ""}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:40:38.094961+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:40:38.094424+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} +8cdc9b41-47bb-440b-9ce8-3fa9d7c81800 authorize_application authentik.providers.saml.views.flows {"flow": "a7ef54bb79594559979d9345c022e086", "http_request": {"args": {"query": "SAMLRequest=nJJBj9MwEIX%2FSuR7ajvpJsTaRCpbISotUG0LB25TZ7q15NjBMwH236Nmi1Qk1MMebc97n%2BfN3BMMfjSriU%2FhCX9MSJz9Hnwgc35oxZSCiUCOTIABybA1u9WnR1MslAEiTOxiEFeS8bZmTJGjjV5km3UrXJ%2Frpa5RN1Dp4nBodF%2FZEnVT9XWpbH1X3tW2Pmo8vBPZN0zkYmhFsVAi2xBNuAnEELgVhSrKXOlcFXtdmWVl1HJRNvq7yNZI7ALwrDwxj0ZKHy34UyQ2jVJKwjh6Z%2BcSeW5BPic4QoB8PhBFeXChd%2BFZJuxdQstSZKu%2FzT%2FEQNOAaYfpp7P49enxP5zyzJntwJLItpcU3r%2F63o7sAifzcb%2Ff5tsvu73o5rGZOYOUfYhpAL5tcr5xfX6cSw0Gdvwiuhv%2FHJChB4Z7eYXqLuvyGQbcrLfRO%2FvyBjwnCOQwsMhW3sdfDwmBsRWcJhSye0X%2Bu5TdnwAAAP%2F%2F&RelayState=VVk1bmU0UDk4clBnUnNOVk5PNFBzV3hQamFHUVgxdEszS094WUN3SEZtWnlLczg0VXV8U2F5SGhydFZ6&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=mW4CQva3Vem85Dq5yo%2BBclqHaloTreonGr4VCel4SvbiRfF5jgQDGEcxn7kcxDw8JZym8iquo7Ivx%2BM%2FuciSTWOMp2n%2FTzbkkR7QxSfYjBLUXTsoh0JBZvh2fVsJMr10rYaW%2Frig8mGmP697eEhRyma9t5HJnu6mkvE%2Biszr3qQmv85nX%2B9Ac6IBOQOsiSheeKqdZm3Yj3gb3IGZjJ5Y8xmGZSmQ%2BLbiB7AerHM8KNUqysRBoyDBcG5YTtTNao4ImNBkCxh0a45Geur3y0LwEmt0lQkY2YJzW4Uh%2B3dKCK2yRqnTZfEK7nvH37Ue0uc1Fx9zH6hjgijSqWfYAYEJWf%2B4sM%2BhMZ8tHV93bLzzaPXH%2FfhAYyo%2FMREW2AKlIYElK6dQA68wblp%2FxYXm%2BXgJoYs%2FHQqPrQLU%2BE2Bd5Me5xvUIfD2KBR3Lx9GsY8%2F0n21HTajAgxga3NuscTGlP9Is%2FLdOxfu9IY3d77ecSTTUWgkcR%2BRyEo1ApUGLWZrAp5WK1IWMbfBf0hhPmQCccjgjN%2B8kDgAE9ETst36804uotZH6VY4iuTJhdnX4EPcFl848E6BwnrnlP%2FyJ16Qb7nQGWn2zGVGFTncEtwH%2FaUb1LKp6yTuMSuSQRcqzNyFuvJDBvmWiGTbGzlt9VCbwtIVTInSI3NLin%2BMErSktt5AFVg%3D"}, "path": "/api/v3/flows/executor/default-provider-authorization-implicit-consent/", "method": "GET"}, "authorized_application": {"pk": "506b118040694dda9405e185f355c34d", "app": "authentik_core", "name": "Grafana SAML", "model_name": "application"}} 172.24.0.1 2023-01-02 16:46:04.805431+00 {"pk": 1, "email": "admin@localhost", "username": "akadmin"} 2024-01-02 16:46:04.80479+00 t {"pk": "1b45e4ae6c1b4ea89691df11c97baeef", "app": "authentik_tenants", "name": "Default tenant", "model_name": "tenant"} \. @@ -2752,6 +2788,9 @@ a3998ed2-5beb-4a26-b395-f5b579d632ca model_updated authentik.events.signals {"mo -- COPY public.authentik_events_notification (uuid, severity, body, created, seen, event_id, user_id) FROM stdin; +2e407e51-2f8e-4386-8f0e-44075f108506 alert Changelog: https://goauthentik.io/docs/releases/2022.12#fixed-in-2022121 2023-01-02 14:17:10.978208+00 f eeb1be95-4ade-4812-93b0-2c87db29716c 1 +b60edc8e-400f-4598-8985-b16194cd6fe1 alert Changelog: https://goauthentik.io/docs/releases/2022.12#fixed-in-2022121 2023-01-02 14:17:11.648064+00 f 12d75a37-20ee-44f4-9f1b-9f99d6dfdeea 1 +b124e245-e4fc-457d-9171-329220ca358f alert Failed to verify signature 2023-01-02 16:20:28.230329+00 f aa49c5e8-b508-4046-907e-cfdca67edf75 1 \. @@ -3065,10 +3104,11 @@ cba52268-0d9a-40bd-ae7f-b723f5e05671 any 417e795d-6832-40b6-b083-4462357471cb any 0a3c97a9-ac46-45cb-8718-a2984fff154a any 081ce9e5-201f-48e9-af60-7c10076f0857 any -0e32dc99-655c-42f4-aca4-f3f271e010fd any 105415a2-7753-4a7f-bdf5-ed9841420289 any e4c83e8e-69f4-4993-a0c2-f99552fd76d0 any 9fdfd009-537e-4b36-8cc8-98f62620f410 any +0e32dc99-655c-42f4-aca4-f3f271e010fd any +506b1180-4069-4dda-9405-e185f355c34d any \. @@ -3082,6 +3122,8 @@ COPY public.authentik_policies_reputation_reputation (reputation_uuid, identifie d36ccca8-c986-4517-8110-761eac3d8be0 akadmin 192.168.48.1 {} -2 2022-10-25 09:17:04.253267+00 f433dfcb-3aa4-4d93-8abc-6fe2c4a77f77 ldapservice 192.168.48.1 {} 5 2022-10-25 09:53:31.155505+00 2f00817d-e5f1-4e9d-bc4e-8210b2ca2258 authentik-admin 192.168.48.1 {} 1 2022-10-25 09:54:01.492491+00 +85999073-85b3-4f12-93b8-a98b770daa41 akadmin 172.24.0.1 {} 1 2023-01-02 14:16:56.149781+00 +0eb5ae80-8ca7-4431-bac6-2032e2e5ef97 authentik-admin 172.24.0.1 {} 1 2023-01-02 16:38:23.763498+00 \. @@ -3182,6 +3224,7 @@ c5d93ce6-8cda-42fa-9c19-3837ba67788a http://schemas.xmlsoap.org/claims/Group \N -- COPY public.authentik_providers_saml_samlprovider (provider_ptr_id, acs_url, audience, issuer, assertion_valid_not_before, assertion_valid_not_on_or_after, session_valid_not_on_or_after, digest_algorithm, signature_algorithm, signing_kp_id, sp_binding, verification_kp_id, name_id_mapping_id) FROM stdin; +2 http://localhost:3000/saml/acs http://localhost:3000/saml/metadata minutes=-5 minutes=5 minutes=86400 http://www.w3.org/2001/04/xmlenc#sha256 http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 6304c995-9003-4fc4-9ba2-3d32016fb04e post \N 38ac59c3-e754-44e7-9790-5108a2cd027c \. @@ -4053,9 +4096,9 @@ COPY public.guardian_groupobjectpermission (id, object_pk, content_type_id, grou -- COPY public.guardian_userobjectpermission (id, object_pk, content_type_id, permission_id, user_id) FROM stdin; -2 0c24aadc-f97e-4720-a70f-72a190b0cafc 15 63 3 -20 efe635b9-2ce7-4de4-977f-9f25b9f36d97 15 63 4 -21 1 30 125 4 +22 efe635b9-2ce7-4de4-977f-9f25b9f36d97 15 63 4 +23 1 30 125 4 +24 0c24aadc-f97e-4720-a70f-72a190b0cafc 15 63 3 \. @@ -4108,14 +4151,14 @@ SELECT pg_catalog.setval('public.auth_permission_id_seq', 356, true); -- Name: authentik_core_provider_id_seq; Type: SEQUENCE SET; Schema: public; Owner: authentik -- -SELECT pg_catalog.setval('public.authentik_core_provider_id_seq', 1, true); +SELECT pg_catalog.setval('public.authentik_core_provider_id_seq', 2, true); -- -- Name: authentik_core_provider_property_mappings_id_seq; Type: SEQUENCE SET; Schema: public; Owner: authentik -- -SELECT pg_catalog.setval('public.authentik_core_provider_property_mappings_id_seq', 1, false); +SELECT pg_catalog.setval('public.authentik_core_provider_property_mappings_id_seq', 7, true); -- @@ -4143,14 +4186,14 @@ SELECT pg_catalog.setval('public.authentik_core_user_id_seq', 8, true); -- Name: authentik_core_user_pb_groups_id_seq; Type: SEQUENCE SET; Schema: public; Owner: authentik -- -SELECT pg_catalog.setval('public.authentik_core_user_pb_groups_id_seq', 5, true); +SELECT pg_catalog.setval('public.authentik_core_user_pb_groups_id_seq', 7, true); -- -- Name: authentik_core_user_user_permissions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: authentik -- -SELECT pg_catalog.setval('public.authentik_core_user_user_permissions_id_seq', 30, true); +SELECT pg_catalog.setval('public.authentik_core_user_user_permissions_id_seq', 34, true); -- @@ -4290,7 +4333,7 @@ SELECT pg_catalog.setval('public.guardian_groupobjectpermission_id_seq', 1, fals -- Name: guardian_userobjectpermission_id_seq; Type: SEQUENCE SET; Schema: public; Owner: authentik -- -SELECT pg_catalog.setval('public.guardian_userobjectpermission_id_seq', 21, true); +SELECT pg_catalog.setval('public.guardian_userobjectpermission_id_seq', 24, true); -- diff --git a/devenv/docker/blocks/auth/authentik/docker-compose.yaml b/devenv/docker/blocks/auth/authentik/docker-compose.yaml index 47ffc4f4dfe..0c70030bb6c 100644 --- a/devenv/docker/blocks/auth/authentik/docker-compose.yaml +++ b/devenv/docker/blocks/auth/authentik/docker-compose.yaml @@ -39,7 +39,7 @@ - "authentik:authentik" authentik: - image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.10.0} + image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.12.1} restart: unless-stopped container_name: authentik command: server @@ -66,7 +66,7 @@ - "authentikredis:authentikredis" authentik-worker: - image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.10.0} + image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.12.1} restart: unless-stopped container_name: authentik-worker command: worker @@ -88,4 +88,4 @@ - ./geoip:/geoip links: - "authentikdb:authentikdb" - - "authentikredis:authentikredis" \ No newline at end of file + - "authentikredis:authentikredis" diff --git a/devenv/docker/blocks/auth/authentik/key.pem b/devenv/docker/blocks/auth/authentik/key.pem new file mode 100644 index 00000000000..0532b7e6809 --- /dev/null +++ b/devenv/docker/blocks/auth/authentik/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDOQQHtZEMgy/WJ +f7gqYMko8UIImFkr5Q1QsIQWalX/34jS8d2Pnl5WPZ+7FqG56ZZ0L4aiBcv7JsA+ +Eu8nCeXk6WiMf25yjsi5Aj8PwLXgjAr+q28Gi6EKRvCX/6zcYYOrLt2YWTc706aW +VIAOUikVFKKgRzqX/yyyF96WzJde/7mQq7zsJFgNMt0L8yHFrl0DXOpRme++t965 +zjVnwCPeQU1Jd5FswoD56ObSQD4Fz4xdeE/hPiAG6TWBHQGjTLawQNvSaUnUf8Ua +QlnqfXaCPcgFgx+t8nCPTQ9VS9cCr/yu1+J4qBm9ytvBHt8nu6lpZE67GaSeMYk+ +HXUfObh3rnczfgGQM1gyv97j0qS26/u8Mbwo4fHt/Xqvdl7wIvU1jLgNeJyimVmj ++l3I9git23Vs8xnh/Cq5+A85FVmbf/PYkOl4djYO2o/XiMUNbl9jN0nhm3xhmC0D +O4FtkHhAwWg4r70aR9kzP/wu0+ugQq12EQOSzKAhs+bJ+7Iju0li0nhjbxhvDacT +xcndJHXPKG+MJOBCjTrfEGA+IANUPVVy+me3efX/lTCC0tInNUu7xrP4ePiKrtGN +5h2bAYUd5xxFm/+7x02aOoyS31REZydymzyzik3dUl6pBfj8bqfbgKgHLc3O7awa +BKcbpQ6RUZz1ahuM9GsrN2mfGb56twIDAQABAoICAQC30wSOua+CiYWbOLPBQWXI +Ec5SrsW683h8k/YFrFxdorLlnx9jIzjdECtOqYsWkXPAtaL+wL/eZdks+d8gIhg9 +fzHpFqIvg8at7ayas5zmNqqN8+kOImAzMqq4Le24tUiOx0RExi8OKULt9P30xZFL +8opG43T7hzgkulVufYXj63NN12KX7IR1rwGH5vgQoajQmH1WoSYmCuZ9H6ZFg9/z +9KuXcPOxxWECW2tttEgTEbn+jH5VN/N/dVZ0bo/JJVQlnojM7X4Z4I6hKsXM0mlP +BxVApG8Vr2BWt82UrfVBtew1QL/d3aSjnSti4cruNLl27ymkY+RuAaRaMhLy85TN +q3vWXoVA0vJiG3mk3vr9pzO3tkbSS4i/xcnRkl62YMY59bZ3YZlGcSqOj+ohcZNs +9IMtn3ivqqSYWCysuyOa5vIhoa/ZmFHVavudGlfQ9CRS08G5u2KNkjyUAlFXwEgy +dXg4FYNhqcZ9vDZ5/kwhgpoP1et/SS+BInM/1sXX23wyRtaq6lEkMmZZ0TWvdkkp +16kDWTfbrol8WTKLB5GAs0tV4sTIe56iH2tuNA8qmXer004R3Ye+9qzIu7tsztlJ +XzX9xwqKGouSuXrVkLTgrwNBLqJUsf7YcPoCTsF7GsucOkpPCxmEPoYT4IzY/jRc +om0YU9VBGdjLY6mBZqE3YQKCAQEA94YN6Ti6rohtNHJFmhx+eqyes8FcQ4iIwmBi +lYC4uBPD8gsNfpFzC3AJgIet0Ua20y9qVLflMRzyRvn51c6b+lq+nzQhFcBmTaZv +LAmo7eefaxGtTptn3kh8c/qw4SBcla5UUbU6AQiTl0rbu7HSxdNxVnIWci2L0wRE +4rzn9daONkfR03wAKVL3UGaGpG5AZtCxolVg0UK/xnRlJtONkMtPIOlKqAe0FQVz +x640bz9zhx1+YFHbuVAgcXaE8SFws35IEwOy0wse+5qcMTmF7D7IO5djxwbnQXOn +txmQYjcQPea7atDwqP7uSuxLlm8eS4H35/E0ej5NSdHXCZDhPwKCAQEA1VEoRUpt +ikRtv6+e9pJH18ypRMyX22F7rWHHKrDcIDQiq4e7gheQCR4jEYx4BoDysZA4Kj7k +yX4N+JsjUl67R3H8kkVCKcp5yEJenXQNYWfXm5FjUrBtYpPhtcUfJpvN2KJDC8SK +zEeVhdbyG7/AQdWmufCqlsclYXLTu9nUUoWx9r5EIgpX0qMbIwmFurrrU5K57VZq +Y3mjoQ5xyLOeXbyLw/0k6fPYPcMFcEop3x3RCliK7ZQbXtEeUT9lMKn51BnYIuzL +mUqefyEFijLTbjCxGOsHCrFWwG4ZHvX1hwQUCIyJ4KFyF2+HePdVJBV7xvoCvMqu +qi9eUy66yF4QiQKCAQEAkQ6/wkjbpoMPNKgvQASo3q2hCAkuT+mI9BjWTXU1S7NR +ptxxWRZWTIKpHujkpXuCkzr7XpCywViMZ6oBjxpY8JwTkGtVZFDv41vBVRaIKHwQ +QggG/8U1VuA2ByYIWLbFa3bRtVy2lHp5PDXDlji9I/ApQJb1ro4gnlanJBW+/qRx +jUADqKPH2G/lvfmHuH9gQKDXKSJN7fTnPJ9j1CAwm4V6ZvgxJ030bElYs9HUqsmN +o8ZWFI6EbRKXIMdrW4DUcmx0QqhtOR8QVjUiifqDEJMudzXeZHoYNpRX2ochxV0t +qIk6lnvCPdfAwKnAFnYiU+/j38rMjUNLuhI+qj18swKCAQEAmQQ9ce7gwrUxULZG +vjLSTC9oIIzuRZUY5ZdlCe+SB5u+XvLfg4MpdBDlMPplQq0O6mY8k7lH3m2I0hNe +9AA2MRrIBZiezGGMmCVbv3B3BK87j0FsUO1zmang18XOAaDzuPk0ROW4dq7GyBoW +0+bpn5AFqA3GIKA75To8Pd0UnBmBPjDfqNdohhUwKyLyha79jZaLvn8lk7O2t5on +7sNBz5WnYHBfIV8kd7FEXncksAKwZ8554dBDDfHN4JQsHwtexVHAo9Tee1h2d1UV +pzusRLey4d/9ziLFywVvCbF/bVl0kWlWogbNU5hSQmiQ73BluKawfgKZ5kRSUjMM +Exy+mQKCAQBIrmOVr5WJDtG5g78w0ALC4b9o+vVb8bW+jFjVULt4uyfMAq4HEdTw +69Sp6uNpZ+QFNUyUEMxdhEtfXliQipOLFtPLzF+U/x2izHyf4mp4I09b8kBUbt2H +t/ajOe3a66X4jk/PQGr6MkuZdPvz61KwxXMufgYTtHpMcdZXs3/baMaDly5F9CbN +YHseNkWS+pjRLEJ+SoiiBylmBCwm8FMkVKiTOu1/kHYg+IaDVOGK4SfNKAKA/zP6 +DpkOGekQdLcv0DqDGzNQrGWpMCJ2CF3kq8USX9F3aHlQLvTrjWQDTTE5lNCaVo3N +4qTzfwHOQoia5dVH9t46UancuuVvC0vk +-----END PRIVATE KEY----- diff --git a/devenv/docker/blocks/auth/saml/docker-compose.yaml b/devenv/docker/blocks/auth/saml/docker-compose.yaml deleted file mode 100644 index c0245a79c04..00000000000 --- a/devenv/docker/blocks/auth/saml/docker-compose.yaml +++ /dev/null @@ -1,16 +0,0 @@ - saml: - container_name: saml - image: kristophjunge/test-saml-idp - environment: - SIMPLESAMLPHP_SP_ENTITY_ID: http://grafana.com - SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE: http://localhost/simplesaml/module.php/saml/sp/saml2-acs.php/test-sp - SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE: http://localhost/simplesaml/module.php/saml/sp/saml2-logout.php/test-sp - SIMPLESAMLPHP_ADMIN_PASSWORD: grafana - SIMPLESAMLPHP_SECRET_SALT: salt - ports: - - "8080:8080" - - "8443:8443" - volumes: - - ./docker/blocks/auth/saml/users.php:/var/www/simplesamlphp/config/authsources.php - - diff --git a/devenv/docker/blocks/auth/saml/notes.md b/devenv/docker/blocks/auth/saml/notes.md deleted file mode 100644 index 3ea67ac4373..00000000000 --- a/devenv/docker/blocks/auth/saml/notes.md +++ /dev/null @@ -1,16 +0,0 @@ -# Notes on Saml Docker Block - -Uses https://github.com/kristophjunge/docker-test-saml-idp as a docker container for saml example. - -## Use - -See docker container docs on how to use this service - https://github.com/kristophjunge/docker-test-saml-idp#test-the-identity-provider-idp - -## Groups & Users - -admins - saml-admin (saml-admin@grafana.com) -editors - saml-editor (saml-editor@grafana.com) -no groups - saml-viewer (saml-viewer@grafana.com) diff --git a/devenv/docker/blocks/auth/saml/users.php b/devenv/docker/blocks/auth/saml/users.php deleted file mode 100644 index 8ffa693c3e7..00000000000 --- a/devenv/docker/blocks/auth/saml/users.php +++ /dev/null @@ -1,21 +0,0 @@ - array( - 'core:AdminPassword', - ), - 'example-userpass' => array( - 'exampleauth:UserPass', - 'saml-admin:grafana' => array( - 'groups' => array('admins'), - 'email' => 'saml-admin@grafana.com', - ), - 'saml-editor:grafana' => array( - 'groups' => array('editors'), - 'email' => 'saml-editor@grafana.com', - ), - 'saml-viewer:grafana' => array( - 'groups' => array(), - 'email' => 'saml-viewer@grafana.com', - ), - ), -); diff --git a/docs/sources/developers/plugins/package-a-plugin.md b/docs/sources/developers/plugins/publish-a-plugin.md similarity index 93% rename from docs/sources/developers/plugins/package-a-plugin.md rename to docs/sources/developers/plugins/publish-a-plugin.md index bc28b2898e3..c5e37a3dea7 100644 --- a/docs/sources/developers/plugins/package-a-plugin.md +++ b/docs/sources/developers/plugins/publish-a-plugin.md @@ -1,11 +1,12 @@ --- aliases: - share-a-plugin/ -title: Package a plugin + - package-a-plugin/ +title: Publish a plugin type: docs --- -# Package a plugin +# Publish a plugin You've just built your first plugin, and now you want to share it with the world. In this guide, you'll learn how to package and share your plugin with others. @@ -17,7 +18,7 @@ There are three steps needed to package a plugin: - Building the plugin - Signing the plugin -- Archiving the plugin +- Packaging the plugin 1. Build the plugin @@ -87,6 +88,7 @@ To speed up the time it takes to review your plugin: - Check that your plugin is ready for review using the [plugin validator](https://github.com/grafana/plugin-validator). - Read our [6 tips for improving your Grafana plugin before you publish](https://grafana.com/blog/2021/01/21/6-tips-for-improving-your-grafana-plugin-before-you-publish/). +- Refer to [plugin-examples](https://github.com/grafana/grafana-plugin-examples) to review best practices for building your plugin. ## Frequently Asked Questions @@ -98,9 +100,13 @@ To speed up the time it takes to review your plugin: - We're not able to give an estimate at this time, though we're constantly working on improving the time it takes to review a plugin. +**Can I decide a date when my plugin will be published?** + +- No. We cannot guarantee specific publishing dates, as plugins are immediately published after a review based on our internal prioritization. + ## Publishing your plugin for the first time -{{< figure src="/static/img/docs/plugins/plugins-submission-create.png" class="docs-image--no-shadow" max-width="850px" >}} +{{< figure src="/static/img/docs/plugins/plugins-submission-create.png" class="docs-image--no-shadow" max-width="650px" >}} 1. [Sign in](https://grafana.com/auth/sign-in) to your Grafana Cloud account. 1. In the left menu, under **Org settings**, click **My Plugins**. @@ -120,7 +126,7 @@ All submissions are manually inspected by a plugin reviewer. For every new plugi - **Code review:** For quality and security purposes, we review the source code for the plugin. If you're unable to make the source code publicly available, let us know in a comment on your plugin submission. - **Tests:** We install your plugin on one of our Grafana instances to test it for basic use. For more advanced plugins, we may ask you to assist us in configuring a test environment for the plugin. This test environment will be used whenever you submit an plugin update. -## Maintain your plugin +## Submit a plugin update To submit an **update** for an already published plugin: diff --git a/go.mod b/go.mod index 696862d7f5f..cf719c1267e 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/google/wire v0.5.0 github.com/gorilla/websocket v1.5.0 github.com/grafana/alerting v0.0.0-20221221211348-c5ab25d1cb8a - github.com/grafana/cuetsy v0.1.1 + github.com/grafana/cuetsy v0.1.5 github.com/grafana/grafana-aws-sdk v0.11.0 github.com/grafana/grafana-azure-sdk-go v1.5.1 github.com/grafana/grafana-plugin-sdk-go v0.147.0 diff --git a/go.sum b/go.sum index d9b7067ec2f..dc0308f71d5 100644 --- a/go.sum +++ b/go.sum @@ -1363,8 +1363,8 @@ github.com/grafana/alerting v0.0.0-20221221211348-c5ab25d1cb8a h1:Ros/f8x4rPGiOy github.com/grafana/alerting v0.0.0-20221221211348-c5ab25d1cb8a/go.mod h1:A+ko8Ui4Ojw9oTi1WMCPH937mFUozN8Y41cqrOfNuy8= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= -github.com/grafana/cuetsy v0.1.1 h1:+1jaDDYCpvKlcOWJgBRbkc5+VZIClCEn5mbI+4PLZqM= -github.com/grafana/cuetsy v0.1.1/go.mod h1:4KWkUOslwvRTpEv7wdQG0jDFTuJmU+0L9x0h4kWxa2A= +github.com/grafana/cuetsy v0.1.5 h1:mnFwAXdbqCsyL8r7kkdUMJ4kOAR26cxIPmrZj7JzTeY= +github.com/grafana/cuetsy v0.1.5/go.mod h1:4KWkUOslwvRTpEv7wdQG0jDFTuJmU+0L9x0h4kWxa2A= github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f h1:FvvSVEbnGeM2bUivGmsiXTi8URJyBU7TcFEEoRe5wWI= github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f/go.mod h1:uPG2nyK4CtgNDmWv7qyzYcdI+S90kHHRWvHnBtEMBXM= github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA= diff --git a/kinds/structured/team/team_kind.cue b/kinds/structured/team/team_kind.cue index 6b1d9da1f2e..c4ead8ceb29 100644 --- a/kinds/structured/team/team_kind.cue +++ b/kinds/structured/team/team_kind.cue @@ -21,13 +21,13 @@ lineage: seqs: [ // TODO - it seems it's a team_member.permission, unlikely it should belong to the team kind permission: #Permission @grafanamaturity(ToMetadata="kind", MaybeRemove) // AccessControl metadata associated with a given resource. - accessControl: [string]: bool @grafanamaturity(ToMetadata="sys") + accessControl?: [string]: bool @grafanamaturity(ToMetadata="sys") // Created indicates when the team was created. created: int64 @grafanamaturity(ToMetadata="sys") // Updated indicates when the team was updated. updated: int64 @grafanamaturity(ToMetadata="sys") - #Permission: 1 | 2 | 4 @cuetsy(kind="enum",memberNames="viewer|editor|admin") + #Permission: 1 | 2 | 4 @cuetsy(kind="enum",memberNames="Viewer|Editor|Admin") }, ] }, diff --git a/package.json b/package.json index 2fa4f7d027e..a8298234a82 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6", "@babel/plugin-proposal-object-rest-spread": "7.20.2", - "@babel/plugin-proposal-optional-chaining": "7.18.9", + "@babel/plugin-proposal-optional-chaining": "7.20.7", "@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-transform-react-constant-elements": "7.20.2", "@babel/plugin-transform-runtime": "7.19.6", @@ -282,7 +282,7 @@ "@sentry/utils": "6.19.7", "@types/react-resizable": "3.0.3", "@types/webpack-env": "1.18.0", - "@visx/event": "2.6.0", + "@visx/event": "2.17.0", "@visx/gradient": "2.10.0", "@visx/group": "2.10.0", "@visx/scale": "2.2.2", diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index 96650fde5cd..884165728ee 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -102,9 +102,9 @@ export const defaultVariableModel: Partial = { }; export enum VariableHide { - DontHide = 0, - HideLabel = 1, - HideVariable = 2, + dontHide = 0, + hideLabel = 1, + hideVariable = 2, } export enum LoadingState { @@ -287,7 +287,7 @@ export enum MappingType { * TODO docs */ export interface ValueMap { - options: Record; + options: Record; type: MappingType.ValueToText; } @@ -447,7 +447,7 @@ export interface Panel { /** * TODO docs - seems to be an old field from old dashboard alerts? */ - thresholds?: Array; + thresholds?: Array; /** * TODO docs * TODO tighter constraint @@ -456,7 +456,7 @@ export interface Panel { /** * TODO docs */ - timeRegions?: Array; + timeRegions?: Array; /** * TODO docs * TODO tighter constraint @@ -494,7 +494,7 @@ export interface FieldConfigSource { matcher: MatcherConfig; properties: Array<{ id: string; - value?: any; + value?: unknown; }>; }>; } @@ -505,7 +505,7 @@ export const defaultFieldConfigSource: Partial = { export interface MatcherConfig { id: string; - options?: any; + options?: unknown; } export const defaultMatcherConfig: Partial = { @@ -546,7 +546,7 @@ export interface FieldConfig { /** * The behavior when clicking on a result */ - links?: Array; + links?: Array; /** * Convert input values into a display string */ diff --git a/packages/grafana-schema/src/raw/team/x/team_types.gen.ts b/packages/grafana-schema/src/raw/team/x/team_types.gen.ts index f76ca40bbd2..177c10168a5 100644 --- a/packages/grafana-schema/src/raw/team/x/team_types.gen.ts +++ b/packages/grafana-schema/src/raw/team/x/team_types.gen.ts @@ -18,7 +18,7 @@ export interface Team { /** * AccessControl metadata associated with a given resource. */ - accessControl: Record; + accessControl?: Record; /** * AvatarUrl is the team's avatar URL. */ diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx index a0454c80009..d114bf03130 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx @@ -72,7 +72,13 @@ export class UnthemedCanvasSpanGraph extends React.PureComponent; + return ( + + ); } } diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.tsx index 132efa0f9a1..e63dd89018b 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.tsx @@ -56,5 +56,9 @@ export default function TickLabels(props: TickLabelsProps) { ); } - return
{ticks}
; + return ( +
+ {ticks} +
+ ); } diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.tsx index ba8d949e2fc..a1d45aa2454 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.tsx @@ -12,23 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { shallow, ShallowWrapper } from 'enzyme'; +import { getAllByTestId, render, screen } from '@testing-library/react'; import React from 'react'; import traceGenerator from '../../demo/trace-generators'; import transformTraceData from '../../model/transform-trace-data'; import { polyfill as polyfillAnimationFrame } from '../../utils/test/requestAnimationFrame'; -import CanvasSpanGraph from './CanvasSpanGraph'; import TickLabels from './TickLabels'; -import ViewingLayer, { ViewingLayerProps, UnthemedViewingLayer } from './ViewingLayer'; -import SpanGraph, { SpanGraphProps } from './index'; +import SpanGraph, { SpanGraphProps, TIMELINE_TICK_INTERVAL } from './index'; describe('', () => { polyfillAnimationFrame(window); - const trace = transformTraceData(traceGenerator.trace({})); + const trace = transformTraceData(traceGenerator.trace({}))!; const props = { trace, updateViewRangeTime: () => {}, @@ -39,52 +37,35 @@ describe('', () => { }, }; - let wrapper: ShallowWrapper; - beforeEach(() => { - wrapper = shallow(); + render(); }); - it('renders a ', () => { - expect(wrapper.find(CanvasSpanGraph).length).toBe(1); + it('renders ', () => { + const canvasSpanGraphComponent = screen.getByTestId('CanvasSpanGraph'); + expect(canvasSpanGraphComponent).toBeTruthy(); }); - it('renders a ', () => { - expect(wrapper.find(TickLabels).length).toBe(1); + it('renders ', () => { + const tickLabelsComponent = screen.getByTestId('TickLabels'); + expect(tickLabelsComponent).toBeTruthy(); }); - it('returns a
if a trace is not provided', () => { - wrapper = shallow(); - expect(wrapper.matchesElement(
)).toBeTruthy(); + it('returns an empty div if a trace is not provided', () => { + const { container } = render(); + expect(container.firstChild).toBeEmptyDOMElement(); }); - it('passes the number of ticks to render to components', () => { - const tickHeader = wrapper.find(TickLabels); - const viewingLayer = wrapper.find(ViewingLayer); - expect(tickHeader.prop('numTicks')).toBeGreaterThan(1); - expect(viewingLayer.prop('numTicks')).toBeGreaterThan(1); - expect(tickHeader.prop('numTicks')).toBe(viewingLayer.prop('numTicks')); + it('renders with the correct numnber of ticks', async () => { + const tickLabelsDiv = screen.getByTestId('TickLabels'); + expect(getAllByTestId(tickLabelsDiv, 'tick').length).toBe(TIMELINE_TICK_INTERVAL + 1); }); - it('passes items to CanvasSpanGraph', () => { - const canvasGraph = wrapper.find(CanvasSpanGraph).first(); - const items = trace?.spans.map((span) => ({ - valueOffset: span.relativeStartTime, - valueWidth: span.duration, - serviceName: span.process.serviceName, - })); - expect(canvasGraph.prop('items')).toEqual(items); - }); - - it('does not regenerate CanvasSpanGraph without new trace', () => { - const canvasGraph = wrapper.find(CanvasSpanGraph).first(); - const items = canvasGraph.prop('items'); - - wrapper.instance().forceUpdate(); - - const newCanvasGraph = wrapper.find(CanvasSpanGraph).first(); - const newItems = newCanvasGraph.prop('items'); - - expect(newItems).toBe(items); + it('renders with the correct value next to each tick', () => { + render(); + expect(screen.getByText(/0.2/)).toBeTruthy(); + expect(screen.getByText(/0.4/)).toBeTruthy(); + expect(screen.getByText(/0.6/)).toBeTruthy(); + expect(screen.getByText(/0.8/)).toBeTruthy(); }); }); diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx index 8133aaaa8e9..3a1a0275e72 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx @@ -25,7 +25,7 @@ import TickLabels from './TickLabels'; import ViewingLayer from './ViewingLayer'; const DEFAULT_HEIGHT = 60; -const TIMELINE_TICK_INTERVAL = 4; +export const TIMELINE_TICK_INTERVAL = 4; export type SpanGraphProps = { height?: number; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.tsx similarity index 100% rename from packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js rename to packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.tsx diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.tsx similarity index 76% rename from packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js rename to packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.tsx index 4aac66b8cd6..02b836d2710 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.tsx @@ -13,14 +13,15 @@ // limitations under the License. import cx from 'classnames'; -import { mount } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; +import DraggableManager, { DraggingUpdate } from 'src/utils/DraggableManager'; -import TimelineColumnResizer, { getStyles } from './TimelineColumnResizer'; +import TimelineColumnResizer, { getStyles, TimelineColumnResizerProps } from './TimelineColumnResizer'; describe('', () => { - let wrapper; - let instance; + let wrapper: ReactWrapper; + let instance: TimelineColumnResizer; const props = { min: 0.1, @@ -31,7 +32,7 @@ describe('', () => { beforeEach(() => { props.onChange.mockReset(); - wrapper = mount(); + wrapper = mount(); instance = wrapper.instance(); }); @@ -57,7 +58,7 @@ describe('', () => { it('returns the draggable bounds via _getDraggingBounds()', () => { const left = 10; const width = 100; - instance._rootElm.getBoundingClientRect = () => ({ left, width }); + instance._rootElm!.getBoundingClientRect = () => ({ left, width } as DOMRect); expect(instance._getDraggingBounds()).toEqual({ width, clientXLeft: left, @@ -69,16 +70,16 @@ describe('', () => { it('handles drag start', () => { const value = Math.random(); expect(wrapper.state('dragPosition')).toBe(null); - instance._handleDragUpdate({ value }); + instance._handleDragUpdate({ value } as DraggingUpdate); expect(wrapper.state('dragPosition')).toBe(value); }); it('handles drag end', () => { - const manager = { resetBounds: jest.fn() }; + const manager = { resetBounds: jest.fn() } as unknown as DraggableManager; const value = Math.random(); wrapper.setState({ dragPosition: 2 * value }); - instance._handleDragEnd({ manager, value }); - expect(manager.resetBounds.mock.calls).toEqual([[]]); + instance._handleDragEnd({ manager, value } as DraggingUpdate); + expect(jest.mocked(manager.resetBounds).mock.calls).toEqual([[]]); expect(wrapper.state('dragPosition')).toBe(null); expect(props.onChange.mock.calls).toEqual([[value]]); }); @@ -86,16 +87,16 @@ describe('', () => { it('does not render a dragging indicator when not dragging', () => { const styles = getStyles(); - expect(wrapper.find('[data-testid="TimelineColumnResizer--dragger"]').prop('style').right).toBe(undefined); + expect(wrapper.find('[data-testid="TimelineColumnResizer--dragger"]').prop('style')?.right).toBe(undefined); expect(wrapper.find('[data-testid="TimelineColumnResizer--dragger"]').prop('className')).toBe(styles.dragger); }); it('renders a dragging indicator when dragging', () => { instance._dragManager.isDragging = () => true; - instance._handleDragUpdate({ value: props.min }); + instance._handleDragUpdate({ value: props.min } as DraggingUpdate); instance.forceUpdate(); wrapper.update(); - expect(wrapper.find('[data-testid="TimelineColumnResizer--dragger"]').prop('style').right).toBeDefined(); + expect(wrapper.find('[data-testid="TimelineColumnResizer--dragger"]').prop('style')?.right).toBeDefined(); const styles = getStyles(); expect(wrapper.find('[data-testid="TimelineColumnResizer--dragger"]').prop('className')).toBe( diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx index 5fc65eaac4e..d8be774af0f 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx @@ -97,7 +97,7 @@ export const getStyles = stylesFactory(() => { }; }); -type TimelineColumnResizerProps = { +export type TimelineColumnResizerProps = { min: number; max: number; onChange: (newSize: number) => void; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.tsx similarity index 94% rename from packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js rename to packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.tsx index 3d5047c097c..a031fcff0c1 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.tsx @@ -15,7 +15,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import TimelineHeaderRow from './TimelineHeaderRow'; +import TimelineHeaderRow, { TimelineHeaderRowProps } from './TimelineHeaderRow'; const nameColumnWidth = 0.25; const setup = () => { @@ -35,7 +35,7 @@ const setup = () => { }, }; - return render(); + return render(); }; describe('TimelineHeaderRow', () => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx index d521890e880..12af287f3ed 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx @@ -56,7 +56,7 @@ const getStyles = (theme: GrafanaTheme2) => { }; }; -type TimelineHeaderRowProps = { +export type TimelineHeaderRowProps = { duration: number; nameColumnWidth: number; numTicks: number; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.tsx similarity index 69% rename from packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js rename to packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.tsx index 923384517ad..f90c2c1fd52 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.tsx @@ -13,18 +13,29 @@ // limitations under the License. import { cx } from '@emotion/css'; -import { mount } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; +import DraggableManager, { DraggingUpdate } from 'src/utils/DraggableManager'; -import TimelineViewingLayer, { getStyles } from './TimelineViewingLayer'; +import { ViewRangeTime } from '../types'; -function mapFromSubRange(viewStart, viewEnd, value) { +import TimelineViewingLayer, { getStyles, TimelineViewingLayerProps } from './TimelineViewingLayer'; + +function mapFromSubRange(viewStart: number, viewEnd: number, value: number) { return viewStart + value * (viewEnd - viewStart); } +// Remove the prefix generated by cx (css-), i.e. change the string from something like: +// 'css-7g92us-TimelineViewingLayerDragged-Timeline...' +// to: +// 'TimelineViewingLayerDragged-Timeline...' +function removeCxPrefix(cxString: string) { + return cxString.split('-').slice(2).join('-'); +} + describe('', () => { - let wrapper; - let instance; + let wrapper: ReactWrapper; + let instance: TimelineViewingLayer; const viewStart = 0.25; const viewEnd = 0.9; @@ -33,14 +44,14 @@ describe('', () => { updateNextViewRangeTime: jest.fn(), updateViewRangeTime: jest.fn(), viewRangeTime: { - current: [viewStart, viewEnd], + current: [viewStart, viewEnd] as [number, number], }, }; beforeEach(() => { props.updateNextViewRangeTime.mockReset(); props.updateViewRangeTime.mockReset(); - wrapper = mount(); + wrapper = mount(); instance = wrapper.instance(); }); @@ -76,19 +87,19 @@ describe('', () => { it('returns the dragging bounds from _getDraggingBounds()', () => { const left = 10; const width = 100; - instance._root.getBoundingClientRect = () => ({ left, width }); + instance._root!.getBoundingClientRect = () => ({ left, width } as DOMRect); expect(instance._getDraggingBounds()).toEqual({ width, clientXLeft: left }); }); it('updates viewRange.time.cursor via _draggerReframe._onMouseMove', () => { const value = 0.5; const cursor = mapFromSubRange(viewStart, viewEnd, value); - instance._draggerReframe._onMouseMove({ value }); + instance._draggerReframe._onMouseMove!({ value } as DraggingUpdate); expect(props.updateNextViewRangeTime.mock.calls).toEqual([[{ cursor }]]); }); it('resets viewRange.time.cursor via _draggerReframe._onMouseLeave', () => { - instance._draggerReframe._onMouseLeave(); + instance._draggerReframe._onMouseLeave!(null as unknown as DraggingUpdate); expect(props.updateNextViewRangeTime.mock.calls).toEqual([[{ cursor: undefined }]]); }); @@ -96,33 +107,33 @@ describe('', () => { const value = 0.5; const shift = mapFromSubRange(viewStart, viewEnd, value); const update = { reframe: { shift, anchor: shift } }; - instance._draggerReframe._onDragStart({ value }); + instance._draggerReframe._onDragStart!({ value } as DraggingUpdate); expect(props.updateNextViewRangeTime.mock.calls).toEqual([[update]]); }); it('handles drag move via _draggerReframe._onDragMove', () => { const anchor = 0.25; - const viewRangeTime = { ...props.viewRangeTime, reframe: { anchor, shift: Math.random() } }; + const viewRangeTime = { ...props.viewRangeTime, reframe: { anchor, shift: Math.random() } } as ViewRangeTime; const value = 0.5; const shift = mapFromSubRange(viewStart, viewEnd, value); // make sure `anchor` is already present on the props wrapper.setProps({ viewRangeTime }); - expect(wrapper.prop('viewRangeTime').reframe.anchor).toBe(anchor); + expect(wrapper.prop('viewRangeTime').reframe?.anchor).toBe(anchor); // the next update should integrate `value` and use the existing anchor - instance._draggerReframe._onDragStart({ value }); + instance._draggerReframe._onDragStart!({ value } as DraggingUpdate); const update = { reframe: { anchor, shift } }; expect(props.updateNextViewRangeTime.mock.calls).toEqual([[update]]); }); it('handles drag end via _draggerReframe._onDragEnd', () => { - const manager = { resetBounds: jest.fn() }; + const manager = { resetBounds: jest.fn() } as unknown as DraggableManager; const value = 0.5; const shift = mapFromSubRange(viewStart, viewEnd, value); const anchor = 0.25; - const viewRangeTime = { ...props.viewRangeTime, reframe: { anchor, shift: Math.random() } }; + const viewRangeTime = { ...props.viewRangeTime, reframe: { anchor, shift: Math.random() } } as ViewRangeTime; wrapper.setProps({ viewRangeTime }); - instance._draggerReframe._onDragEnd({ manager, value }); - expect(manager.resetBounds.mock.calls).toEqual([[]]); + instance._draggerReframe._onDragEnd!({ manager, value } as DraggingUpdate); + expect(jest.mocked(manager.resetBounds).mock.calls).toEqual([[]]); expect(props.updateViewRangeTime.mock.calls).toEqual([[anchor, shift, 'timeline-header']]); }); }); @@ -139,7 +150,12 @@ describe('', () => { // cursor is rendered when solo expect(wrapper.find('[data-testid="TimelineViewingLayer--cursorGuide"]').length).toBe(1); // cursor is skipped when shiftStart, shiftEnd, or reframe are present - let viewRangeTime = { ...baseViewRangeTime, shiftStart: cursor }; + let viewRangeTime: ViewRangeTime = { + ...baseViewRangeTime, + shiftStart: cursor, + shiftEnd: cursor, + reframe: { anchor: cursor, shift: cursor }, + }; wrapper.setProps({ viewRangeTime }); expect(wrapper.find('[data-testid="TimelineViewingLayer--cursorGuide"]').length).toBe(0); viewRangeTime = { ...baseViewRangeTime, shiftEnd: cursor }; @@ -157,9 +173,16 @@ describe('', () => { expect( wrapper .find('[data-testid="Dragged"]') - .prop('className') + .prop('className')! .indexOf( - cx(styles.dragged, styles.draggedDraggingLeft, styles.draggedDraggingRight, styles.draggedReframeDrag) + removeCxPrefix( + cx( + styles.TimelineViewingLayerDragged, + styles.TimelineViewingLayerDraggedDraggingLeft, + styles.TimelineViewingLayerDraggedDraggingRight, + styles.TimelineViewingLayerDraggedReframeDrag + ) + ) ) >= 0 ).toBe(true); }); @@ -171,9 +194,16 @@ describe('', () => { expect( wrapper .find('[data-testid="Dragged"]') - .prop('className') + .prop('className')! .indexOf( - cx(styles.dragged, styles.draggedDraggingLeft, styles.draggedDraggingRight, styles.draggedShiftDrag) + removeCxPrefix( + cx( + styles.TimelineViewingLayerDragged, + styles.TimelineViewingLayerDraggedDraggingLeft, + styles.TimelineViewingLayerDraggedDraggingRight, + styles.TimelineViewingLayerDraggedShiftDrag + ) + ) ) >= 0 ).toBe(true); }); @@ -181,13 +211,20 @@ describe('', () => { it('renders the shiftEnd dragging', () => { const viewRangeTime = { ...props.viewRangeTime, shiftEnd: viewStart }; wrapper.setProps({ viewRangeTime }); - // expect(wrapper.find('.isDraggingLeft.isShiftDrag').length).toBe(1); const styles = getStyles(); expect( wrapper .find('[data-testid="Dragged"]') - .prop('className') - .indexOf(cx(styles.dragged, styles.draggedDraggingLeft, styles.draggedShiftDrag)) >= 0 + .prop('className')! + .indexOf( + removeCxPrefix( + cx( + styles.TimelineViewingLayerDragged, + styles.TimelineViewingLayerDraggedDraggingLeft, + styles.TimelineViewingLayerDraggedShiftDrag + ) + ) + ) >= 0 ).toBe(true); }); }); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx index e414f4e54f1..adfb8624894 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx @@ -79,7 +79,7 @@ export const getStyles = stylesFactory(() => { }; }); -type TimelineViewingLayerProps = { +export type TimelineViewingLayerProps = { /** * `boundsInvalidator` is an arbitrary prop that lets the component know the * bounds for dragging need to be recalculated. In practice, the name column diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 603f158611a..84bb08d7fe4 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "net/http" - "net/url" "golang.org/x/oauth2" @@ -258,7 +257,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { hs.HooksService.RunLoginHook(&loginInfo, ctx) metrics.MApiLoginOAuth.Inc() - if redirectTo, err := url.QueryUnescape(ctx.GetCookie("redirect_to")); err == nil && len(redirectTo) > 0 { + if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 { if err := hs.ValidateRedirectTo(redirectTo); err == nil { cookies.DeleteCookie(ctx.Resp, "redirect_to", hs.CookieOptionsFromCfg) ctx.Redirect(redirectTo) diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 67628695fc4..e66574085a9 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -271,7 +271,7 @@ func (hs *HTTPServer) GetPluginMarkdown(c *models.ReqContext) response.Response // fallback try readme if len(content) == 0 { - content, err = hs.pluginMarkdown(c.Req.Context(), pluginID, "help") + content, err = hs.pluginMarkdown(c.Req.Context(), pluginID, "readme") if err != nil { return response.Error(501, "Could not get markdown file", err) } diff --git a/pkg/codegen/util_go.go b/pkg/codegen/util_go.go index fef81091403..cdd464e2914 100644 --- a/pkg/codegen/util_go.go +++ b/pkg/codegen/util_go.go @@ -220,7 +220,7 @@ func DecoderCompactor() dstutil.ApplyFunc { c.Delete() } case *dst.Field: - if id, is := x.Type.(*dst.Ident); is { + if id, is := ddepoint(x.Type).(*dst.Ident); is { if expr, has := replace[id.Name]; has { x.Type = expr } diff --git a/pkg/kinds/team/team_types_gen.go b/pkg/kinds/team/team_types_gen.go index 223a4a91247..c38820d7667 100644 --- a/pkg/kinds/team/team_types_gen.go +++ b/pkg/kinds/team/team_types_gen.go @@ -25,7 +25,7 @@ type Permission int // Team defines model for team. type Team struct { // AccessControl metadata associated with a given resource. - AccessControl map[string]bool `json:"accessControl"` + AccessControl map[string]bool `json:"accessControl,omitempty"` // AvatarUrl is the team's avatar URL. AvatarUrl *string `json:"avatarUrl,omitempty"` diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index 8f81c437c64..6d5f1bb4687 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -2,6 +2,7 @@ package authn import ( "context" + "fmt" "net/http" "strconv" "strings" @@ -16,6 +17,7 @@ import ( const ( ClientAPIKey = "auth.client.api-key" // #nosec G101 ClientAnonymous = "auth.client.anonymous" + ClientBasic = "auth.client.basic" ) type ClientParams struct { @@ -48,8 +50,9 @@ type Request struct { } const ( - APIKeyIDPrefix = "api-key:" - ServiceAccountIDPrefix = "service-account:" + NamespaceUser = "user" + NamespaceAPIKey = "api-key" + NamespaceServiceAccount = "service-account" ) type Identity struct { @@ -105,6 +108,11 @@ func (i *Identity) NamespacedID() (string, int64) { } func (i *Identity) SignedInUser() *user.SignedInUser { + var isGrafanaAdmin bool + if i.IsGrafanaAdmin != nil { + isGrafanaAdmin = *i.IsGrafanaAdmin + } + u := &user.SignedInUser{ UserID: 0, OrgID: i.OrgID, @@ -116,7 +124,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser { Name: i.Name, Email: i.Email, OrgCount: i.OrgCount, - IsGrafanaAdmin: *i.IsGrafanaAdmin, + IsGrafanaAdmin: isGrafanaAdmin, IsAnonymous: i.IsAnonymous(), IsDisabled: i.IsDisabled, HelpFlags1: i.HelpFlags1, @@ -124,19 +132,21 @@ func (i *Identity) SignedInUser() *user.SignedInUser { Teams: i.Teams, } - // For now, we need to set different fields of the signed-in user based on the identity "type" - if strings.HasPrefix(i.ID, APIKeyIDPrefix) { - id, _ := strconv.ParseInt(strings.TrimPrefix(i.ID, APIKeyIDPrefix), 10, 64) + namespace, id := i.NamespacedID() + if namespace == NamespaceAPIKey { u.ApiKeyID = id - } else if strings.HasPrefix(i.ID, ServiceAccountIDPrefix) { - id, _ := strconv.ParseInt(strings.TrimPrefix(i.ID, ServiceAccountIDPrefix), 10, 64) + } else { u.UserID = id - u.IsServiceAccount = true + u.IsServiceAccount = namespace == NamespaceServiceAccount } return u } +func NamespacedID(namespace string, id int64) string { + return fmt.Sprintf("%s:%d", namespace, id) +} + func IdentityFromSignedInUser(id string, usr *user.SignedInUser) *Identity { return &Identity{ ID: id, diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 602db60823e..bf58c809a2e 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -7,11 +7,15 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/authn" sync "github.com/grafana/grafana/pkg/services/authn/authnimpl/usersync" "github.com/grafana/grafana/pkg/services/authn/clients" + "github.com/grafana/grafana/pkg/services/login" + "github.com/grafana/grafana/pkg/services/loginattempt" "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "go.opentelemetry.io/otel/attribute" @@ -20,7 +24,11 @@ import ( // make sure service implements authn.Service interface var _ authn.Service = new(Service) -func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, orgService org.Service, apikeyService apikey.Service, userService user.Service) *Service { +func ProvideService( + cfg *setting.Cfg, tracer tracing.Tracer, orgService org.Service, accessControlService accesscontrol.Service, + apikeyService apikey.Service, userService user.Service, loginAttempts loginattempt.Service, quotaService quota.Service, + authInfoService login.AuthInfoService, +) *Service { s := &Service{ log: log.New("authn.service"), cfg: cfg, @@ -35,9 +43,14 @@ func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, orgService org.Serv s.clients[authn.ClientAnonymous] = clients.ProvideAnonymous(cfg, orgService) } + // FIXME (kalleep): handle cfg.DisableLogin as well? + if s.cfg.BasicAuthEnabled && !s.cfg.DisableLogin { + s.clients[authn.ClientBasic] = clients.ProvideBasic(userService, loginAttempts) + } + // FIXME (jguer): move to User package - userSyncService := &sync.UserSync{} - orgUserSyncService := &sync.OrgSync{} + userSyncService := sync.ProvideUserSync(userService, authInfoService, quotaService) + orgUserSyncService := sync.ProvideOrgSync(userService, orgService, accessControlService) s.RegisterPostAuthHook(userSyncService.SyncUser) s.RegisterPostAuthHook(orgUserSyncService.SyncOrgUser) diff --git a/pkg/services/authn/authnimpl/usersync/orgsync.go b/pkg/services/authn/authnimpl/usersync/orgsync.go index fa2e4fa677e..8aba2b781c5 100644 --- a/pkg/services/authn/authnimpl/usersync/orgsync.go +++ b/pkg/services/authn/authnimpl/usersync/orgsync.go @@ -15,6 +15,10 @@ import ( "github.com/grafana/grafana/pkg/services/user" ) +func ProvideOrgSync(userService user.Service, orgService org.Service, accessControl accesscontrol.Service) *OrgSync { + return &OrgSync{userService, orgService, accessControl, log.New("org.sync")} +} + type OrgSync struct { userService user.Service orgService org.Service diff --git a/pkg/services/authn/authnimpl/usersync/usersync.go b/pkg/services/authn/authnimpl/usersync/usersync.go index 69f4d12995a..33db88f8645 100644 --- a/pkg/services/authn/authnimpl/usersync/usersync.go +++ b/pkg/services/authn/authnimpl/usersync/usersync.go @@ -13,6 +13,10 @@ import ( "github.com/grafana/grafana/pkg/services/user" ) +func ProvideUserSync(userService user.Service, authInfoService login.AuthInfoService, quotaService quota.Service) *UserSync { + return &UserSync{userService, authInfoService, quotaService, log.New("user.sync")} +} + type UserSync struct { userService user.Service authInfoService login.AuthInfoService diff --git a/pkg/services/authn/clients/api_key.go b/pkg/services/authn/clients/api_key.go index 9eeddba0d68..73a8213613e 100644 --- a/pkg/services/authn/clients/api_key.go +++ b/pkg/services/authn/clients/api_key.go @@ -3,7 +3,6 @@ package clients import ( "context" "errors" - "fmt" "strings" "time" @@ -18,11 +17,6 @@ import ( "github.com/grafana/grafana/pkg/util/errutil" ) -const ( - basicPrefix = "Basic " - bearerPrefix = "Bearer " -) - var ( ErrAPIKeyInvalid = errutil.NewBase(errutil.StatusUnauthorized, "api-key.invalid", errutil.WithPublicMessage("Invalid API key")) ErrAPIKeyExpired = errutil.NewBase(errutil.StatusUnauthorized, "api-key.expired", errutil.WithPublicMessage("Expired API key")) @@ -46,14 +40,6 @@ type APIKey struct { apiKeyService apikey.Service } -func (s *APIKey) ClientParams() *authn.ClientParams { - return &authn.ClientParams{ - SyncUser: false, - AllowSignUp: false, - EnableDisabledUsers: false, - } -} - func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r)) if err != nil { @@ -85,7 +71,7 @@ func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide // if the api key don't belong to a service account construct the identity and return it if apiKey.ServiceAccountId == nil || *apiKey.ServiceAccountId < 1 { return &authn.Identity{ - ID: fmt.Sprintf("%s%d", authn.APIKeyIDPrefix, apiKey.Id), + ID: authn.NamespacedID(authn.NamespaceAPIKey, apiKey.Id), OrgID: apiKey.OrgId, OrgRoles: map[int64]org.RoleType{apiKey.OrgId: apiKey.Role}, }, nil @@ -104,7 +90,7 @@ func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide return nil, ErrServiceAccountDisabled.Errorf("Disabled service account") } - return authn.IdentityFromSignedInUser(fmt.Sprintf("%s%d", authn.ServiceAccountIDPrefix, *apiKey.ServiceAccountId), usr), nil + return authn.IdentityFromSignedInUser(authn.NamespacedID(authn.NamespaceServiceAccount, usr.UserID), usr), nil } func (s *APIKey) getAPIKey(ctx context.Context, token string) (*apikey.APIKey, error) { @@ -159,6 +145,10 @@ func (s *APIKey) getFromTokenLegacy(ctx context.Context, token string) (*apikey. return keyQuery.Result, nil } +func (s *APIKey) ClientParams() *authn.ClientParams { + return &authn.ClientParams{} +} + func (s *APIKey) Test(ctx context.Context, r *authn.Request) bool { return looksLikeApiKey(getTokenFromRequest(r)) } diff --git a/pkg/services/authn/clients/basic.go b/pkg/services/authn/clients/basic.go new file mode 100644 index 00000000000..fea5c39e911 --- /dev/null +++ b/pkg/services/authn/clients/basic.go @@ -0,0 +1,105 @@ +package clients + +import ( + "context" + "crypto/subtle" + "strings" + + "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/loginattempt" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/util/errutil" +) + +var ( + ErrBasicAuthCredentials = errutil.NewBase(errutil.StatusUnauthorized, "basic-auth.invalid-credentials", errutil.WithPublicMessage("Invalid username or password")) + ErrDecodingBasicAuthHeader = errutil.NewBase(errutil.StatusBadRequest, "basic-auth.invalid-header", errutil.WithPublicMessage("Invalid Basic Auth Header")) +) + +var _ authn.Client = new(Basic) + +func ProvideBasic(userService user.Service, loginAttempts loginattempt.Service) *Basic { + return &Basic{userService, loginAttempts} +} + +type Basic struct { + userService user.Service + loginAttempts loginattempt.Service +} + +func (c *Basic) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { + username, password, err := util.DecodeBasicAuthHeader(getBasicAuthHeaderFromRequest(r)) + if err != nil { + return nil, ErrDecodingBasicAuthHeader.Errorf("failed to decode basic auth header: %w", err) + } + + ok, err := c.loginAttempts.Validate(ctx, username) + if err != nil { + return nil, err + } + if !ok { + return nil, ErrBasicAuthCredentials.Errorf("too many consecutive incorrect login attempts for user - login for user temporarily blocked") + } + + if len(password) == 0 { + return nil, ErrBasicAuthCredentials.Errorf("no password provided") + } + + // FIXME (kalleep): decide if we should handle ldap here + usr, err := c.userService.GetByLogin(ctx, &user.GetUserByLoginQuery{LoginOrEmail: username}) + if err != nil { + return nil, ErrBasicAuthCredentials.Errorf("failed to fetch user: %w", err) + } + + if ok := comparePassword(password, usr.Salt, usr.Password); !ok { + _ = c.loginAttempts.Add(ctx, username, r.HTTPRequest.RemoteAddr) + return nil, ErrBasicAuthCredentials.Errorf("incorrect password provided") + } + + signedInUser, err := c.userService.GetSignedInUserWithCacheCtx(ctx, &user.GetSignedInUserQuery{ + UserID: usr.ID, + OrgID: r.OrgID, + }) + + if err != nil { + return nil, ErrBasicAuthCredentials.Errorf("failed to fetch user: %w", err) + } + + return authn.IdentityFromSignedInUser(authn.NamespacedID(authn.NamespaceUser, signedInUser.UserID), signedInUser), nil +} + +func (c *Basic) ClientParams() *authn.ClientParams { + return &authn.ClientParams{} +} + +func (c *Basic) Test(ctx context.Context, r *authn.Request) bool { + return looksLikeBasicAuthRequest(r) +} + +func looksLikeBasicAuthRequest(r *authn.Request) bool { + return getBasicAuthHeaderFromRequest(r) != "" +} + +func getBasicAuthHeaderFromRequest(r *authn.Request) string { + if r.HTTPRequest == nil { + return "" + } + + header := r.HTTPRequest.Header.Get(authorizationHeaderName) + if header == "" { + return "" + } + + if !strings.HasPrefix(header, basicPrefix) { + return "" + } + + return header +} + +func comparePassword(password, salt, hash string) bool { + // It is ok to ignore the error here because util.EncodePassword can never return a error + hashedPassword, _ := util.EncodePassword(password, salt) + return subtle.ConstantTimeCompare([]byte(hashedPassword), []byte(hash)) == 1 +} diff --git a/pkg/services/authn/clients/basic_test.go b/pkg/services/authn/clients/basic_test.go new file mode 100644 index 00000000000..727b8a66c9f --- /dev/null +++ b/pkg/services/authn/clients/basic_test.go @@ -0,0 +1,121 @@ +package clients + +import ( + "context" + "net/http" + "testing" + + "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/loginattempt/loginattempttest" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/user/usertest" + "github.com/grafana/grafana/pkg/util" + "github.com/stretchr/testify/assert" +) + +func TestBasic_Authenticate(t *testing.T) { + type TestCase struct { + desc string + req *authn.Request + blockLogin bool + expectedErr error + expectedSignedInUser *user.SignedInUser + expectedIdentity *authn.Identity + } + + tests := []TestCase{ + { + desc: "should successfully authenticate user with correct password", + req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "password")}}}}, + expectedErr: nil, + expectedSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: "Viewer"}, + expectedIdentity: &authn.Identity{ID: "user:1", OrgID: 1, OrgRoles: map[int64]org.RoleType{1: "Viewer"}, IsGrafanaAdmin: boolPtr(false)}, + }, + { + desc: "should fail for incorrect password", + req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "wrong")}}}}, + expectedErr: ErrBasicAuthCredentials, + }, + { + desc: "should fail for empty password", + req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "")}}}}, + expectedErr: ErrBasicAuthCredentials, + }, + { + desc: "should if login is blocked by to many attempts", + req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "")}}}}, + blockLogin: true, + expectedErr: ErrBasicAuthCredentials, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + hashed, _ := util.EncodePassword("password", "salt") + c := ProvideBasic(&usertest.FakeUserService{ + ExpectedUser: &user.User{ + Password: hashed, + Salt: "salt", + }, + ExpectedSignedInUser: tt.expectedSignedInUser, + }, loginattempttest.FakeLoginAttemptService{ + ExpectedValid: !tt.blockLogin, + }) + + identity, err := c.Authenticate(context.Background(), tt.req) + if tt.expectedErr != nil { + assert.ErrorIs(t, err, tt.expectedErr) + assert.Nil(t, identity) + } else { + assert.NoError(t, err) + assert.EqualValues(t, *tt.expectedIdentity, *identity) + } + }) + } +} + +func TestBasic_Test(t *testing.T) { + type TestCase struct { + desc string + req *authn.Request + expected bool + } + + tests := []TestCase{ + { + desc: "should succeed when authorization header is set with basic prefix", + req: &authn.Request{ + HTTPRequest: &http.Request{ + Header: map[string][]string{ + authorizationHeaderName: {encodeBasicAuth("user", "password")}, + }, + }, + }, + expected: true, + }, + { + desc: "should fail when no http request is passed", + req: &authn.Request{}, + }, + { + desc: "should fail when no http authorization header is set in http request", + req: &authn.Request{ + HTTPRequest: &http.Request{Header: map[string][]string{}}, + }, + }, + { + desc: "should fail when authorization header is set but without basic prefix", + req: &authn.Request{ + HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {"something"}}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + c := ProvideBasic(usertest.NewUserServiceFake(), loginattempttest.FakeLoginAttemptService{}) + assert.Equal(t, tt.expected, c.Test(context.Background(), tt.req)) + }) + } +} diff --git a/pkg/services/authn/clients/constants.go b/pkg/services/authn/clients/constants.go new file mode 100644 index 00000000000..1cddacfdfaf --- /dev/null +++ b/pkg/services/authn/clients/constants.go @@ -0,0 +1,7 @@ +package clients + +const ( + basicPrefix = "Basic " + bearerPrefix = "Bearer " + authorizationHeaderName = "Authorization" +) diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index 36df35cfa38..7daa0605eed 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -396,6 +396,26 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo } func (h *ContextHandler) initContextWithBasicAuth(reqContext *models.ReqContext, orgID int64) bool { + if h.features.IsEnabled(featuremgmt.FlagAuthnService) { + identity, ok, err := h.authnService.Authenticate(reqContext.Req.Context(), authn.ClientBasic, &authn.Request{HTTPRequest: reqContext.Req}) + if !ok { + return false + } + + // include auth header in context + ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization") + *reqContext.Req = *reqContext.Req.WithContext(ctx) + + if err != nil { + writeErr(reqContext, err) + return true + } + + reqContext.IsSignedIn = true + reqContext.SignedInUser = identity.SignedInUser() + return true + } + if !h.Cfg.BasicAuthEnabled { return false } diff --git a/public/app/core/components/NavBar/NavBar.tsx b/public/app/core/components/NavBar/NavBar.tsx index 9fc506b3ef0..d0a5e58ac86 100644 --- a/public/app/core/components/NavBar/NavBar.tsx +++ b/public/app/core/components/NavBar/NavBar.tsx @@ -7,7 +7,7 @@ import { useLocation } from 'react-router-dom'; import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data'; import { config, locationSearchToObject, locationService, reportInteraction } from '@grafana/runtime'; -import { Icon, useTheme2, CustomScrollbar } from '@grafana/ui'; +import { useTheme2, CustomScrollbar, IconButton } from '@grafana/ui'; import { getKioskMode } from 'app/core/navigation/kiosk'; import { useSelector } from 'app/types'; @@ -90,8 +90,14 @@ export const NavBar = React.memo(() => { }} > -
setMenuOpen(!menuOpen)} key="hamburger"> - +
+ setMenuOpen(!menuOpen)} + />
({ }), mobileSidemenuLogo: css({ alignItems: 'center', - cursor: 'pointer', display: 'flex', flexDirection: 'row', justifyContent: 'space-between', diff --git a/public/app/core/components/PluginHelp/PluginHelp.tsx b/public/app/core/components/PluginHelp/PluginHelp.tsx index 7ead1046e22..4531abca50a 100644 --- a/public/app/core/components/PluginHelp/PluginHelp.tsx +++ b/public/app/core/components/PluginHelp/PluginHelp.tsx @@ -1,83 +1,32 @@ -import React, { PureComponent } from 'react'; +import React from 'react'; +import { useAsync } from 'react-use'; import { renderMarkdown } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; +import { LoadingPlaceholder } from '@grafana/ui'; interface Props { - plugin: { - name: string; - id: string; - }; - type: string; + pluginId: string; } -interface State { - isError: boolean; - isLoading: boolean; - help: string; -} +export function PluginHelp({ pluginId }: Props) { + const { value, loading, error } = useAsync(async () => { + return getBackendSrv().get(`/api/plugins/${pluginId}/markdown/query_help`); + }, []); -export class PluginHelp extends PureComponent { - state = { - isError: false, - isLoading: false, - help: '', - }; + const renderedMarkdown = renderMarkdown(value); - componentDidMount(): void { - this.loadHelp(); + if (loading) { + return ; } - constructPlaceholderInfo() { - return 'No plugin help or readme markdown file was found'; + if (error) { + return

An error occurred when loading help.

; } - loadHelp = () => { - const { plugin, type } = this.props; - this.setState({ isLoading: true }); - - getBackendSrv() - .get(`/api/plugins/${plugin.id}/markdown/${type}`) - .then((response: string) => { - const helpHtml = renderMarkdown(response); - - if (response === '' && type === 'help') { - this.setState({ - isError: false, - isLoading: false, - help: this.constructPlaceholderInfo(), - }); - } else { - this.setState({ - isError: false, - isLoading: false, - help: helpHtml, - }); - } - }) - .catch(() => { - this.setState({ - isError: true, - isLoading: false, - }); - }); - }; - - render() { - const { type } = this.props; - const { isError, isLoading, help } = this.state; - - if (isLoading) { - return

Loading help...

; - } - - if (isError) { - return

'Error occurred when loading help'

; - } - - if (type === 'panel_help' && help === '') { - } - - return
; + if (value === '') { + return

No query help could be found.

; } + + return
; } diff --git a/public/app/features/alerting/unified/RuleViewer.test.tsx b/public/app/features/alerting/unified/RuleViewer.test.tsx index d5d4d328cfb..a131e4a7d26 100644 --- a/public/app/features/alerting/unified/RuleViewer.test.tsx +++ b/public/app/features/alerting/unified/RuleViewer.test.tsx @@ -2,17 +2,33 @@ import { act, render, screen } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; +import { byRole } from 'testing-library-selector'; -import { DataSourceJsonData, PluginMeta } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { contextSrv } from 'app/core/services/context_srv'; import { configureStore } from 'app/store/configureStore'; +import { AccessControlAction } from 'app/types'; import { CombinedRule } from 'app/types/unified-alerting'; -import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto'; import { RuleViewer } from './RuleViewer'; import { useCombinedRule } from './hooks/useCombinedRule'; -import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; +import { useIsRuleEditable } from './hooks/useIsRuleEditable'; +import { getCloudRule, getGrafanaRule } from './mocks'; + +const mockGrafanaRule = getGrafanaRule({ name: 'Test alert' }); +const mockCloudRule = getCloudRule({ name: 'cloud test alert' }); +const mockRoute: GrafanaRouteComponentProps<{ id?: string; sourceName?: string }> = { + route: { + path: '/', + component: RuleViewer, + }, + queryParams: { returnTo: '/alerting/list' }, + match: { params: { id: 'test1', sourceName: 'grafana' }, isExact: false, url: 'asdf', path: '' }, + history: locationService.getHistory(), + location: { pathname: '', hash: '', search: '', state: '' }, + staticContext: {}, +}; jest.mock('./hooks/useCombinedRule'); jest.mock('@grafana/runtime', () => ({ @@ -40,6 +56,20 @@ const renderRuleViewer = () => { ); }); }; + +const ui = { + actionButtons: { + edit: byRole('link', { name: /edit/i }), + delete: byRole('button', { name: /delete/i }), + silence: byRole('link', { name: 'Silence' }), + }, +}; +jest.mock('./hooks/useIsRuleEditable'); + +const mocks = { + useIsRuleEditable: jest.mocked(useIsRuleEditable), +}; + describe('RuleViewer', () => { let mockCombinedRule: jest.MockedFn; @@ -59,6 +89,7 @@ describe('RuleViewer', () => { requestId: 'A', error: undefined, }); + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: false }); await renderRuleViewer(); expect(screen.getByText(/view rule/i)).toBeInTheDocument(); @@ -73,82 +104,142 @@ describe('RuleViewer', () => { requestId: 'A', error: undefined, }); + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: false }); await renderRuleViewer(); expect(screen.getByText(/view rule/i)).toBeInTheDocument(); expect(screen.getByText(/cloud test alert/i)).toBeInTheDocument(); }); }); -const mockGrafanaRule = { - name: 'Test alert', - query: 'up', - labels: {}, - annotations: {}, - group: { - name: 'Prom up alert', - rules: [], - }, - namespace: { - rulesSource: GRAFANA_RULES_SOURCE_NAME, - name: 'Alerts', - groups: [], - }, - rulerRule: { - for: '', - annotations: {}, - labels: {}, - grafana_alert: { - condition: 'B', - exec_err_state: GrafanaAlertStateDecision.Alerting, - namespace_id: 11, - namespace_uid: 'namespaceuid123', - no_data_state: GrafanaAlertStateDecision.NoData, - title: 'Test alert', - uid: 'asdf23', - data: [], - }, - }, -}; +describe('RuleDetails RBAC', () => { + describe('Grafana rules action buttons in details', () => { + let mockCombinedRule: jest.MockedFn; -const mockCloudRule = { - name: 'Cloud test alert', - labels: {}, - query: 'up == 0', - annotations: {}, - group: { - name: 'test', - rules: [], - }, - promRule: { - health: 'ok', - name: 'cloud up alert', - query: 'up == 0', - type: 'alerting', - }, - namespace: { - name: 'prom test alerts', - groups: [], - rulesSource: { - name: 'prom test', - type: 'prometheus', - uid: 'asdf23', - id: 1, - meta: {} as PluginMeta, - jsonData: {} as DataSourceJsonData, - access: 'proxy', - readOnly: false, - }, - }, -}; + beforeEach(() => { + mockCombinedRule = jest.mocked(useCombinedRule); + }); -const mockRoute: GrafanaRouteComponentProps<{ id?: string; sourceName?: string }> = { - route: { - path: '/', - component: RuleViewer, - }, - queryParams: { returnTo: '/alerting/list' }, - match: { params: { id: 'test1', sourceName: 'grafana' }, isExact: false, url: 'asdf', path: '' }, - history: locationService.getHistory(), - location: { pathname: '', hash: '', search: '', state: '' }, - staticContext: {}, -}; + afterEach(() => { + mockCombinedRule.mockReset(); + }); + it('Should render Edit button for users with the update permission', async () => { + // Arrange + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true }); + mockCombinedRule.mockReturnValue({ + result: mockGrafanaRule as CombinedRule, + loading: false, + dispatched: true, + requestId: 'A', + error: undefined, + }); + + // Act + await renderRuleViewer(); + + // Assert + expect(ui.actionButtons.edit.get()).toBeInTheDocument(); + }); + + it('Should render Delete button for users with the delete permission', async () => { + // Arrange + mockCombinedRule.mockReturnValue({ + result: mockGrafanaRule as CombinedRule, + loading: false, + dispatched: true, + requestId: 'A', + error: undefined, + }); + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isRemovable: true }); + + // Act + await renderRuleViewer(); + + // Assert + expect(ui.actionButtons.delete.get()).toBeInTheDocument(); + }); + + it('Should not render Silence button for users wihout the instance create permission', async () => { + // Arrange + mockCombinedRule.mockReturnValue({ + result: mockGrafanaRule as CombinedRule, + loading: false, + dispatched: true, + requestId: 'A', + error: undefined, + }); + jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false); + + // Act + await renderRuleViewer(); + + // Assert + expect(ui.actionButtons.silence.query()).not.toBeInTheDocument(); + }); + + it('Should render Silence button for users with the instance create permissions', async () => { + // Arrange + mockCombinedRule.mockReturnValue({ + result: mockGrafanaRule as CombinedRule, + loading: false, + dispatched: true, + requestId: 'A', + error: undefined, + }); + jest + .spyOn(contextSrv, 'hasPermission') + .mockImplementation((action) => action === AccessControlAction.AlertingInstanceCreate); + + // Act + await renderRuleViewer(); + + // Assert + expect(ui.actionButtons.silence.query()).toBeInTheDocument(); + }); + }); + describe('Cloud rules action buttons', () => { + let mockCombinedRule: jest.MockedFn; + + beforeEach(() => { + mockCombinedRule = jest.mocked(useCombinedRule); + }); + + afterEach(() => { + mockCombinedRule.mockReset(); + }); + it('Should render edit button for users with the update permission', async () => { + // Arrange + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true }); + mockCombinedRule.mockReturnValue({ + result: mockCloudRule as CombinedRule, + loading: false, + dispatched: true, + requestId: 'A', + error: undefined, + }); + + // Act + await renderRuleViewer(); + + // Assert + expect(ui.actionButtons.edit.query()).toBeInTheDocument(); + }); + + it('Should render Delete button for users with the delete permission', async () => { + // Arrange + mockCombinedRule.mockReturnValue({ + result: mockCloudRule as CombinedRule, + loading: false, + dispatched: true, + requestId: 'A', + error: undefined, + }); + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isRemovable: true }); + + // Act + await renderRuleViewer(); + + // Assert + expect(ui.actionButtons.delete.query()).toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index 800f1db9a45..3b493ed5f45 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -170,7 +170,7 @@ export function RuleViewer({ match }: RuleViewerProps) { {rule.name} - +
diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx index f0267f90617..6bdc835d8ea 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx @@ -9,24 +9,57 @@ import { configureStore } from 'app/store/configureStore'; import { AccessControlAction } from 'app/types'; import { CombinedRule } from 'app/types/unified-alerting'; -import { mockCombinedRule } from '../../mocks'; +import { useIsRuleEditable } from '../../hooks/useIsRuleEditable'; +import { getCloudRule, getGrafanaRule } from '../../mocks'; import { RuleDetails } from './RuleDetails'; +jest.mock('../../hooks/useIsRuleEditable'); + +const mocks = { + useIsRuleEditable: jest.mocked(useIsRuleEditable), +}; + const ui = { actionButtons: { - edit: byRole('link', { name: 'Edit' }), - delete: byRole('button', { name: 'Delete' }), + edit: byRole('link', { name: /edit/i }), + delete: byRole('button', { name: /delete/i }), silence: byRole('link', { name: 'Silence' }), }, }; jest.spyOn(contextSrv, 'accessControlEnabled').mockReturnValue(true); +beforeEach(() => { + jest.clearAllMocks(); +}); + describe('RuleDetails RBAC', () => { describe('Grafana rules action buttons in details', () => { const grafanaRule = getGrafanaRule({ name: 'Grafana' }); + it('Should not render Edit button for users with the update permission', () => { + // Arrange + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true }); + + // Act + renderRuleDetails(grafanaRule); + + // Assert + expect(ui.actionButtons.edit.query()).not.toBeInTheDocument(); + }); + + it('Should not render Delete button for users with the delete permission', () => { + // Arrange + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isRemovable: true }); + + // Act + renderRuleDetails(grafanaRule); + + // Assert + expect(ui.actionButtons.delete.query()).not.toBeInTheDocument(); + }); + it('Should not render Silence button for users wihout the instance create permission', () => { // Arrange jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false); @@ -51,6 +84,31 @@ describe('RuleDetails RBAC', () => { expect(ui.actionButtons.silence.query()).toBeInTheDocument(); }); }); + describe('Cloud rules action buttons', () => { + const cloudRule = getCloudRule({ name: 'Cloud' }); + + it('Should not render Edit button for users with the update permission', () => { + // Arrange + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true }); + + // Act + renderRuleDetails(cloudRule); + + // Assert + expect(ui.actionButtons.edit.query()).not.toBeInTheDocument(); + }); + + it('Should not render Delete button for users with the delete permission', () => { + // Arrange + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isRemovable: true }); + + // Act + renderRuleDetails(cloudRule); + + // Assert + expect(ui.actionButtons.delete.query()).not.toBeInTheDocument(); + }); + }); }); function renderRuleDetails(rule: CombinedRule) { @@ -64,14 +122,3 @@ function renderRuleDetails(rule: CombinedRule) { ); } - -function getGrafanaRule(override?: Partial) { - return mockCombinedRule({ - namespace: { - groups: [], - name: 'Grafana', - rulesSource: 'grafana', - }, - ...override, - }); -} diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index 6c102205073..5d078a0f6a6 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -34,7 +34,7 @@ export const RuleDetails: FC = ({ rule }) => { return (
- +
{} diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx index cd66f5547fd..9913bbbbbba 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx @@ -1,29 +1,41 @@ import { css } from '@emotion/css'; -import React, { FC, Fragment } from 'react'; +import React, { FC, Fragment, useState } from 'react'; +import { useLocation } from 'react-router-dom'; -import { GrafanaTheme2, textUtil } from '@grafana/data'; -import { Button, HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2, textUtil, urlUtil } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Button, ClipboardButton, ConfirmModal, HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui'; +import { useAppNotification } from 'app/core/copy/appNotification'; import { contextSrv } from 'app/core/services/context_srv'; -import { AccessControlAction } from 'app/types'; +import { AccessControlAction, useDispatch } from 'app/types'; import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; +import { useIsRuleEditable } from '../../hooks/useIsRuleEditable'; import { useStateHistoryModal } from '../../hooks/useStateHistoryModal'; +import { deleteRuleAction } from '../../state/actions'; import { getAlertmanagerByUid } from '../../utils/alertmanager'; import { Annotation } from '../../utils/constants'; -import { isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource'; +import { getRulesSourceName, isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource'; import { createExploreLink, makeRuleBasedSilenceLink } from '../../utils/misc'; +import * as ruleId from '../../utils/rule-id'; import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; interface Props { rule: CombinedRule; rulesSource: RulesSource; + isViewMode: boolean; } -export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { +export const RuleDetailsActionButtons: FC = ({ rule, rulesSource, isViewMode }) => { const style = useStyles2(getStyles); - const { group } = rule; + const { namespace, group, rulerRule } = rule; const alertId = isGrafanaRulerRule(rule.rulerRule) ? rule.rulerRule.grafana_alert.id ?? '' : ''; const { StateHistoryModal, showStateHistoryModal } = useStateHistoryModal(alertId); + const dispatch = useDispatch(); + const location = useLocation(); + const notifyApp = useAppNotification(); + + const [ruleToDelete, setRuleToDelete] = useState(); const alertmanagerSourceName = isGrafanaRulesSource(rulesSource) ? rulesSource @@ -32,9 +44,39 @@ export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { const hasExplorePermission = contextSrv.hasPermission(AccessControlAction.DataSourcesExplore); const buttons: JSX.Element[] = []; + const rightButtons: JSX.Element[] = []; + + const deleteRule = () => { + if (ruleToDelete && ruleToDelete.rulerRule) { + const identifier = ruleId.fromRulerRule( + getRulesSourceName(ruleToDelete.namespace.rulesSource), + ruleToDelete.namespace.name, + ruleToDelete.group.name, + ruleToDelete.rulerRule + ); + + dispatch(deleteRuleAction(identifier, { navigateTo: isViewMode ? '/alerting/list' : undefined })); + setRuleToDelete(undefined); + } + }; + const buildShareUrl = () => { + if (isCloudRulesSource(rulesSource)) { + const { appUrl, appSubUrl } = config; + const baseUrl = appSubUrl !== '' ? `${appUrl}${appSubUrl}/` : config.appUrl; + const ruleUrl = `${encodeURIComponent(rulesSource.name)}/${encodeURIComponent(rule.name)}`; + return `${baseUrl}alerting/${ruleUrl}/find`; + } + + return window.location.href.split('?')[0]; + }; const isFederated = isFederatedRuleGroup(group); + const rulesSourceName = getRulesSourceName(rulesSource); + const isProvisioned = isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); + const { isEditable, isRemovable } = useIsRuleEditable(rulesSourceName, rulerRule); + + const returnTo = location.pathname + location.search; // explore does not support grafana rule queries atm // neither do "federated rules" if (isCloudRulesSource(rulesSource) && hasExplorePermission && !isFederated) { @@ -128,14 +170,77 @@ export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { ); } - if (buttons.length) { - return ( -
- {buttons.length ? buttons :
} -
- ); + if (isViewMode) { + if (isEditable && rulerRule && !isFederated && !isProvisioned) { + const sourceName = getRulesSourceName(rulesSource); + const identifier = ruleId.fromRulerRule(sourceName, namespace.name, group.name, rulerRule); + + const editURL = urlUtil.renderUrl( + `${config.appSubUrl}/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`, + { + returnTo, + } + ); + rightButtons.push( + { + notifyApp.error('Error while copying URL', copiedText); + }} + className={style.button} + size="sm" + getText={buildShareUrl} + > + Copy link to rule + + ); + + rightButtons.push( + + Edit + + ); + } + + if (isRemovable && rulerRule && !isFederated && !isProvisioned) { + rightButtons.push( + + ); + } } + if (buttons.length || rightButtons.length) { + return ( + <> +
+ {buttons.length ? buttons :
} + {rightButtons.length ? rightButtons :
} +
+ {!!ruleToDelete && ( + setRuleToDelete(undefined)} + /> + )} + + ); + } return null; }; @@ -150,7 +255,6 @@ export const getStyles = (theme: GrafanaTheme2) => ({ `, button: css` height: 24px; - margin-top: ${theme.spacing(1)}; font-size: ${theme.typography.size.sm}; `, }); diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx index e53ad1f831c..c11484efd18 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx @@ -9,7 +9,7 @@ import { configureStore } from 'app/store/configureStore'; import { CombinedRule } from 'app/types/unified-alerting'; import { useIsRuleEditable } from '../../hooks/useIsRuleEditable'; -import { mockCombinedRule, mockDataSource, mockPromAlertingRule, mockRulerAlertingRule } from '../../mocks'; +import { getCloudRule, getGrafanaRule } from '../../mocks'; import { RulesTable } from './RulesTable'; @@ -41,30 +41,6 @@ function renderRulesTable(rule: CombinedRule) { ); } -function getGrafanaRule(override?: Partial) { - return mockCombinedRule({ - namespace: { - groups: [], - name: 'Grafana', - rulesSource: 'grafana', - }, - ...override, - }); -} - -function getCloudRule(override?: Partial) { - return mockCombinedRule({ - namespace: { - groups: [], - name: 'Cortex', - rulesSource: mockDataSource(), - }, - promRule: mockPromAlertingRule(), - rulerRule: mockRulerAlertingRule(), - ...override, - }); -} - describe('RulesTable RBAC', () => { describe('Grafana rules action buttons', () => { const grafanaRule = getGrafanaRule({ name: 'Grafana' }); diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 89b8742000c..e0f524eb91e 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -516,3 +516,26 @@ export function mockStore(recipe: (state: StoreState) => void) { return configureStore(produce(defaultState, recipe)); } + +export function getGrafanaRule(override?: Partial) { + return mockCombinedRule({ + namespace: { + groups: [], + name: 'Grafana', + rulesSource: 'grafana', + }, + ...override, + }); +} +export function getCloudRule(override?: Partial) { + return mockCombinedRule({ + namespace: { + groups: [], + name: 'Cortex', + rulesSource: mockDataSource(), + }, + promRule: mockPromAlertingRule(), + rulerRule: mockRulerAlertingRule(), + ...override, + }); +} diff --git a/public/app/features/connections/constants.ts b/public/app/features/connections/constants.ts index 29d9c00ab3b..03d3b306e86 100644 --- a/public/app/features/connections/constants.ts +++ b/public/app/features/connections/constants.ts @@ -15,5 +15,5 @@ export const ROUTES = { // Connect Data ConnectData: `/${ROUTE_BASE_ID}/connect-data`, - DataSourcesDetails: `/${ROUTE_BASE_ID}/connect-data/datasources/:id`, + DataSourcesDetails: `/${ROUTE_BASE_ID}/datasources/:id`, } as const; diff --git a/public/app/features/query/components/QueryGroup.tsx b/public/app/features/query/components/QueryGroup.tsx index df1841a3ade..42ec327726d 100644 --- a/public/app/features/query/components/QueryGroup.tsx +++ b/public/app/features/query/components/QueryGroup.tsx @@ -466,7 +466,7 @@ export class QueryGroup extends PureComponent { {this.renderAddQueryRow(dsSettings, styles)} {isHelpOpen && ( - + )} diff --git a/public/app/plugins/datasource/cloudwatch/Search.tsx b/public/app/plugins/datasource/cloudwatch/Search.tsx index e7af681ffe1..a7af3070bf3 100644 --- a/public/app/plugins/datasource/cloudwatch/Search.tsx +++ b/public/app/plugins/datasource/cloudwatch/Search.tsx @@ -1,14 +1,11 @@ import { debounce } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; -import { Icon, Input, useStyles2 } from '@grafana/ui'; - -import getStyles from './components/styles'; +import { Icon, Input } from '@grafana/ui'; // TODO: consider moving search into grafana/ui, this is mostly the same as that in azure monitor const Search = ({ searchFn, searchPhrase }: { searchPhrase: string; searchFn: (searchPhrase: string) => void }) => { const [searchFilter, setSearchFilter] = useState(searchPhrase); - const styles = useStyles2(getStyles); const debouncedSearch = useMemo(() => debounce(searchFn, 600), [searchFn]); @@ -21,8 +18,6 @@ const Search = ({ searchFn, searchPhrase }: { searchPhrase: string; searchFn: (s return ( } value={searchFilter} diff --git a/public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.test.tsx b/public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.test.tsx index 5dc04fc59fb..f3eda22db87 100644 --- a/public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.test.tsx @@ -177,4 +177,44 @@ describe('CrossAccountLogsQueryField', () => { }, ]); }); + + const labelText = + 'Only the first 50 results can be shown. If you do not see an expected log group, try narrowing down your search.'; + it('should not display max result info label in case less than 50 logs groups are being displayed', async () => { + const defer = new Deferred(); + const fetchLogGroups = jest.fn(async () => { + await Promise.all([defer.promise]); + return []; + }); + render(); + await userEvent.click(screen.getByText('Select Log Groups')); + expect(screen.queryByText(labelText)).not.toBeInTheDocument(); + defer.resolve(); + await waitFor(() => expect(screen.queryByText(labelText)).not.toBeInTheDocument()); + }); + + it('should display max result info label in case 50 or more logs groups are being displayed', async () => { + const defer = new Deferred(); + const fetchLogGroups = jest.fn(async () => { + await Promise.all([defer.promise]); + return Array(50).map((i) => ({ + value: `logGroup${i}`, + text: `logGroup${i}`, + label: `logGroup${i}`, + })); + }); + render(); + await userEvent.click(screen.getByText('Select Log Groups')); + expect(screen.queryByText(labelText)).not.toBeInTheDocument(); + defer.resolve(); + await waitFor(() => expect(screen.getByText(labelText)).toBeInTheDocument()); + }); + + it('should display log groups counter label', async () => { + render(); + await userEvent.click(screen.getByText('Select Log Groups')); + await waitFor(() => expect(screen.getByText('0 log groups selected')).toBeInTheDocument()); + await userEvent.click(screen.getByLabelText('logGroup2')); + await waitFor(() => expect(screen.getByText('1 log group selected')).toBeInTheDocument()); + }); }); diff --git a/public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.tsx index b328eeca6ee..59e156b3a01 100644 --- a/public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/CrossAccountLogsQueryField.tsx @@ -2,13 +2,14 @@ import React, { useMemo, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { EditorField, Space } from '@grafana/experimental'; -import { Button, Checkbox, IconButton, LoadingPlaceholder, Modal, useStyles2 } from '@grafana/ui'; +import { Button, Checkbox, Icon, Label, LoadingPlaceholder, Modal, useStyles2 } from '@grafana/ui'; import Search from '../Search'; import { SelectableResourceValue } from '../api'; import { DescribeLogGroupsRequest } from '../types'; import { Account, ALL_ACCOUNTS_OPTION } from './Account'; +import { SelectedLogsGroups } from './SelectedLogsGroups'; import getStyles from './styles'; type CrossAccountLogsQueryProps = { @@ -81,15 +82,17 @@ export const CrossAccountLogsQueryField = (props: CrossAccountLogsQueryProps) => <>
- - { - searchFn(phrase, searchAccountId); - setSearchPhrase(phrase); - }} - searchPhrase={searchPhrase} - /> - +
+ + { + searchFn(phrase, searchAccountId); + setSearchPhrase(phrase); + }} + searchPhrase={searchPhrase} + /> + +
{ @@ -102,6 +105,16 @@ export const CrossAccountLogsQueryField = (props: CrossAccountLogsQueryProps) =>
+ {!isLoading && selectableLogGroups.length >= 25 && ( + <> + + + + )}
@@ -149,6 +162,10 @@ export const CrossAccountLogsQueryField = (props: CrossAccountLogsQueryProps) => + +
-
- {props.selectedLogGroups.map((lg) => ( -
- {lg.label} - props.onChange(props.selectedLogGroups.filter((slg) => slg.value !== lg.value))} - /> -
- ))} -
+ ); }; diff --git a/public/app/plugins/datasource/cloudwatch/components/SelectedLogsGroups.test.tsx b/public/app/plugins/datasource/cloudwatch/components/SelectedLogsGroups.test.tsx new file mode 100644 index 00000000000..b514d386903 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/components/SelectedLogsGroups.test.tsx @@ -0,0 +1,93 @@ +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { SelectedLogsGroups } from './SelectedLogsGroups'; + +const defaultProps = { + selectedLogGroups: [ + { + value: 'aws/lambda/lambda-name1', + label: 'aws/lambda/lambda-name1', + text: 'aws/lambda/lambda-name1', + }, + { + value: 'aws/lambda/lambda-name2', + label: 'aws/lambda/lambda-name2', + text: 'aws/lambda/lambda-name2', + }, + ], + onChange: jest.fn(), +}; + +describe('SelectedLogsGroups', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + describe("'Show more' button", () => { + it('should not be displayed in case 0 logs have been selected', async () => { + render(); + await waitFor(() => expect(screen.queryByText('Show all')).not.toBeInTheDocument()); + }); + it('should not be displayed in case logs group have been selected but theyre less than 10', async () => { + render(); + await waitFor(() => expect(screen.queryByText('Show all')).not.toBeInTheDocument()); + }); + it('should be displayed in case more than 10 log groups have been selected', async () => { + const selectedLogGroups = Array(12).map((i) => ({ + value: `logGroup${i}`, + text: `logGroup${i}`, + label: `logGroup${i}`, + })); + render(); + await waitFor(() => expect(screen.getByText('Show all')).toBeInTheDocument()); + }); + }); + + describe("'Clear selection' button", () => { + it('should not be displayed in case 0 logs have been selected', async () => { + render(); + await waitFor(() => expect(screen.queryByText('Clear selection')).not.toBeInTheDocument()); + }); + it('should be displayed in case at least one log group have been selected', async () => { + const selectedLogGroups = Array(11).map((i) => ({ + value: `logGroup${i}`, + text: `logGroup${i}`, + label: `logGroup${i}`, + })); + render(); + await waitFor(() => expect(screen.getByText('Clear selection')).toBeInTheDocument()); + }); + + it('should display confirm dialog before clearing all selections', async () => { + const selectedLogGroups = Array(11).map((i) => ({ + value: `logGroup${i}`, + text: `logGroup${i}`, + label: `logGroup${i}`, + })); + render(); + await waitFor(() => userEvent.click(screen.getByText('Clear selection'))); + await waitFor(() => + expect(screen.getByText('Are you sure you want to clear all log groups?')).toBeInTheDocument() + ); + await waitFor(() => userEvent.click(screen.getByLabelText('Confirm Modal Danger Button'))); + expect(defaultProps.onChange).toHaveBeenCalledWith([]); + }); + }); + + describe("'Clear selection' button", () => { + it('should not be displayed in case 0 logs have been selected', async () => { + render(); + await waitFor(() => expect(screen.queryByText('Clear selection')).not.toBeInTheDocument()); + }); + it('should be displayed in case at least one log group have been selected', async () => { + const selectedLogGroups = Array(11).map((i) => ({ + value: `logGroup${i}`, + text: `logGroup${i}`, + label: `logGroup${i}`, + })); + render(); + await waitFor(() => expect(screen.getByText('Clear selection')).toBeInTheDocument()); + }); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/components/SelectedLogsGroups.tsx b/public/app/plugins/datasource/cloudwatch/components/SelectedLogsGroups.tsx new file mode 100644 index 00000000000..0733da6aabd --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/components/SelectedLogsGroups.tsx @@ -0,0 +1,84 @@ +import React, { useEffect, useState } from 'react'; + +import { Button, ConfirmModal, useStyles2 } from '@grafana/ui'; + +import { SelectableResourceValue } from '../api'; + +import getStyles from './styles'; + +type CrossAccountLogsQueryProps = { + selectedLogGroups: SelectableResourceValue[]; + onChange: (selectedLogGroups: SelectableResourceValue[]) => void; +}; + +const MAX_VISIBLE_LOG_GROUPS = 6; + +export const SelectedLogsGroups = ({ selectedLogGroups, onChange }: CrossAccountLogsQueryProps) => { + const styles = useStyles2(getStyles); + const [showConfirm, setShowConfirm] = useState(false); + const [visibleSelectecLogGroups, setVisibleSelectecLogGroups] = useState( + selectedLogGroups.slice(0, MAX_VISIBLE_LOG_GROUPS) + ); + + useEffect(() => { + setVisibleSelectecLogGroups(selectedLogGroups.slice(0, MAX_VISIBLE_LOG_GROUPS)); + }, [selectedLogGroups]); + + return ( + <> +
+ {visibleSelectecLogGroups.map((lg) => ( + + ))} + {visibleSelectecLogGroups.length !== selectedLogGroups.length && ( + + )} + {selectedLogGroups.length > 0 && ( + + )} +
+ { + setShowConfirm(false); + onChange([]); + }} + onDismiss={() => setShowConfirm(false)} + /> + + ); +}; diff --git a/public/app/plugins/datasource/cloudwatch/components/styles.ts b/public/app/plugins/datasource/cloudwatch/components/styles.ts index 5371a277ece..7aebe20cce2 100644 --- a/public/app/plugins/datasource/cloudwatch/components/styles.ts +++ b/public/app/plugins/datasource/cloudwatch/components/styles.ts @@ -10,12 +10,31 @@ const getStyles = (theme: GrafanaTheme2) => ({ selectedLogGroupsContainer: css({ marginLeft: theme.spacing(0.5), + marginBottom: theme.spacing(0.5), display: 'flex', flexFlow: 'wrap', + gap: theme.spacing(1), + button: { + margin: 'unset', + }, + }), + + limitLabel: css({ + color: theme.colors.text.secondary, + textAlign: 'center', + maxWidth: 'none', + svg: { + marginRight: theme.spacing(0.5), + }, + }), + + logGroupCountLabel: css({ + color: theme.colors.text.secondary, + maxWidth: 'none', }), tableScroller: css({ - maxHeight: '50vh', + maxHeight: '40vh', overflow: 'auto', }), @@ -61,24 +80,16 @@ const getStyles = (theme: GrafanaTheme2) => ({ display: 'flex', }), + searchField: css({ + width: '100%', + marginRight: theme.spacing(1), + }), + resultLimit: css({ margin: '4px 0', fontStyle: 'italic', }), - selectedLogGroup: css({ - background: theme.colors.background.secondary, - borderRadius: theme.shape.borderRadius(), - margin: theme.spacing(0, 1, 1, 0), - padding: theme.spacing(0.5, 0, 0.5, 1), - color: theme.colors.text.primary, - fontSize: theme.typography.size.sm, - }), - - search: css({ - marginRight: '10px', - }), - removeButton: css({ verticalAlign: 'middle', marginLeft: theme.spacing(0.5), diff --git a/public/app/plugins/datasource/phlare/datasource.test.ts b/public/app/plugins/datasource/phlare/datasource.test.ts new file mode 100644 index 00000000000..10f6d6d1469 --- /dev/null +++ b/public/app/plugins/datasource/phlare/datasource.test.ts @@ -0,0 +1,66 @@ +import { AbstractLabelOperator, DataSourceInstanceSettings, PluginMetaInfo, PluginType } from '@grafana/data'; + +import { PhlareDataSource } from './datasource'; + +describe('Phlare data source', () => { + let ds: PhlareDataSource; + beforeEach(() => { + ds = new PhlareDataSource(defaultSettings); + }); + + describe('importing queries', () => { + it('keeps all labels and values', async () => { + const queries = await ds.importFromAbstractQueries([ + { + refId: 'A', + labelMatchers: [ + { name: 'foo', operator: AbstractLabelOperator.Equal, value: 'bar' }, + { name: 'foo2', operator: AbstractLabelOperator.Equal, value: 'bar2' }, + ], + }, + ]); + expect(queries[0].labelSelector).toBe('{foo="bar", foo2="bar2"}'); + }); + }); + + describe('exporting queries', () => { + it('keeps all labels and values', async () => { + const queries = await ds.exportToAbstractQueries([ + { + refId: 'A', + labelSelector: '{foo="bar", foo2="bar2"}', + queryType: 'both', + profileTypeId: '', + groupBy: [''], + }, + ]); + expect(queries).toMatchObject([ + { + refId: 'A', + labelMatchers: [ + { name: 'foo', operator: AbstractLabelOperator.Equal, value: 'bar' }, + { name: 'foo2', operator: AbstractLabelOperator.Equal, value: 'bar2' }, + ], + }, + ]); + }); + }); +}); + +const defaultSettings: DataSourceInstanceSettings = { + id: 0, + uid: 'phlare', + type: 'profiling', + name: 'phlare', + access: 'proxy', + meta: { + id: 'phlare', + name: 'phlare', + type: PluginType.datasource, + info: {} as PluginMetaInfo, + module: '', + baseUrl: '', + }, + jsonData: {}, + readOnly: false, +}; diff --git a/public/app/plugins/datasource/phlare/datasource.ts b/public/app/plugins/datasource/phlare/datasource.ts index aa80869c12c..95da2c417de 100644 --- a/public/app/plugins/datasource/phlare/datasource.ts +++ b/public/app/plugins/datasource/phlare/datasource.ts @@ -1,8 +1,11 @@ +import Prism, { Grammar } from 'prismjs'; import { Observable, of } from 'rxjs'; -import { DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/data'; +import { AbstractQuery, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/data'; import { DataSourceWithBackend } from '@grafana/runtime'; +import { extractLabelMatchers, toPromLikeExpr } from '../prometheus/language_utils'; + import { normalizeQuery } from './QueryEditor/QueryEditor'; import { PhlareDataSourceOptions, Query, ProfileTypeMessage, SeriesMessage } from './types'; @@ -45,4 +48,58 @@ export class PhlareDataSource extends DataSourceWithBackend { return await super.getResource('labelNames'); } + + async importFromAbstractQueries(abstractQueries: AbstractQuery[]): Promise { + return abstractQueries.map((abstractQuery) => this.importFromAbstractQuery(abstractQuery)); + } + + importFromAbstractQuery(labelBasedQuery: AbstractQuery): Query { + return { + refId: labelBasedQuery.refId, + labelSelector: toPromLikeExpr(labelBasedQuery), + queryType: 'both', + profileTypeId: '', + groupBy: [''], + }; + } + + async exportToAbstractQueries(queries: Query[]): Promise { + return queries.map((query) => this.exportToAbstractQuery(query)); + } + + exportToAbstractQuery(query: Query): AbstractQuery { + const phlareQuery = query.labelSelector; + if (!phlareQuery || phlareQuery.length === 0) { + return { refId: query.refId, labelMatchers: [] }; + } + const tokens = Prism.tokenize(phlareQuery, grammar); + return { + refId: query.refId, + labelMatchers: extractLabelMatchers(tokens), + }; + } } + +const grammar: Grammar = { + 'context-labels': { + pattern: /\{[^}]*(?=}?)/, + greedy: true, + inside: { + comment: { + pattern: /#.*/, + }, + 'label-key': { + pattern: /[a-zA-Z_]\w*(?=\s*(=|!=|=~|!~))/, + alias: 'attr-name', + greedy: true, + }, + 'label-value': { + pattern: /"(?:\\.|[^\\"])*"/, + greedy: true, + alias: 'attr-value', + }, + punctuation: /[{]/, + }, + }, + punctuation: /[{}(),.]/, +}; diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 99dc23f8e3c..b4132cf9e45 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -181,7 +181,7 @@ export class TempoDatasource extends DataSourceWithBackend { - setupAutocompleteFn(editor, monaco); + setupAutocompleteFn(editor, monaco, setupRegisterInteractionCommand(editor)); setupActions(editor, monaco, onRunQuery); setupPlaceholder(editor, monaco, styles); setupAutoSize(editor); @@ -101,6 +102,17 @@ function setupActions(editor: monacoTypes.editor.IStandaloneCodeEditor, monaco: }); } +function setupRegisterInteractionCommand(editor: monacoTypes.editor.IStandaloneCodeEditor): string | null { + return editor.addCommand(0, function (_, label, type: CompletionType) { + const properties: Record = { datasourceType: 'tempo', type }; + // Filter out the label for TAG_VALUE completions to avoid potentially exposing sensitive data + if (type !== 'TAG_VALUE') { + properties.label = label; + } + reportInteraction('grafana_traces_traceql_completion', properties); + }); +} + function setupAutoSize(editor: monacoTypes.editor.IStandaloneCodeEditor) { const container = editor.getDomNode(); const updateHeight = () => { @@ -156,9 +168,14 @@ function useAutocomplete(datasource: TempoDatasource) { }, []); // This should be run in monaco onEditorDidMount - return (editor: monacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => { + return ( + editor: monacoTypes.editor.IStandaloneCodeEditor, + monaco: Monaco, + registerInteractionCommandId: string | null + ) => { providerRef.current.editor = editor; providerRef.current.monaco = monaco; + providerRef.current.setRegisterInteractionCommandId(registerInteractionCommandId); const { dispose } = monaco.languages.registerCompletionItemProvider(langId, providerRef.current); autocompleteDisposeFun.current = dispose; diff --git a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts index 3ba809ab443..889098bc52d 100644 --- a/public/app/plugins/datasource/tempo/traceql/autocomplete.ts +++ b/public/app/plugins/datasource/tempo/traceql/autocomplete.ts @@ -13,9 +13,11 @@ interface Props { */ export class CompletionProvider implements monacoTypes.languages.CompletionItemProvider { languageProvider: TempoLanguageProvider; + registerInteractionCommandId: string | null; constructor(props: Props) { this.languageProvider = props.languageProvider; + this.registerInteractionCommandId = null; } triggerCharacters = ['{', '.', '[', '(', '=', '~', ' ', '"']; @@ -57,14 +59,19 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP // so that monaco keeps the order we use const maxIndexDigits = items.length.toString().length; const suggestions: monacoTypes.languages.CompletionItem[] = items.map((item, index) => { - const suggestion = { + const suggestion: monacoTypes.languages.CompletionItem = { kind: getMonacoCompletionItemKind(item.type, this.monaco!), label: item.label, insertText: item.insertText, sortText: index.toString().padStart(maxIndexDigits, '0'), // to force the order we have range, + command: { + id: this.registerInteractionCommandId || 'noOp', + title: 'Report Interaction', + arguments: [item.label, item.type], + }, }; - fixSuggestion(suggestion, item.type, model, offset, this.monaco!); + fixSuggestion(suggestion, item.type, model, offset); return suggestion; }); return { suggestions }; @@ -78,6 +85,13 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP tags.forEach((t) => (this.tags[t] = new Set())); } + /** + * Set the ID for the registerInteraction command, to be used to keep track of how many completions are used by the users + */ + setRegisterInteractionCommandId(id: string | null) { + this.registerInteractionCommandId = id; + } + private overrideTagName(tagName: string): string { switch (tagName) { case 'status': @@ -88,7 +102,7 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP } private async getTagValues(tagName: string): Promise>> { - let tagValues: Array> = []; + let tagValues: Array>; if (this.cachedValues.hasOwnProperty(tagName)) { tagValues = this.cachedValues[tagName]; @@ -404,11 +418,10 @@ function getRangeAndOffset(monaco: Monaco, model: monacoTypes.editor.ITextModel, * here. */ function fixSuggestion( - suggestion: monacoTypes.languages.CompletionItem & { range: monacoTypes.IRange }, + suggestion: monacoTypes.languages.CompletionItem, itemType: CompletionType, model: monacoTypes.editor.ITextModel, - offset: number, - monaco: Monaco + offset: number ) { if (itemType === 'TAG_NAME') { const match = model @@ -427,10 +440,10 @@ function fixSuggestion( } // Adjust the range, so that we will replace the whole tag. - suggestion.range = monaco.Range.lift({ + suggestion.range = { ...suggestion.range, startColumn: offset - tag.length + 1, - }); + }; } } } diff --git a/yarn.lock b/yarn.lock index f4e61441540..a1462379ca0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1258,6 +1258,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0": + version: 7.20.0 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.20.0" + dependencies: + "@babel/types": ^7.20.0 + checksum: 34da8c832d1c8a546e45d5c1d59755459ffe43629436707079989599b91e8c19e50e73af7a4bd09c95402d389266731b0d9c5f69e372d8ebd3a709c05c80d7dd + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.15.4": version: 7.15.4 resolution: "@babel/helper-split-export-declaration@npm:7.15.4" @@ -1853,6 +1862,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-optional-chaining@npm:7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-proposal-optional-chaining@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/plugin-syntax-optional-chaining": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 274b8932335bd064ca24cf1a4da2b2c20c92726d4bfa8b0cb5023857479b8481feef33505c16650c7b9239334e5c6959babc924816324c4cf223dd91c7ca79bc + languageName: node + linkType: hard + "@babel/plugin-proposal-private-methods@npm:^7.12.1, @babel/plugin-proposal-private-methods@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-private-methods@npm:7.18.6" @@ -3669,6 +3691,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.20.0": + version: 7.20.7 + resolution: "@babel/types@npm:7.20.7" + dependencies: + "@babel/helper-string-parser": ^7.19.4 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: b39af241f0b72bba67fd6d0d23914f6faec8c0eba8015c181cbd5ea92e59fc91a52a1ab490d3520c7dbd19ddb9ebb76c476308f6388764f16d8201e37fae6811 + languageName: node + linkType: hard + "@babel/types@npm:^7.20.2, @babel/types@npm:^7.20.5": version: 7.20.5 resolution: "@babel/types@npm:7.20.5" @@ -12743,13 +12776,13 @@ __metadata: languageName: node linkType: hard -"@visx/event@npm:2.6.0": - version: 2.6.0 - resolution: "@visx/event@npm:2.6.0" +"@visx/event@npm:2.17.0": + version: 2.17.0 + resolution: "@visx/event@npm:2.17.0" dependencies: "@types/react": "*" - "@visx/point": 2.6.0 - checksum: a3155bab7d964f592702981a2a6cae3742d9f23732ec64c2c3717c8b17350e22006fb13a96201cbe78bdd35170c8363ab645bf0c4c8f8dee41be64618fd38c47 + "@visx/point": 2.17.0 + checksum: 360a9ee2a163c0e0200f98cadcd9f0c98f53725d4be62e49d326aeb8ca7cd837305f28f326f03d9ae49910ca67e1185a97827f0ef76efb0b901fb54c56c97bd3 languageName: node linkType: hard @@ -12778,10 +12811,10 @@ __metadata: languageName: node linkType: hard -"@visx/point@npm:2.6.0": - version: 2.6.0 - resolution: "@visx/point@npm:2.6.0" - checksum: 56be8e31abaf10a182d328141a0929ea28d2956d4c06469fc51cde3f30f64ae035aaa0771f44010c3484ae63c7cd1bc9b782cde6cfcf9713190c20b301ab181e +"@visx/point@npm:2.17.0": + version: 2.17.0 + resolution: "@visx/point@npm:2.17.0" + checksum: 9cb18e41f1f47e46b85bdbbdd1c930a654ed8b731b3a5da0920e2b3a261d887c066ec97079293d4f5195a1e04d842faa2f2bf0519876cb27ea6ee82fc5ff8855 languageName: node linkType: hard @@ -21921,7 +21954,7 @@ __metadata: "@babel/plugin-proposal-class-properties": 7.18.6 "@babel/plugin-proposal-nullish-coalescing-operator": 7.18.6 "@babel/plugin-proposal-object-rest-spread": 7.20.2 - "@babel/plugin-proposal-optional-chaining": 7.18.9 + "@babel/plugin-proposal-optional-chaining": 7.20.7 "@babel/plugin-syntax-dynamic-import": 7.8.3 "@babel/plugin-transform-react-constant-elements": 7.20.2 "@babel/plugin-transform-runtime": 7.19.6 @@ -22045,7 +22078,7 @@ __metadata: "@types/webpack-env": 1.18.0 "@typescript-eslint/eslint-plugin": 5.42.0 "@typescript-eslint/parser": 5.42.0 - "@visx/event": 2.6.0 + "@visx/event": 2.17.0 "@visx/gradient": 2.10.0 "@visx/group": 2.10.0 "@visx/scale": 2.2.2