This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 3ec51dd  fix(admin): unpublished-edits prompt re-shows after login + 
align verb gates (#11)
3ec51dd is described below

commit 3ec51dd44617af02349cb958dedcbbd151b31476
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Sun May 24 08:04:14 2026 +0800

    fix(admin): unpublished-edits prompt re-shows after login + align verb 
gates (#11)
    
    Two small, related fixes for the "You have unpublished local edits"
    prompt that appears for editors with browser-local template drafts.
    
    - auth.login() now clears the prompt's `sessionStorage` dismissal key
      on every successful login. Previously the per-tab dismissal carried
      across the logout / login boundary in the same browser tab, so an
      operator who had dismissed the modal earlier would log back in and
      not see the reminder for drafts they hadn't pushed. Since /login
      lives outside AppShell, the next AppShell mount re-reads the
      (now-clean) sessionStorage and the modal opens reliably whenever
      drafts exist.
    - TemplateConflictPrompt's gate for layer drafts is now `dashboard:write`
      (was `dashboard:read`), matching the symmetric `overview:write` check
      used for overview drafts. The comment above the component already
      said "kinds the user can edit"; the prior read-verb worked in the
      default role policy only because dashboard:read and dashboard:write
      resolve to the same roles (operator + admin). A future role that
      grants read-only dashboard access would have seen the prompt
      without being able to act on it.
    
    Net effect: viewers + maintainers see no prompt (unchanged — they
    can't push drafts anyway); operator + admin see the prompt reliably
    on every fresh login, whatever they did in a prior session.
---
 apps/ui/src/shell/TemplateConflictPrompt.vue |  6 +++++-
 apps/ui/src/state/auth.ts                    | 10 ++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/apps/ui/src/shell/TemplateConflictPrompt.vue 
b/apps/ui/src/shell/TemplateConflictPrompt.vue
index 24af840..e0fec2a 100644
--- a/apps/ui/src/shell/TemplateConflictPrompt.vue
+++ b/apps/ui/src/shell/TemplateConflictPrompt.vue
@@ -40,7 +40,11 @@ const { bundle } = useConfigBundle();
 const auth = useAuthStore();
 const { edits } = useLocalTemplateEdits();
 
-const canEditLayers = computed<boolean>(() => auth.hasVerb('dashboard:read'));
+// Both checks are `:write` for symmetry — the prompt nudges operators
+// who can act on the draft (publish or discard). `dashboard:read` would
+// resolve to the same roles in the default policy today, but a role with
+// read-only dashboard access shouldn't be nagged about edits it can't push.
+const canEditLayers = computed<boolean>(() => auth.hasVerb('dashboard:write'));
 const canEditOverviews = computed<boolean>(() => 
auth.hasVerb('overview:write'));
 
 interface DraftItem {
diff --git a/apps/ui/src/state/auth.ts b/apps/ui/src/state/auth.ts
index b3249a9..d08c15b 100644
--- a/apps/ui/src/state/auth.ts
+++ b/apps/ui/src/state/auth.ts
@@ -42,6 +42,16 @@ export const useAuthStore = defineStore('auth', () => {
       user.value = await bffClient.session.login(username, password);
       // New login session → re-prompt the local-vs-remote template choice.
       useTemplatePreference().reset();
+      // Clear the per-session "unpublished local edits" prompt dismissal
+      // so a fresh login sees the reminder reliably. Without this, a
+      // dismissal from an earlier session in the same browser tab keeps
+      // the modal hidden across the logout / login boundary — operators
+      // log back in to find drafts they pushed nothing about.
+      try {
+        sessionStorage.removeItem('horizon:localDraftPrompt:dismissed');
+      } catch {
+        /* private mode — module-level state still resets on AppShell re-mount 
*/
+      }
       return true;
     } catch (err) {
       if (err instanceof BffApiError && err.status === 401) {

Reply via email to