From 3fa6ce2e3481d0604d51f3a48c0ca3d6f95395ab Mon Sep 17 00:00:00 2001 From: "Jens L." Date: Wed, 18 Jun 2025 13:36:40 +0200 Subject: [PATCH] enterprise/web/admin: OSM for events (#9287) * web: fix esbuild issue with style sheets Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious pain. This fix better identifies the value types (instances) being passed from various sources in the repo to the three *different* kinds of style processors we're using (the native one, the polyfill one, and whatever the heck Storybook does internally). Falling back to using older CSS instantiating techniques one era at a time seems to do the trick. It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content (FLoUC), it's the logic with which we're left. In standard mode, the following warning appears on the console when running a Flow: ``` Autofocus processing was blocked because a document already has a focused element. ``` In compatibility mode, the following **error** appears on the console when running a Flow: ``` crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'. at initDomMutationObservers (crawler-inject.js:1106:18) at crawler-inject.js:1114:24 at Array.forEach () at initDomMutationObservers (crawler-inject.js:1114:10) at crawler-inject.js:1549:1 initDomMutationObservers @ crawler-inject.js:1106 (anonymous) @ crawler-inject.js:1114 initDomMutationObservers @ crawler-inject.js:1114 (anonymous) @ crawler-inject.js:1549 ``` Despite this error, nothing seems to be broken and flows work as anticipated. * initial OSM for events Signed-off-by: Jens Langhammer * remove card title Signed-off-by: Jens Langhammer * split with volume Signed-off-by: Jens Langhammer * add pin Signed-off-by: Jens Langhammer * basic map selection Signed-off-by: Jens Langhammer * update pin Signed-off-by: Jens Langhammer * rewrite map points to be more imperative Signed-off-by: Jens Langhammer * zoom to fit Signed-off-by: Jens Langhammer --------- Signed-off-by: Jens Langhammer Co-authored-by: Ken Sternberg --- authentik/events/api/events.py | 1 + web/package-lock.json | 262 ++++++++++++++++++- web/package.json | 2 + web/src/admin/events/EventListPage.ts | 55 +++- web/src/admin/events/EventMap.ts | 139 ++++++++++ web/src/admin/events/EventVolumeChart.ts | 8 +- web/src/assets/images/map_pin.svg | 77 ++++++ web/src/common/events.ts | 2 + web/src/components/events/ObjectChangelog.ts | 1 - 9 files changed, 533 insertions(+), 14 deletions(-) create mode 100644 web/src/admin/events/EventMap.ts create mode 100644 web/src/assets/images/map_pin.svg diff --git a/authentik/events/api/events.py b/authentik/events/api/events.py index c073ae62bb..e00dbe1b20 100644 --- a/authentik/events/api/events.py +++ b/authentik/events/api/events.py @@ -138,6 +138,7 @@ class EventViewSet(ModelViewSet): from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField return [ + StrField(Event, "event_uuid"), StrField(Event, "app", suggest_options=True), StrField(Event, "client_ip"), JSONSearchField(Event, "user"), diff --git a/web/package-lock.json b/web/package-lock.json index 46438130a1..e458fe540b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -30,6 +30,8 @@ "@mdx-js/mdx": "^3.1.0", "@mrmarble/djangoql-completion": "^0.8.3", "@open-wc/lit-helpers": "^0.7.0", + "@openlayers-elements/core": "^0.4.0", + "@openlayers-elements/maps": "^0.4.0", "@patternfly/elements": "^4.1.0", "@patternfly/patternfly": "^4.224.2", "@sentry/browser": "^9.30.0", @@ -2589,6 +2591,48 @@ "@lit/reactive-element": "^1.0.0 || ^2.0.0" } }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-style-spec": { + "version": "13.28.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz", + "integrity": "sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/unitbezier": "^0.0.0", + "csscolorparser": "~1.0.2", + "json-stringify-pretty-compact": "^2.0.0", + "minimist": "^1.2.6", + "rw": "^1.3.3", + "sort-object": "^0.3.2" + }, + "bin": { + "gl-style-composite": "bin/gl-style-composite.js", + "gl-style-format": "bin/gl-style-format.js", + "gl-style-migrate": "bin/gl-style-migrate.js", + "gl-style-validate": "bin/gl-style-validate.js" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", + "license": "ISC" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz", + "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==", + "license": "BSD-2-Clause" + }, "node_modules/@mdx-js/mdx": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", @@ -3071,6 +3115,27 @@ "lit": "^2.0.0 || ^3.0.0" } }, + "node_modules/@openlayers-elements/core": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@openlayers-elements/core/-/core-0.4.0.tgz", + "integrity": "sha512-msY2QGYCYf5Zph16j08KszgqtHmMORCK7B5afpe5iM8c3FFSfjijUffiw93MGeowoN4Yo5jfkxuI2plpyidR0A==", + "license": "MIT", + "dependencies": { + "lit": "^3.1.4", + "ol": "^7.5.0" + } + }, + "node_modules/@openlayers-elements/maps": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@openlayers-elements/maps/-/maps-0.4.0.tgz", + "integrity": "sha512-uxGW3Lt1BVA8eC0HykXLZA4a3EfCU44FdGaudC4Xu0s+XYPOEPxCGLDCsWSuy67NvEUTFb+odu6mRDLofxdquA==", + "license": "MIT", + "dependencies": { + "@openlayers-elements/core": "^0.4.0", + "lit": "^3.1.4", + "ol": "^7.5.0" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -3971,6 +4036,12 @@ "lit": "^3.2.1" } }, + "node_modules/@petamoriken/float16": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.2.tgz", + "integrity": "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -12178,6 +12249,12 @@ "dev": true, "license": "MIT" }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -13381,6 +13458,12 @@ "node": ">= 0.4" } }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -15962,6 +16045,43 @@ "node": ">=6.9.0" } }, + "node_modules/geotiff": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.1.3.tgz", + "integrity": "sha512-PT6uoF5a1+kbC3tHmZSUsLHBp2QJlHasxxxxPW47QIY1VBKpFB+FcDvX+MxER6UzgLQZ0xDzJ9s48B9JbOCTqA==", + "license": "MIT", + "dependencies": { + "@petamoriken/float16": "^3.4.7", + "lerc": "^3.0.0", + "pako": "^2.0.4", + "parse-headers": "^2.0.2", + "quick-lru": "^6.1.1", + "web-worker": "^1.2.0", + "xml-utils": "^1.0.2", + "zstddec": "^0.1.0" + }, + "engines": { + "node": ">=10.19" + } + }, + "node_modules/geotiff/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/geotiff/node_modules/quick-lru": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz", + "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -18528,6 +18648,12 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-pretty-compact": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz", + "integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -18967,6 +19093,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/lerc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", + "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==", + "license": "Apache-2.0" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -19496,6 +19628,12 @@ "dev": true, "license": "MIT" }, + "node_modules/mapbox-to-css-font": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.5.tgz", + "integrity": "sha512-VJ6nB8emkO9VODI0Fk+TQ/0zKBTqmf/Pkt8Xv0kHstoc0iXRajA00DAid4Kc3K5xeFIOoiZrVxijEzj0GLVO2w==", + "license": "BSD-2-Clause" + }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -20924,7 +21062,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -22048,6 +22185,34 @@ "dev": true, "optional": true }, + "node_modules/ol": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/ol/-/ol-7.5.2.tgz", + "integrity": "sha512-HJbb3CxXrksM6ct367LsP3N+uh+iBBMdP3DeGGipdV9YAYTP0vTJzqGnoqQ6C2IW4qf8krw9yuyQbc9fjOIaOQ==", + "license": "BSD-2-Clause", + "dependencies": { + "earcut": "^2.2.3", + "geotiff": "^2.0.7", + "ol-mapbox-style": "^10.1.0", + "pbf": "3.2.1", + "rbush": "^3.0.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/openlayers" + } + }, + "node_modules/ol-mapbox-style": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-10.7.0.tgz", + "integrity": "sha512-S/UdYBuOjrotcR95Iq9AejGYbifKeZE85D9VtH11ryJLQPTZXZSW1J5bIXcr4AlAH6tyjPPHTK34AdkwB32Myw==", + "license": "BSD-2-Clause", + "dependencies": { + "@mapbox/mapbox-gl-style-spec": "^13.23.1", + "mapbox-to-css-font": "^2.4.1", + "ol": "^7.3.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -22378,6 +22543,12 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/parse-headers": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", + "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==", + "license": "MIT" + }, "node_modules/parse-ms": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", @@ -22528,6 +22699,19 @@ "node": ">= 14.16" } }, + "node_modules/pbf": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", + "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "license": "BSD-3-Clause", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/peek-readable": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", @@ -22989,6 +23173,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "license": "MIT" + }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -23204,6 +23394,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "license": "ISC" + }, "node_modules/ramda": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", @@ -23325,6 +23521,15 @@ "node": ">= 0.8" } }, + "node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "license": "MIT", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/rc9": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", @@ -24242,6 +24447,15 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/resolve.exports": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", @@ -25083,6 +25297,22 @@ "node": ">= 14" } }, + "node_modules/sort-asc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz", + "integrity": "sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.1.1.tgz", + "integrity": "sha512-jfZacW5SKOP97BF5rX5kQfJmRVZP5/adDUTY8fCSPvNcXDVpUEe2pr/iKGlcyZzchRJZrswnp68fgk3qBXgkJw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -25119,6 +25349,18 @@ "node": ">=0.10.0" } }, + "node_modules/sort-object": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-0.3.2.tgz", + "integrity": "sha512-aAQiEdqFTTdsvUFxXm3umdo04J7MRljoVGbBlkH7BgNsMvVNAJyGj7C/wV1A8wHWAJj/YikeZbfuCKqhggNWGA==", + "dependencies": { + "sort-asc": "^0.1.0", + "sort-desc": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sort-object-keys": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz", @@ -28351,6 +28593,12 @@ "license": "MIT", "optional": true }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, "node_modules/webauthn-polyfills": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/webauthn-polyfills/-/webauthn-polyfills-0.1.7.tgz", @@ -28925,6 +29173,12 @@ "repeat-string": "^1.5.2" } }, + "node_modules/xml-utils": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz", + "integrity": "sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==", + "license": "CC0-1.0" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -29182,6 +29436,12 @@ "zod": "^3.18.0" } }, + "node_modules/zstddec": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz", + "integrity": "sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==", + "license": "MIT AND BSD-3-Clause" + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/web/package.json b/web/package.json index f9caf5d87d..aaae6cdf55 100644 --- a/web/package.json +++ b/web/package.json @@ -101,6 +101,8 @@ "@mdx-js/mdx": "^3.1.0", "@mrmarble/djangoql-completion": "^0.8.3", "@open-wc/lit-helpers": "^0.7.0", + "@openlayers-elements/core": "^0.4.0", + "@openlayers-elements/maps": "^0.4.0", "@patternfly/elements": "^4.1.0", "@patternfly/patternfly": "^4.224.2", "@sentry/browser": "^9.30.0", diff --git a/web/src/admin/events/EventListPage.ts b/web/src/admin/events/EventListPage.ts index 758491a0dc..ff2ecf92da 100644 --- a/web/src/admin/events/EventListPage.ts +++ b/web/src/admin/events/EventListPage.ts @@ -1,3 +1,7 @@ +import "#elements/Tabs"; +import { WithLicenseSummary } from "#elements/mixins/license"; +import { updateURLParams } from "#elements/router/RouteMatch"; +import "@goauthentik/admin/events/EventMap"; import "@goauthentik/admin/events/EventVolumeChart"; import { EventGeo, EventUser } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; @@ -15,10 +19,12 @@ import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { Event, EventsApi } from "@goauthentik/api"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; + +import { Event, EventsApi, LicenseSummaryStatusEnum } from "@goauthentik/api"; @customElement("ak-event-list") -export class EventListPage extends TablePage { +export class EventListPage extends WithLicenseSummary(TablePage) { expandable = true; supportsQL = true; @@ -39,11 +45,15 @@ export class EventListPage extends TablePage { order = "-created"; static get styles(): CSSResult[] { - return super.styles.concat(css` - .pf-m-no-padding-bottom { - padding-bottom: 0; - } - `); + // @ts-expect-error + return super.styles.concat( + PFGrid, + css` + .pf-m-no-padding-bottom { + padding-bottom: 0; + } + `, + ); } async apiEndpoint(): Promise> { @@ -62,16 +72,39 @@ export class EventListPage extends TablePage { } renderSectionBefore(): TemplateResult { - return html` -
+ if (this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed) { + return html`
-
- `; + ) => { + this.search = `event_uuid = "${ev.detail.eventId}"`; + this.page = 1; + updateURLParams({ + search: this.search, + tablePage: this.page, + }); + this.fetch(); + }} + > +
`; + } + return html``; } row(item: EventWithContext): SlottedTemplateResult[] { diff --git a/web/src/admin/events/EventMap.ts b/web/src/admin/events/EventMap.ts new file mode 100644 index 0000000000..d56074dd4d --- /dev/null +++ b/web/src/admin/events/EventMap.ts @@ -0,0 +1,139 @@ +import { EventWithContext } from "#common/events"; +import { globalAK } from "#common/global"; +import { PaginatedResponse } from "#elements/table/Table"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@openlayers-elements/core/ol-layer-vector"; +import type OlLayerVector from "@openlayers-elements/core/ol-layer-vector"; +import "@openlayers-elements/core/ol-map"; +import type OlMap from "@openlayers-elements/core/ol-map"; +import "@openlayers-elements/maps/ol-layer-openstreetmap"; +import "@openlayers-elements/maps/ol-select"; +import Feature from "ol/Feature"; +import { Point } from "ol/geom"; +import { fromLonLat } from "ol/proj"; +import Icon from "ol/style/Icon"; +import Style from "ol/style/Style"; + +import { CSSResult, PropertyValues, TemplateResult, css, html } from "lit"; +import { customElement, property, query } from "lit/decorators.js"; + +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { Event } from "@goauthentik/api"; + +/** + * + * @event {select-event} - Fired when an event is selected on the map. ID of the event is contained + * in the `Event.detail` field. + * + */ +@customElement("ak-events-map") +export class EventMap extends AKElement { + @property({ attribute: false }) + events?: PaginatedResponse; + + @query("ol-layer-vector") + vectorLayer?: OlLayerVector; + + @query("ol-map") + map?: OlMap; + + @property({ type: Number }) + zoomPaddingPx = 100; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFCard, + css` + .pf-c-card, + ol-map { + height: 24rem; + } + :host([theme="dark"]) ol-map { + filter: invert(100%) hue-rotate(180deg); + } + `, + ]; + } + + updated(_changedProperties: PropertyValues): void { + if (!_changedProperties.has("events")) { + return; + } + if (!this.vectorLayer?.source || !this.map?.map) { + return; + } + // Remove all existing points + this.vectorLayer.source.clear(); + // Re-add them + this.events?.results + .filter((event) => { + if (!Object.hasOwn(event.context, "geo")) { + return false; + } + const geo = (event as EventWithContext).context.geo; + if (!geo?.lat || !geo.long) { + return false; + } + return true; + }) + .forEach((event) => { + const geo = (event as EventWithContext).context.geo!; + const point = new Point(fromLonLat([geo.long!, geo.lat!])); + const feature = new Feature({ + geometry: point, + }); + feature.setStyle( + new Style({ + image: new Icon({ + anchor: [0.5, 1], + offset: [0, 0], + opacity: 1, + scale: 1, + rotateWithView: false, + rotation: 0, + src: `${globalAK().api.base}static/dist/assets/images/map_pin.svg`, + }), + }), + ); + feature.setId(event.pk); + this.vectorLayer?.source?.addFeature(feature); + }); + // Zoom to show points better + this.map.map.getView().fit(this.vectorLayer.source.getExtent(), { + padding: [ + this.zoomPaddingPx, + this.zoomPaddingPx, + this.zoomPaddingPx, + this.zoomPaddingPx, + ], + duration: 500, + maxZoom: 4.5, + }); + } + + render(): TemplateResult { + return html`
+ + ) => { + const eventId = ev.detail.feature.getId(); + this.dispatchEvent( + new CustomEvent("select-event", { + composed: true, + bubbles: true, + detail: { + eventId: eventId, + }, + }), + ); + }} + > + + + +
`; + } +} diff --git a/web/src/admin/events/EventVolumeChart.ts b/web/src/admin/events/EventVolumeChart.ts index fa38d0e6e8..07f36f0761 100644 --- a/web/src/admin/events/EventVolumeChart.ts +++ b/web/src/admin/events/EventVolumeChart.ts @@ -11,11 +11,14 @@ import { EventVolume, EventsApi, EventsEventsListRequest } from "@goauthentik/ap @customElement("ak-events-volume-chart") export class EventVolumeChart extends EventChart { + @property({ attribute: "with-map", type: Boolean }) + withMap = false; + _query?: EventsEventsListRequest; @property({ attribute: false }) set query(value: EventsEventsListRequest | undefined) { - if (JSON.stringify(this._query) === JSON.stringify(value)) return; + if (JSON.stringify(value) !== JSON.stringify(this._query)) return; this._query = value; this.refreshHandler(); } @@ -24,6 +27,9 @@ export class EventVolumeChart extends EventChart { return super.styles.concat( PFCard, css` + :host([with-map]) .pf-c-card { + height: 24rem; + } .pf-c-card { height: 20rem; } diff --git a/web/src/assets/images/map_pin.svg b/web/src/assets/images/map_pin.svg new file mode 100644 index 0000000000..f2a2a0072d --- /dev/null +++ b/web/src/assets/images/map_pin.svg @@ -0,0 +1,77 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/web/src/common/events.ts b/web/src/common/events.ts index 418c9a0a10..e07fac5672 100644 --- a/web/src/common/events.ts +++ b/web/src/common/events.ts @@ -12,6 +12,8 @@ export interface EventGeo { city?: string; country?: string; continent?: string; + lat?: number; + long?: number; } export interface EventModel { diff --git a/web/src/components/events/ObjectChangelog.ts b/web/src/components/events/ObjectChangelog.ts index 653d5ee37f..7cc5665d9d 100644 --- a/web/src/components/events/ObjectChangelog.ts +++ b/web/src/components/events/ObjectChangelog.ts @@ -76,7 +76,6 @@ export class ObjectChangelog extends Table { html`
${formatElapsedTime(item.created)}
${item.created.toLocaleString()}`, html`
${item.clientIp || msg("-")}
- ${EventGeo(item)}`, ]; }