Skip to content

Add an optional theme-aware header account menu - #2376

Closed
miaolinopenvox wants to merge 3 commits into
lissy93:masterfrom
miaolinopenvox:codex/header-account-menu
Closed

miaolinopenvox wants to merge 3 commits into
lissy93:masterfrom
miaolinopenvox:codex/header-account-menu

Conversation

@miaolinopenvox

Copy link
Copy Markdown

Category

Feature

Overview

Dashy currently exposes account details inside its settings menu. This adds an optional header disclosure so users can see their current username, sign-in state and Dashy administrator status without opening settings.

Enable it with:

appConfig:
  showAccountMenu: true

The option defaults to false. The new control uses existing navigation/settings theme variables and delegates sign-in and sign-out to AuthButtons. It supports keyboard access, Escape/focus return, long names and narrow screens. When authentication is not configured, no identity or administrator badge is invented.

Account changes refresh on existing auth notifications, cross-tab storage events and window focus, plus a local 15-second check for cookie/token expiry. This does not poll the identity provider or promise immediate upstream revocation detection. OIDC user-loaded events now persist refreshed username/group/role information and notify the store, rather than updating only the token. The badge is informational and does not change server authorization.

The optional component is lazy-loaded: eager loading exposed an existing configuration/store dependency cycle in the production bundle, which was caught in browser testing.

Issue Number

Related discussion: #1823 (current-user display in a comment). This does not claim to resolve the original OIDC administrator-permission issue.

Additional Info

  • Schema: adds boolean appConfig.showAccountMenu, default false; documents behavior and adds English/Simplified Chinese labels. Existing UI remains unchanged when disabled.
  • Authentication: reuses native built-in, OIDC, Keycloak and header-mapped identity decisions and actions. This does not add mixed-mode emergency authentication or a new authorization mechanism.
  • Validation: 60 targeted tests passed on this branch, covering account presentation, native cookie/token decisions, expiry, OIDC refresh, keyboard behavior, cross-tab notification handling, native actions, existing OIDC server checks and config-save authorization. Targeted ESLint passed without warnings; production build and a fresh production-browser guest-startup check passed.
  • Browser coverage: the same feature backported onto 4.7.0 was additionally exercised with synthetic built-in administrator and ordinary-user accounts, native login/logout, cross-tab logout, Chinese labels and long names at 375px. All 54 built-in themes were switched and captured; the new control stayed within the viewport. Light/dark/current examples were visually inspected. This is not a claim that every upstream palette meets WCAG contrast, and mocked provider tests are not end-to-end acceptance of every external IdP.
  • Reviewer steps: enable the option with guest access and test accounts; compare guest, ordinary user and administrator states; open the disclosure with keyboard, Escape and tab navigation; switch themes and narrow the viewport; log out in another tab. With no auth configured or the option disabled, no account control should appear.
  • AI disclosure: implemented and tested with OpenAI Codex assistance, under the repository owner's direction.

Screenshots below use a local synthetic configuration, not a production organization.

Guest account menu

Administrator menu on a narrow screen

Copilot AI balanced review requested due to automatic review settings October 7, 2026 05:48
@netlify

netlify Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for dashy-dev ready!

Name Link
🔨 Latest commit 267da13
🔍 Latest deploy log https://app.netlify.com/projects/dashy-dev/deploys/6ac5dd3566123200094ce14b
😎 Deploy Preview https://deploy-preview-2376--dashy-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the ✨ New Feature [PR] Contains implementation of a new feature label Oct 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Responsive header positioning can clip account actions and overlap the account trigger with navigation.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds an optional, theme-aware header account menu that displays identity and administrator status while reusing existing authentication actions.

Changes:

  • Adds a lazy-loaded disclosure with keyboard support.
  • Refreshes account information from local session changes and OIDC events.
  • Adds configuration, translations, documentation, and targeted tests.
File Description
tests/​unit/​account-state.test.js Tests account presentation decisions.
tests/​unit/​account-state-integration.test.js Tests native session and expiry handling.
tests/​unit/​account-oidc-refresh.test.js Tests refreshed OIDC identity and roles.
tests/​components/​account-native-actions.test.js Tests existing authentication actions.
tests/​components/​account-menu.test.js Tests disclosure interaction and refresh.
src/​utils/​config/​ConfigSchema.json Defines the optional configuration flag.
src/​utils/​auth/​OidcAuth.js Persists refreshed identity and notifies the store.
src/​utils/​auth/​AccountState.js Derives display-only account information.
src/​components/​Settings/​AuthButtons.vue Adds reusable presentation options.
src/​components/​PageStrcture/​Header.vue Integrates the lazy-loaded account menu.
src/​components/​PageStrcture/​AccountMenu.vue Implements the themed account disclosure.
src/​assets/​locales/​zh-CN.json Adds Simplified Chinese labels.
src/​assets/​locales/​en.json Adds English labels.
docs/​configuring.md Documents configuration and session behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

.account-panel {
position: absolute;
top: calc(100% + 0.25rem);
right: 0;
Comment on lines +9 to +11
<div class="header-actions">
<Nav :links="pageInfo.navLinks" :user-hidden="!navVisible" class="nav" />
<AccountMenu v-if="$store.getters.appConfig.showAccountMenu" />

@lissy93 lissy93 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @miaolinopenvox
Thanks for the PR, it looks good :)

