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": "条日志",
