Compare commits

...

49 Commits

Author SHA1 Message Date
b865ed4392 web: keeping up with the navigational changes mandated by brands and anterprise 2024-06-13 13:16:40 -07:00
f0afac0b87 Merge branch 'main' into web/sidebar-with-live-content-3
* main: (1301 commits)
  website/developer-docs: add a baby Style Guide (#9900)
  website/integrations: gitlab: update certificate key pair location and specify sha (#9925)
  root: handle asgi exception (#10085)
  website: bump prettier from 3.3.1 to 3.3.2 in /website (#10082)
  web: bump prettier from 3.3.1 to 3.3.2 in /web (#10081)
  core: bump google-api-python-client from 2.132.0 to 2.133.0 (#10083)
  web: bump prettier from 3.3.1 to 3.3.2 in /tests/wdio (#10079)
  web: bump chromedriver from 125.0.3 to 126.0.0 in /tests/wdio (#10078)
  web: bump @sentry/browser from 8.8.0 to 8.9.1 in /web in the sentry group (#10080)
  web: bump braces from 3.0.2 to 3.0.3 in /web (#10077)
  website: bump braces from 3.0.2 to 3.0.3 in /website (#10076)
  web: bump braces from 3.0.2 to 3.0.3 in /tests/wdio (#10075)
  core: bump azure-identity from 1.16.0 to 1.16.1 (#10071)
  rbac: filters: fix missing attribute for unauthenticated requests (#10061)
  tests/e2e: docker-compose.yml: remove version element forgotten last time (#10067)
  providers/microsoft_entra: fix error when updating connection attributes (#10039)
  website/integrations: aws: fix about service link (#10062)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#10060)
  core: bump github.com/redis/go-redis/v9 from 9.5.2 to 9.5.3 (#10046)
  core: bump github.com/gorilla/websocket from 1.5.1 to 1.5.2 (#10047)
  ...
2024-06-13 11:06:19 -07:00
a31588668d Not sure how a messed-up version of this got into this commit, but it needs to go. 2024-01-08 14:20:17 -08:00
9768684c3c Merge branch 'dev' into web/sidebar-with-live-content-3
* dev: (131 commits)
  web: Replace  calls to `rootInterface()?.tenant?` with a contextual `this.tenant` object (#7778)
  web: abstract `rootInterface()?.config?.capabilities.includes()` into `.can()` (#7737)
  web: update some locale details (#8090)
  web: bump the eslint group in /web with 2 updates (#8082)
  web: bump rollup from 4.9.2 to 4.9.4 in /web (#8083)
  core: bump github.com/redis/go-redis/v9 from 9.3.1 to 9.4.0 (#8085)
  web: bump the eslint group in /tests/wdio with 2 updates (#8086)
  website: bump @types/react from 18.2.46 to 18.2.47 in /website (#8088)
  stages/user_login: only set last_ip in session if a binding is given (#8074)
  providers/oauth2: fix missing nonce in token endpoint not being saved (#8073)
  core: bump goauthentik.io/api/v3 from 3.2023105.3 to 3.2023105.5 (#8066)
  providers/oauth2: fix missing nonce in id_token (#8072)
  rbac: fix error when looking up permissions for now uninstalled apps (#8068)
  web/flows: fix device picker incorrect foreground color (#8067)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#8061)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#8062)
  website: bump postcss from 8.4.32 to 8.4.33 in /website (#8063)
  web: bump the sentry group in /web with 2 updates (#8064)
  core: bump golang.org/x/sync from 0.5.0 to 0.6.0 (#8065)
  website/docs: add link to our example flows (#8052)
  ...
2024-01-08 13:44:49 -08:00
cde94c2377 Merge branch 'main' into web/sidebar-with-live-content-3
* main:
  web: dark/light theme fixes (#7872)
  web: replace 'description-list' with list of descriptions (#7392)
  web: expressing success (#7830)
  web: fix turnstile types after update (#7854)
  core: bump github.com/google/uuid from 1.4.0 to 1.5.0 (#7866)
  website: bump @types/react from 18.2.43 to 18.2.45 in /website (#7865)
  web: bump wdio-wait-for from 3.0.9 to 3.0.10 in /tests/wdio (#7867)
  website/blog: okta part two blog (#7863)
  web: bump lit-analyzer from 2.0.1 to 2.0.2 in /web (#7858)
  web: bump the babel group in /web with 4 updates (#7856)
  web: bump the eslint group in /web with 2 updates (#7857)
  web: bump rollup from 4.7.0 to 4.8.0 in /web (#7859)
  web: bump the eslint group in /tests/wdio with 2 updates (#7860)
  web: refactor the table renderer for legibility  (#7433)
  documentation: Improve explanation of `kubernetes_json_patches` (#7832)
  root: update security policy to include link to cure53 report (#7853)
2023-12-13 11:25:41 -08:00
b0e852afca Merge branch 'main' into web/sidebar-with-live-content-3
* main:
  scripts: postgres, redis: only listen on localhost (#7849)
  website: bump @types/react from 18.2.42 to 18.2.43 in /website (#7840)
  web: bump ts-node from 10.9.1 to 10.9.2 in /tests/wdio (#7846)
  core: bump github.com/go-openapi/runtime from 0.26.0 to 0.26.2 (#7841)
  website: bump prettier from 3.1.0 to 3.1.1 in /website (#7839)
  web: bump the esbuild group in /web with 2 updates (#7842)
  web: bump rollup from 4.6.1 to 4.7.0 in /web (#7843)
  web: bump prettier from 3.1.0 to 3.1.1 in /web (#7844)
  web: bump the wdio group in /tests/wdio with 2 updates (#7845)
  web: bump prettier from 3.1.0 to 3.1.1 in /tests/wdio (#7847)
  translate: Updates for file web/xliff/en.xlf in fr (#7851)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#7850)
  core: bump python from 3.12.0-slim-bookworm to 3.12.1-slim-bookworm
  web/flows: update flow background (#7833)
  web/flows: fix logo height (#7834)
  Fix cache related image build issues
2023-12-11 08:28:43 -08:00
e35cefb63e Just a little clean-up. 2023-12-08 12:38:51 -08:00
2a11356961 Merge branch 'dev' into web/sidebar-with-live-content-3
* dev: (72 commits)
  web/flows: show logo in card (#7824)
  blueprints: improve file change handler (#7813)
  web/user: fix search not updating app (#7825)
  web: bump the storybook group in /web with 5 updates (#7819)
  core: compile backend translations (#7827)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in de (#7812)
  core: bump github.com/go-openapi/strfmt from 0.21.8 to 0.21.9 (#7814)
  ci: bump actions/stale from 8 to 9 (#7815)
  web: bump the wdio group in /tests/wdio with 1 update (#7816)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7820)
  web: bump the sentry group in /web with 2 updates (#7817)
  web: bump vite-tsconfig-paths from 4.2.1 to 4.2.2 in /web (#7818)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7821)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#7822)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#7823)
  web: bump typescript from 5.3.2 to 5.3.3 in /web (#7806)
  website: bump typescript from 5.3.2 to 5.3.3 in /website (#7807)
  web: bump typescript from 5.3.2 to 5.3.3 in /tests/wdio (#7808)
  core: bump goauthentik.io/api/v3 from 3.2023104.1 to 3.2023104.2 (#7809)
  ci: bump actions/setup-go from 4 to 5
  ...
2023-12-08 12:26:33 -08:00
a3673906c7 web: provide three-tier sidebar with subcategories
This commit implements a three-tier sidebar with subcategories.

The first thing is that we've refactored the Sidebar into a holistic entity; rather than be built in
pieces, it's constructed declaratively from a tree of entries, much in the same way routes are, and
for much the same reason<sup>1</sup>.

The AdminSidebar element only provides the list of entries to show and some of the controls
necessary to show/hide the sidebar.  Because the sidebar requires a rich collection of objects
retrieved from the back-end, to avoid cluttering the AdminSidebar each of those sublists of
TypeCreate have been isolated into their own controllers.

The SidebarTypeController isn't even a strongly reactive controller; all it does is fetch the
TypeCreate collection and notify the client object that the fetch has completed. The client can then
call the `.entries()` method on the controller to get the sub-tree of entries for the TypeCreate
object.

The Sidebar has been slightly (!) refactored so that it's emphatic about what it does: it shows the
brand, nav, and user sections of the sidebar. The styling has been moved to a separate file, the
better to emphasize this.

The SidebarItems file is where all the magic-- and a lot of ugly-- is hidden.

The main purpose of the SidebarItems is to render the tree of entries passed to it.  That's it.  But
it also has to be able to read the URL and highlight which entry is currently being shown by the
router, and it has to be able to open up all the parent objects of the "current" link so that the
user gets a clear sense of where they are navigationally.

Most messy: the `reclick()` function intercepts clicks on anchors and, using the same logic as the
router, decides if the router would *not* handle the navigation event.  If the router would not, it
takes on the responsibility for reaching into the currently visible table, modifying the filter and
triggering a new `.fetch()`.

Somewhere along the way I boyscoutted another `switch` statement or two into lookup expressions.

---

<sup>1</sup>&nbsp; One of the reasons for this is that the Administrator Application's sidebar,
routes, and command palette will all get their data from a single source of truth, and that single
source will be independent of any of those.  This is a step in that direction.
2023-11-28 11:11:35 -08:00
f2834cc7e2 web: provide tier-3 click capability.
This commit enables the search function to work when changing from one tier 3 link to another in
the tier 2 category (Providers, Events, Stages, etc.).  Because the router ignores such changes,
we must bring those changes to the attention of the receiver by hand.

This commit makes the recepient Table object locatable with a data attribute tag and a painstakingly
researched path to its most common location in our code.

It then checks any `anchor:click` event from the Sidebar to see if its likely to be elided
by the router.  If it is, we take over, identify the Table object, set or clear its search
state to the desired state, and call the `.fetch()` method on the Table to initiate a new
search with that state.

This is, frankly, _gross_.  The URL and internal states mirror the actual desired state more
or less by accident.  But it's what we've got to work with at the moment.
2023-11-27 15:48:24 -08:00
5b898bef01 Merge branch 'main' into web/sidebar-with-live-content-3
* main: (47 commits)
  web: bump the wdio group in /tests/wdio with 2 updates (#7702)
  events: fix lint (#7700)
  events: add better fallback for sanitize_item to ensure everything can be saved as JSON (#7694)
  web: bump the wdio group in /tests/wdio with 4 updates (#7696)
  events: include user agent in events (#7693)
  web: fix labels on group view page (#7677)
  website/docs: Add OIDC auth integration with Nextcloud (#7406)
  web: fix locale (#7689)
  core: bump python from 3.11.5-bookworm to 3.12.0-bookworm (#7048)
  translate: Updates for file web/xliff/en.xlf in zh_TW (#7688)
  web: bump pyright from 1.1.336 to 1.1.337 in /web (#7681)
  core: bump sentry-sdk from 1.35.0 to 1.36.0 (#7683)
  website: bump prism-react-renderer from 2.2.0 to 2.3.0 in /website (#7685)
  web: bump the sentry group in /web with 2 updates (#7679)
  web: bump rollup from 4.5.0 to 4.5.1 in /web (#7680)
  web: bump @types/codemirror from 5.60.14 to 5.60.15 in /web (#7682)
  web: bump the wdio group in /tests/wdio with 2 updates (#7684)
  website: bump react-tooltip from 5.23.0 to 5.24.0 in /website (#7686)
  core: bump goauthentik.io/api/v3 from 3.2023103.4 to 3.2023104.1 (#7687)
  website/blog: Blog on security (#7671)
  ...
2023-11-27 15:07:10 -08:00
6b9201907d Merge branch 'dev' into web/sidebar-with-live-content-3
* dev: (21 commits)
  sources/ldap: clean-up certs written from db (#7617)
  web: bump the eslint group in /tests/wdio with 1 update (#7635)
  core: compile backend translations (#7637)
  core: bump psycopg from 3.1.12 to 3.1.13 (#7625)
  core: bump ruff from 0.1.5 to 0.1.6 (#7626)
  core: bump twilio from 8.10.1 to 8.10.2 (#7627)
  web: bump the eslint group in /web with 1 update (#7629)
  web: bump the esbuild group in /web with 2 updates (#7630)
  web: bump rollup from 4.4.1 to 4.5.0 in /web (#7631)
  web: bump core-js from 3.33.2 to 3.33.3 in /web (#7633)
  core: bump goauthentik.io/api/v3 from 3.2023103.3 to 3.2023103.4 (#7634)
  web: bump the wdio group in /tests/wdio with 4 updates (#7636)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_TW (#7628)
  root: specify node and python versions in respective config files, deduplicate in CI (#7620)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7619)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7618)
  tests: better per-test timeouts (#7612)
  web: bump API Client version (#7613)
  stages/identification: add option to pretend user exists (#7610)
  events: stop spam (#7611)
  ...
2023-11-20 07:51:04 -08:00
2ec8932891 Merge branch 'main' into dev
* main: (63 commits)
  sources/ldap: clean-up certs written from db (#7617)
  web: bump the eslint group in /tests/wdio with 1 update (#7635)
  core: compile backend translations (#7637)
  core: bump psycopg from 3.1.12 to 3.1.13 (#7625)
  core: bump ruff from 0.1.5 to 0.1.6 (#7626)
  core: bump twilio from 8.10.1 to 8.10.2 (#7627)
  web: bump the eslint group in /web with 1 update (#7629)
  web: bump the esbuild group in /web with 2 updates (#7630)
  web: bump rollup from 4.4.1 to 4.5.0 in /web (#7631)
  web: bump core-js from 3.33.2 to 3.33.3 in /web (#7633)
  core: bump goauthentik.io/api/v3 from 3.2023103.3 to 3.2023103.4 (#7634)
  web: bump the wdio group in /tests/wdio with 4 updates (#7636)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_TW (#7628)
  root: specify node and python versions in respective config files, deduplicate in CI (#7620)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7619)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7618)
  tests: better per-test timeouts (#7612)
  web: bump API Client version (#7613)
  stages/identification: add option to pretend user exists (#7610)
  events: stop spam (#7611)
  ...
2023-11-20 07:50:46 -08:00
a9886b047e web: further refinements to the sidebar
This commit restores the onHashChange functionality, using an
on-demand reverse map (there really aren't that many objects in the
nav tree) to make sure all of the parent entities are also listed
in the "expanded" listing to make sure the target object is still
visible.  Along the way, several type lever errors were corrected.
Two major pieces of functionality were extracted from the Sidebar
function as they're mostly consumers/filters of the information
provided, and don't need to be in the Sidebar itself.
2023-11-17 14:47:47 -08:00
a0dfe7ce78 fixed bug where the record could get lost. 2023-11-17 11:44:24 -08:00
c471428c6b Revert to a prior version; fix bottom border so tier-2 elements with children don't have a separator, as before. 2023-11-17 11:34:51 -08:00
83e934f80c Removed the consoe.log 2023-11-17 11:18:43 -08:00
5386f0f4c3 Streamline TypeCreate lists. This commit removes the highly repetitive definitions for each
of the TypeCreate objects and replaces them with a single generic ReactiveController, which it
then instantiates six times, but at least they're shorter!
2023-11-17 11:15:58 -08:00
d5875a597b Merge branch 'main' into web/sidebar-with-live-content-3
* main: (42 commits)
  stages/authenticator_totp: fix API validation error due to choices (#7608)
  website: fix pricing page inconsistency (#7607)
  web: bump API Client version (#7602)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7603)
  core: bump goauthentik.io/api/v3 from 3.2023103.2 to 3.2023103.3 (#7606)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7604)
  Revert "web: bump @lit-labs/context from 0.4.1 to 0.5.1 in /web (#7486)"
  root: fix API schema for kotlin (#7601)
  web: bump @lit-labs/context from 0.4.1 to 0.5.1 in /web (#7486)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7583)
  events: fix missing model_* events when not directly authenticated (#7588)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_TW (#7594)
  providers/scim: fix missing schemas attribute for User and Group (#7477)
  core: bump pydantic from 2.5.0 to 2.5.1 (#7592)
  web/admin: contextually add user to group when creating user from group page (#7586)
  website/blog: title and slug change (#7585)
  events: sanitize functions (#7587)
  stages/email: use uuid for email confirmation token instead of username (#7581)
  website/blog: Blog about zero trust and wireguard (#7567)
  ci: translation-advice: avoid commenting after make i18n-extract
  ...
2023-11-17 09:34:15 -08:00
25ecc21d6d Merge branch 'dev' into web/sidebar-with-live-content-3
* dev:
2023-11-17 09:34:09 -08:00
ff78f2f00a web: almost there with the sidebar
The actual behavior is more or less what I expected.  What's missing is:

- Persistence of location (the hover effect fades with a click anywhere else)
- Proper testing of the oddities
- Full (or any!) responsiveness when moving between third-tier links in the same category

Stretch goal:
- Remembering the state of the sidebar when transitioning between the user and the admin (this will require using some localstorage, I suspect).

I also think that in my rush there's a bit of lost internal coherency.  I'd like to figure out what's wiggling around my brain and solve that discomfort.
2023-11-16 14:59:02 -08:00
3c277f14c8 web: sidebar third tier
The third tier works!  The only problem is that route isn't responsive, and I'm not sure why.
If you leave the `Providers` and go somewhere else, then click on a third-tier, the filter
works fine.  But if you click on one third-tier and then another, the filter doesn't
change.  Must investigate.
2023-11-16 11:03:16 -08:00
d539884204 web: continuing with the Sidebar
I've finally reached a stage where I have a framework I can build upon, but what
a pain in the posterior it was to get here.  Keeping the entire navigation list
within a single DOM is a solid idea, but porting from the original code to this
proved to be unreasonably kludegy.  Instead, I started from scratch, adding each
step along the way, a sort of Transformation Priority Premise, and testing each
step to make sure it was all behaving as expected.

So far, so good.

The remaining details are not trivial: I have to figure out how to express the
different classes of actions, and get the third tier working, but at least
the React version gives us hints.
2023-11-16 10:38:36 -08:00
476adef4ea Merge branch 'main' into web/sidebar-with-live-content-2
* main: (24 commits)
  internal: remove special route for /outpost.goauthentik.io (#7539)
  providers/proxy: Fix duplicate cookies when using file system store. (#7541)
  web: bump API Client version (#7543)
  sources/ldap: add check command to verify ldap connectivity (#7263)
  internal: remove deprecated metrics (#7540)
  core: compile backend translations (#7538)
  web: bump prettier from 3.0.3 to 3.1.0 in /web (#7528)
  web: bump @trivago/prettier-plugin-sort-imports from 4.2.1 to 4.3.0 in /web (#7531)
  web: bump rollup from 4.3.0 to 4.4.0 in /web (#7529)
  core: bump celery from 5.3.4 to 5.3.5 (#7536)
  web: bump @formatjs/intl-listformat from 7.5.1 to 7.5.2 in /web (#7530)
  web: bump prettier from 3.0.3 to 3.1.0 in /tests/wdio (#7532)
  web: bump @trivago/prettier-plugin-sort-imports from 4.2.1 to 4.3.0 in /tests/wdio (#7533)
  website: bump prettier from 3.0.3 to 3.1.0 in /website (#7534)
  website: bump prism-react-renderer from 2.1.0 to 2.2.0 in /website (#7535)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_TW (#7537)
  root: Restructure broker / cache / channel / result configuration (#7097)
  core: bump twilio from 8.10.0 to 8.10.1 (#7474)
  web: bump axios from 1.5.0 to 1.6.1 in /web (#7518)
  web: bump wdio-wait-for from 3.0.7 to 3.0.8 in /tests/wdio (#7514)
  ...
2023-11-14 10:25:04 -08:00
3e905cc956 web: refactor sidebar capabilities for categorical subsections
Move open/close logic into the ak-admin-sidebar itself.

This commit removes the responsibility for opening/closing the sidebar from the interface parent
code and places it inside the sidebar entirely.  Since the Django invocation passes none of the
properties ak-interface-admin is capable of receiving, this seems like a safe operation.

The sidebar now assumes the responsibility for hooking up the window event listeners for open/close
and resize.

On connection to the DOM, and on resize, the sidebar checks to see if the viewport width meets the
criteria for a behavioral change (slide-overlay vs slide-push), and on slide-push automatically
opens the sidebar on the assumption that there's plenty of room. In order to support more dynamic
styling going forward, I've substituted the 1280px with 80rem, which is the same, but allows for
some better styling if someone with older eyes needs to "zoom in" on the whole thing with a larger
font size.

The hide/show code involves "reaching up" to touch the host's classList.  There's a comment
indicating that this is a slightly fragile thing to do, but in a well-known way.
2023-11-13 15:36:39 -08:00
e3b1ba63a6 Function to help generate sizing solutions across Javascript and CSS. 2023-11-13 14:53:05 -08:00
2aed74bd9f Merge branch 'main' into dev
* main: (24 commits)
  internal: remove special route for /outpost.goauthentik.io (#7539)
  providers/proxy: Fix duplicate cookies when using file system store. (#7541)
  web: bump API Client version (#7543)
  sources/ldap: add check command to verify ldap connectivity (#7263)
  internal: remove deprecated metrics (#7540)
  core: compile backend translations (#7538)
  web: bump prettier from 3.0.3 to 3.1.0 in /web (#7528)
  web: bump @trivago/prettier-plugin-sort-imports from 4.2.1 to 4.3.0 in /web (#7531)
  web: bump rollup from 4.3.0 to 4.4.0 in /web (#7529)
  core: bump celery from 5.3.4 to 5.3.5 (#7536)
  web: bump @formatjs/intl-listformat from 7.5.1 to 7.5.2 in /web (#7530)
  web: bump prettier from 3.0.3 to 3.1.0 in /tests/wdio (#7532)
  web: bump @trivago/prettier-plugin-sort-imports from 4.2.1 to 4.3.0 in /tests/wdio (#7533)
  website: bump prettier from 3.0.3 to 3.1.0 in /website (#7534)
  website: bump prism-react-renderer from 2.1.0 to 2.2.0 in /website (#7535)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_TW (#7537)
  root: Restructure broker / cache / channel / result configuration (#7097)
  core: bump twilio from 8.10.0 to 8.10.1 (#7474)
  web: bump axios from 1.5.0 to 1.6.1 in /web (#7518)
  web: bump wdio-wait-for from 3.0.7 to 3.0.8 in /tests/wdio (#7514)
  ...
2023-11-13 09:07:42 -08:00
2545815f08 Merge branch 'main' into web/sidebar-with-live-content
* main: (23 commits)
  web: bump API Client version (#7513)
  web: bump the sentry group in /web with 2 updates (#7500)
  core: bump pytest-django from 4.6.0 to 4.7.0 (#7497)
  core: bump black from 23.10.1 to 23.11.0 (#7498)
  core: bump ruff from 0.1.4 to 0.1.5 (#7499)
  core: bump golang.org/x/oauth2 from 0.13.0 to 0.14.0 (#7501)
  web: bump the wdio group in /tests/wdio with 3 updates (#7502)
  release: 2023.10.3
  ci: fix permissions for release pipeline to publish binaries (#7512)
  website/docs: update release notes for 2023.10.3
  core: fix worker beat toggle inverted (#7508)
  website/docs: update release notes for 2023.10.3
  website/docs: fix anchor link (#7492)
  consistent variable name
  add more tooltips and add device authn/authz
  fix wrong color
  events: fix gdpr compliance always running
  website/docs: Fix a small grammar issue (#7490)
  core: bump golang from 1.21.3-bookworm to 1.21.4-bookworm
  web: bump pyright from 1.1.334 to 1.1.335 in /web
  ...
2023-11-09 13:17:58 -08:00
657089eac9 Merge branch 'main' into dev
* main: (23 commits)
  web: bump API Client version (#7513)
  web: bump the sentry group in /web with 2 updates (#7500)
  core: bump pytest-django from 4.6.0 to 4.7.0 (#7497)
  core: bump black from 23.10.1 to 23.11.0 (#7498)
  core: bump ruff from 0.1.4 to 0.1.5 (#7499)
  core: bump golang.org/x/oauth2 from 0.13.0 to 0.14.0 (#7501)
  web: bump the wdio group in /tests/wdio with 3 updates (#7502)
  release: 2023.10.3
  ci: fix permissions for release pipeline to publish binaries (#7512)
  website/docs: update release notes for 2023.10.3
  core: fix worker beat toggle inverted (#7508)
  website/docs: update release notes for 2023.10.3
  website/docs: fix anchor link (#7492)
  consistent variable name
  add more tooltips and add device authn/authz
  fix wrong color
  events: fix gdpr compliance always running
  website/docs: Fix a small grammar issue (#7490)
  core: bump golang from 1.21.3-bookworm to 1.21.4-bookworm
  web: bump pyright from 1.1.334 to 1.1.335 in /web
  ...
2023-11-09 10:53:55 -08:00
19e8b675ae web: refactor sidebar capabilities for categorical subsections
The project "Change Admin UI lists to have sublists per type" requires some initial changes to the
UI to facilitate this request. The AdminSidebar is the principle target of this project, and it is
embedded in the AdminInterface. To facilitate editing the AdminSidebar as an independent entity,
AdminInterface has been moved into its own folder and the AdminSidebar extracted as a standalone Web
Component. This removes, oh, about half the code from AdminInterface. A little cleanup with
`classMap` was also committed.

The rollup config was adjusted to find the new AdminInterface location.

The Sidebar uses the global `config: Config` object to check for Enterprise capabilities. Rather
than plumb all the way down through the Interface => AdminInterface -> AdminSidebar, I chose to make
provide an alternative way of reaching the `config` object, as a *context*. Other configuration
objects (Me, UiConfig, Tenant) interfaces will be contextualized as demand warrants.

Demand will warrant.  Just not yet. <sup>1</sup>

The Sidebar has been refactored only slightly; the renderers are entirely the same as they were
prior to extraction. What has been changed is the source of information: when we retrieve the
current version we story *only* the information, and use type information to ensure that the version
we store is the version we care about. The same is true of `impersonation`; we care only about the
name of the person being impersonated being present, so we don't store anything else.

Fetches have been moved from `firstUpdated` to the constructor.  No reason to have the sidebar
render twice if the network returns before the render is scheduled.

Because the path used to identify the user being impersonated has changed, the `str()` references in
the XLIFF files had to be adjusted. **This change is to a variable only and does not require
translation.**

---
<sup>1</sup> The code is littered with checks to `me()?`, `uiConfig?`, `config?`, etc. In the
*context* of being logged in as an administrator those should never be in doubt. I intend to make
our interfaces not have any doubt.
2023-11-07 14:13:20 -08:00
bdd92f63d8 Revert "Due for amendment"
This reverts commit 829ad5d3f2.
2023-11-07 13:37:00 -08:00
829ad5d3f2 Due for amendment 2023-11-07 13:29:53 -08:00
58639a5d03 Merge branch 'main' into dev
* main:
  Web: bugfix: broken backchannel selector (#7480)
  web: rollback dependabot context (#7479)
2023-11-07 10:11:01 -08:00
67cae13f93 web: rollback dependabot's upgrade of context
The most frustrating part of this is that I RAN THIS, dammit, with the updated
context and the current Wizard, and it finished the End-to-End tests without
complaint.
2023-11-07 07:45:28 -08:00
100a6f02f1 Merge branch 'main' into dev
* main:
  web: bump @types/chart.js from 2.9.39 to 2.9.40 in /web
  website/integrations: add FreshRSS (#7301)
  web: bump the eslint group in /web with 2 updates
  core: bump uvicorn from 0.24.0 to 0.24.0.post1
  web: bump the storybook group in /web with 5 updates
  web: bump the eslint group in /tests/wdio with 2 updates
  web: bump @types/codemirror from 5.60.12 to 5.60.13 in /web
  web: bump mermaid from 10.6.0 to 10.6.1 in /web
  translate: Updates for file web/xliff/en.xlf in fr (#7461)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7459)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7458)
  web: bump @lit/localize-tools from 0.7.0 to 0.7.1 in /web (#7369)
  web: bump @lit-labs/context from 0.4.1 to 0.5.1 in /web (#7368)
2023-11-07 07:34:30 -08:00
242e5b492b Merge branch 'main' into dev
* main:
  web/flows: attempt to fix bitwareden android compatibility (#7455)
  sources/oauth: fix patreon (#7454)
  website: bump the docusaurus group in /website with 3 updates (#7400)
  web/admin: fix chart label on dashboard user page (#7434)
  core: bump github.com/gorilla/sessions from 1.2.1 to 1.2.2 (#7446)
  core: bump github.com/gorilla/mux from 1.8.0 to 1.8.1 (#7443)
  core: bump github.com/spf13/cobra from 1.7.0 to 1.8.0 (#7442)
  core: bump github.com/gorilla/websocket from 1.5.0 to 1.5.1 (#7445)
  core: bump golang.org/x/sync from 0.4.0 to 0.5.0 (#7441)
  core: bump github.com/gorilla/securecookie from 1.1.1 to 1.1.2 (#7440)
  core: bump github.com/gorilla/handlers from 1.5.1 to 1.5.2 (#7444)
  web: bump rollup from 4.2.0 to 4.3.0 in /web (#7448)
  web: bump the eslint group in /web with 2 updates (#7447)
  core: bump uvicorn from 0.23.2 to 0.24.0 (#7450)
  core: bump selenium from 4.15.1 to 4.15.2 (#7449)
  core: bump ruff from 0.1.3 to 0.1.4 (#7451)
  web: bump the eslint group in /tests/wdio with 2 updates (#7452)
2023-11-06 09:40:19 -08:00
48495f3c53 Merge branch 'main' into dev
* main:
  providers/proxy: fix closed redis client (#7385)
  ci: explicitly give write permissions to packages (#7428)
  core: bump selenium from 4.15.0 to 4.15.1 (#7422)
  web: bump yaml from 2.3.3 to 2.3.4 in /web (#7420)
  core: bump sentry-sdk from 1.33.1 to 1.34.0 (#7421)
  web: bump the wdio group in /tests/wdio with 4 updates (#7423)
  providers/oauth2: set auth_via for token and other endpoints (#7417)
  website/blog: draft for happy bday blog (#7408)
2023-11-03 08:45:02 -07:00
77549753c2 Merge branch 'main' into dev
* main:
  translate: Updates for file web/xliff/en.xlf in fr (#7416)
  website: bump react-tooltip from 5.21.6 to 5.22.0 in /website (#7412)
  core: bump selenium from 4.14.0 to 4.15.0 (#7411)
  core: bump django from 4.2.6 to 4.2.7 (#7413)
  web: bump the eslint group in /web with 1 update (#7414)
  web: bump the eslint group in /tests/wdio with 1 update (#7415)
  root: Improve multi arch Docker image build speed (#7355)
2023-11-02 08:15:01 -07:00
3b19aa1915 Merge branch 'main' into dev
* main:
  website/integrations: argocd: add missing url in ArgoCD configuration (#7404)
  core: bump sentry-sdk from 1.32.0 to 1.33.1 (#7397)
  core: bump webauthn from 1.11.0 to 1.11.1 (#7399)
  core: bump github.com/redis/go-redis/v9 from 9.2.1 to 9.3.0 (#7396)
  core: bump twisted from 23.8.0 to 23.10.0 (#7398)
  web: bump the sentry group in /web with 2 updates (#7401)
  web: bump pyright from 1.1.333 to 1.1.334 in /web (#7402)
  web: bump rollup from 4.1.5 to 4.2.0 in /web (#7403)
  core: bump pytest-django from 4.5.2 to 4.6.0 (#7387)
  web: bump the eslint group in /tests/wdio with 2 updates (#7388)
  web: bump the sentry group in /web with 2 updates (#7366)
  web: bump the eslint group in /web with 2 updates (#7389)
  web: bump core-js from 3.33.1 to 3.33.2 in /web (#7390)
  stages/email: fix duplicate querystring encoding (#7386)
  web/admin: fix html error on oauth2 provider page (#7384)
2023-11-01 09:57:21 -07:00
6653bd8224 Merge branch 'main' into dev
* main:
  web: bump rollup from 4.1.4 to 4.1.5 in /web (#7370)
  website/integrations: add SonarQube (#7167)
  web: bump the storybook group in /web with 5 updates (#7382)
  core: bump goauthentik.io/api/v3 from 3.2023101.1 to 3.2023102.1 (#7378)
  web: bump ts-lit-plugin from 2.0.0 to 2.0.1 in /web (#7379)
  web: bump @rollup/plugin-replace from 5.0.4 to 5.0.5 in /web (#7380)
  web: bump API Client version (#7365)
  website/docs: add 2023.8.4 release notes
  release: 2023.10.2
  security: fix oobe-flow reuse when akadmin is deleted (#7361)
  website/docs: prepare 2023.10.2 release notes (#7362)
  website/docs: add missing breaking change due to APPEND_SLASH (#7360)
  lifecycle: rework otp_merge migration (#7359)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7354)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7353)
  website/docs: add warning about Helm breaking change in 2024.x (#7351)
2023-10-30 08:51:07 -07:00
639a8ceb5a Merge branch 'main' into dev
* main: (38 commits)
  crypto: fix race conditions when creating self-signed certificates on startup (#7344)
  blueprints: fix entries with state: absent not being deleted if their serializer has errors (#7345)
  web/admin: fix @change handler for ak-radio elements (#7348)
  rbac: handle lookup error (#7341)
  website/docs: add warning about upgrading to 2023.10 (#7340)
  web/admin: fix role form reacting to enter (#7330)
  core: bump github.com/google/uuid from 1.3.1 to 1.4.0 (#7333)
  core: bump goauthentik.io/api/v3 from 3.2023083.10 to 3.2023101.1 (#7334)
  core: bump ruff from 0.1.2 to 0.1.3 (#7335)
  core: bump pydantic-scim from 0.0.7 to 0.0.8 (#7336)
  website/blogs: Blog dockers (#7328)
  providers/proxy: attempt to fix duplicate cookie (#7324)
  stages/email: fix sending emails from task (#7325)
  web: bump API Client version (#7321)
  website/docs: update release notes for 2023.10.1 (#7316)
  release: 2023.10.1
  lifecycle: fix otp merge migration (#7315)
  root: fix pylint errors (#7312)
  web: bump API Client version (#7311)
  release: 2023.10.0
  ...
2023-10-27 09:47:08 -07:00
0449fd07c5 Merge branch 'main' into dev
* main: (28 commits)
  web: fix typo in traefik name
  web/admin: disable wizard banner for now (#7294)
  web/admin: small fixes (#7292)
  core: Use branding_title in the end session page (#7282)
  web: bump pyright from 1.1.332 to 1.1.333 in /web (#7287)
  website: bump react-tooltip from 5.21.5 to 5.21.6 in /website (#7283)
  web: bump the sentry group in /web with 2 updates (#7285)
  web: bump the eslint group in /web with 1 update (#7286)
  core: bump ruff from 0.1.1 to 0.1.2 (#7289)
  core: bump pytest from 7.4.2 to 7.4.3 (#7288)
  web: bump the wdio group in /tests/wdio with 3 updates (#7290)
  website/blogs: fixed typo in blog (#7281)
  core: bump pylint from 2.17.7 to 3.0.2 (#7270)
  web: bump the eslint group in /tests/wdio with 2 updates (#7274)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7278)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7277)
  ci: bump actions/setup-node from 3 to 4 (#7268)
  core: bump pylint-django from 2.5.4 to 2.5.5 (#7271)
  web: bump the eslint group in /web with 2 updates (#7269)
  web: bump @trivago/prettier-plugin-sort-imports from 4.2.0 to 4.2.1 in /tests/wdio (#7275)
  ...
2023-10-25 10:04:37 -07:00
8e892373a1 Merge branch 'main' into dev
* main:
  core: bump pylint-django from 2.5.3 to 2.5.4 (#7255)
  core: bump goauthentik.io/api/v3 from 3.2023083.9 to 3.2023083.10 (#7256)
  web: bump the wdio group in /tests/wdio with 1 update (#7258)
  web: bump the eslint group in /tests/wdio with 1 update (#7257)
  sources/oauth: fix name clash (#7253)
  web: bump the eslint group in /web with 1 update (#7250)
  web: bump mermaid from 10.5.0 to 10.5.1 in /web (#7247)
  web: break circular dependency between AKElement & Interface. (#7165)
2023-10-23 08:52:36 -07:00
8713a1d120 Merge branch 'main' into web/theme-controller-2
* main:
  sources/oauth: fix oidc well-known parsing (#7248)
  web/admin: improve user email button labels (#7233)
2023-10-20 14:12:57 -07:00
0123bf61ab web: fix broken typescript references
This built... and then it didn't?  Anyway, the current fix is to
provide type information the AkInterface for the data that consumers
require.
2023-10-20 10:31:44 -07:00
e8edbdb4ae Merge branch 'main' into web/theme-controller-2
* main:
  web: bump API Client version (#7246)
  sources/oauth: include default JWKS URLs for OAuth sources (#6992)
  sources/oauth: periodically update OAuth sources' OIDC configuration (#7245)
  website/blogs: Fix sso blog to remove 3rd reason (#7230)
  lifecycle: fix otp_merge migration again (#7244)
  web: bump core-js from 3.33.0 to 3.33.1 in /web (#7243)
  core: bump node from 20 to 21 (#7237)
  web: fix bad comment that was confusing lit-analyze (#7234)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7235)
  core: bump ruff from 0.1.0 to 0.1.1 (#7238)
  core: bump twilio from 8.9.1 to 8.10.0 (#7239)
  web: bump the storybook group in /web with 5 updates (#7240)
  web: bump the wdio group in /tests/wdio with 4 updates (#7241)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7236)
  web: isolate clipboard handling (#7229)
2023-10-20 08:28:00 -07:00
83338f8c32 Merge branch 'main' into web/theme-controller-2
* main:
  web/flows: update flow background (#7232)
  web/admin: fix prompt form and codemirror mode (#7231)
  web/admin: decrease wizard hint padding (#7227)
2023-10-19 16:28:34 -07:00
e51b36c614 Merge branch 'main' into web/theme-controller-2
* main: (57 commits)
  stages/email: Fix query parameters getting lost in Email links (#5376)
  core/rbac: fix missing field when removing perm, add delete from object page (#7226)
  website/integrations: grafana: add Helm and Terraform config examples (#7121)
  web: bump @types/codemirror from 5.60.11 to 5.60.12 in /web (#7223)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7224)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7225)
  website/blogs: blog about sso tax (#7202)
  web: Application wizard v2 with tests (#7004)
  web: bump API Client version (#7220)
  core: bump goauthentik.io/api/v3 from 3.2023083.7 to 3.2023083.8 (#7221)
  providers/radius: TOTP MFA support (#7217)
  web: bump API Client version (#7218)
  stage/deny: add custom message (#7144)
  docs: update full-dev-setup docs (#7205)
  enterprise: bump license usage task frequency (#7215)
  web: bump the storybook group in /web with 5 updates (#7212)
  web: bump the sentry group in /web with 2 updates (#7211)
  Revert "web: Updates to the Context and Tasks libraries from lit. (#7168)"
  web: bump @types/codemirror from 5.60.10 to 5.60.11 in /web (#7209)
  web: bump @types/chart.js from 2.9.38 to 2.9.39 in /web (#7206)
  ...
2023-10-19 08:59:15 -07:00
314d89b1b7 web: break circular dependency between AKElement & Interface.
This commit changes the way the root node of the web application shell is
discovered by child components, such that the base class shared by both
no longer results in a circular dependency between the two models.

I've run this in isolation and have seen no failures of discovery; the identity
token exists as soon as the Interface is constructed and is found by every item
on the page.
2023-10-13 08:22:46 -07:00
18 changed files with 792 additions and 217 deletions

View File

@ -17,7 +17,6 @@ import "@goauthentik/elements/notifications/NotificationDrawer";
import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch"; import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch";
import "@goauthentik/elements/router/RouterOutlet"; import "@goauthentik/elements/router/RouterOutlet";
import "@goauthentik/elements/sidebar/Sidebar"; import "@goauthentik/elements/sidebar/Sidebar";
import "@goauthentik/elements/sidebar/SidebarItem";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";

View File

@ -1,5 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants"; import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants";
import { eventActionLabels } from "@goauthentik/common/labels";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { import {
@ -7,17 +8,63 @@ import {
WithCapabilitiesConfig, WithCapabilitiesConfig,
} from "@goauthentik/elements/Interface/capabilitiesProvider"; } from "@goauthentik/elements/Interface/capabilitiesProvider";
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
import "@goauthentik/elements/sidebar/Sidebar";
import {
SidebarAttributes,
SidebarEntry,
SidebarEventHandler,
} from "@goauthentik/elements/sidebar/types";
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle"; import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
import { spread } from "@open-wc/lit-helpers";
import { msg, str } from "@lit/localize"; import { msg, str } from "@lit/localize";
import { TemplateResult, html, nothing } from "lit"; import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { map } from "lit/directives/map.js";
import { AdminApi, CoreApi, UiThemeEnum, Version } from "@goauthentik/api"; import { AdminApi } from "@goauthentik/api";
import { CoreApi, Version } from "@goauthentik/api";
import type { SessionUser, UserSelf } from "@goauthentik/api"; import type { SessionUser, UserSelf } from "@goauthentik/api";
import { flowDesignationTable } from "../flows/utils";
import ConnectionTypesController from "./SidebarEntries/ConnectionTypesController";
import PolicyTypesController from "./SidebarEntries/PolicyTypesController";
import PropertyMappingsController from "./SidebarEntries/PropertyMappingsController";
import ProviderTypesController from "./SidebarEntries/ProviderTypesController";
import SourceTypesController from "./SidebarEntries/SourceTypesController";
import StageTypesController from "./SidebarEntries/StageTypesController";
/**
* AdminSidebar
*
* The AdminSidebar has two responsibilities:
*
* 1. Control the styling of the sidebar host, specifically when to show it and whether to show
* it as an overlay or as a push.
* 2. Control what content the sidebar will receive. The sidebar takes a tree, maximally three deep,
* of type SidebarEventHandler.
*/
type SidebarUrl = string;
export type LocalSidebarEntry = [
// - null: This entry is not a link.
// - string: the url for the entry
// - SidebarEventHandler: a function to run if the entry is clicked.
SidebarUrl | SidebarEventHandler | null,
// The visible text of the entry.
string,
// Attributes to which the sidebar responds. See the sidebar for details.
(SidebarAttributes | string[] | null)?, // eslint-disable-line
// Children of the entry
LocalSidebarEntry[]?,
];
const localToSidebarEntry = (l: LocalSidebarEntry): SidebarEntry => ({
path: l[0],
label: l[1],
...(l[2] ? { attributes: Array.isArray(l[2]) ? { activeWhen: l[2] } : l[2] } : {}),
...(l[3] ? { children: l[3].map(localToSidebarEntry) } : {}),
});
@customElement("ak-admin-sidebar") @customElement("ak-admin-sidebar")
export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) { export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
@property({ type: Boolean, reflect: true }) @property({ type: Boolean, reflect: true })
@ -29,6 +76,13 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
@state() @state()
impersonation: UserSelf["username"] | null = null; impersonation: UserSelf["username"] | null = null;
private connectionTypes = new ConnectionTypesController(this);
private policyTypes = new PolicyTypesController(this);
private propertyMapper = new PropertyMappingsController(this);
private providerTypes = new ProviderTypesController(this);
private sourceTypes = new SourceTypesController(this);
private stageTypes = new StageTypesController(this);
constructor() { constructor() {
super(); super();
new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then((version) => { new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then((version) => {
@ -74,19 +128,6 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
super.disconnectedCallback(); super.disconnectedCallback();
} }
render() {
return html`
<ak-sidebar
class="pf-c-page__sidebar ${this.open ? "pf-m-expanded" : "pf-m-collapsed"} ${this
.activeTheme === UiThemeEnum.Light
? "pf-m-light"
: ""}"
>
${this.renderSidebarItems()}
</ak-sidebar>
`;
}
updated() { updated() {
// This is permissible as`:host.classList` is not one of the properties Lit uses as a // This is permissible as`:host.classList` is not one of the properties Lit uses as a
// scheduling trigger. This sort of shenanigans can trigger an loop, in that it will trigger // scheduling trigger. This sort of shenanigans can trigger an loop, in that it will trigger
@ -97,118 +138,86 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) {
this.classList.add(this.open ? "pf-m-expanded" : "pf-m-collapsed"); this.classList.add(this.open ? "pf-m-expanded" : "pf-m-collapsed");
} }
renderSidebarItems(): TemplateResult { get sidebarItems(): SidebarEntry[] {
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
// commonplace and singular enough to merit its own handler.
type SidebarEntry = [
path: string | null,
label: string,
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
children?: SidebarEntry[],
];
// prettier-ignore
const sidebarContent: SidebarEntry[] = [
["/if/user/", msg("User interface"), { "?isAbsoluteLink": true, "?highlight": true }],
[null, msg("Dashboards"), { "?expanded": true }, [
["/administration/overview", msg("Overview")],
["/administration/dashboard/users", msg("User Statistics")],
["/administration/system-tasks", msg("System Tasks")]]],
[null, msg("Applications"), null, [
["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
["/outpost/outposts", msg("Outposts")]]],
[null, msg("Events"), null, [
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
["/events/rules", msg("Notification Rules")],
["/events/transports", msg("Notification Transports")]]],
[null, msg("Customization"), null, [
["/policy/policies", msg("Policies")],
["/core/property-mappings", msg("Property Mappings")],
["/blueprints/instances", msg("Blueprints")],
["/policy/reputation", msg("Reputation scores")]]],
[null, msg("Flows and Stages"), null, [
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
["/flow/stages", msg("Stages")],
["/flow/stages/prompts", msg("Prompts")]]],
[null, msg("Directory"), null, [
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
["/core/tokens", msg("Tokens and App passwords")],
["/flow/stages/invitations", msg("Invitations")]]],
[null, msg("System"), null, [
["/core/brands", msg("Brands")],
["/crypto/certificates", msg("Certificates")],
["/outpost/integrations", msg("Outpost Integrations")],
["/admin/settings", msg("Settings")]]],
];
// Typescript requires the type here to correctly type the recursive path
type SidebarRenderer = (_: SidebarEntry) => TemplateResult;
const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => {
const properties = Array.isArray(attributes)
? { ".activeWhen": attributes }
: attributes ?? {};
if (path) {
properties["path"] = path;
}
return html`<ak-sidebar-item ${spread(properties)}>
${label ? html`<span slot="label">${label}</span>` : nothing}
${map(children, renderOneSidebarItem)}
</ak-sidebar-item>`;
};
// prettier-ignore
return html`
${this.renderNewVersionMessage()}
${this.renderImpersonationMessage()}
${map(sidebarContent, renderOneSidebarItem)}
${this.renderEnterpriseMenu()}
`;
}
renderNewVersionMessage() {
return this.version && this.version !== VERSION
? html`
<ak-sidebar-item ?highlight=${true}>
<span slot="label"
>${msg("A newer version of the frontend is available.")}</span
>
</ak-sidebar-item>
`
: nothing;
}
renderImpersonationMessage() {
const reload = () => const reload = () =>
new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => { new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => {
window.location.reload(); window.location.reload();
}); });
return this.impersonation // prettier-ignore
? html`<ak-sidebar-item ?highlight=${true} @click=${reload}> const newVersionMessage: LocalSidebarEntry[] =
<span slot="label" this.version && this.version !== VERSION
>${msg( ? [[ "https://goauthentik.io", msg("A newer version of the frontend is available."),
str`You're currently impersonating ${this.impersonation}. Click to stop.`, { highlight: true }]]
)}</span : [];
>
</ak-sidebar-item>` // prettier-ignore
: nothing; const impersonationMessage: LocalSidebarEntry[] = this.impersonation
? [[reload, msg(str`You're currently impersonating ${this.impersonation}. Click to stop.`)]]
: [];
// prettier-ignore
const enterpriseMenu: LocalSidebarEntry[] = this.can(CapabilitiesEnum.IsEnterprise)
? [[null, msg("Enterprise"), null, [["/enterprise/licenses", msg("Licenses")]]]]
: [];
const flowTypes: LocalSidebarEntry[] = flowDesignationTable.map(([_designation, label]) => [
`/flow/flows;${encodeURIComponent(JSON.stringify({ search: label }))}`,
label,
]);
const eventTypes: LocalSidebarEntry[] = eventActionLabels.map(([_action, label]) => [
`/events/log;${encodeURIComponent(JSON.stringify({ search: label }))}`,
label,
]);
// prettier-ignore
const localSidebar: LocalSidebarEntry[] = [
...(newVersionMessage),
...(impersonationMessage),
["/if/user/", msg("User interface"), { isAbsoluteLink: true, highlight: true }],
[null, msg("Dashboards"), { expanded: true }, [
["/administration/overview", msg("Overview")],
["/administration/dashboard/users", msg("User Statistics")],
["/administration/system-tasks", msg("System Tasks")]]],
[null, msg("Applications"), null, [
["/core/applications", msg("Applications"), [`^/core/applications(/(?<slug>${SLUG_REGEX}))?$`]],
["/core/providers", msg("Providers"), [`^/core/providers(/(?<id>${ID_REGEX}))?$`], this.providerTypes.entries()],
["/outpost/outposts", msg("Outposts")]]],
[null, msg("Events"), null, [
["/events/log", msg("Logs"), [`^/events/log(/(?<id>${UUID_REGEX}))?$`], eventTypes],
["/events/rules", msg("Notification Rules")],
["/events/transports", msg("Notification Transports")]]],
[null, msg("Customisation"), null, [
["/policy/policies", msg("Policies"), null, this.policyTypes.entries()],
["/core/property-mappings", msg("Property Mappings"), null, this.propertyMapper.entries()],
["/blueprints/instances", msg("Blueprints")],
["/policy/reputation", msg("Reputation scores")]]],
[null, msg("Flows and Stages"), null, [
["/flow/flows", msg("Flows"), [`^/flow/flows(/(?<slug>${SLUG_REGEX}))?$`], flowTypes],
["/flow/stages", msg("Stages"), null, this.stageTypes.entries()],
["/flow/stages/prompts", msg("Prompts")]]],
[null, msg("Directory"), null, [
["/identity/users", msg("Users"), [`^/identity/users(/(?<id>${ID_REGEX}))?$`]],
["/identity/groups", msg("Groups"), [`^/identity/groups(/(?<id>${UUID_REGEX}))?$`]],
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
["/core/sources", msg("Federation and Social login"), [`^/core/sources(/(?<slug>${SLUG_REGEX}))?$`], this.sourceTypes.entries()],
["/core/tokens", msg("Tokens and App passwords")],
["/flow/stages/invitations", msg("Invitations")]]],
[null, msg("System"), null, [
["/core/brands", msg("Brands")],
["/crypto/certificates", msg("Certificates")],
["/outpost/integrations", msg("Outpost Integrations"), null, this.connectionTypes.entries()],
["/admin/settings", msg("Settings")]]],
...(enterpriseMenu)
];
return localSidebar.map(localToSidebarEntry);
} }
renderEnterpriseMenu() { render() {
return this.can(CapabilitiesEnum.IsEnterprise) return html`
? html` <ak-sidebar class="pf-c-page__sidebar" .entries=${this.sidebarItems}></ak-sidebar>
<ak-sidebar-item> `;
<span slot="label">${msg("Enterprise")}</span>
<ak-sidebar-item path="/enterprise/licenses">
<span slot="label">${msg("Licenses")}</span>
</ak-sidebar-item>
</ak-sidebar-item>
`
: nothing;
} }
} }

View File

@ -0,0 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { OutpostsApi } from "@goauthentik/api";
import { createTypesController } from "./GenericTypesController";
export const ConnectionTypesController = createTypesController(
() => new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllTypesList(),
"/outpost/integrations",
);
export default ConnectionTypesController;

View File

@ -0,0 +1,55 @@
import { ReactiveControllerHost } from "lit";
import { TypeCreate } from "@goauthentik/api";
import { LocalSidebarEntry } from "../AdminSidebar";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Fetcher = () => Promise<TypeCreate[]>;
const typeCreateToSidebar = (baseUrl: string, tcreate: TypeCreate[]): LocalSidebarEntry[] =>
tcreate.map((t) => [
`${baseUrl};${encodeURIComponent(JSON.stringify({ search: t.name }))}`,
t.name,
]);
/**
* createTypesController
*
* The Sidebar accesses a number objects of `TypeCreate`, which all have the exact same type, just
* different accessors for generating the lists and different paths to which they respond. This
* function is a template for a (simple) reactive controller that fetches the data for that type on
* construction, then informs the host that the data is available.
*/
/**
* TODO (2023-11-17): This function is unlikely to survive in this form. It would be nice if it were more
* generic, able to take a converter that can handle more that TypeCreate[] as its inbound argument,
* since we need to refine what's displayed and on what the search is conducted.
*
*/
export function createTypesController(
fetch: Fetcher,
path: string,
converter = typeCreateToSidebar,
) {
return class GenericTypesController {
createTypes: TypeCreate[] = [];
host: ReactiveControllerHost;
constructor(host: ReactiveControllerHost) {
this.host = host;
fetch().then((types) => {
this.createTypes = types;
host.requestUpdate();
});
}
entries(): LocalSidebarEntry[] {
return converter(path, this.createTypes);
}
};
}
export default createTypesController;

View File

@ -0,0 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { PoliciesApi } from "@goauthentik/api";
import { createTypesController } from "./GenericTypesController";
export const PolicyTypesController = createTypesController(
() => new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList(),
"/policy/policies",
);
export default PolicyTypesController;

View File

@ -0,0 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { PropertymappingsApi } from "@goauthentik/api";
import { createTypesController } from "./GenericTypesController";
export const PropertyMappingsController = createTypesController(
() => new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTypesList(),
"/core/property-mappings",
);
export default PropertyMappingsController;

View File

@ -0,0 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ProvidersApi } from "@goauthentik/api";
import { createTypesController } from "./GenericTypesController";
export const ProviderTypesController = createTypesController(
() => new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList(),
"/core/providers",
);
export default ProviderTypesController;

View File

@ -0,0 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SourcesApi } from "@goauthentik/api";
import { createTypesController } from "./GenericTypesController";
export const SourceTypesController = createTypesController(
() => new SourcesApi(DEFAULT_CONFIG).sourcesAllTypesList(),
"/core/sources",
);
export default SourceTypesController;

View File

@ -0,0 +1,12 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { StagesApi } from "@goauthentik/api";
import { createTypesController } from "./GenericTypesController";
export const StageTypesController = createTypesController(
() => new StagesApi(DEFAULT_CONFIG).stagesAllTypesList(),
"/flow/stages",
);
export default StageTypesController;

View File

@ -6,40 +6,33 @@ export function RenderFlowOption(flow: Flow): string {
return `${flow.slug} (${flow.name})`; return `${flow.slug} (${flow.name})`;
} }
type FlowDesignationPair = [FlowDesignationEnum, string];
export const flowDesignationTable: FlowDesignationPair[] = [
[FlowDesignationEnum.Authentication, msg("Authentication")],
[FlowDesignationEnum.Authorization, msg("Authorization")],
[FlowDesignationEnum.Enrollment, msg("Enrollment")],
[FlowDesignationEnum.Invalidation, msg("Invalidation")],
[FlowDesignationEnum.Recovery, msg("Recovery")],
[FlowDesignationEnum.StageConfiguration, msg("Stage Configuration")],
[FlowDesignationEnum.Unenrollment, msg("Unenrollment")],
];
// prettier-ignore
const flowDesignations = new Map(flowDesignationTable);
export function DesignationToLabel(designation: FlowDesignationEnum): string { export function DesignationToLabel(designation: FlowDesignationEnum): string {
switch (designation) { return flowDesignations.get(designation) ?? msg("Unknown designation");
case FlowDesignationEnum.Authentication:
return msg("Authentication");
case FlowDesignationEnum.Authorization:
return msg("Authorization");
case FlowDesignationEnum.Enrollment:
return msg("Enrollment");
case FlowDesignationEnum.Invalidation:
return msg("Invalidation");
case FlowDesignationEnum.Recovery:
return msg("Recovery");
case FlowDesignationEnum.StageConfiguration:
return msg("Stage Configuration");
case FlowDesignationEnum.Unenrollment:
return msg("Unenrollment");
case FlowDesignationEnum.UnknownDefaultOpenApi:
return msg("Unknown designation");
}
} }
const layoutToLabel = new Map<FlowLayoutEnum, string>([
[FlowLayoutEnum.Stacked, msg("Stacked")],
[FlowLayoutEnum.ContentLeft, msg("Content left")],
[FlowLayoutEnum.ContentRight, msg("Content right")],
[FlowLayoutEnum.SidebarLeft, msg("Sidebar left")],
[FlowLayoutEnum.SidebarRight, msg("Sidebar right")],
]);
export function LayoutToLabel(layout: FlowLayoutEnum): string { export function LayoutToLabel(layout: FlowLayoutEnum): string {
switch (layout) { return layoutToLabel.get(layout) ?? msg("Unknown layout");
case FlowLayoutEnum.Stacked:
return msg("Stacked");
case FlowLayoutEnum.ContentLeft:
return msg("Content left");
case FlowLayoutEnum.ContentRight:
return msg("Content right");
case FlowLayoutEnum.SidebarLeft:
return msg("Sidebar left");
case FlowLayoutEnum.SidebarRight:
return msg("Sidebar right");
case FlowLayoutEnum.UnknownDefaultOpenApi:
return msg("Unknown layout");
}
} }

View File

@ -2,6 +2,8 @@ import { msg } from "@lit/localize";
import { Device, EventActions, IntentEnum, SeverityEnum, UserTypeEnum } from "@goauthentik/api"; import { Device, EventActions, IntentEnum, SeverityEnum, UserTypeEnum } from "@goauthentik/api";
type Pair<T> = [T, string];
/* Various tables in the API for which we need to supply labels */ /* Various tables in the API for which we need to supply labels */
export const intentEnumToLabel = new Map<IntentEnum, string>([ export const intentEnumToLabel = new Map<IntentEnum, string>([
@ -14,7 +16,7 @@ export const intentEnumToLabel = new Map<IntentEnum, string>([
export const intentToLabel = (intent: IntentEnum) => intentEnumToLabel.get(intent); export const intentToLabel = (intent: IntentEnum) => intentEnumToLabel.get(intent);
export const eventActionToLabel = new Map<EventActions | undefined, string>([ export const eventActionLabels: Pair<EventActions>[] = [
[EventActions.Login, msg("Login")], [EventActions.Login, msg("Login")],
[EventActions.LoginFailed, msg("Failed login")], [EventActions.LoginFailed, msg("Failed login")],
[EventActions.Logout, msg("Logout")], [EventActions.Logout, msg("Logout")],
@ -43,7 +45,9 @@ export const eventActionToLabel = new Map<EventActions | undefined, string>([
[EventActions.ModelDeleted, msg("Model deleted")], [EventActions.ModelDeleted, msg("Model deleted")],
[EventActions.EmailSent, msg("Email sent")], [EventActions.EmailSent, msg("Email sent")],
[EventActions.UpdateAvailable, msg("Update available")], [EventActions.UpdateAvailable, msg("Update available")],
]); ];
export const eventActionToLabel = new Map<EventActions | undefined, string>(eventActionLabels);
export const actionToLabel = (action?: EventActions): string => export const actionToLabel = (action?: EventActions): string =>
eventActionToLabel.get(action) ?? action ?? ""; eventActionToLabel.get(action) ?? action ?? "";

View File

@ -0,0 +1,56 @@
import { css } from "lit";
import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
export const sidebarStyles = [
PFBase,
PFPage,
PFNav,
css`
:host {
z-index: 100;
}
.pf-c-nav__link.pf-m-current::after,
.pf-c-nav__link.pf-m-current:hover::after,
.pf-c-nav__item.pf-m-current:not(.pf-m-expanded) .pf-c-nav__link::after {
--pf-c-nav__link--m-current--after--BorderColor: #fd4b2d;
}
:host([theme="light"]) {
border-right-color: transparent !important;
}
.pf-c-nav__section + .pf-c-nav__section {
--pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm);
}
.pf-c-nav__list .sidebar-brand {
max-height: 82px;
margin-bottom: -0.5rem;
}
nav {
display: flex;
flex-direction: column;
max-height: 100vh;
height: 100%;
overflow-y: hidden;
}
ak-sidebar-items {
flex-grow: 1;
overflow-y: auto;
}
.pf-c-nav__link {
--pf-c-nav__link--PaddingTop: 0.5rem;
--pf-c-nav__link--PaddingRight: 0.5rem;
--pf-c-nav__link--PaddingBottom: 0.5rem;
}
.pf-c-nav__section-title {
font-size: 12px;
}
.pf-c-nav__item {
--pf-c-nav__item--MarginTop: 0px;
}
`,
];

View File

@ -1,79 +1,32 @@
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/sidebar/SidebarBrand"; import "@goauthentik/elements/sidebar/SidebarBrand";
import "@goauthentik/elements/sidebar/SidebarItems";
import "@goauthentik/elements/sidebar/SidebarUser"; import "@goauthentik/elements/sidebar/SidebarUser";
import { CSSResult, TemplateResult, css, html } from "lit"; import { html } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { UiThemeEnum } from "@goauthentik/api"; import { UiThemeEnum } from "@goauthentik/api";
import { sidebarStyles } from "./Sidebar.css.js";
import type { SidebarEntry } from "./types";
@customElement("ak-sidebar") @customElement("ak-sidebar")
export class Sidebar extends AKElement { export class Sidebar extends AKElement {
static get styles(): CSSResult[] { @property({ type: Array })
return [ entries: SidebarEntry[] = [];
PFBase,
PFPage,
PFNav,
css`
:host {
z-index: 100;
}
.pf-c-nav__link.pf-m-current::after,
.pf-c-nav__link.pf-m-current:hover::after,
.pf-c-nav__item.pf-m-current:not(.pf-m-expanded) .pf-c-nav__link::after {
--pf-c-nav__link--m-current--after--BorderColor: #fd4b2d;
}
:host([theme="light"]) {
border-right-color: transparent !important;
}
.pf-c-nav__section + .pf-c-nav__section { static get styles() {
--pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm); return sidebarStyles;
}
.pf-c-nav__list .sidebar-brand {
max-height: 82px;
margin-bottom: -0.5rem;
}
nav {
display: flex;
flex-direction: column;
max-height: 100vh;
height: 100%;
overflow-y: hidden;
}
.pf-c-nav__list {
flex-grow: 1;
overflow-y: auto;
}
.pf-c-nav__link {
--pf-c-nav__link--PaddingTop: 0.5rem;
--pf-c-nav__link--PaddingRight: 0.5rem;
--pf-c-nav__link--PaddingBottom: 0.5rem;
}
.pf-c-nav__section-title {
font-size: 12px;
}
.pf-c-nav__item {
--pf-c-nav__item--MarginTop: 0px;
}
`,
];
} }
render(): TemplateResult { render() {
return html`<nav return html`<nav
class="pf-c-nav ${this.activeTheme === UiThemeEnum.Light ? "pf-m-light" : ""}" class="pf-c-nav ${this.activeTheme === UiThemeEnum.Light ? "pf-m-light" : ""}"
aria-label="Global" aria-label="Global"
> >
<ak-sidebar-brand></ak-sidebar-brand> <ak-sidebar-brand></ak-sidebar-brand>
<ul class="pf-c-nav__list"> <ak-sidebar-items .entries=${this.entries}></ak-sidebar-items>
<slot></slot>
</ul>
<ak-sidebar-user></ak-sidebar-user> <ak-sidebar-user></ak-sidebar-user>
</nav>`; </nav>`;
} }

View File

@ -0,0 +1,86 @@
import { css } from "lit";
import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
export const sidebarItemStyles = [
PFBase,
PFPage,
PFNav,
css`
:host {
z-index: 100;
box-shadow: none !important;
}
.highlighted {
background-color: var(--ak-accent);
margin: 16px;
}
.highlighted .pf-c-nav__link {
padding-left: 0.5rem;
}
.pf-c-nav__link.pf-m-current::after,
.pf-c-nav__link.pf-m-current:hover::after,
.pf-c-nav__item.pf-m-current:not(.pf-m-expanded) .pf-c-nav__link::after {
--pf-c-nav__link--m-current--after--BorderColor: #fd4b2d;
}
.pf-c-nav__item .pf-c-nav__item::before {
border-bottom-width: 0;
}
.pf-c-nav__section + .pf-c-nav__section {
--pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm);
}
.pf-c-nav__list .sidebar-brand {
max-height: 82px;
margin-bottom: -0.5rem;
}
.pf-c-nav__toggle {
width: calc(var(--pf-c-nav__toggle--FontSize) + calc(2 * var(--pf-global--spacer--md)));
}
nav {
display: flex;
flex-direction: column;
max-height: 100vh;
height: 100%;
overflow-y: hidden;
}
.pf-c-nav__list {
flex: 1 0 1fr;
overflow-y: auto;
}
.pf-c-nav__link {
--pf-c-nav__link--PaddingTop: 0.5rem;
--pf-c-nav__link--PaddingRight: 0.5rem;
--pf-c-nav__link--PaddingBottom: 0.5rem;
}
.pf-c-nav__link a {
flex: 1 0 max-content;
color: var(--pf-c-nav__link--Color);
}
a.pf-c-nav__link:hover {
color: var(--pf-c-nav__link--Color);
text-decoration: var(--pf-global--link--TextDecoration--hover);
}
.pf-c-nav__section-title {
font-size: 12px;
}
.pf-c-nav__item {
--pf-c-nav__item--MarginTop: 0px;
}
.pf-c-nav__toggle-icon {
padding: var(--pf-global--spacer--sm) var(--pf-global--spacer--md);
}
`,
];

View File

@ -0,0 +1,247 @@
import { ROUTE_SEPARATOR } from "@goauthentik/common/constants";
import { AKElement } from "@goauthentik/elements/Base";
import { findTable } from "@goauthentik/elements/table/TablePage";
import { TemplateResult, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { map } from "lit/directives/map.js";
import { UiThemeEnum } from "@goauthentik/api";
import { sidebarItemStyles } from "./SidebarItems.css.js";
import type { SidebarEntry } from "./types";
import { entryKey, findMatchForNavbarUrl, makeParentMap } from "./utils";
/**
* Display the sidebar item tree.
*
* Along with the `reclick()` complaint down below, the other thing I dislike about this design is
* that it's effectively two different programs glued together. The first responds to the `click`
* and performs the navigation, which either triggers the router or triggers a new search on the
* existing view. The second responds to the navigation change event when the URL is changed by the
* navigation event, at which point it figures out which entry to highlight as "current," which
* causes the re-render.
*/
@customElement("ak-sidebar-items")
export class SidebarItems extends AKElement {
static get styles() {
return sidebarItemStyles;
}
@property({ type: Array })
entries: SidebarEntry[] = [];
expanded: Set<string> = new Set();
@state()
current = "";
constructor() {
super();
this.renderItem = this.renderItem.bind(this);
this.toggleExpand = this.toggleExpand.bind(this);
this.onHashChange = this.onHashChange.bind(this);
this.reclick = this.reclick.bind(this);
}
connectedCallback() {
super.connectedCallback();
this.onHashChange();
window.addEventListener("hashchange", this.onHashChange);
}
disconnectedCallback() {
window.removeEventListener("hashchange", this.onHashChange);
super.disconnectedCallback();
}
expandParents(entry: SidebarEntry) {
const reverseMap = makeParentMap(this.entries);
let start: SidebarEntry | undefined = reverseMap.get(entry);
while (start) {
this.expanded.add(entryKey(start));
start = reverseMap.get(start);
}
}
onHashChange() {
this.current = "";
const match = findMatchForNavbarUrl(this.entries);
if (match) {
this.current = entryKey(match);
this.expandParents(match);
}
}
toggleExpand(entry: SidebarEntry) {
const key = entryKey(entry);
if (this.expanded.has(key)) {
this.expanded.delete(key);
} else {
this.expanded.add(key);
}
this.requestUpdate();
}
// This is gross and feels like 2007: using a path from the root through the shadowDoms (see
// `TablePage:findTable()`), this code finds the element that *should* be triggered by an event
// on the URL, and forcibly injects the text of the search and the click of the search button.
reclick(ev: Event, path: string) {
const oldPath = window.location.hash.split(ROUTE_SEPARATOR)[0];
const [curPath, ...curSearchComponents] = path.split(ROUTE_SEPARATOR);
const curSearch: string =
curSearchComponents.length > 0 ? curSearchComponents.join(ROUTE_SEPARATOR) : "";
if (curPath !== oldPath) {
// A Tier 1 or Tier 2 change should be handled by the router. (So should a Tier 3
// change, but... here we are.)
return;
}
const table = findTable();
if (!table) {
return;
}
// Always wrap the minimal exceptional code possible in an IIFE and supply the failure
// alternative. Turn exceptions into expressions with the smallest functional rewind
// whenever possible.
const search = (() => {
try {
return curSearch ? JSON.parse(decodeURIComponent(curSearch)) : { search: "" };
} catch {
return { search: "" };
}
})();
if ("search" in search) {
ev.preventDefault();
ev.stopPropagation();
table.search = search.search;
table.fetch();
}
}
render(): TemplateResult {
console.log("C:", this.current);
const lightThemed = { "pf-m-light": this.activeTheme === UiThemeEnum.Light };
return html` <nav class="pf-c-nav ${classMap(lightThemed)}" aria-label="Navigation">
<ul class="pf-c-nav__list">
${map(this.entries, this.renderItem)}
</ul>
</nav>`;
}
renderItem(entry: SidebarEntry) {
// Ensure the attributes are undefined, not null; they can be null in the placeholders, but
// not when being forwarded to the correct renderer.
const hasChildren = !!(entry.children && entry.children.length > 0);
// This is grossly imperative, in that it HAS to come before the content is rendered to make
// sure the content gets the right settings with respect to expansion.
if (entry.attributes?.expanded) {
this.expanded.add(entryKey(entry));
delete entry.attributes.expanded;
}
const content =
entry.path && hasChildren
? this.renderLinkAndChildren(entry)
: hasChildren
? this.renderLabelAndChildren(entry)
: entry.path
? this.renderLink(entry)
: this.renderLabel(entry);
const expanded = {
"highlighted": !!entry.attributes?.highlight,
"pf-m-expanded": this.expanded.has(entryKey(entry)),
"pf-m-expandable": hasChildren,
};
return html`<li class="pf-c-nav__item ${classMap(expanded)}">${content}</li>`;
}
getLinkClasses(entry: SidebarEntry) {
const a = entry.attributes ?? {};
const key = entryKey(entry);
return {
"pf-m-current": key === this.current,
"pf-c-nav__link": true,
"highlight": !!(typeof a.highlight === "function" ? a.highlight() : a.highlight),
};
}
renderLabel(entry: SidebarEntry) {
return html`<div class=${classMap(this.getLinkClasses(entry))}>${entry.label}</div>`;
}
// note the responsibilities pushed up to the caller
renderLink(entry: SidebarEntry) {
if (typeof entry.path === "function") {
return html` <a @click=${entry.path} class=${classMap(this.getLinkClasses(entry))}>
${entry.label}
</a>`;
}
const path = `${entry.attributes?.isAbsoluteLink ? "" : "#"}${entry.path}`;
return html` <a
href=${path}
@click=${(ev: Event) => this.reclick(ev, path)}
class=${classMap(this.getLinkClasses(entry))}
>
${entry.label}
</a>`;
}
renderChildren(children: SidebarEntry[]) {
return html`<section class="pf-c-nav__subnav">
<ul class="pf-c-nav__list">
${map(children, this.renderItem)}
</ul>
</section>`;
}
renderLabelAndChildren(entry: SidebarEntry): TemplateResult {
const handler = () => this.toggleExpand(entry);
const current = { "pf-m-current": this.current === entryKey(entry) };
return html` <div class="pf-c-nav__link ${classMap(current)}">
<div class="ak-nav__link">${entry.label}</div>
<span class="pf-c-nav__toggle" @click=${handler}>
<span class="pf-c-nav__toggle-icon">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</span>
</span>
</div>
${this.expanded.has(entryKey(entry))
? this.renderChildren(entry.children ?? [])
: nothing}`;
}
renderLinkAndChildren(entry: SidebarEntry): TemplateResult {
const handler = () => this.toggleExpand(entry);
const current = { "pf-m-current": this.current === entryKey(entry) };
const path = `${entry.attributes?.isAbsoluteLink ? "" : "#"}${entry.path}`;
return html` <div class="pf-c-nav__link ${classMap(current)}">
<a
href=${path}
@click=${(ev: Event) => this.reclick(ev, path)}
class="ak-nav__link"
>
${entry.label}
</a>
<span class="pf-c-nav__toggle" @click=${handler}>
<span class="pf-c-nav__toggle-icon">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</span>
</span>
</div>
${this.expanded.has(entryKey(entry))
? this.renderChildren(entry.children ?? [])
: nothing}`;
}
}

View File

@ -0,0 +1,21 @@
import { TemplateResult } from "lit";
export type SidebarEventHandler = () => void;
export type SidebarAttributes = {
isAbsoluteLink?: boolean | (() => boolean);
highlight?: boolean | (() => boolean);
expanded?: boolean | (() => boolean);
activeWhen?: string[];
isActive?: boolean;
};
export type SidebarEntry = {
path: string | SidebarEventHandler | null;
label: string;
attributes?: SidebarAttributes | null; // eslint-disable-line
children?: SidebarEntry[];
};
// Typescript requires the type here to correctly type the recursive path
export type SidebarRenderer = (_: SidebarEntry) => TemplateResult;

View File

@ -0,0 +1,60 @@
import { ROUTE_SEPARATOR } from "@goauthentik/common/constants";
import { SidebarEntry } from "./types";
export function entryKey(entry: SidebarEntry) {
return `${entry.path || "no-path"}:${entry.label}`;
}
// "Never store what you can calculate." (At least, if it's cheap.)
/**
* Takes tree and creates a map where every key is an entry in the tree and every value is that
* entry's parent.
*/
export function makeParentMap(entries: SidebarEntry[]) {
const reverseMap = new WeakMap<SidebarEntry, SidebarEntry>();
function reverse(entry: SidebarEntry) {
(entry.children ?? []).forEach((e) => {
reverseMap.set(e, entry);
reverse(e);
});
}
entries.forEach(reverse);
return reverseMap;
}
/**
* Given the current path and the collection of entries, identify which entry is currently live.
*
*/
const trailingSlash = new RegExp("/$");
const fixed = (s: string) => s.replace(trailingSlash, "");
function scanner(entry: SidebarEntry, activePath: string): SidebarEntry | undefined {
if (typeof entry.path === "string" && fixed(activePath) === fixed(entry.path)) {
return entry;
}
for (const matcher of entry.attributes?.activeWhen ?? []) {
const matchtest = new RegExp(matcher);
if (matchtest.test(activePath)) {
return entry;
}
}
return (entry.children ?? []).find((e) => scanner(e, activePath));
}
export function findMatchForNavbarUrl(entries: SidebarEntry[]) {
const activePath = window.location.hash.slice(1, Infinity).split(ROUTE_SEPARATOR)[0];
for (const entry of entries) {
const result = scanner(entry, activePath);
if (result) {
return result;
}
}
return undefined;
}

View File

@ -20,6 +20,11 @@ export abstract class TablePage<T> extends Table<T> {
return super.styles.concat(PFPage, PFContent, PFSidebar); return super.styles.concat(PFPage, PFContent, PFSidebar);
} }
constructor() {
super();
this.dataset.akApiTable = "true";
}
renderSidebarBefore(): TemplateResult { renderSidebarBefore(): TemplateResult {
return html``; return html``;
} }
@ -92,3 +97,18 @@ export abstract class TablePage<T> extends Table<T> {
${this.renderSectionAfter()}`; ${this.renderSectionAfter()}`;
} }
} }
// This painstakingly researched path is nonetheless surprisingly robust; it works for every extant
// TablePage, but only because Jens has been utterly consistent in where he puts his TablePage
// elements with respect to the Interface object. If we ever re-arrange this code, we're going
// to have to re-arrange this as well.
export function findTable<T, U extends TablePage<T>>(): U | undefined {
return (
(document.body
?.querySelector("[data-ak-interface-root]")
?.shadowRoot?.querySelector("ak-locale-context")
?.querySelector("ak-router-outlet")
?.shadowRoot?.querySelector("[data-ak-api-table]") as U) ?? undefined
);
}