Although there's room to refine is a bit, mostly:

  • Try and keep the files this PR touches to a minimum, most of it should be able to be confined to AccountMenu.vue
  • Be sure not to duplicate code/logic that we already have
  • Careful with making changes to OIDC functionality in Auth.js
  • Remove all those tests, and re-write based on testing the actual complexities
  • Take a quick read through the code, some of it is much longer and more complex than it needs to be, and will add maintainability overhead for future developers

Comment thread docs/configuring.md
Comment on lines +487 to +502
### Header account menu

Set `appConfig.showAccountMenu: true` to show account information beside the navigation.
Signed-out visitors see **Guest / Not signed in**. Signed-in users see their Dashy
username and, when applicable, a **Dashy admin** badge. Open the disclosure to use
Dashy's existing sign-in or sign-out action. OIDC and Keycloak sign-out may also
end the provider session. No authentication configured means no account menu,
even though an unprotected dashboard can allow configuration changes.

The menu uses the active theme's navigation and settings colors and supports long
names, narrow screens, keyboard navigation and Escape to close. Its state follows
Dashy's existing local session checks: auth changes, other-tab storage changes,
window focus, and a local check every 15 seconds for cookie/token expiry. These
checks do not contact the identity provider or guarantee immediate detection of
provider-side account revocation. The badge describes Dashy permissions, not an
identity provider's administrator role, and never authorizes server operations.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in the auth docs instead maybe?

Or at least this file is the wrong place, since none of the other config options have a paragraph at the bottom.

Comment on lines +9 to +10
<span v-if="showGreeting && greeting" class="user-type-note">{{ greeting }}</span>
<span v-else-if="showGreeting && isGuest" class="user-type-note">{{ $t('settings.please-login') }}</span>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this change existing functionality? What is showGreeting from, I can't see where it's being passed in, so presumably it's always false and these 2 elements won't get shown anymore?

import { userStateEnum } from '@/utils/config/defaults';

/** Display-only snapshot. Server authorization never depends on this value. */
export default function getAccountState(auth = {}) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file needed?
You could instead read $store.getters.userState for userType and call getCurrentUser() / isUserAdmin() directly. Saves duplicating logic which might diverge/change later.

// Mirror token renewals into localStorage so Bearer attachment stays fresh
this.userManager.events.addUserLoaded((user) => {
if (user?.id_token) localStorage.setItem(localStorageKeys.ID_TOKEN, user.id_token);
if (user?.id_token) this.persistUserInfo(user);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will now sign the user in twice.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And changes here make me nervous. If I break something, it can brick OICD users out of their system, and OIDC has been really hard to test, since there's so many different OIDC providers/methods. So if possible, I want to keep as much of the logic the same as before, since at least we know that works 😅

Comment on lines +7 to +8
<span class="account-status">{{ $t(account.loggedIn
? 'account-menu.signed-in' : 'account-menu.signed-out') }}</span>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should update my check-locale script, so that we don't get that new warning that this translation is unused.

In the meantime if you want to solve the warning, you can write it like this:

account.loggedIn ? $t('account-menu.signed-in') : $t('account-menu.signed-out'))

(basically just always use a raw string for the $t() param instead of a variable).

setup(state); await wrapper.find('button').trigger('click');
await vi.advanceTimersByTimeAsync(500); expect(actions.logout).toHaveBeenCalledOnce();
});
it('local logout invokes native logout, auth notification and login navigation', async () => {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I am wrong, but is this sharing one mock across both OIDC and KC providers? Is that ok? Maybe, but it's unclear.

Comment on lines +28 to +30
fixture.loaded({ id_token: 'new-fixture-only', profile: { preferred_username: 'alice-renamed', groups: [] } });
expect(localStorage.setItem).toHaveBeenCalledWith(localStorageKeys.USERNAME, 'alice-renamed');
expect(localStorage.setItem).toHaveBeenCalledWith(localStorageKeys.ISADMIN, false);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expects to have been called, but this would have happened, because you just called it. It should really assert AUTH_CHANGED properly.

Comment on lines +7 to +20
vi.mock('@/store', () => ({ default: { commit: fixture.commit } }));
vi.mock('@/utils/i18n', () => ({ default: { global: { t: key => key } } }));
vi.mock('@/utils/Toast', () => ({ toast: vi.fn() }));
vi.mock('@/utils/logging/CoolConsole', () => ({ statusMsg: vi.fn(), statusErrorMsg: vi.fn() }));
vi.mock('@/utils/logging/ErrorHandler', () => ({ default: vi.fn() }));
vi.mock('oidc-client-ts', () => ({
WebStorageStateStore: class {},
Log: { setLogger: vi.fn(), setLevel: vi.fn() },
UserManager: class {
events = { addSilentRenewError() {}, addUserSignedOut() {}, addUserLoaded(fn) { fixture.loaded = fn; } };
getUser() { return Promise.resolve(null); }
},
}));
import { initOidcAuth } from '@/utils/auth/OidcAuth';

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this isn't very clear. I think it's just checking what it just called was called? But no assertion that it worked, or value was correct.

There should be some warnings in the console which should help with this.

} }));
let wrapper;
let store;
const guest = { name: '', loggedIn: false, admin: false, userType: 2 };

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userType: 2? I think it should be userStateEnum, and it's 4 which is keycloak enabled, not 2.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much of this is duplicated from account-state-integration. Can probably consolidate this.

@lissy93

lissy93 commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Thanks Miao, but closing for now as no response on the feedback.

@lissy93 lissy93 closed this Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ New Feature [PR] Contains implementation of a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants