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

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

commit d470058ea61c634cb9af87d78ace23004abe619f
Author: Wu Sheng <[email protected]>
AuthorDate: Wed Jun 24 00:32:09 2026 +0800

    feat(explore): Log inspect Kubernetes Pod logs source
    
    The third + final cross-layer Log inspect source. Kubernetes pod logs are
    on-demand, container-scoped, and never persisted, so there is no
    cold-stage concept (the global Cold toggle is a no-op here).
    
    - Pick entity: layer -> service -> pod (instance), plus a required
      Container dropdown lazy-loaded for the picked pod.
    - Conditions: Keywords + a SECOND-precision window; reuses the existing
      per-layer pod-log routes (listContainers + ondemandPodLogs) via
      bff.log.pod* — no new BFF route or explore branch.
    - Result: a dense read-only line list; OAP's errorReason (tailing is
      disabled by default on OAP) surfaces as a hint instead of a blank pane.
---
 CHANGELOG.md                                       |   1 +
 .../features/operate/explore/ExploreLogView.vue    | 267 ++++++++++++++++++++-
 apps/ui/src/i18n/locales/de.json                   |   7 +-
 apps/ui/src/i18n/locales/en.json                   |   7 +-
 apps/ui/src/i18n/locales/es.json                   |   7 +-
 apps/ui/src/i18n/locales/fr.json                   |   7 +-
 apps/ui/src/i18n/locales/ja.json                   |   7 +-
 apps/ui/src/i18n/locales/ko.json                   |   7 +-
 apps/ui/src/i18n/locales/pt.json                   |   7 +-
 apps/ui/src/i18n/locales/zh-CN.json                |   7 +-
 10 files changed, 296 insertions(+), 28 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6f1796..1434517 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ The version line is shared by every package in the monorepo 
(apps + shared packa
 - **Log inspect uses the full width.** The cross-layer Log inspect form 
(Target + Keywords / Tags / Trace ID / Time / Limit conditions) now spans the 
whole page instead of sharing a two-column strip with empty space.
 - **Clicking a log row opens a centered popout.** Both the cross-layer Log 
inspect and the per-layer Logs tab now open the same full-payload popout on row 
click — format-aware pretty-print (JSON pretty-printed by content type), the 
tags table, service / instance / endpoint / time meta, a copy button, and the 
trace link. Escape or the close button dismisses it.
 - **Log inspect can now query Browser errors across the page.** A new 
**Browser** source on Log inspect (beside Raw) queries the BROWSER layer's JS 
error logs from anywhere — pick a browser service, filter by category (AJAX / 
RESOURCE / VUE / PROMISE / JS / UNKNOWN) and time window, and read the error 
list (message, category, page path, app version, time, minified `line:col`). 
Clicking a row opens a popout with the error meta, the raw stack, and the same 
source-map de-obfuscation control [...]
+- **Log inspect can now read Kubernetes Pod logs across the page.** A new 
**Kubernetes Pod logs** source on Log inspect (beside Raw and Browser) tails a 
specific pod's container logs on demand from the K8s API through OAP — pick a 
layer, service, pod, and container, set a trailing window (30s … 30m) and 
optional keyword filters, and read the dense, read-only log lines (timestamp + 
content). These logs are streamed live and never persisted, so there is no 
cold-stage. When on-demand pod-lo [...]
 
 ## 0.7.0
 
diff --git a/apps/ui/src/features/operate/explore/ExploreLogView.vue 
b/apps/ui/src/features/operate/explore/ExploreLogView.vue
index 1bc6721..132640b 100644
--- a/apps/ui/src/features/operate/explore/ExploreLogView.vue
+++ b/apps/ui/src/features/operate/explore/ExploreLogView.vue
@@ -23,17 +23,20 @@
   opens the shared LogDetailPopout with the full payload. The
   resolved-query panel surfaces the exact condition the BFF ran.
 
-  Two sources: Log · raw (queryLogs) and Log · browser (BROWSER-layer JS
-  errors). The Browser source swaps the entity to a browser SERVICE picker
+  Three sources: Log · raw (queryLogs), Log · browser (BROWSER-layer JS
+  errors), and Log · Kubernetes Pod logs (on-demand container tail through
+  OAP). The Browser source swaps the entity to a browser SERVICE picker
   and the conditions to Category + Time + Limit, renders an error list, and
   the row-click popout adds the source-map de-obfuscation control (the same
-  resolve flow the per-layer Browser Logs tab uses). The next increment adds
-  the k8s pod-logs source — its toggle is present but disabled.
+  resolve flow the per-layer Browser Logs tab uses). The Kubernetes Pod logs
+  source pins a specific pod (instance) + container and does a one-shot
+  on-demand window fetch (the same BFF route the per-layer Pod Logs tab
+  tails) — these logs are never persisted, so there is no cold-stage.
 -->
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { bffClient } from '@/api/client';
+import { bff, bffClient } from '@/api/client';
 import type {
   BrowserErrorCategory,
   BrowserErrorRow,
@@ -46,8 +49,10 @@ import type {
   LogsResponse,
   SourceMapDescriptor,
 } from '@/api/client';
+import type { PodLogLine } from '@/api/scopes/log';
 import { useLayers } from '@/shell/useLayers';
 import { useTracePopout } from '@/layer/traces/useTracePopout';
+import { WINDOW_OPTS as POD_WINDOW_OPTS } from 
'@/layer/pod-logs/useLayerPodLogs';
 import TypeaheadSelect from '@/components/primitives/TypeaheadSelect.vue';
 import LogStreamPanel from '@/render/widgets/LogStreamPanel.vue';
 import LogDetailPopout from '@/render/widgets/LogDetailPopout.vue';
@@ -59,8 +64,9 @@ const { availableLayers } = useLayers();
 const { openTrace } = useTracePopout();
 
 // ── source. `raw` = queryLogs (any service across layers); `browser` =
-// BROWSER-layer JS errors. The k8s pod-logs source lands next (disabled). ─
-type LogSource = 'raw' | 'browser';
+// BROWSER-layer JS errors; `pods` = on-demand Kubernetes Pod logs (a
+// specific pod + container, never persisted). ─────────────────────────
+type LogSource = 'raw' | 'browser' | 'pods';
 const logSource = ref<LogSource>('raw');
 
 // ── browser entity — a BROWSER-layer service. Reuses the Pick metadata
@@ -165,6 +171,50 @@ watch(pickServiceId, () => {
   void loadEndpoints();
 });
 
+// ── pods entity — a specific pod (instance) + container. Reuses the Pick
+// cascade above (layer → service → instance), where the instance IS the
+// pod; the container list is lazy-loaded on-demand from the pod's id. ───
+const podContainer = ref<string>('');
+const podContainers = ref<string[]>([]);
+const containersLoading = ref(false);
+const containersError = ref<string | null>(null);
+
+async function loadContainers(): Promise<void> {
+  podContainer.value = '';
+  podContainers.value = [];
+  containersError.value = null;
+  const id = pickInstanceId.value;
+  if (!pickLayer.value || !id) return;
+  containersLoading.value = true;
+  try {
+    const r = await bff.log.podContainers(pickLayer.value, id);
+    if (r.errorReason) {
+      containersError.value = r.errorReason;
+    } else if (!r.reachable) {
+      containersError.value = r.error ?? t('OAP unreachable');
+    } else {
+      podContainers.value = r.containers;
+      // Auto-pick the first container (OAP lists the app container first).
+      podContainer.value = r.containers[0] ?? '';
+    }
+  } catch (e) {
+    containersError.value = e instanceof Error ? e.message : String(e);
+  } finally {
+    containersLoading.value = false;
+  }
+}
+// Only the pods source needs containers — fetch when its pinned pod changes.
+watch(pickInstanceId, () => {
+  if (logSource.value === 'pods') void loadContainers();
+});
+// Switching INTO pods with a pod already picked: list its containers now.
+watch(logSource, (src) => {
+  if (src === 'pods' && pickInstanceId.value && podContainers.value.length === 
0) {
+    void loadContainers();
+  }
+});
+const podContainerOptions = computed(() => podContainers.value.map((c) => ({ 
value: c, label: c })));
+
 const layerOptions = computed(() => availableLayers.value.map((l) => ({ value: 
l.key, label: l.name || l.key })));
 const serviceOptions = computed(() =>
   services.value.map((s) => ({ value: s.id, label: s.name, hint: s.normal === 
false ? 'virtual' : undefined })),
@@ -179,6 +229,10 @@ const endpointOptions = computed(() => [
 ]);
 const instanceSel = computed<string>({ get: () => pickInstanceId.value, set: 
(v) => (pickInstanceId.value = v ?? '') });
 const endpointSel = computed<string>({ get: () => pickEndpointId.value, set: 
(v) => (pickEndpointId.value = v ?? '') });
+// Pods source: the instance is REQUIRED (it is the pod), so no "All
+// instances" sentinel row — just the raw instance list.
+const podInstanceOptions = computed(() => instances.value.map((i) => ({ value: 
i.id, label: i.name })));
+const podInstanceSel = computed<string>({ get: () => pickInstanceId.value, 
set: (v) => (pickInstanceId.value = v ?? '') });
 
 const typeService = ref<string>('');
 const typeReal = ref<boolean>(true);
@@ -228,6 +282,12 @@ const cond = reactive({
 const WINDOWS = [15, 30, 60, 180, 360, 720, 1440];
 const LIMITS = [20, 50, 100, 200];
 
+// ── pods condition — a trailing SECOND-precision window (live tail), in
+// seconds. Reuses the per-layer Pod Logs window options. Keywords share
+// the `cond.keywords` field above (→ keywordsOfContent). No cold-stage:
+// pod logs are never persisted. ────────────────────────────────────────
+const podWindowSeconds = ref<number>(60);
+
 // ── browser condition: error category (ALL = no filter; the rest mirror
 // OAP's ErrorCategory enum verbatim). Time + Limit are shared with raw. ─
 const BROWSER_CATEGORIES: BrowserErrorCategory[] = ['ALL', 'AJAX', 'RESOURCE', 
'VUE', 'PROMISE', 'JS', 'UNKNOWN'];
@@ -280,11 +340,22 @@ const hasQueried = ref(false);
 const errorMsg = ref<string | null>(null);
 const logsResp = ref<LogsResponse | null>(null);
 const browserResp = ref<BrowserErrorsResponse | null>(null);
-const resolved = ref<ExploreResolved | null>(null);
+// Pod-log one-shot result. `errorReason` carries OAP's verbatim reason
+// (feature disabled / stale pod) so the pane shows a hint, not a blank.
+const podLines = ref<PodLogLine[]>([]);
+const podErrorReason = ref<string | null>(null);
+// The pods source bypasses the BFF explore resolver (it uses the
+// dedicated pod-log route), so its resolved-query echo is built UI-side.
+// `ExploreResolved.source` only types raw/browser, so widen the panel's
+// shape to also accept this local pod echo.
+type ResolvedEcho = ExploreResolved | { source: string; condition: 
Record<string, unknown> };
+const resolved = ref<ResolvedEcho | null>(null);
 const showResolved = ref(false);
 
 const canRun = computed(() => {
   if (logSource.value === 'browser') return !!browserServiceId.value;
+  // A pod log needs a specific pod + container — both required.
+  if (logSource.value === 'pods') return !!pickInstanceId.value && 
!!podContainer.value;
   return true;
 });
 
@@ -331,16 +402,58 @@ function buildRequest(): ExploreRequest {
   };
 }
 
+async function runPodQuery(): Promise<void> {
+  const keywords = parseKeywords(cond.keywords);
+  // Echo the exact pod-log condition into the Resolved-query panel —
+  // built UI-side because this source bypasses the explore.ts resolver.
+  resolved.value = {
+    source: 'pods',
+    condition: {
+      serviceInstanceId: pickInstanceId.value,
+      container: podContainer.value,
+      windowSeconds: podWindowSeconds.value,
+      ...(keywords.length > 0 ? { keywordsOfContent: keywords } : {}),
+    },
+  };
+  try {
+    const r = await bff.log.podLogs(pickLayer.value, {
+      serviceInstanceId: pickInstanceId.value,
+      container: podContainer.value,
+      windowSeconds: podWindowSeconds.value,
+      ...(keywords.length > 0 ? { keywordsOfContent: keywords } : {}),
+    });
+    if (r.errorReason) {
+      podErrorReason.value = r.errorReason;
+    } else if (!r.reachable) {
+      errorMsg.value = r.error ?? t('OAP unreachable');
+    } else {
+      podLines.value = r.lines;
+    }
+  } catch (e) {
+    errorMsg.value = e instanceof Error ? e.message : String(e);
+  }
+}
+
 async function runQuery(): Promise<void> {
   if (!canRun.value) return;
   running.value = true;
   hasQueried.value = true;
   errorMsg.value = null;
+  podErrorReason.value = null;
   closeDetail();
   // Cascade-clear: drop the prior result before the new query lands so
   // operators never read stale rows as the new state.
   logsResp.value = null;
   browserResp.value = null;
+  podLines.value = [];
+  if (logSource.value === 'pods') {
+    try {
+      await runPodQuery();
+    } finally {
+      running.value = false;
+    }
+    return;
+  }
   const req = buildRequest();
   try {
     const res = await bffClient.explore.query(req);
@@ -366,6 +479,16 @@ const rows = computed<LogRow[]>(() => (hasQueried.value ? 
logsResp.value?.logs ?
 const browserRows = computed<BrowserErrorRow[]>(() =>
   hasQueried.value && logSource.value === 'browser' ? browserResp.value?.logs 
?? [] : [],
 );
+const podRows = computed<PodLogLine[]>(() =>
+  hasQueried.value && logSource.value === 'pods' ? podLines.value : [],
+);
+/** Pod log line timestamp (ms epoch from the BFF) → HH:MM:SS, browser-local. 
*/
+function fmtPodTime(ts: number | null): string {
+  if (ts == null) return '';
+  const d = new Date(ts);
+  const pad = (n: number) => String(n).padStart(2, '0');
+  return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
+}
 
 // ── detail — clicking a row opens the shared full-payload popout. The
 // stream stays full-width; the popout owns its own Escape / close +
@@ -455,6 +578,8 @@ watch(logSource, () => {
   errorMsg.value = null;
   logsResp.value = null;
   browserResp.value = null;
+  podLines.value = [];
+  podErrorReason.value = null;
   resolved.value = null;
   closeDetail();
 });
@@ -472,7 +597,7 @@ watch(logSource, () => {
       <div class="seg">
         <button :class="{ on: logSource === 'raw' }" @click="logSource = 
'raw'">{{ t('Raw') }}</button>
         <button :class="{ on: logSource === 'browser' }" @click="logSource = 
'browser'">{{ t('Browser') }}</button>
-        <button disabled :title="t('coming next')">{{ t('Pod logs') }}</button>
+        <button :class="{ on: logSource === 'pods' }" :title="t('Kubernetes 
Pod logs')" @click="logSource = 'pods'">{{ t('Kubernetes Pod logs') }}</button>
       </div>
     </div>
 
@@ -498,6 +623,52 @@ watch(logSource, () => {
         </div>
       </div>
 
+      <!-- Kubernetes Pod logs source: a specific pod (instance) + container,
+           both required. Reuses the layer → service → instance Pick cascade
+           (the instance IS the pod); the container list is lazy-loaded from
+           the pod's id. No Type mode, no endpoint — a pod log scopes to one
+           container. -->
+      <div v-else-if="logSource === 'pods'" class="iq-target">
+        <div class="iq-target-h">
+          <span>{{ t('Kubernetes Pod logs') }} <small class="dim">{{ 
t('required — pick a pod and a container') }}</small></span>
+        </div>
+        <div class="iq-grid">
+          <label class="cf">
+            <span>{{ t('Layer') }}</span>
+            <TypeaheadSelect v-model="pickLayer" :aria-label="t('Layer')" 
:options="layerOptions" :placeholder="t('Any layer')" class="cf-tas" />
+          </label>
+          <label class="cf">
+            <span>{{ t('Service') }}</span>
+            <TypeaheadSelect
+              v-model="pickServiceId" :aria-label="t('Service')" 
:options="serviceOptions" :disabled="!pickLayer || servicesLoading"
+              :placeholder="servicesLoading ? t('Reading…') : t('Pick a 
service')" class="cf-tas"
+            />
+          </label>
+          <label class="cf">
+            <span>{{ t('Pod') }}</span>
+            <TypeaheadSelect
+              v-model="podInstanceSel" :aria-label="t('Pod')" 
:options="podInstanceOptions" :disabled="!pickServiceId"
+              :placeholder="t('Select a pod…')" class="cf-tas"
+            />
+          </label>
+          <label class="cf">
+            <span>{{ t('Container') }}</span>
+            <TypeaheadSelect
+              v-model="podContainer" :aria-label="t('Container')" 
:options="podContainerOptions"
+              :disabled="!pickInstanceId || containersLoading"
+              :placeholder="containersLoading ? t('Loading…') : 
(podContainers.length ? t('Select a container…') : t('No containers'))"
+              class="cf-tas"
+            />
+          </label>
+        </div>
+        <!-- Listing a pod's containers can itself return OAP's errorReason
+             (feature disabled / stale pod) — surface it before Run. -->
+        <div v-if="containersError" class="iq-pod-banner">
+          <strong>{{ t('Logs unavailable:') }}</strong> {{ containersError }}
+          <span class="dim">{{ t('— pick a currently-running pod, or check 
that on-demand pod logs are enabled on OAP.') }}</span>
+        </div>
+      </div>
+
       <div v-else class="iq-target">
         <div class="iq-target-h">
             <span>{{ t('Target') }} <small class="dim">{{ t('optional — blank 
queries all services') }}</small></span>
@@ -554,7 +725,7 @@ watch(logSource, () => {
 
         <div class="iq-conditions">
           <div class="iq-conditions-h">
-            <span class="kicker iq-cond-kicker">{{ logSource === 'browser' ? 
t('Browser errors') : 'Logs' }}</span>
+            <span class="kicker iq-cond-kicker">{{ logSource === 'browser' ? 
t('Browser errors') : (logSource === 'pods' ? t('Kubernetes Pod logs') : 
'Logs') }}</span>
             <button v-if="resolved" class="iq-resolved-tog" 
@click="showResolved = !showResolved">
               {{ showResolved ? '▾' : '▸' }} {{ t('Resolved query') }}
               <span class="dim">{{ resolved.source }}</span>
@@ -572,6 +743,12 @@ watch(logSource, () => {
                 </select>
               </label>
             </template>
+            <template v-else-if="logSource === 'pods'">
+              <label class="cf cf-wide">
+                <span>{{ t('Keywords') }}</span>
+                <input v-model="cond.keywords" class="cf-input mono" 
type="text" :placeholder="t('space- or comma-separated, AND-joined')" />
+              </label>
+            </template>
             <template v-else>
               <label class="cf cf-wide">
                 <span>{{ t('Keywords') }}</span>
@@ -586,7 +763,15 @@ watch(logSource, () => {
                 <input v-model="cond.traceId" class="cf-input mono" 
type="text" :placeholder="t('paste a trace id')" />
               </label>
             </template>
-            <label class="cf iq-time" :class="{ 'cf-wide': isCustomRange }">
+            <!-- Pods: a trailing SECOND-precision window (seconds) — the
+                 logs are tailed live, never persisted, so no custom range. -->
+            <label v-if="logSource === 'pods'" class="cf iq-time">
+              <span>{{ t('Window') }}</span>
+              <select v-model.number="podWindowSeconds" class="cf-input">
+                <option v-for="o in POD_WINDOW_OPTS" :key="o.value" 
:value="o.value">{{ t(o.label) }}</option>
+              </select>
+            </label>
+            <label v-else class="cf iq-time" :class="{ 'cf-wide': 
isCustomRange }">
               <span>{{ t('Time') }}</span>
               <template v-if="isCustomRange">
                 <span class="cf-range">
@@ -601,7 +786,7 @@ watch(logSource, () => {
                 <option :value="CUSTOM_RANGE_SENTINEL">{{ t('Custom…') 
}}</option>
               </select>
             </label>
-            <label class="cf">
+            <label v-if="logSource !== 'pods'" class="cf">
               <span>{{ t('Limit') }}</span>
               <select v-model.number="cond.limit" class="cf-input">
                 <option v-for="l in LIMITS" :key="l" :value="l">{{ l 
}}</option>
@@ -650,6 +835,35 @@ watch(logSource, () => {
       </article>
     </div>
 
+    <!-- Kubernetes Pod logs source: a read-only dense pane of on-demand log
+         lines (timestamp + content), matching the per-layer Pod Logs look.
+         OAP's errorReason (feature disabled / stale pod) shows as a hint. -->
+    <div v-else-if="logSource === 'pods'" class="iq-result">
+      <div v-if="!hasQueried" class="iq-empty">{{ t('Pick a pod and a 
container, then run a query.') }}</div>
+      <div v-else-if="running && podRows.length === 0" class="iq-empty">{{ 
t('Reading data…') }}</div>
+      <div v-else-if="errorMsg" class="iq-err">{{ errorMsg }}</div>
+      <div v-else-if="podErrorReason" class="iq-pod-banner">
+        <strong>{{ t('Logs unavailable:') }}</strong> {{ podErrorReason }}
+        <span class="dim">{{ t('— pick a currently-running pod, or check that 
on-demand pod logs are enabled on OAP.') }}</span>
+      </div>
+      <div v-else-if="podRows.length === 0" class="iq-empty">{{ t('No logs in 
this window.') }}</div>
+
+      <article v-else class="iq-list-card sw-card">
+        <header class="iq-list-head">
+          <h4>{{ t('Kubernetes Pod logs') }}</h4>
+          <span class="hint">{{ podRows.length }} {{ t('lines') }}</span>
+        </header>
+        <div class="iq-stream-scroll">
+          <div class="pl-stream">
+            <div v-for="(l, idx) in podRows" :key="`pl-${idx}`" class="pl-row">
+              <span class="pl-time mono dim">{{ fmtPodTime(l.timestamp) 
}}</span>
+              <span class="pl-content mono">{{ l.content }}</span>
+            </div>
+          </div>
+        </div>
+      </article>
+    </div>
+
     <div v-else class="iq-result">
       <div v-if="!hasQueried" class="iq-empty">{{ t('Run a query — name a 
service or leave it blank.') }}</div>
       <div v-else-if="running && rows.length === 0" class="iq-empty">{{ 
t('Reading data…') }}</div>
@@ -766,4 +980,33 @@ watch(logSource, () => {
   border-radius: 3px; padding: 0 5px; font-variant-numeric: tabular-nums;
 }
 .be-msg-body { flex: 1; min-width: 0; overflow: hidden; text-overflow: 
ellipsis; white-space: nowrap; }
+
+/* Pod-log line list — read-only dense lines (timestamp + content) in the
+   same dark vocabulary as the per-layer Pod Logs pane. Plain text, no
+   row-click popout (each line is just a string). */
+.pl-stream { font-size: 11.5px; }
+.pl-row {
+  display: grid;
+  grid-template-columns: 70px 1fr;
+  gap: 10px;
+  align-items: baseline;
+  padding: 2px 12px;
+  border-bottom: 1px solid var(--sw-line);
+}
+.pl-row:hover { background: var(--sw-bg-2); }
+.pl-time { font-size: 10.5px; flex: 0 0 auto; font-variant-numeric: 
tabular-nums; }
+.pl-content { color: var(--sw-fg-1); white-space: pre-wrap; word-break: 
break-word; }
+
+/* On-demand pod logs return OAP's errorReason when the feature is disabled
+   or the pod can't be resolved — surface it as a hint, not a blank pane. */
+.iq-pod-banner {
+  margin: 8px 0 0;
+  padding: 8px 12px;
+  border: 1px solid rgba(240, 160, 75, 0.5);
+  background: rgba(240, 160, 75, 0.1);
+  border-radius: 4px;
+  font-size: 11.5px;
+  color: #f0a04b;
+}
+.iq-pod-banner .dim { color: var(--sw-fg-3); }
 </style>
diff --git a/apps/ui/src/i18n/locales/de.json b/apps/ui/src/i18n/locales/de.json
index acc4ad3..755348e 100644
--- a/apps/ui/src/i18n/locales/de.json
+++ b/apps/ui/src/i18n/locales/de.json
@@ -1464,7 +1464,11 @@
   "No BROWSER layer": "Keine BROWSER-Ebene",
   "Pick a browser service": "Browser-Service auswählen",
   "Pick a browser service and run a query.": "Wähle einen Browser-Service und 
führe eine Abfrage aus.",
-  "Pod logs": "Pod-Logs",
+  "Kubernetes Pod logs": "Kubernetes Pod-Logs",
+  "required — pick a pod and a container": "erforderlich — Pod und Container 
auswählen",
+  "Pick a service": "Service auswählen",
+  "No containers": "Keine Container",
+  "Pick a pod and a container, then run a query.": "Wähle einen Pod und einen 
Container und führe dann eine Abfrage aus.",
   "required — pick a BROWSER service": "erforderlich — BROWSER-Service 
auswählen",
   "errors": "Fehler",
   "Copy": "Kopieren",
@@ -1484,7 +1488,6 @@
   "Service name": "Service-Name",
   "Time": "Zeit",
   "Value": "Wert",
-  "coming next": "demnächst",
   "e.g. agent::checkout": "z. B. agent::checkout",
   "level=ERROR, …": "level=ERROR, …",
   "logs": "Logs",
diff --git a/apps/ui/src/i18n/locales/en.json b/apps/ui/src/i18n/locales/en.json
index add41c4..fffabd3 100644
--- a/apps/ui/src/i18n/locales/en.json
+++ b/apps/ui/src/i18n/locales/en.json
@@ -1477,7 +1477,11 @@
   "No logs in this window.": "No logs in this window.",
   "Pick a browser service": "Pick a browser service",
   "Pick a browser service and run a query.": "Pick a browser service and run a 
query.",
-  "Pod logs": "Pod logs",
+  "Kubernetes Pod logs": "Kubernetes Pod logs",
+  "required — pick a pod and a container": "required — pick a pod and a 
container",
+  "Pick a service": "Pick a service",
+  "No containers": "No containers",
+  "Pick a pod and a container, then run a query.": "Pick a pod and a 
container, then run a query.",
   "required — pick a BROWSER service": "required — pick a BROWSER service",
   "errors": "errors",
   "Pick": "Pick",
@@ -1491,7 +1495,6 @@
   "Service name": "Service name",
   "Time": "Time",
   "Value": "Value",
-  "coming next": "coming next",
   "e.g. agent::checkout": "e.g. agent::checkout",
   "level=ERROR, …": "level=ERROR, …",
   "logs": "logs",
diff --git a/apps/ui/src/i18n/locales/es.json b/apps/ui/src/i18n/locales/es.json
index d663893..c41a2e1 100644
--- a/apps/ui/src/i18n/locales/es.json
+++ b/apps/ui/src/i18n/locales/es.json
@@ -1464,7 +1464,11 @@
   "No BROWSER layer": "Sin capa BROWSER",
   "Pick a browser service": "Elige un servicio de browser",
   "Pick a browser service and run a query.": "Elige un servicio de browser y 
ejecuta una consulta.",
-  "Pod logs": "Logs de pod",
+  "Kubernetes Pod logs": "Logs de Pod de Kubernetes",
+  "required — pick a pod and a container": "obligatorio — elige un pod y un 
contenedor",
+  "Pick a service": "Elige un servicio",
+  "No containers": "Sin contenedores",
+  "Pick a pod and a container, then run a query.": "Elige un pod y un 
contenedor, luego ejecuta una consulta.",
   "required — pick a BROWSER service": "obligatorio — elige un servicio 
BROWSER",
   "errors": "errores",
   "Copy": "Copiar",
@@ -1484,7 +1488,6 @@
   "Service name": "Nombre del Service",
   "Time": "Tiempo",
   "Value": "Valor",
-  "coming next": "próximamente",
   "e.g. agent::checkout": "p. ej. agent::checkout",
   "level=ERROR, …": "level=ERROR, …",
   "logs": "logs",
diff --git a/apps/ui/src/i18n/locales/fr.json b/apps/ui/src/i18n/locales/fr.json
index b914685..c433ecb 100644
--- a/apps/ui/src/i18n/locales/fr.json
+++ b/apps/ui/src/i18n/locales/fr.json
@@ -1464,7 +1464,11 @@
   "No BROWSER layer": "Aucune couche BROWSER",
   "Pick a browser service": "Choisir un service browser",
   "Pick a browser service and run a query.": "Choisissez un service browser et 
lancez une requête.",
-  "Pod logs": "Logs de pod",
+  "Kubernetes Pod logs": "Logs de Pod Kubernetes",
+  "required — pick a pod and a container": "requis — choisir un pod et un 
conteneur",
+  "Pick a service": "Choisir un service",
+  "No containers": "Aucun conteneur",
+  "Pick a pod and a container, then run a query.": "Choisissez un pod et un 
conteneur, puis lancez une requête.",
   "required — pick a BROWSER service": "requis — choisir un service BROWSER",
   "errors": "erreurs",
   "Copy": "Copier",
@@ -1484,7 +1488,6 @@
   "Service name": "Nom du Service",
   "Time": "Temps",
   "Value": "Valeur",
-  "coming next": "à venir",
   "e.g. agent::checkout": "p. ex. agent::checkout",
   "level=ERROR, …": "level=ERROR, …",
   "logs": "logs",
diff --git a/apps/ui/src/i18n/locales/ja.json b/apps/ui/src/i18n/locales/ja.json
index bc21b28..f7f96ef 100644
--- a/apps/ui/src/i18n/locales/ja.json
+++ b/apps/ui/src/i18n/locales/ja.json
@@ -1464,7 +1464,11 @@
   "No BROWSER layer": "BROWSER レイヤーがありません",
   "Pick a browser service": "browser サービスを選択",
   "Pick a browser service and run a query.": "browser サービスを選択してクエリを実行してください。",
-  "Pod logs": "Pod ログ",
+  "Kubernetes Pod logs": "Kubernetes Pod ログ",
+  "required — pick a pod and a container": "必須 — Pod とコンテナを選択",
+  "Pick a service": "サービスを選択",
+  "No containers": "コンテナがありません",
+  "Pick a pod and a container, then run a query.": "Pod 
とコンテナを選択してからクエリを実行してください。",
   "required — pick a BROWSER service": "必須 — BROWSER サービスを選択",
   "errors": "件のエラー",
   "Copy": "コピー",
@@ -1484,7 +1488,6 @@
   "Service name": "Service 名",
   "Time": "時間",
   "Value": "値",
-  "coming next": "近日対応",
   "e.g. agent::checkout": "例:agent::checkout",
   "level=ERROR, …": "level=ERROR, …",
   "logs": "件のログ",
diff --git a/apps/ui/src/i18n/locales/ko.json b/apps/ui/src/i18n/locales/ko.json
index d435f86..b8d01a1 100644
--- a/apps/ui/src/i18n/locales/ko.json
+++ b/apps/ui/src/i18n/locales/ko.json
@@ -1464,7 +1464,11 @@
   "No BROWSER layer": "BROWSER 레이어 없음",
   "Pick a browser service": "browser 서비스 선택",
   "Pick a browser service and run a query.": "browser 서비스를 선택하고 쿼리를 실행하세요.",
-  "Pod logs": "Pod 로그",
+  "Kubernetes Pod logs": "Kubernetes Pod 로그",
+  "required — pick a pod and a container": "필수 — Pod와 컨테이너를 선택하세요",
+  "Pick a service": "서비스 선택",
+  "No containers": "컨테이너 없음",
+  "Pick a pod and a container, then run a query.": "Pod와 컨테이너를 선택한 다음 쿼리를 
실행하세요.",
   "required — pick a BROWSER service": "필수 — BROWSER 서비스를 선택하세요",
   "errors": "개 오류",
   "Copy": "복사",
@@ -1484,7 +1488,6 @@
   "Service name": "Service 이름",
   "Time": "시간",
   "Value": "값",
-  "coming next": "곧 제공",
   "e.g. agent::checkout": "예: agent::checkout",
   "level=ERROR, …": "level=ERROR, …",
   "logs": "개 로그",
diff --git a/apps/ui/src/i18n/locales/pt.json b/apps/ui/src/i18n/locales/pt.json
index f4dd05f..be407b3 100644
--- a/apps/ui/src/i18n/locales/pt.json
+++ b/apps/ui/src/i18n/locales/pt.json
@@ -1464,7 +1464,11 @@
   "No BROWSER layer": "Sem camada BROWSER",
   "Pick a browser service": "Escolha um serviço de browser",
   "Pick a browser service and run a query.": "Escolha um serviço de browser e 
execute uma consulta.",
-  "Pod logs": "Logs de pod",
+  "Kubernetes Pod logs": "Logs de Pod do Kubernetes",
+  "required — pick a pod and a container": "obrigatório — escolha um pod e um 
contêiner",
+  "Pick a service": "Escolha um serviço",
+  "No containers": "Sem contêineres",
+  "Pick a pod and a container, then run a query.": "Escolha um pod e um 
contêiner e execute uma consulta.",
   "required — pick a BROWSER service": "obrigatório — escolha um serviço 
BROWSER",
   "errors": "erros",
   "Copy": "Copiar",
@@ -1484,7 +1488,6 @@
   "Service name": "Nome do Service",
   "Time": "Tempo",
   "Value": "Valor",
-  "coming next": "em breve",
   "e.g. agent::checkout": "ex. agent::checkout",
   "level=ERROR, …": "level=ERROR, …",
   "logs": "logs",
diff --git a/apps/ui/src/i18n/locales/zh-CN.json 
b/apps/ui/src/i18n/locales/zh-CN.json
index 9f8d8af..e6ac06e 100644
--- a/apps/ui/src/i18n/locales/zh-CN.json
+++ b/apps/ui/src/i18n/locales/zh-CN.json
@@ -1464,7 +1464,11 @@
   "No BROWSER layer": "无 BROWSER 层",
   "Pick a browser service": "选择一个 browser 服务",
   "Pick a browser service and run a query.": "选择一个 browser 服务并运行查询。",
-  "Pod logs": "Pod 日志",
+  "Kubernetes Pod logs": "Kubernetes Pod 日志",
+  "required — pick a pod and a container": "必填 — 选择一个 Pod 和一个容器",
+  "Pick a service": "选择服务",
+  "No containers": "无容器",
+  "Pick a pod and a container, then run a query.": "选择一个 Pod 和一个容器,然后运行查询。",
   "required — pick a BROWSER service": "必填 — 选择一个 BROWSER 服务",
   "errors": "条错误",
   "Copy": "复制",
@@ -1484,7 +1488,6 @@
   "Service name": "Service 名称",
   "Time": "时间",
   "Value": "值",
-  "coming next": "即将推出",
   "e.g. agent::checkout": "例如 agent::checkout",
   "level=ERROR, …": "level=ERROR, …",
   "logs": "条日志",

Reply via email to