This is an automated email from the ASF dual-hosted git repository. wu-sheng pushed a commit to branch fix/editor-source-default-remote in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
commit cff010acda56768ec76fa55ab2fafd42f8af87c1 Author: Wu Sheng <[email protected]> AuthorDate: Tue May 26 21:24:06 2026 +0800 fix(admin): default editor to remote + reset truly discards local draft Two operator-facing inconsistencies on the Layer Dashboards and Overview Templates admin editors: 1. **Editor source default = remote.** Remote is what `pickLayerContent` / `pickOverviewContent` in bundle.ts serve to end users for synced / diverged / remote-only rows. The editor now matches: on every (re-)mount it loads remote whenever reachable (local draft still wins when present; falls through to bundled only on bundled-fallback). The `from {source}` pill renders only when the operator has stepped off the default (saved a LOCAL draft, or hit Reset to BUNDLED) — labelling the default state was noise that diverged between the two pages (Layer Dashboards showed "from remote", Overview Templates showed nothing). 2. **Reset really discards.** The dropdown items "Reset to bundled" / "Reset to remote" are tooltipped "Discard current edits and reload …", but the implementation called `persistLocal(new_content)` after `loadFrom(src)`. On a diverged row that wrote the freshly-loaded content into localEdits as a brand-new draft — so right after hitting reset the TemplateConflictPrompt would pop up ("You have unpublished local edits · Layer · ActiveMQ"). Reset now calls `localEdits.remove(editName)` instead; subsequent edits in the editor re-create a draft naturally on the next change. Adds the new 'from {source}' + 'local' keys to all eight locale catalogs ('remote' and 'bundled' were already there). --- .../admin/layer-templates/LayerDashboardsAdmin.vue | 54 ++++++++++++------- .../overview-templates/OverviewTemplatesAdmin.vue | 63 ++++++++++------------ apps/ui/src/i18n/locales/de.json | 4 +- apps/ui/src/i18n/locales/en.json | 4 +- apps/ui/src/i18n/locales/es.json | 4 +- apps/ui/src/i18n/locales/fr.json | 4 +- apps/ui/src/i18n/locales/ja.json | 4 +- apps/ui/src/i18n/locales/ko.json | 4 +- apps/ui/src/i18n/locales/pt.json | 4 +- apps/ui/src/i18n/locales/zh-CN.json | 4 +- 10 files changed, 87 insertions(+), 62 deletions(-) diff --git a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue index 3cd07d6..c65d270 100644 --- a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue +++ b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue @@ -253,7 +253,14 @@ watch( * is the baseline for `dirty` (= unsaved edits since the last load/save). * Saving always writes LOCAL; you then publish LOCAL → OAP. * ─────────────────────────────────────────────────────────────────── */ -const editorSource = ref<'local' | 'bundled' | 'remote'>('bundled'); +// Remote is the canonical baseline (what the runtime menu renders for +// synced + diverged + remote-only rows). The editor opens from remote +// on every (re-)mount and stays there until the operator explicitly +// loads bundled (Reset to bundled) or saves a local draft. The default +// `'remote'` here matches what `syncDraft()` will pick on the first +// pass — pre-seeding the same value lets the source pill stay hidden +// (labelling the default would be noise). +const editorSource = ref<'local' | 'bundled' | 'remote'>('remote'); const loadedSnapshot = ref<string>(''); const editName = computed(() => layerEditName(selectedKey.value)); const hasLocalDraft = computed(() => localEdits.has(editName.value)); @@ -293,22 +300,22 @@ function loadFrom(src: 'local' | 'bundled' | 'remote'): void { editorSource.value = src; saveMsg.value = null; } -/** Seed the editor when the selected layer changes. Priority mirrors - * what the operator actually sees in the live menu / dashboards: +/** Seed the editor when the selected layer changes. Remote is the + * canonical baseline — it's what `pickLayerContent` in the runtime + * bundle serves to end users for synced / diverged / remote-only + * rows, so the editor opens from remote whenever remote is reachable. + * Priority: * 1. Local draft — unpublished in-progress edits in this browser. - * 2. Remote — when bundled and remote diverged (or the layer is - * remote-only), OAP is the source of truth and the runtime - * bundle loads it via `pickLayerContent` (bundle.ts). Showing - * bundled here would silently disagree with the live UI. - * 3. Bundled — synced rows are byte-equal anyway; bundled-fallback - * is the bundle's only source. */ + * 2. Remote — the default for every re-mount when remote exists. + * 3. Bundled — only when remote is absent (bundled-fallback). The + * operator can also hit "Reset to bundled" explicitly to swap; + * that path goes through `resetTo`, not this seed function. */ function syncDraft(): void { if (hasLocalDraft.value) { loadFrom('local'); return; } - const badge = sync.badgeFor(editName.value); - if ((badge === 'diverged' || badge === 'remote-only') && remoteAvailable.value) { + if (remoteAvailable.value) { loadFrom('remote'); return; } @@ -329,12 +336,17 @@ function persistLocal(content: AdminLayerTemplate): void { loadedSnapshot.value = JSON.stringify(content); } -// "Reset to ▾" dropdown — reload the editor from a source AND adopt it as -// the local draft (so the choice persists + Push/diff status updates). +// "Reset to ▾" dropdown — discard the current local draft and reload the +// editor from the picked source. The op-facing tooltip says "Discard +// current edits and reload …", so the action must DROP the local draft +// rather than re-stage the new content as a fresh draft (which the +// previous implementation did via persistLocal, triggering the +// TemplateConflictPrompt right after every reset). Subsequent edits in +// the editor re-create a local draft naturally on the next change. const resetDropdownOpen = ref(false); function resetTo(src: 'bundled' | 'remote'): void { loadFrom(src); - if (draft.template) persistLocal(draft.template); + localEdits.remove(editName.value); resetDropdownOpen.value = false; } @@ -1599,13 +1611,17 @@ const namingTest = computed<NamingTestResult>(() => { </div> <div class="actions"> <span v-if="saveMsg" class="save-msg">{{ saveMsg }}</span> - <!-- Where the editor content was loaded from — distinct from - the sync-status chip by the title (prefixed "from "). --> + <!-- Source pill. Remote is the canonical default — the + editor opens from remote on every (re-)mount, so a + "from remote" label would be noise on the common + path. Render only when the operator has stepped off + the default (loaded their LOCAL draft, or hit Reset + to BUNDLED). --> <span - v-if="editorSource === 'local' || !isSynced" + v-if="editorSource === 'local' || editorSource === 'bundled'" class="src-tag" - :title="`Editing from: ${editorSource}`" - >from {{ editorSource }}</span> + :title="`Editing from: ${t(editorSource)}`" + >{{ t('from {source}', { source: t(editorSource) }) }}</span> <!-- Reset the editor to a source (discard current content). --> <div class="reset-dd"> <button class="sw-btn" type="button" @click="resetDropdownOpen = !resetDropdownOpen"> diff --git a/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue b/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue index 353665e..c17236c 100644 --- a/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue +++ b/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue @@ -553,7 +553,13 @@ const isSynced = computed<boolean>( ); const draft = ref<OverviewDashboard | null>(null); -const editorSource = ref<'local' | 'bundled' | 'remote'>('bundled'); +// Remote is the canonical baseline. The editor opens from remote on +// every (re-)mount (`defaultEditorSource` returns 'remote' whenever +// remote is reachable + no local draft exists) and stays there until +// the operator explicitly hits "Reset to bundled" or saves a local +// draft. The initial ref matches so the source pill can stay hidden +// on the default path. +const editorSource = ref<'local' | 'bundled' | 'remote'>('remote'); const loadedSnapshot = ref<string>(''); function bundledContent(): OverviewDashboard | null { @@ -587,12 +593,15 @@ function persistLocal(content: OverviewDashboard): void { loadedSnapshot.value = JSON.stringify(content); } -// "Reset to ▾" dropdown — reload from a source AND adopt it as the local -// draft (so the choice persists + Push/diff status updates). +// "Reset to ▾" dropdown — discard the current local draft and reload +// the editor from the picked source. Symmetric with the layer +// dashboards editor: reset is "discard, reload", not "re-stage as a +// new draft" (which would trigger TemplateConflictPrompt right after +// every reset). Subsequent edits re-create a local draft naturally. const resetDropdownOpen = ref(false); function resetTo(src: 'bundled' | 'remote'): void { loadFrom(src); - if (draft.value) persistLocal(draft.value); + if (editName.value) localEdits.remove(editName.value); resetDropdownOpen.value = false; } @@ -667,25 +676,16 @@ const isDirty = computed<boolean>(() => ); // Which source to seed the editor from for the current selection. -// Priority mirrors what the operator sees on the live overview pages: +// Remote is the canonical baseline — it's what `pickOverviewContent` +// in the runtime bundle serves to end users — so the editor opens +// from remote on every re-mount when remote is reachable. Priority: // 1. Local draft — unpublished in-progress edits in this browser. -// 2. Remote — when bundled and remote diverged (or the row is -// remote-only), OAP is the source of truth and the runtime bundle -// loads remote via `pickOverviewContent` (bundle.ts). Loading -// bundled here silently disagrees with the live UI. -// 3. Bundled — synced rows are byte-equal anyway; bundled-fallback is -// the bundle's only source. -// 4. Last resort: remote when there's no bundled at all -// (defensive — every overview ships a bundled default today). +// 2. Remote — the default for every re-entry when remote exists. +// 3. Bundled — fallback for bundled-only overviews (no OAP row). function defaultEditorSource(): 'local' | 'bundled' | 'remote' { if (hasLocalDraft.value) return 'local'; - if (editorSource.value === 'remote' && remoteAvailable.value) return 'remote'; - const badge = editName.value ? sync.badgeFor(editName.value) : null; - if ((badge === 'diverged' || badge === 'remote-only') && remoteAvailable.value) { - return 'remote'; - } - if (bundledContent()) return 'bundled'; if (remoteAvailable.value) return 'remote'; + if (bundledContent()) return 'bundled'; return 'bundled'; } @@ -1058,24 +1058,17 @@ function widgetKindLabel(type: OverviewWidget['type']): string { <!-- Source / save / publish actions, right-aligned (same row as the title + tabs, mirroring the layer dashboards editor). --> <div class="ot__head-actions"> - <!-- Source pill. Mirrors the layer dashboards editor wording - ("from <source>") so operators get the same hint about - which bytes are currently in the buffer: - - local = unpublished browser draft - - remote = OAP-live (default for diverged / remote- - only rows so the editor agrees with what - the live menu renders) - - bundled = shipped default (only when the row is - bundled-fallback or the operator just - hit "Reset to bundled") - Hidden when the row is "synced" (bundled === remote) and - no local draft exists, because in that case "from - anything" is unambiguous noise. --> + <!-- Source pill. Remote is the canonical default — the + editor opens from remote on every (re-)mount, so a + "from remote" label would be noise on the common + path. Render only when the operator has stepped off + the default (loaded their LOCAL draft, or hit Reset + to BUNDLED). --> <span - v-if="editorSource === 'local' || !isSynced" + v-if="editorSource === 'local' || editorSource === 'bundled'" class="ot__src" - :title="`Editing from: ${editorSource}`" - >from {{ editorSource }}</span> + :title="`Editing from: ${t(editorSource)}`" + >{{ t('from {source}', { source: t(editorSource) }) }}</span> <div class="reset-dd"> <button type="button" class="ot__btn" @click="resetDropdownOpen = !resetDropdownOpen"> reset to <span class="caret" :class="{ open: resetDropdownOpen }">›</span> diff --git a/apps/ui/src/i18n/locales/de.json b/apps/ui/src/i18n/locales/de.json index aef836c..c83264d 100644 --- a/apps/ui/src/i18n/locales/de.json +++ b/apps/ui/src/i18n/locales/de.json @@ -1157,5 +1157,7 @@ "Window exceeds {h}h cap": "Fenster überschreitet Obergrenze von {h} h", "last {window}": "letzte {window}", "Metrics Analysis Language - …": "Metrics Analysis Language - …", - "Operate · OAP configuration": "Betrieb · OAP-Konfiguration" + "Operate · OAP configuration": "Betrieb · OAP-Konfiguration", + "from {source}": "aus {source}", + "local": "lokal" } diff --git a/apps/ui/src/i18n/locales/en.json b/apps/ui/src/i18n/locales/en.json index 3cc5141..fa35d34 100644 --- a/apps/ui/src/i18n/locales/en.json +++ b/apps/ui/src/i18n/locales/en.json @@ -1157,5 +1157,7 @@ "Window exceeds {h}h cap": "Window exceeds {h}h cap", "last {window}": "last {window}", "Metrics Analysis Language - …": "Metrics Analysis Language - …", - "Operate · OAP configuration": "Operate · OAP configuration" + "Operate · OAP configuration": "Operate · OAP configuration", + "from {source}": "from {source}", + "local": "local" } diff --git a/apps/ui/src/i18n/locales/es.json b/apps/ui/src/i18n/locales/es.json index 7f9a23d..9cd38d7 100644 --- a/apps/ui/src/i18n/locales/es.json +++ b/apps/ui/src/i18n/locales/es.json @@ -1157,5 +1157,7 @@ "Window exceeds {h}h cap": "La ventana supera el tope de {h}h", "last {window}": "últimos {window}", "Metrics Analysis Language - …": "Metrics Analysis Language - …", - "Operate · OAP configuration": "Operar · Configuración de OAP" + "Operate · OAP configuration": "Operar · Configuración de OAP", + "from {source}": "desde {source}", + "local": "local" } diff --git a/apps/ui/src/i18n/locales/fr.json b/apps/ui/src/i18n/locales/fr.json index 5471b28..a1585b7 100644 --- a/apps/ui/src/i18n/locales/fr.json +++ b/apps/ui/src/i18n/locales/fr.json @@ -1157,5 +1157,7 @@ "Window exceeds {h}h cap": "La fenêtre dépasse le plafond de {h} h", "last {window}": "dernier(s) {window}", "Metrics Analysis Language - …": "Metrics Analysis Language - …", - "Operate · OAP configuration": "Exploitation · Configuration OAP" + "Operate · OAP configuration": "Exploitation · Configuration OAP", + "from {source}": "depuis {source}", + "local": "local" } diff --git a/apps/ui/src/i18n/locales/ja.json b/apps/ui/src/i18n/locales/ja.json index 0864c8a..1723d61 100644 --- a/apps/ui/src/i18n/locales/ja.json +++ b/apps/ui/src/i18n/locales/ja.json @@ -1157,5 +1157,7 @@ "Window exceeds {h}h cap": "範囲が {h} 時間の上限を超えています", "last {window}": "直近 {window}", "Metrics Analysis Language - …": "Metrics Analysis Language - …", - "Operate · OAP configuration": "運用 · OAP 設定" + "Operate · OAP configuration": "運用 · OAP 設定", + "from {source}": "{source} から", + "local": "ローカル" } diff --git a/apps/ui/src/i18n/locales/ko.json b/apps/ui/src/i18n/locales/ko.json index 3d08e58..c73ae71 100644 --- a/apps/ui/src/i18n/locales/ko.json +++ b/apps/ui/src/i18n/locales/ko.json @@ -1157,5 +1157,7 @@ "Window exceeds {h}h cap": "범위가 {h}시간 상한을 초과합니다", "last {window}": "최근 {window}", "Metrics Analysis Language - …": "Metrics Analysis Language - …", - "Operate · OAP configuration": "운영 · OAP 구성" + "Operate · OAP configuration": "운영 · OAP 구성", + "from {source}": "{source}에서", + "local": "로컬" } diff --git a/apps/ui/src/i18n/locales/pt.json b/apps/ui/src/i18n/locales/pt.json index 52a347a..3e3a99a 100644 --- a/apps/ui/src/i18n/locales/pt.json +++ b/apps/ui/src/i18n/locales/pt.json @@ -1157,5 +1157,7 @@ "Window exceeds {h}h cap": "A janela excede o limite de {h}h", "last {window}": "últimos {window}", "Metrics Analysis Language - …": "Metrics Analysis Language - …", - "Operate · OAP configuration": "Operar · Configuração do OAP" + "Operate · OAP configuration": "Operar · Configuração do OAP", + "from {source}": "a partir de {source}", + "local": "local" } diff --git a/apps/ui/src/i18n/locales/zh-CN.json b/apps/ui/src/i18n/locales/zh-CN.json index 86ce28c..e86ec24 100644 --- a/apps/ui/src/i18n/locales/zh-CN.json +++ b/apps/ui/src/i18n/locales/zh-CN.json @@ -1157,5 +1157,7 @@ "Window exceeds {h}h cap": "窗口超过 {h} 小时上限", "last {window}": "最近 {window}", "Metrics Analysis Language - …": "Metrics Analysis Language - …", - "Operate · OAP configuration": "运维 · OAP 配置" + "Operate · OAP configuration": "运维 · OAP 配置", + "from {source}": "来自 {source}", + "local": "本地" }
