feat: Command palette in Cmd+K search (#4861)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Amir Raminfar
2026-07-25 10:45:26 -07:00
committed by GitHub
parent df5a60479c
commit f348b57721
20 changed files with 624 additions and 57 deletions
+5
View File
@@ -219,6 +219,7 @@ declare global {
const useCloudConfig: typeof import('./composable/cloudConfig').useCloudConfig const useCloudConfig: typeof import('./composable/cloudConfig').useCloudConfig
const useCloudLogSearch: typeof import('./composable/cloudLogSearch').useCloudLogSearch const useCloudLogSearch: typeof import('./composable/cloudLogSearch').useCloudLogSearch
const useColorMode: typeof import('@vueuse/core').useColorMode const useColorMode: typeof import('@vueuse/core').useColorMode
const useCommands: typeof import('./composable/commands').useCommands
const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog
const useContainerActions: typeof import('./composable/containerActions').useContainerActions const useContainerActions: typeof import('./composable/containerActions').useContainerActions
const useContainerStore: typeof import('./stores/container').useContainerStore const useContainerStore: typeof import('./stores/container').useContainerStore
@@ -423,6 +424,9 @@ declare global {
export type { CloudLogHit } from './composable/cloudLogSearch' export type { CloudLogHit } from './composable/cloudLogSearch'
import('./composable/cloudLogSearch') import('./composable/cloudLogSearch')
// @ts-ignore // @ts-ignore
export type { CommandSection, Command } from './composable/commands'
import('./composable/commands')
// @ts-ignore
export type { DrawerWidth } from './composable/drawer' export type { DrawerWidth } from './composable/drawer'
import('./composable/drawer') import('./composable/drawer')
// @ts-ignore // @ts-ignore
@@ -661,6 +665,7 @@ declare module 'vue' {
readonly useCloudConfig: UnwrapRef<typeof import('./composable/cloudConfig')['useCloudConfig']> readonly useCloudConfig: UnwrapRef<typeof import('./composable/cloudConfig')['useCloudConfig']>
readonly useCloudLogSearch: UnwrapRef<typeof import('./composable/cloudLogSearch')['useCloudLogSearch']> readonly useCloudLogSearch: UnwrapRef<typeof import('./composable/cloudLogSearch')['useCloudLogSearch']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']> readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useCommands: UnwrapRef<typeof import('./composable/commands')['useCommands']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']> readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useContainerActions: UnwrapRef<typeof import('./composable/containerActions')['useContainerActions']> readonly useContainerActions: UnwrapRef<typeof import('./composable/containerActions')['useContainerActions']>
readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']> readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']>
+26 -1
View File
@@ -4,6 +4,7 @@ import { mount } from "@vue/test-utils";
import FuzzySearchModal from "./FuzzySearchModal.vue"; import FuzzySearchModal from "./FuzzySearchModal.vue";
import { Container } from "@/models/Container"; import { Container } from "@/models/Container";
import { lightTheme } from "@/stores/settings";
import { beforeEach, describe, expect, test, vi } from "vitest"; import { beforeEach, describe, expect, test, vi } from "vitest";
import { createI18n } from "vue-i18n"; import { createI18n } from "vue-i18n";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
@@ -16,7 +17,7 @@ vi.mock("vue-router");
vi.mock("@/stores/config", () => ({ vi.mock("@/stores/config", () => ({
__esModule: true, __esModule: true,
default: { base: "", hosts: [{ name: "localhost", id: "localhost" }] }, default: { base: "", hosts: [{ name: "localhost", id: "localhost" }], enableActions: true },
withBase: (path: string) => path, withBase: (path: string) => path,
})); }));
@@ -119,4 +120,28 @@ describe("<FuzzySearchModal />", () => {
await wrapper.find("input").trigger("keydown.enter"); await wrapper.find("input").trigger("keydown.enter");
expect(useRouter().push).toHaveBeenCalledWith({ name: "/container/[id]", params: { id: "567" } }); expect(useRouter().push).toHaveBeenCalledWith({ name: "/container/[id]", params: { id: "567" } });
}); });
test("matches commands by keyword", async () => {
const wrapper = createFuzzySearchModal();
await wrapper.find("input").setValue("theme");
const items = wrapper.findAll("li").map((li) => li.text());
expect(items).toContain("command-palette.theme-dark");
});
test("theme commands set the theme explicitly", async () => {
lightTheme.value = "auto";
const wrapper = createFuzzySearchModal();
await wrapper.find("input").setValue("dark theme");
await wrapper.find("input").trigger("keydown.enter");
expect(lightTheme.value).toBe("dark");
await wrapper.find("input").setValue("light theme");
await wrapper.find("input").trigger("keydown.enter");
expect(lightTheme.value).toBe("light");
await wrapper.find("input").setValue("system theme");
await wrapper.find("input").trigger("keydown.enter");
expect(lightTheme.value).toBe("auto");
});
}); });
+136 -56
View File
@@ -9,11 +9,11 @@
tabindex="0" tabindex="0"
class="text-base-content placeholder:text-base-content/40 flex-1 bg-transparent text-base outline-none" class="text-base-content placeholder:text-base-content/40 flex-1 bg-transparent text-base outline-none"
ref="input" ref="input"
@keydown.down="selectedIndex = Math.min(selectedIndex + 1, data.length - 1)" @keydown.down="selectedIndex = Math.min(selectedIndex + 1, totalCount - 1)"
@keydown.up="selectedIndex = Math.max(selectedIndex - 1, 0)" @keydown.up="selectedIndex = Math.max(selectedIndex - 1, 0)"
@keydown.enter.exact="onEnter" @keydown.enter.exact="onEnter"
@keydown.shift.enter.exact.prevent="runLogSearch" @keydown.shift.enter.exact.prevent="runLogSearch"
@keydown.alt.enter="addColumn(data[selectedIndex].item)" @keydown.alt.enter.exact.prevent="onPin"
v-model="query" v-model="query"
:placeholder="placeholderCopy" :placeholder="placeholderCopy"
/> />
@@ -29,51 +29,80 @@
<!-- Body: results + log search CTA. Only renders when there is something <!-- Body: results + log search CTA. Only renders when there is something
to show keeps the empty modal compact. --> to show keeps the empty modal compact. -->
<div v-if="results.length || logSearchVisible" class="border-base-content/10 border-t"> <div v-if="totalCount || logSearchVisible" class="border-base-content/10 border-t">
<!-- Containers section --> <!-- Scroll container spans both sections so the flat Commands + Containers
<template v-if="results.length"> list scrolls as one, matching the unified selection index. -->
<div class="text-base-content/40 px-4 pt-3 pb-1.5 text-xs font-semibold tracking-wider uppercase"> <div class="max-h-[50vh] overflow-y-auto overscroll-contain">
{{ $t("cloud-search.containers-section") }} · {{ data.length }} <!-- Commands section -->
</div> <template v-if="commandEntries.length">
<ul class="max-h-[50vh] overflow-y-auto overscroll-contain pb-1"> <div class="text-base-content/40 px-4 pt-3 pb-1.5 text-xs font-semibold tracking-wider uppercase">
<li v-for="(result, index) in data" ref="listItems"> {{ $t("command-palette.section-commands") }} · {{ commandEntries.length }}
<a </div>
class="hover:bg-base-content/5 flex cursor-pointer items-center gap-3 px-4 py-2" <ul class="pb-1">
:class="{ 'bg-base-content/10': index === selectedIndex }" <li v-for="(command, index) in commandEntries" :ref="(el) => setItemRef(el, index)">
@click.prevent="selected(result.item)" <a
> class="hover:bg-base-content/5 flex cursor-pointer items-center gap-3 px-4 py-2"
<div :class="result.item.state === 'running' ? 'text-primary' : 'text-base-content/50'"> :class="{ 'bg-base-content/10': index === selectedIndex }"
<template v-if="result.item.type === 'container'"> @click.prevent="runCommand(command)"
<octicon:container-24 class="size-4" />
</template>
<template v-else-if="result.item.type === 'service'">
<ph:stack-simple class="size-4" />
</template>
<template v-else-if="result.item.type === 'stack'">
<ph:stack class="size-4" />
</template>
</div>
<div class="min-w-0 flex-1 truncate text-sm">
<template v-if="config.hosts.length > 1 && result.item.host">
<span class="text-base-content/50 font-light">{{ result.item.host }}</span>
<span class="text-base-content/30"> / </span>
</template>
<span class="text-base-content" data-name v-html="matchedName(result)"></span>
</div>
<RelativeTime :date="result.item.created" class="text-base-content/40 text-xs" />
<span
@click.stop.prevent="addColumn(result.item)"
:title="$t('tooltip.pin-column')"
class="text-base-content/40 hover:text-secondary"
> >
<ic:sharp-keyboard-return v-if="index === selectedIndex" class="size-4" /> <component :is="command.icon" class="text-base-content/60 size-4 shrink-0" />
<cil:columns v-else-if="result.item.type === 'container'" class="size-4" /> <span class="min-w-0 flex-1 truncate text-sm">{{ command.title }}</span>
</span> <ic:sharp-keyboard-return v-if="index === selectedIndex" class="text-base-content/40 size-4" />
</a> </a>
</li> </li>
</ul> </ul>
</template> </template>
<!-- Containers section -->
<template v-if="containerEntries.length">
<div
class="text-base-content/40 px-4 pt-3 pb-1.5 text-xs font-semibold tracking-wider uppercase"
:class="{ 'border-base-content/10 mt-1 border-t': commandEntries.length }"
>
{{ $t("cloud-search.containers-section") }} · {{ containerEntries.length }}
</div>
<ul class="pb-1">
<li
v-for="(result, index) in containerEntries"
:ref="(el) => setItemRef(el, commandEntries.length + index)"
>
<a
class="hover:bg-base-content/5 flex cursor-pointer items-center gap-3 px-4 py-2"
:class="{ 'bg-base-content/10': commandEntries.length + index === selectedIndex }"
@click.prevent="selected(result.item)"
>
<div :class="result.item.state === 'running' ? 'text-primary' : 'text-base-content/50'">
<template v-if="result.item.type === 'container'">
<octicon:container-24 class="size-4" />
</template>
<template v-else-if="result.item.type === 'service'">
<ph:stack-simple class="size-4" />
</template>
<template v-else-if="result.item.type === 'stack'">
<ph:stack class="size-4" />
</template>
</div>
<div class="min-w-0 flex-1 truncate text-sm">
<template v-if="config.hosts.length > 1 && result.item.host">
<span class="text-base-content/50 font-light">{{ result.item.host }}</span>
<span class="text-base-content/30"> / </span>
</template>
<span class="text-base-content" data-name v-html="matchedName(result)"></span>
</div>
<RelativeTime :date="result.item.created" class="text-base-content/40 text-xs" />
<span
@click.stop.prevent="addColumn(result.item)"
:title="$t('tooltip.pin-column')"
class="text-base-content/40 hover:text-secondary"
>
<ic:sharp-keyboard-return v-if="commandEntries.length + index === selectedIndex" class="size-4" />
<cil:columns v-else-if="result.item.type === 'container'" class="size-4" />
</span>
</a>
</li>
</ul>
</template>
</div>
<!-- Log search CTA --> <!-- Log search CTA -->
<div <div
v-if="logSearchVisible" v-if="logSearchVisible"
@@ -130,7 +159,7 @@
<div <div
class="bg-base-300/40 border-base-content/10 text-base-content/50 flex items-center gap-4 border-t px-4 py-2 text-xs" class="bg-base-300/40 border-base-content/10 text-base-content/50 flex items-center gap-4 border-t px-4 py-2 text-xs"
> >
<span v-if="results.length" class="flex items-center gap-1.5"> <span v-if="totalCount" class="flex items-center gap-1.5">
<kbd class="kbd kbd-xs"></kbd> {{ $t("cloud-search.open-container") }} <kbd class="kbd kbd-xs"></kbd> {{ $t("cloud-search.open-container") }}
</span> </span>
<span v-if="cloudSearch.available.value && logSearchVisible" class="flex items-center gap-1"> <span v-if="cloudSearch.available.value && logSearchVisible" class="flex items-center gap-1">
@@ -164,6 +193,7 @@ import { useFuse } from "@vueuse/integrations/useFuse";
import { type FuseResult } from "fuse.js"; import { type FuseResult } from "fuse.js";
import { useCloudConfig } from "@/composable/cloudConfig"; import { useCloudConfig } from "@/composable/cloudConfig";
import { useCloudLogSearch } from "@/composable/cloudLogSearch"; import { useCloudLogSearch } from "@/composable/cloudLogSearch";
import { useCommands, type Command } from "@/composable/commands";
const close = defineEmit(); const close = defineEmit();
@@ -176,9 +206,15 @@ const route = useRoute();
const initialQuery = route?.path === "/cloud/search" && typeof route.query?.q === "string" ? route.query.q : ""; const initialQuery = route?.path === "/cloud/search" && typeof route.query?.q === "string" ? route.query.q : "";
const query = ref(initialQuery); const query = ref(initialQuery);
const input = ref<HTMLInputElement>(); const input = ref<HTMLInputElement>();
const listItems = ref<HTMLInputElement[]>(); const listItems = ref<(Element | null)[]>([]);
const selectedIndex = ref(0); const selectedIndex = ref(0);
// Function ref into a single flat array so Commands and Containers share one
// selection index for arrow-key navigation and scroll-into-view.
function setItemRef(el: any, index: number) {
listItems.value[index] = (el?.$el ?? el) as Element | null;
}
const containerStore = useContainerStore(); const containerStore = useContainerStore();
const pinnedStore = usePinnedLogsStore(); const pinnedStore = usePinnedLogsStore();
const { visibleContainers } = storeToRefs(containerStore); const { visibleContainers } = storeToRefs(containerStore);
@@ -268,6 +304,20 @@ const { results: fuseResults } = useFuse(query, list, {
const results = computed(() => (query.value ? fuseResults.value : [])); const results = computed(() => (query.value ? fuseResults.value : []));
// Commands palette. Fuzzy-matched against title/keywords while typing; the
// context commands (container actions) show up front on an empty query.
const { commands, contextCommands } = useCommands();
const { results: commandFuseResults } = useFuse(query, commands, {
fuseOptions: {
keys: ["title", "keywords"],
useExtendedSearch: true,
threshold: 0.3,
},
});
const commandEntries = computed<Command[]>(() =>
query.value ? commandFuseResults.value.map((r) => r.item) : contextCommands.value,
);
const data = computed(() => { const data = computed(() => {
return [...results.value].sort((a: FuseResult<Item>, b: FuseResult<Item>) => { return [...results.value].sort((a: FuseResult<Item>, b: FuseResult<Item>) => {
if (a.score === b.score) { if (a.score === b.score) {
@@ -284,14 +334,26 @@ const data = computed(() => {
}); });
}); });
watch(query, (data) => { // Container hits, mirrors the previously named `data` list for the template.
if (data.length > 0) { const containerEntries = computed(() => data.value);
selectedIndex.value = 0; const totalCount = computed(() => commandEntries.value.length + containerEntries.value.length);
// Reset to the top only when the user types. Live SSE container add/remove
// changes totalCount too, and resetting on that would snap the selection back
// to 0 while the palette is open.
watch(query, () => {
selectedIndex.value = 0;
});
// Keep the selection in bounds when the result count shrinks underneath it.
watch(totalCount, (count) => {
if (selectedIndex.value > count - 1) {
selectedIndex.value = Math.max(count - 1, 0);
} }
}); });
watch(selectedIndex, () => { watch(selectedIndex, () => {
listItems.value?.[selectedIndex.value].scrollIntoView({ block: "end" }); listItems.value?.[selectedIndex.value]?.scrollIntoView({ block: "nearest" });
}); });
function selected(item: Item) { function selected(item: Item) {
@@ -305,17 +367,35 @@ function selected(item: Item) {
close(); close();
} }
async function runCommand(command: Command) {
close();
await command.perform();
}
function onEnter() { function onEnter() {
// Plain Enter prefers a container match if one is selected. With no // Commands come first in the flat list, then containers. With nothing
// container matches (cloud-only query like "OOM"), fall back to log search // selectable (cloud-only query like "OOM"), fall back to log search so the
// so the user isn't stuck on a popup that does nothing. // user isn't stuck on a popup that does nothing.
if (data.value.length > 0) { const commandCount = commandEntries.value.length;
selected(data.value[selectedIndex.value].item); if (selectedIndex.value < commandCount) {
runCommand(commandEntries.value[selectedIndex.value]);
} else if (containerEntries.value.length > 0) {
selected(containerEntries.value[selectedIndex.value - commandCount].item);
} else if (cloudSearch.available.value && logSearchVisible.value) { } else if (cloudSearch.available.value && logSearchVisible.value) {
runLogSearch(); runLogSearch();
} }
} }
function onPin() {
// Alt+Enter pins a container column. Only meaningful when a container row is
// selected, not a command.
const commandCount = commandEntries.value.length;
if (selectedIndex.value >= commandCount) {
const entry = containerEntries.value[selectedIndex.value - commandCount];
if (entry?.item.type === "container") addColumn(entry.item);
}
}
function runLogSearch() { function runLogSearch() {
if (!cloudSearch.available.value) return; if (!cloudSearch.available.value) return;
const q = query.value.trim(); const q = query.value.trim();
+201
View File
@@ -0,0 +1,201 @@
import type { Component } from "vue";
import { Container } from "@/models/Container";
import { useContainerActions } from "@/composable/containerActions";
import config from "@/stores/config";
import {
lightTheme,
compact,
showTimestamp,
softWrap,
showAllContainers,
showStd,
smallerScrollbars,
} from "@/stores/settings";
import mdiThemeLightDark from "~icons/mdi/theme-light-dark";
import mdiWhiteBalanceSunny from "~icons/mdi/white-balance-sunny";
import mdiWeatherNight from "~icons/mdi/weather-night";
import mdiFormatLineSpacing from "~icons/mdi/format-line-spacing";
import mdiClockOutline from "~icons/mdi/clock-outline";
import mdiWrap from "~icons/mdi/wrap";
import mdiEyeOutline from "~icons/mdi/eye-outline";
import mdiFormatListBulleted from "~icons/mdi/format-list-bulleted";
import mdiUnfoldMoreHorizontal from "~icons/mdi/unfold-more-horizontal";
import mdiCogOutline from "~icons/mdi/cog-outline";
import carbonRestart from "~icons/carbon/restart";
import mdiStop from "~icons/mdi/stop";
import mdiPlay from "~icons/mdi/play";
import mdiDownload from "~icons/mdi/download";
export type CommandSection = "container" | "settings" | "navigation";
export type Command = {
id: string;
title: string;
section: CommandSection;
icon: Component;
keywords?: string;
perform: () => unknown;
};
// Central registry for the Cmd+K command palette. Commands are recomputed on
// every access so context-sensitive entries (container actions, current
// toggle labels) stay in sync with the route and settings.
export function useCommands() {
const { t } = useI18n();
const router = useRouter();
const route = useRoute();
const containerStore = useContainerStore();
const currentId = computed(() =>
route?.name === "/container/[id]" && typeof route.params.id === "string" ? route.params.id : "",
);
// Null-safe: containerStore.currentContainer is a stubbed action under
// @pinia/testing, so guard against it being absent.
const currentContainerRef = containerStore.currentContainer?.(currentId);
const currentContainer = computed(() => currentContainerRef?.value as Container | undefined);
// Bound to the current container. The cast is safe because the action
// handlers only read container.value when invoked, and container commands are
// only pushed into the list when currentContainer is truthy — so the handlers
// never run against an undefined container.
const { start, stop, restart, update } = useContainerActions(currentContainer as Ref<Container>);
const commands = computed<Command[]>(() => {
const list: Command[] = [];
const container = currentContainer.value;
if (container && config.enableActions) {
const name = container.name;
list.push({
id: "container.restart",
section: "container",
icon: carbonRestart,
title: t("command-palette.restart-container", { name }),
keywords: "restart reboot",
perform: restart,
});
if (container.state === "running") {
list.push({
id: "container.stop",
section: "container",
icon: mdiStop,
title: t("command-palette.stop-container", { name }),
keywords: "stop kill halt",
perform: stop,
});
} else {
list.push({
id: "container.start",
section: "container",
icon: mdiPlay,
title: t("command-palette.start-container", { name }),
keywords: "start run",
perform: start,
});
}
list.push({
id: "container.update",
section: "container",
icon: mdiDownload,
title: t("command-palette.update-container", { name }),
keywords: "update pull recreate upgrade",
perform: update,
});
}
list.push(
// lightTheme is tri-state, so expose each value as its own command rather
// than a single toggle — that keeps "auto" (follow OS) reachable and makes
// the target theme explicit instead of depending on the current state.
{
id: "settings.theme-auto",
section: "settings",
icon: mdiThemeLightDark,
title: t("command-palette.theme-auto"),
keywords: "theme auto system color mode appearance",
perform: () => (lightTheme.value = "auto"),
},
{
id: "settings.theme-light",
section: "settings",
icon: mdiWhiteBalanceSunny,
title: t("command-palette.theme-light"),
keywords: "theme light color mode appearance",
perform: () => (lightTheme.value = "light"),
},
{
id: "settings.theme-dark",
section: "settings",
icon: mdiWeatherNight,
title: t("command-palette.theme-dark"),
keywords: "theme dark color mode appearance",
perform: () => (lightTheme.value = "dark"),
},
{
id: "settings.toggle-compact",
section: "settings",
icon: mdiFormatLineSpacing,
title: t("command-palette.toggle-compact"),
keywords: "compact density spacing",
perform: () => (compact.value = !compact.value),
},
{
id: "settings.toggle-timestamps",
section: "settings",
icon: mdiClockOutline,
title: t("command-palette.toggle-timestamps"),
keywords: "timestamp time date",
perform: () => (showTimestamp.value = !showTimestamp.value),
},
{
id: "settings.toggle-soft-wrap",
section: "settings",
icon: mdiWrap,
title: t("command-palette.toggle-soft-wrap"),
keywords: "wrap soft line",
perform: () => (softWrap.value = !softWrap.value),
},
{
id: "settings.toggle-stopped",
section: "settings",
icon: mdiEyeOutline,
title: t("command-palette.toggle-stopped"),
keywords: "stopped hidden all containers exited",
perform: () => (showAllContainers.value = !showAllContainers.value),
},
{
id: "settings.toggle-std",
section: "settings",
icon: mdiFormatListBulleted,
title: t("command-palette.toggle-std"),
keywords: "stdout stderr std labels stream",
perform: () => (showStd.value = !showStd.value),
},
{
id: "settings.toggle-scrollbars",
section: "settings",
icon: mdiUnfoldMoreHorizontal,
title: t("command-palette.toggle-scrollbars"),
keywords: "scrollbar smaller thin",
perform: () => (smallerScrollbars.value = !smallerScrollbars.value),
},
{
id: "navigation.settings",
section: "navigation",
icon: mdiCogOutline,
title: t("command-palette.open-settings"),
keywords: "settings preferences options config",
perform: () => router.push("/settings"),
},
);
return list;
});
// Commands shown before the user types anything: the context-sensitive
// container actions so e.g. Restart is one keystroke away on a container page.
const contextCommands = computed(() => commands.value.filter((c) => c.section === "container"));
return { commands, contextCommands };
}
+16
View File
@@ -408,3 +408,19 @@ cloud-search:
container-removed: "Containeren er slettet" container-removed: "Containeren er slettet"
cta-settings: "Cloud-indstillinger" cta-settings: "Cloud-indstillinger"
container-removed-pill: "slettet" container-removed-pill: "slettet"
command-palette:
section-commands: Kommandoer
restart-container: Genstart {name}
stop-container: Stop {name}
start-container: Start {name}
update-container: Opdater {name}
theme-auto: Brug systemtema
theme-light: Skift til lyst tema
theme-dark: Skift til mørkt tema
toggle-compact: Skift kompakt tilstand
toggle-timestamps: Skift tidsstempler
toggle-soft-wrap: Skift blød ombrydning
toggle-stopped: Skift stoppede containere
toggle-std: Skift stdout/stderr-etiketter
toggle-scrollbars: Skift mindre rullepaneler
open-settings: Åbn indstillinger
+16
View File
@@ -408,3 +408,19 @@ cloud-search:
container-removed: "Container wurde gelöscht" container-removed: "Container wurde gelöscht"
cta-settings: "Cloud-Einstellungen" cta-settings: "Cloud-Einstellungen"
container-removed-pill: "gelöscht" container-removed-pill: "gelöscht"
command-palette:
section-commands: Befehle
restart-container: "{name} neu starten"
stop-container: "{name} stoppen"
start-container: "{name} starten"
update-container: "{name} aktualisieren"
theme-auto: Systemdesign verwenden
theme-light: Zu hellem Design wechseln
theme-dark: Zu dunklem Design wechseln
toggle-compact: Kompaktmodus umschalten
toggle-timestamps: Zeitstempel umschalten
toggle-soft-wrap: Zeilenumbruch umschalten
toggle-stopped: Gestoppte Container umschalten
toggle-std: stdout/stderr-Labels umschalten
toggle-scrollbars: Kleinere Scrollleisten umschalten
open-settings: Einstellungen öffnen
+16
View File
@@ -160,6 +160,22 @@ cloud-search:
container-removed: Container has been deleted container-removed: Container has been deleted
cta-settings: "Cloud settings" cta-settings: "Cloud settings"
container-removed-pill: removed container-removed-pill: removed
command-palette:
section-commands: Commands
restart-container: Restart {name}
stop-container: Stop {name}
start-container: Start {name}
update-container: Update {name}
theme-auto: Use system theme
theme-light: Switch to light theme
theme-dark: Switch to dark theme
toggle-compact: Toggle compact mode
toggle-timestamps: Toggle timestamps
toggle-soft-wrap: Toggle soft wrap
toggle-stopped: Toggle stopped containers
toggle-std: Toggle stdout/stderr labels
toggle-scrollbars: Toggle smaller scrollbars
open-settings: Open settings
settings: settings:
help-support: > help-support: >
Please support Dozzle by donating or sponsoring us on GitHub. Your contributions help us improve Dozzle for Please support Dozzle by donating or sponsoring us on GitHub. Your contributions help us improve Dozzle for
+16
View File
@@ -156,6 +156,22 @@ cloud-search:
container-removed: "El contenedor ha sido eliminado" container-removed: "El contenedor ha sido eliminado"
cta-settings: "Configuración de Cloud" cta-settings: "Configuración de Cloud"
container-removed-pill: "eliminado" container-removed-pill: "eliminado"
command-palette:
section-commands: Comandos
restart-container: Reiniciar {name}
stop-container: Detener {name}
start-container: Iniciar {name}
update-container: Actualizar {name}
theme-auto: Usar tema del sistema
theme-light: Cambiar a tema claro
theme-dark: Cambiar a tema oscuro
toggle-compact: Alternar modo compacto
toggle-timestamps: Alternar marcas de tiempo
toggle-soft-wrap: Alternar ajuste de línea
toggle-stopped: Alternar contenedores detenidos
toggle-std: Alternar etiquetas stdout/stderr
toggle-scrollbars: Alternar barras de desplazamiento más pequeñas
open-settings: Abrir configuración
settings: settings:
help-support: > help-support: >
Por favor, apoya a Dozzle donando o patrocinándonos en GitHub. Tus contribuciones nos ayudan a mejorar Dozzle para todos. ¡Gracias! 🙏🏼 Por favor, apoya a Dozzle donando o patrocinándonos en GitHub. Tus contribuciones nos ayudan a mejorar Dozzle para todos. ¡Gracias! 🙏🏼
+16
View File
@@ -408,3 +408,19 @@ cloud-search:
container-removed: "Le conteneur a été supprimé" container-removed: "Le conteneur a été supprimé"
cta-settings: "Paramètres Cloud" cta-settings: "Paramètres Cloud"
container-removed-pill: "supprimé" container-removed-pill: "supprimé"
command-palette:
section-commands: Commandes
restart-container: Redémarrer {name}
stop-container: Arrêter {name}
start-container: Démarrer {name}
update-container: Mettre à jour {name}
theme-auto: Utiliser le thème du système
theme-light: Passer au thème clair
theme-dark: Passer au thème sombre
toggle-compact: Basculer le mode compact
toggle-timestamps: Basculer les horodatages
toggle-soft-wrap: Basculer le retour à la ligne
toggle-stopped: Basculer les conteneurs arrêtés
toggle-std: Basculer les étiquettes stdout/stderr
toggle-scrollbars: Basculer les barres de défilement réduites
open-settings: Ouvrir les paramètres
+16
View File
@@ -420,3 +420,19 @@ cloud-search:
container-removed: "Kontainer telah dihapus" container-removed: "Kontainer telah dihapus"
cta-settings: "Pengaturan Cloud" cta-settings: "Pengaturan Cloud"
container-removed-pill: "dihapus" container-removed-pill: "dihapus"
command-palette:
section-commands: Perintah
restart-container: Mulai ulang {name}
stop-container: Hentikan {name}
start-container: Jalankan {name}
update-container: Perbarui {name}
theme-auto: Gunakan tema sistem
theme-light: Beralih ke tema terang
theme-dark: Beralih ke tema gelap
toggle-compact: Alihkan mode ringkas
toggle-timestamps: Alihkan stempel waktu
toggle-soft-wrap: Alihkan bungkus lembut
toggle-stopped: Alihkan kontainer yang dihentikan
toggle-std: Alihkan label stdout/stderr
toggle-scrollbars: Alihkan bilah gulir lebih kecil
open-settings: Buka pengaturan
+16
View File
@@ -408,3 +408,19 @@ cloud-search:
container-removed: "Il container è stato eliminato" container-removed: "Il container è stato eliminato"
cta-settings: "Impostazioni Cloud" cta-settings: "Impostazioni Cloud"
container-removed-pill: "eliminato" container-removed-pill: "eliminato"
command-palette:
section-commands: Comandi
restart-container: Riavvia {name}
stop-container: Ferma {name}
start-container: Avvia {name}
update-container: Aggiorna {name}
theme-auto: Usa tema di sistema
theme-light: Passa al tema chiaro
theme-dark: Passa al tema scuro
toggle-compact: Attiva/disattiva modalità compatta
toggle-timestamps: Attiva/disattiva timestamp
toggle-soft-wrap: Attiva/disattiva a capo automatico
toggle-stopped: Attiva/disattiva container fermati
toggle-std: Attiva/disattiva etichette stdout/stderr
toggle-scrollbars: Attiva/disattiva barre di scorrimento più piccole
open-settings: Apri impostazioni
+16
View File
@@ -412,3 +412,19 @@ cloud-search:
container-removed: "컨테이너가 삭제되었습니다" container-removed: "컨테이너가 삭제되었습니다"
cta-settings: "Cloud 설정" cta-settings: "Cloud 설정"
container-removed-pill: "삭제됨" container-removed-pill: "삭제됨"
command-palette:
section-commands: 명령
restart-container: "{name} 재시작"
stop-container: "{name} 중지"
start-container: "{name} 시작"
update-container: "{name} 업데이트"
theme-auto: 시스템 테마 사용
theme-light: 밝은 테마로 전환
theme-dark: 어두운 테마로 전환
toggle-compact: 컴팩트 모드 전환
toggle-timestamps: 타임스탬프 전환
toggle-soft-wrap: 자동 줄바꿈 전환
toggle-stopped: 중지된 컨테이너 전환
toggle-std: stdout/stderr 라벨 전환
toggle-scrollbars: 작은 스크롤바 전환
open-settings: 설정 열기
+16
View File
@@ -409,3 +409,19 @@ cloud-search:
container-removed: "Container is verwijderd" container-removed: "Container is verwijderd"
cta-settings: "Cloud-instellingen" cta-settings: "Cloud-instellingen"
container-removed-pill: "verwijderd" container-removed-pill: "verwijderd"
command-palette:
section-commands: Opdrachten
restart-container: "{name} opnieuw starten"
stop-container: "{name} stoppen"
start-container: "{name} starten"
update-container: "{name} bijwerken"
theme-auto: Systeemthema gebruiken
theme-light: Overschakelen naar licht thema
theme-dark: Overschakelen naar donker thema
toggle-compact: Compacte modus omschakelen
toggle-timestamps: Tijdstempels omschakelen
toggle-soft-wrap: Zachte terugloop omschakelen
toggle-stopped: Gestopte containers omschakelen
toggle-std: stdout/stderr-labels omschakelen
toggle-scrollbars: Kleinere schuifbalken omschakelen
open-settings: Instellingen openen
+16
View File
@@ -411,3 +411,19 @@ cloud-search:
container-removed: "Kontener został usunięty" container-removed: "Kontener został usunięty"
cta-settings: "Ustawienia Cloud" cta-settings: "Ustawienia Cloud"
container-removed-pill: "usunięty" container-removed-pill: "usunięty"
command-palette:
section-commands: Polecenia
restart-container: Uruchom ponownie {name}
stop-container: Zatrzymaj {name}
start-container: Uruchom {name}
update-container: Zaktualizuj {name}
theme-auto: Użyj motywu systemowego
theme-light: Przełącz na jasny motyw
theme-dark: Przełącz na ciemny motyw
toggle-compact: Przełącz tryb kompaktowy
toggle-timestamps: Przełącz znaczniki czasu
toggle-soft-wrap: Przełącz zawijanie wierszy
toggle-stopped: Przełącz zatrzymane kontenery
toggle-std: Przełącz etykiety stdout/stderr
toggle-scrollbars: Przełącz mniejsze paski przewijania
open-settings: Otwórz ustawienia
+16
View File
@@ -408,3 +408,19 @@ cloud-search:
container-removed: "O container foi excluído" container-removed: "O container foi excluído"
cta-settings: "Configurações da Cloud" cta-settings: "Configurações da Cloud"
container-removed-pill: "removido" container-removed-pill: "removido"
command-palette:
section-commands: Comandos
restart-container: Reiniciar {name}
stop-container: Parar {name}
start-container: Iniciar {name}
update-container: Atualizar {name}
theme-auto: Usar tema do sistema
theme-light: Mudar para tema claro
theme-dark: Mudar para tema escuro
toggle-compact: Alternar modo compacto
toggle-timestamps: Alternar carimbos de data/hora
toggle-soft-wrap: Alternar quebra de linha
toggle-stopped: Alternar contêineres parados
toggle-std: Alternar rótulos stdout/stderr
toggle-scrollbars: Alternar barras de rolagem menores
open-settings: Abrir configurações
+16
View File
@@ -408,3 +408,19 @@ cloud-search:
container-removed: "Контейнер был удалён" container-removed: "Контейнер был удалён"
cta-settings: "Настройки Cloud" cta-settings: "Настройки Cloud"
container-removed-pill: "удалён" container-removed-pill: "удалён"
command-palette:
section-commands: Команды
restart-container: Перезапустить {name}
stop-container: Остановить {name}
start-container: Запустить {name}
update-container: Обновить {name}
theme-auto: Использовать системную тему
theme-light: Переключить на светлую тему
theme-dark: Переключить на тёмную тему
toggle-compact: Переключить компактный режим
toggle-timestamps: Переключить метки времени
toggle-soft-wrap: Переключить мягкий перенос
toggle-stopped: Переключить остановленные контейнеры
toggle-std: Переключить метки stdout/stderr
toggle-scrollbars: Переключить уменьшенные полосы прокрутки
open-settings: Открыть настройки
+16
View File
@@ -414,3 +414,19 @@ cloud-search:
container-removed: "Vsebnik je bil izbrisan" container-removed: "Vsebnik je bil izbrisan"
cta-settings: "Nastavitve Cloud" cta-settings: "Nastavitve Cloud"
container-removed-pill: "izbrisano" container-removed-pill: "izbrisano"
command-palette:
section-commands: Ukazi
restart-container: Znova zaženi {name}
stop-container: Ustavi {name}
start-container: Zaženi {name}
update-container: Posodobi {name}
theme-auto: Uporabi sistemsko temo
theme-light: Preklopi na svetlo temo
theme-dark: Preklopi na temno temo
toggle-compact: Preklopi strnjeni način
toggle-timestamps: Preklopi časovne žige
toggle-soft-wrap: Preklopi mehko prelamljanje
toggle-stopped: Preklopi ustavljene vsebnike
toggle-std: Preklopi oznake stdout/stderr
toggle-scrollbars: Preklopi manjše drsnike
open-settings: Odpri nastavitve
+16
View File
@@ -412,3 +412,19 @@ cloud-search:
container-removed: "Konteyner silindi" container-removed: "Konteyner silindi"
cta-settings: "Cloud ayarları" cta-settings: "Cloud ayarları"
container-removed-pill: "silindi" container-removed-pill: "silindi"
command-palette:
section-commands: Komutlar
restart-container: "{name} yeniden başlat"
stop-container: "{name} durdur"
start-container: "{name} başlat"
update-container: "{name} güncelle"
theme-auto: Sistem temasını kullan
theme-light: Açık temaya geç
theme-dark: Koyu temaya geç
toggle-compact: Kompakt modu aç/kapat
toggle-timestamps: Zaman damgalarını aç/kapat
toggle-soft-wrap: Yumuşak kaydırmayı aç/kapat
toggle-stopped: Durdurulmuş konteynerleri aç/kapat
toggle-std: stdout/stderr etiketlerini aç/kapat
toggle-scrollbars: Daha küçük kaydırma çubuklarını aç/kapat
open-settings: Ayarları
+16
View File
@@ -411,3 +411,19 @@ cloud-search:
container-removed: "容器已被刪除" container-removed: "容器已被刪除"
cta-settings: "Cloud 設定" cta-settings: "Cloud 設定"
container-removed-pill: "已刪除" container-removed-pill: "已刪除"
command-palette:
section-commands: 命令
restart-container: 重新啟動 {name}
stop-container: 停止 {name}
start-container: 啟動 {name}
update-container: 更新 {name}
theme-auto: 使用系統主題
theme-light: 切換到淺色主題
theme-dark: 切換到深色主題
toggle-compact: 切換精簡模式
toggle-timestamps: 切換時間戳記
toggle-soft-wrap: 切換軟換行
toggle-stopped: 切換已停止的容器
toggle-std: 切換 stdout/stderr 標籤
toggle-scrollbars: 切換較小的捲軸
open-settings: 開啟設定
+16
View File
@@ -408,3 +408,19 @@ cloud-search:
container-removed: "容器已被删除" container-removed: "容器已被删除"
cta-settings: "Cloud 设置" cta-settings: "Cloud 设置"
container-removed-pill: "已删除" container-removed-pill: "已删除"
command-palette:
section-commands: 命令
restart-container: 重启 {name}
stop-container: 停止 {name}
start-container: 启动 {name}
update-container: 更新 {name}
theme-auto: 使用系统主题
theme-light: 切换到浅色主题
theme-dark: 切换到深色主题
toggle-compact: 切换紧凑模式
toggle-timestamps: 切换时间戳
toggle-soft-wrap: 切换软换行
toggle-stopped: 切换已停止的容器
toggle-std: 切换 stdout/stderr 标签
toggle-scrollbars: 切换更小的滚动条
open-settings: 打开设置