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 c612e0e  ui shell: gate main zone on menu + bundle init, 
sidebar/topbar stay live
c612e0e is described below

commit c612e0e75361d3b5a9edb174182239a6f98cc3bd
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 17 17:27:03 2026 +0800

    ui shell: gate main zone on menu + bundle init, sidebar/topbar stay live
    
    In the login → deep-URL redirect case, the operator was landing
    inside per-page code (LayerShell etc.) before the menu or config
    bundle had returned, and every page's loading fallback fired
    independently — the "Layer not found" flash this morning was the
    visible symptom.
    
    Now AppShell holds the main zone (RouterView) behind a single
    init gate that flips once BOTH:
      - useLayers().isLoading is false AND (layers populated OR
        isError — degraded mode proceeds, with GlobalConnectivityBanner
        taking over the messaging).
      - configBundle.loaded is true (either localStorage hydration or
        the network preload settled).
    
    Sidebar + topbar render immediately so the EventTicker can stream
    init progress and the operator sees the framework is alive. The
    init card lives in the main pane only.
---
 apps/ui/src/shell/AppShell.vue | 58 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 55 insertions(+), 3 deletions(-)

diff --git a/apps/ui/src/shell/AppShell.vue b/apps/ui/src/shell/AppShell.vue
index b8f2991..568ec81 100644
--- a/apps/ui/src/shell/AppShell.vue
+++ b/apps/ui/src/shell/AppShell.vue
@@ -15,15 +15,16 @@
   limitations under the License.
 -->
 <script setup lang="ts">
-import { onMounted } from 'vue';
+import { computed, onMounted } from 'vue';
 import { RouterView } from 'vue-router';
 import AppSidebar from './AppSidebar.vue';
 import AppTopbar from './AppTopbar.vue';
 import GlobalConnectivityBanner from './GlobalConnectivityBanner.vue';
 import TracePopout from '@/layer/traces/TracePopout.vue';
 import ZipkinTracePopout from '@/layer/traces/ZipkinTracePopout.vue';
-import { ensureConfigBundle } from '@/controls/configBundle';
+import { ensureConfigBundle, useConfigBundle } from '@/controls/configBundle';
 import { useClickTracking } from '@/controls/useClickTracking';
+import { useLayers } from '@/shell/useLayers';
 
 // Kick the config preload once the shell mounts (i.e. after the auth
 // guard has let the user through). All layer dashboard configs +
@@ -39,6 +40,24 @@ onMounted(() => {
 // each framework load. See `controls/useClickTracking.ts` for the
 // suppression rules (the ticker itself, form inputs, decorative bits).
 useClickTracking();
+
+// Init gate. The main zone (RouterView) waits until BOTH the layer
+// registry and the dashboard-config bundle have settled, so on a
+// login → deep-URL redirect the user sees a single shell-level
+// "initializing…" placeholder instead of every page rendering
+// against empty layer state and flashing its own "not found"
+// fallback. We treat an OAP-unreachable settle as "ready" too — the
+// app proceeds in degraded mode and GlobalConnectivityBanner takes
+// over the messaging. The sidebar + topbar stay mounted throughout
+// so the EventTicker can stream init progress.
+const { layers, isLoading: layersLoading, isError: layersError } = useLayers();
+const { loaded: bundleLoaded } = useConfigBundle();
+const menuSettled = computed<boolean>(
+  () => !layersLoading.value && (layers.value.length > 0 || layersError.value),
+);
+const initReady = computed<boolean>(
+  () => menuSettled.value && bundleLoaded.value,
+);
 </script>
 
 <template>
@@ -50,7 +69,16 @@ useClickTracking();
            (`:12800`) poll reports unreachable. Admin-port (`:17128`)
            failures render per-page via AdminFeatureWarning, not here. -->
       <GlobalConnectivityBanner />
-      <RouterView />
+      <!-- Shell-level init placeholder. Visible until the layer
+           registry + config bundle have both loaded. Per-page code
+           runs against fully-populated state from the first paint. -->
+      <div v-if="!initReady" class="sw-init">
+        <div class="sw-card sw-init-card">
+          <h2>Initializing…</h2>
+          <p>Loading layer registry and dashboard templates. Watch the topbar 
event line for progress.</p>
+        </div>
+      </div>
+      <RouterView v-else />
     </main>
     <!-- Global trace-id popout: any page can call 
useTracePopout().openTrace(id)
          and this modal renders the waterfall + span detail. -->
@@ -62,3 +90,27 @@ useClickTracking();
     <ZipkinTracePopout />
   </div>
 </template>
+
+<style scoped>
+.sw-init {
+  padding: 48px 20px;
+  display: flex;
+  justify-content: center;
+}
+.sw-init-card {
+  max-width: 480px;
+  padding: 24px 28px;
+  text-align: center;
+}
+.sw-init-card h2 {
+  margin: 0 0 6px;
+  font-size: 15px;
+  color: var(--sw-fg-0);
+}
+.sw-init-card p {
+  margin: 0;
+  font-size: 12px;
+  color: var(--sw-fg-2);
+  line-height: 1.5;
+}
+</style>

Reply via email to