Merge branch 'main' into laura/feat/validate-target-query
This commit is contained in:
+4
-7
@@ -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"]
|
||||
|
||||
@@ -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*:
|
||||
|
||||
@@ -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-----
|
||||
@@ -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);
|
||||
|
||||
|
||||
--
|
||||
|
||||
@@ -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"
|
||||
- "authentikredis:authentikredis"
|
||||
|
||||
@@ -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-----
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
$config = array(
|
||||
'admin' => 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',
|
||||
),
|
||||
),
|
||||
);
|
||||
+11
-5
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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")
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
+2
-2
@@ -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",
|
||||
|
||||
@@ -102,9 +102,9 @@ export const defaultVariableModel: Partial<VariableModel> = {
|
||||
};
|
||||
|
||||
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<string, unknown>;
|
||||
options: Record<string, ValueMappingResult>;
|
||||
type: MappingType.ValueToText;
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ export interface Panel {
|
||||
/**
|
||||
* TODO docs - seems to be an old field from old dashboard alerts?
|
||||
*/
|
||||
thresholds?: Array<any>;
|
||||
thresholds?: Array<unknown>;
|
||||
/**
|
||||
* TODO docs
|
||||
* TODO tighter constraint
|
||||
@@ -456,7 +456,7 @@ export interface Panel {
|
||||
/**
|
||||
* TODO docs
|
||||
*/
|
||||
timeRegions?: Array<any>;
|
||||
timeRegions?: Array<unknown>;
|
||||
/**
|
||||
* 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<FieldConfigSource> = {
|
||||
|
||||
export interface MatcherConfig {
|
||||
id: string;
|
||||
options?: any;
|
||||
options?: unknown;
|
||||
}
|
||||
|
||||
export const defaultMatcherConfig: Partial<MatcherConfig> = {
|
||||
@@ -546,7 +546,7 @@ export interface FieldConfig {
|
||||
/**
|
||||
* The behavior when clicking on a result
|
||||
*/
|
||||
links?: Array<any>;
|
||||
links?: Array<unknown>;
|
||||
/**
|
||||
* Convert input values into a display string
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface Team {
|
||||
/**
|
||||
* AccessControl metadata associated with a given resource.
|
||||
*/
|
||||
accessControl: Record<string, unknown>;
|
||||
accessControl?: Record<string, boolean>;
|
||||
/**
|
||||
* AvatarUrl is the team's avatar URL.
|
||||
*/
|
||||
|
||||
@@ -72,7 +72,13 @@ export class UnthemedCanvasSpanGraph extends React.PureComponent<CanvasSpanGraph
|
||||
}
|
||||
|
||||
render() {
|
||||
return <canvas className={getStyles(this.props.theme).CanvasSpanGraph} ref={this._setCanvasRef} />;
|
||||
return (
|
||||
<canvas
|
||||
className={getStyles(this.props.theme).CanvasSpanGraph}
|
||||
ref={this._setCanvasRef}
|
||||
data-testid="CanvasSpanGraph"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,5 +56,9 @@ export default function TickLabels(props: TickLabelsProps) {
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={styles.TickLabels}>{ticks}</div>;
|
||||
return (
|
||||
<div className={styles.TickLabels} data-testid="TickLabels">
|
||||
{ticks}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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('<SpanGraph>', () => {
|
||||
polyfillAnimationFrame(window);
|
||||
|
||||
const trace = transformTraceData(traceGenerator.trace({}));
|
||||
const trace = transformTraceData(traceGenerator.trace({}))!;
|
||||
const props = {
|
||||
trace,
|
||||
updateViewRangeTime: () => {},
|
||||
@@ -39,52 +37,35 @@ describe('<SpanGraph>', () => {
|
||||
},
|
||||
};
|
||||
|
||||
let wrapper: ShallowWrapper<ViewingLayerProps, {}, UnthemedViewingLayer>;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SpanGraph {...(props as unknown as SpanGraphProps)} />);
|
||||
render(<SpanGraph {...(props as unknown as SpanGraphProps)} />);
|
||||
});
|
||||
|
||||
it('renders a <CanvasSpanGraph />', () => {
|
||||
expect(wrapper.find(CanvasSpanGraph).length).toBe(1);
|
||||
it('renders <CanvasSpanGraph />', () => {
|
||||
const canvasSpanGraphComponent = screen.getByTestId('CanvasSpanGraph');
|
||||
expect(canvasSpanGraphComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders a <TickLabels />', () => {
|
||||
expect(wrapper.find(TickLabels).length).toBe(1);
|
||||
it('renders <TickLabels />', () => {
|
||||
const tickLabelsComponent = screen.getByTestId('TickLabels');
|
||||
expect(tickLabelsComponent).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns a <div> if a trace is not provided', () => {
|
||||
wrapper = shallow(<SpanGraph {...({ ...props, trace: null } as unknown as SpanGraphProps)} />);
|
||||
expect(wrapper.matchesElement(<div />)).toBeTruthy();
|
||||
it('returns an empty div if a trace is not provided', () => {
|
||||
const { container } = render(<SpanGraph {...({ ...props, trace: null } as unknown as SpanGraphProps)} />);
|
||||
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 <TickLabels /> 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 <TickLabels /> with the correct value next to each tick', () => {
|
||||
render(<TickLabels numTicks={5} duration={1} />);
|
||||
expect(screen.getByText(/0.2/)).toBeTruthy();
|
||||
expect(screen.getByText(/0.4/)).toBeTruthy();
|
||||
expect(screen.getByText(/0.6/)).toBeTruthy();
|
||||
expect(screen.getByText(/0.8/)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
+14
-13
@@ -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('<TimelineColumnResizer>', () => {
|
||||
let wrapper;
|
||||
let instance;
|
||||
let wrapper: ReactWrapper<TimelineColumnResizerProps, {}, TimelineColumnResizer>;
|
||||
let instance: TimelineColumnResizer;
|
||||
|
||||
const props = {
|
||||
min: 0.1,
|
||||
@@ -31,7 +32,7 @@ describe('<TimelineColumnResizer>', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
props.onChange.mockReset();
|
||||
wrapper = mount(<TimelineColumnResizer {...props} />);
|
||||
wrapper = mount(<TimelineColumnResizer {...(props as unknown as TimelineColumnResizerProps)} />);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
@@ -57,7 +58,7 @@ describe('<TimelineColumnResizer>', () => {
|
||||
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('<TimelineColumnResizer>', () => {
|
||||
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('<TimelineColumnResizer>', () => {
|
||||
|
||||
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(
|
||||
+1
-1
@@ -97,7 +97,7 @@ export const getStyles = stylesFactory(() => {
|
||||
};
|
||||
});
|
||||
|
||||
type TimelineColumnResizerProps = {
|
||||
export type TimelineColumnResizerProps = {
|
||||
min: number;
|
||||
max: number;
|
||||
onChange: (newSize: number) => void;
|
||||
|
||||
+2
-2
@@ -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(<TimelineHeaderRow {...props} />);
|
||||
return render(<TimelineHeaderRow {...(props as unknown as TimelineHeaderRowProps)} />);
|
||||
};
|
||||
|
||||
describe('TimelineHeaderRow', () => {
|
||||
+1
-1
@@ -56,7 +56,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
};
|
||||
};
|
||||
|
||||
type TimelineHeaderRowProps = {
|
||||
export type TimelineHeaderRowProps = {
|
||||
duration: number;
|
||||
nameColumnWidth: number;
|
||||
numTicks: number;
|
||||
|
||||
+63
-26
@@ -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-<id>), 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('<TimelineViewingLayer>', () => {
|
||||
let wrapper;
|
||||
let instance;
|
||||
let wrapper: ReactWrapper<TimelineViewingLayerProps, {}, TimelineViewingLayer>;
|
||||
let instance: TimelineViewingLayer;
|
||||
|
||||
const viewStart = 0.25;
|
||||
const viewEnd = 0.9;
|
||||
@@ -33,14 +44,14 @@ describe('<TimelineViewingLayer>', () => {
|
||||
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(<TimelineViewingLayer {...props} />);
|
||||
wrapper = mount(<TimelineViewingLayer {...(props as unknown as TimelineViewingLayerProps)} />);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
@@ -76,19 +87,19 @@ describe('<TimelineViewingLayer>', () => {
|
||||
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('<TimelineViewingLayer>', () => {
|
||||
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('<TimelineViewingLayer>', () => {
|
||||
// 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('<TimelineViewingLayer>', () => {
|
||||
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('<TimelineViewingLayer>', () => {
|
||||
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('<TimelineViewingLayer>', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package clients
|
||||
|
||||
const (
|
||||
basicPrefix = "Basic "
|
||||
bearerPrefix = "Bearer "
|
||||
authorizationHeaderName = "Authorization"
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
}}
|
||||
>
|
||||
<FocusScope>
|
||||
<div className={styles.mobileSidemenuLogo} onClick={() => setMenuOpen(!menuOpen)} key="hamburger">
|
||||
<Icon name="bars" size="xl" />
|
||||
<div className={styles.mobileSidemenuLogo} key="hamburger">
|
||||
<IconButton
|
||||
name="bars"
|
||||
tooltip="Toggle menu"
|
||||
tooltipPlacement="bottom"
|
||||
size="xl"
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NavBarToggle
|
||||
@@ -212,7 +218,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
}),
|
||||
mobileSidemenuLogo: css({
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
|
||||
@@ -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<Props, State> {
|
||||
state = {
|
||||
isError: false,
|
||||
isLoading: false,
|
||||
help: '',
|
||||
};
|
||||
const renderedMarkdown = renderMarkdown(value);
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadHelp();
|
||||
if (loading) {
|
||||
return <LoadingPlaceholder text="Loading help..." />;
|
||||
}
|
||||
|
||||
constructPlaceholderInfo() {
|
||||
return 'No plugin help or readme markdown file was found';
|
||||
if (error) {
|
||||
return <h3>An error occurred when loading help.</h3>;
|
||||
}
|
||||
|
||||
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 <h2>Loading help...</h2>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <h3>'Error occurred when loading help'</h3>;
|
||||
}
|
||||
|
||||
if (type === 'panel_help' && help === '') {
|
||||
}
|
||||
|
||||
return <div className="markdown-html" dangerouslySetInnerHTML={{ __html: help }} />;
|
||||
if (value === '') {
|
||||
return <h3>No query help could be found.</h3>;
|
||||
}
|
||||
|
||||
return <div className="markdown-html" dangerouslySetInnerHTML={{ __html: renderedMarkdown }} />;
|
||||
}
|
||||
|
||||
@@ -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<typeof useCombinedRule>;
|
||||
|
||||
@@ -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<typeof useCombinedRule>;
|
||||
|
||||
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<typeof useCombinedRule>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,7 +170,7 @@ export function RuleViewer({ match }: RuleViewerProps) {
|
||||
<Icon name="bell" size="lg" /> {rule.name}
|
||||
</h4>
|
||||
<RuleState rule={rule} isCreating={false} isDeleting={false} />
|
||||
<RuleDetailsActionButtons rule={rule} rulesSource={rulesSource} />
|
||||
<RuleDetailsActionButtons rule={rule} rulesSource={rulesSource} isViewMode={true} />
|
||||
</div>
|
||||
<div className={styles.details}>
|
||||
<div className={styles.leftSide}>
|
||||
|
||||
@@ -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) {
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function getGrafanaRule(override?: Partial<CombinedRule>) {
|
||||
return mockCombinedRule({
|
||||
namespace: {
|
||||
groups: [],
|
||||
name: 'Grafana',
|
||||
rulesSource: 'grafana',
|
||||
},
|
||||
...override,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const RuleDetails: FC<Props> = ({ rule }) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RuleDetailsActionButtons rule={rule} rulesSource={rulesSource} />
|
||||
<RuleDetailsActionButtons rule={rule} rulesSource={rulesSource} isViewMode={false} />
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.leftSide}>
|
||||
{<EvaluationBehaviorSummary rule={rule} />}
|
||||
|
||||
+118
-14
@@ -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<Props> = ({ rule, rulesSource }) => {
|
||||
export const RuleDetailsActionButtons: FC<Props> = ({ 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<CombinedRule>();
|
||||
|
||||
const alertmanagerSourceName = isGrafanaRulesSource(rulesSource)
|
||||
? rulesSource
|
||||
@@ -32,9 +44,39 @@ export const RuleDetailsActionButtons: FC<Props> = ({ 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<Props> = ({ rule, rulesSource }) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (buttons.length) {
|
||||
return (
|
||||
<div className={style.wrapper}>
|
||||
<HorizontalGroup width="auto">{buttons.length ? buttons : <div />}</HorizontalGroup>
|
||||
</div>
|
||||
);
|
||||
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(
|
||||
<ClipboardButton
|
||||
key="copy"
|
||||
icon="copy"
|
||||
onClipboardError={(copiedText) => {
|
||||
notifyApp.error('Error while copying URL', copiedText);
|
||||
}}
|
||||
className={style.button}
|
||||
size="sm"
|
||||
getText={buildShareUrl}
|
||||
>
|
||||
Copy link to rule
|
||||
</ClipboardButton>
|
||||
);
|
||||
|
||||
rightButtons.push(
|
||||
<LinkButton className={style.button} size="xs" key="edit" variant="secondary" icon="pen" href={editURL}>
|
||||
Edit
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (isRemovable && rulerRule && !isFederated && !isProvisioned) {
|
||||
rightButtons.push(
|
||||
<Button
|
||||
className={style.button}
|
||||
size="xs"
|
||||
type="button"
|
||||
key="delete"
|
||||
variant="secondary"
|
||||
icon="trash-alt"
|
||||
onClick={() => setRuleToDelete(rule)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (buttons.length || rightButtons.length) {
|
||||
return (
|
||||
<>
|
||||
<div className={style.wrapper}>
|
||||
<HorizontalGroup width="auto">{buttons.length ? buttons : <div />}</HorizontalGroup>
|
||||
<HorizontalGroup width="auto">{rightButtons.length ? rightButtons : <div />}</HorizontalGroup>
|
||||
</div>
|
||||
{!!ruleToDelete && (
|
||||
<ConfirmModal
|
||||
isOpen={true}
|
||||
title="Delete rule"
|
||||
body="Deleting this rule will permanently remove it from your alert rule list. Are you sure you want to delete this rule?"
|
||||
confirmText="Yes, delete"
|
||||
icon="exclamation-triangle"
|
||||
onConfirm={deleteRule}
|
||||
onDismiss={() => 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};
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -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<CombinedRule>) {
|
||||
return mockCombinedRule({
|
||||
namespace: {
|
||||
groups: [],
|
||||
name: 'Grafana',
|
||||
rulesSource: 'grafana',
|
||||
},
|
||||
...override,
|
||||
});
|
||||
}
|
||||
|
||||
function getCloudRule(override?: Partial<CombinedRule>) {
|
||||
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' });
|
||||
|
||||
@@ -516,3 +516,26 @@ export function mockStore(recipe: (state: StoreState) => void) {
|
||||
|
||||
return configureStore(produce(defaultState, recipe));
|
||||
}
|
||||
|
||||
export function getGrafanaRule(override?: Partial<CombinedRule>) {
|
||||
return mockCombinedRule({
|
||||
namespace: {
|
||||
groups: [],
|
||||
name: 'Grafana',
|
||||
rulesSource: 'grafana',
|
||||
},
|
||||
...override,
|
||||
});
|
||||
}
|
||||
export function getCloudRule(override?: Partial<CombinedRule>) {
|
||||
return mockCombinedRule({
|
||||
namespace: {
|
||||
groups: [],
|
||||
name: 'Cortex',
|
||||
rulesSource: mockDataSource(),
|
||||
},
|
||||
promRule: mockPromAlertingRule(),
|
||||
rulerRule: mockRulerAlertingRule(),
|
||||
...override,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -466,7 +466,7 @@ export class QueryGroup extends PureComponent<Props, State> {
|
||||
{this.renderAddQueryRow(dsSettings, styles)}
|
||||
{isHelpOpen && (
|
||||
<Modal title="Data source help" isOpen={true} onDismiss={this.onCloseHelp}>
|
||||
<PluginHelp plugin={dsSettings.meta} type="query_help" />
|
||||
<PluginHelp pluginId={dsSettings.meta.id} />
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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 (
|
||||
<Input
|
||||
className={styles.search}
|
||||
width={64}
|
||||
aria-label="log group search"
|
||||
prefix={<Icon name="search" />}
|
||||
value={searchFilter}
|
||||
|
||||
+40
@@ -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(<CrossAccountLogsQueryField {...defaultProps} fetchLogGroups={fetchLogGroups} />);
|
||||
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(<CrossAccountLogsQueryField {...defaultProps} fetchLogGroups={fetchLogGroups} />);
|
||||
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(<CrossAccountLogsQueryField {...defaultProps} selectedLogGroups={[]} />);
|
||||
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());
|
||||
});
|
||||
});
|
||||
|
||||
+28
-23
@@ -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) =>
|
||||
<>
|
||||
<Modal className={styles.modal} title="Select Log Groups" isOpen={isModalOpen} onDismiss={toggleModal}>
|
||||
<div className={styles.logGroupSelectionArea}>
|
||||
<EditorField label="Log Group Name">
|
||||
<Search
|
||||
searchFn={(phrase) => {
|
||||
searchFn(phrase, searchAccountId);
|
||||
setSearchPhrase(phrase);
|
||||
}}
|
||||
searchPhrase={searchPhrase}
|
||||
/>
|
||||
</EditorField>
|
||||
<div className={styles.searchField}>
|
||||
<EditorField label="Log Group Name">
|
||||
<Search
|
||||
searchFn={(phrase) => {
|
||||
searchFn(phrase, searchAccountId);
|
||||
setSearchPhrase(phrase);
|
||||
}}
|
||||
searchPhrase={searchPhrase}
|
||||
/>
|
||||
</EditorField>
|
||||
</div>
|
||||
|
||||
<Account
|
||||
onChange={(accountId?: string) => {
|
||||
@@ -102,6 +105,16 @@ export const CrossAccountLogsQueryField = (props: CrossAccountLogsQueryProps) =>
|
||||
</div>
|
||||
<Space layout="block" v={2} />
|
||||
<div>
|
||||
{!isLoading && selectableLogGroups.length >= 25 && (
|
||||
<>
|
||||
<Label className={styles.limitLabel}>
|
||||
<Icon name="info-circle"></Icon>
|
||||
Only the first 50 results can be shown. If you do not see an expected log group, try narrowing down your
|
||||
search.
|
||||
</Label>
|
||||
<Space layout="block" v={1} />
|
||||
</>
|
||||
)}
|
||||
<div className={styles.tableScroller}>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
@@ -149,6 +162,10 @@ export const CrossAccountLogsQueryField = (props: CrossAccountLogsQueryProps) =>
|
||||
</div>
|
||||
</div>
|
||||
<Space layout="block" v={2} />
|
||||
<Label className={styles.logGroupCountLabel}>
|
||||
{selectedLogGroups.length} log group{selectedLogGroups.length !== 1 && 's'} selected
|
||||
</Label>
|
||||
<Space layout="block" v={1.5} />
|
||||
<div>
|
||||
<Button onClick={handleApply} type="button" className={styles.addBtn}>
|
||||
Add log groups
|
||||
@@ -165,19 +182,7 @@ export const CrossAccountLogsQueryField = (props: CrossAccountLogsQueryProps) =>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles.selectedLogGroupsContainer}>
|
||||
{props.selectedLogGroups.map((lg) => (
|
||||
<div key={lg.value} className={styles.selectedLogGroup}>
|
||||
{lg.label}
|
||||
<IconButton
|
||||
size="sm"
|
||||
name="times"
|
||||
className={styles.removeButton}
|
||||
onClick={() => props.onChange(props.selectedLogGroups.filter((slg) => slg.value !== lg.value))}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<SelectedLogsGroups selectedLogGroups={props.selectedLogGroups} onChange={props.onChange}></SelectedLogsGroups>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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(<SelectedLogsGroups {...defaultProps} selectedLogGroups={[]} />);
|
||||
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(<SelectedLogsGroups {...defaultProps} />);
|
||||
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(<SelectedLogsGroups {...defaultProps} selectedLogGroups={selectedLogGroups} />);
|
||||
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(<SelectedLogsGroups {...defaultProps} selectedLogGroups={[]} />);
|
||||
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(<SelectedLogsGroups {...defaultProps} selectedLogGroups={selectedLogGroups} />);
|
||||
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(<SelectedLogsGroups {...defaultProps} selectedLogGroups={selectedLogGroups} />);
|
||||
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(<SelectedLogsGroups {...defaultProps} selectedLogGroups={[]} />);
|
||||
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(<SelectedLogsGroups {...defaultProps} selectedLogGroups={selectedLogGroups} />);
|
||||
await waitFor(() => expect(screen.getByText('Clear selection')).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className={styles.selectedLogGroupsContainer}>
|
||||
{visibleSelectecLogGroups.map((lg) => (
|
||||
<Button
|
||||
key={lg.value}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="times"
|
||||
className={styles.removeButton}
|
||||
onClick={() => {
|
||||
onChange(selectedLogGroups.filter((slg) => slg.value !== lg.value));
|
||||
}}
|
||||
>
|
||||
{lg.label}
|
||||
</Button>
|
||||
))}
|
||||
{visibleSelectecLogGroups.length !== selectedLogGroups.length && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
className={styles.removeButton}
|
||||
onClick={() => setVisibleSelectecLogGroups(selectedLogGroups)}
|
||||
>
|
||||
Show all
|
||||
</Button>
|
||||
)}
|
||||
{selectedLogGroups.length > 0 && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="times"
|
||||
fill="outline"
|
||||
className={styles.removeButton}
|
||||
onClick={() => setShowConfirm(true)}
|
||||
>
|
||||
Clear selection
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<ConfirmModal
|
||||
isOpen={showConfirm}
|
||||
title="Clear Log Group Selection"
|
||||
body="Are you sure you want to clear all log groups?"
|
||||
confirmText="Yes"
|
||||
dismissText="No"
|
||||
icon="exclamation-triangle"
|
||||
onConfirm={() => {
|
||||
setShowConfirm(false);
|
||||
onChange([]);
|
||||
}}
|
||||
onDismiss={() => setShowConfirm(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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<Query, PhlareDataSou
|
||||
async getLabelNames(): Promise<string[]> {
|
||||
return await super.getResource('labelNames');
|
||||
}
|
||||
|
||||
async importFromAbstractQueries(abstractQueries: AbstractQuery[]): Promise<Query[]> {
|
||||
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<AbstractQuery[]> {
|
||||
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: /[{}(),.]/,
|
||||
};
|
||||
|
||||
@@ -181,7 +181,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
|
||||
// Check whether this is a trace ID or traceQL query by checking if it only contains hex characters
|
||||
if (queryValue.trim().match(hexOnlyRegex)) {
|
||||
// There's only hex characters so let's assume that this is a trace ID
|
||||
reportInteraction('grafana_traces_traceql_traceID_queried', {
|
||||
reportInteraction('grafana_traces_traceID_queried', {
|
||||
datasourceType: 'tempo',
|
||||
app: options.app ?? '',
|
||||
query: queryValue ?? '',
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { languages } from 'monaco-editor';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { CodeEditor, Monaco, monacoTypes, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { createErrorNotification } from '../../../../core/copy/appNotification';
|
||||
@@ -10,7 +11,7 @@ import { notifyApp } from '../../../../core/reducers/appNotification';
|
||||
import { dispatch } from '../../../../store/store';
|
||||
import { TempoDatasource } from '../datasource';
|
||||
|
||||
import { CompletionProvider } from './autocomplete';
|
||||
import { CompletionProvider, CompletionType } from './autocomplete';
|
||||
import { languageDefinition } from './traceql';
|
||||
|
||||
interface Props {
|
||||
@@ -51,7 +52,7 @@ export function TraceQLEditor(props: Props) {
|
||||
}}
|
||||
onBeforeEditorMount={ensureTraceQL}
|
||||
onEditorDidMount={(editor, monaco) => {
|
||||
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<string, unknown> = { 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;
|
||||
|
||||
@@ -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<string>()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Array<SelectableValue<string>>> {
|
||||
let tagValues: Array<SelectableValue<string>> = [];
|
||||
let tagValues: Array<SelectableValue<string>>;
|
||||
|
||||
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,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user