Removes fuzzysearch and uses fuse.js (#1888)
* Removes fuzzysearch and uses fuse.js * Fix search bug * Adds locale * Fixes more locale * Fixes search to not update with stats * Fixes sort order
This commit is contained in:
@@ -3,26 +3,29 @@
|
||||
<o-autocomplete
|
||||
ref="autocomplete"
|
||||
v-model="query"
|
||||
placeholder="Search containers using ⌘ + k or ctrl + k"
|
||||
field="name"
|
||||
:placeholder="$t('placeholder.search-containers')"
|
||||
open-on-focus
|
||||
keep-first
|
||||
expanded
|
||||
:data="results"
|
||||
:data="data"
|
||||
@select="selected"
|
||||
>
|
||||
<template #default="props">
|
||||
<template #default="{ option: item }">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<span class="icon is-small" :class="props.option.state">
|
||||
<span class="icon is-small" :class="item.state">
|
||||
<octicon-container-24 />
|
||||
</span>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
{{ props.option.name }}
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="media-right">
|
||||
<span class="icon is-small column-icon" @click.stop.prevent="addColumn(props.option)" title="Pin as column">
|
||||
<span
|
||||
class="icon is-small column-icon"
|
||||
@click.stop.prevent="addColumn(item)"
|
||||
:title="$t('tooltip.pin-column')"
|
||||
>
|
||||
<cil-columns />
|
||||
</span>
|
||||
</div>
|
||||
@@ -33,55 +36,61 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import fuzzysort from "fuzzysort";
|
||||
import { type Container } from "@/types/Container";
|
||||
import { useFuse } from "@vueuse/integrations/useFuse";
|
||||
|
||||
const { maxResults = 20 } = defineProps<{
|
||||
const { maxResults: resultLimit = 20 } = defineProps<{
|
||||
maxResults?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["close"]);
|
||||
const emit = defineEmits<{
|
||||
(e: "close"): void;
|
||||
}>();
|
||||
|
||||
const query = ref("");
|
||||
const autocomplete = ref<HTMLElement>();
|
||||
const router = useRouter();
|
||||
const store = useContainerStore();
|
||||
const { containers } = storeToRefs(store);
|
||||
const preparedContainers = computed(() =>
|
||||
containers.value.map(({ name, id, created, state }) =>
|
||||
reactive({
|
||||
name,
|
||||
|
||||
const list = computed(() => {
|
||||
return containers.value.map(({ id, created, name, state }) => {
|
||||
return {
|
||||
id,
|
||||
created,
|
||||
name,
|
||||
state,
|
||||
preparedName: fuzzysort.prepare(name),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const results = computed(() => {
|
||||
const options = {
|
||||
limit: maxResults,
|
||||
key: "preparedName",
|
||||
};
|
||||
if (query.value) {
|
||||
const results = fuzzysort.go(query.value, preparedContainers.value, options);
|
||||
results.forEach((result) => {
|
||||
if (result.obj.state === "running") {
|
||||
// @ts-ignore
|
||||
result.score += 1;
|
||||
}
|
||||
});
|
||||
return [...results].sort((a, b) => b.score - a.score).map((i) => i.obj);
|
||||
} else {
|
||||
return [...preparedContainers.value].sort((a, b) => b.created - a.created);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => nextTick(() => autocomplete.value?.focus()));
|
||||
const { results } = useFuse(query, list, {
|
||||
fuseOptions: { keys: ["name"], includeScore: true },
|
||||
resultLimit,
|
||||
matchAllWhenSearchEmpty: true,
|
||||
});
|
||||
|
||||
function selected(item: { id: string; name: string }) {
|
||||
router.push({ name: "container-id", params: { id: item.id } });
|
||||
const data = computed(() => {
|
||||
return results.value
|
||||
.sort((a, b) => {
|
||||
if (a.score === b.score) {
|
||||
if (a.item.state === "running" && b.item.state !== "running") {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else if (a.score && b.score) {
|
||||
return a.score - b.score;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
.map(({ item }) => item);
|
||||
});
|
||||
watchOnce(autocomplete, () => autocomplete.value?.focus());
|
||||
|
||||
function selected({ id }: { id: string }) {
|
||||
router.push({ name: "container-id", params: { id } });
|
||||
emit("close");
|
||||
}
|
||||
function addColumn(container: Container) {
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
class="input"
|
||||
type="text"
|
||||
:placeholder="$t('placeholder.search-containers')"
|
||||
v-model="search"
|
||||
@keyup.esc="search.value = null"
|
||||
v-model="query"
|
||||
@keyup.esc="query = ''"
|
||||
@keyup.enter="onEnter()"
|
||||
/>
|
||||
<span class="icon is-left">
|
||||
@@ -63,13 +63,13 @@
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="panel-tabs" v-if="!search">
|
||||
<p class="panel-tabs" v-if="query === ''">
|
||||
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
|
||||
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
|
||||
</p>
|
||||
<router-link
|
||||
:to="{ name: 'container-id', params: { id: item.id } }"
|
||||
v-for="item in results.slice(0, 10)"
|
||||
v-for="item in data.slice(0, 10)"
|
||||
:key="item.id"
|
||||
class="panel-block"
|
||||
>
|
||||
@@ -86,54 +86,59 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import fuzzysort from "fuzzysort";
|
||||
import SearchIcon from "~icons/mdi-light/magnify";
|
||||
import { useFuse } from "@vueuse/integrations/useFuse";
|
||||
|
||||
const { base, version, secured } = config;
|
||||
const containerStore = useContainerStore();
|
||||
const { containers } = storeToRefs(containerStore);
|
||||
const router = useRouter();
|
||||
|
||||
const sort = ref("running");
|
||||
const search = ref();
|
||||
const sort = $ref("running");
|
||||
const query = ref("");
|
||||
|
||||
const results = computed(() => {
|
||||
if (search.value) {
|
||||
return fuzzysort.go(search.value, containers.value, { key: "name" }).map((i) => i.obj);
|
||||
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
|
||||
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
|
||||
|
||||
const { results } = useFuse(query, containers, {
|
||||
fuseOptions: { keys: ["name"] },
|
||||
matchAllWhenSearchEmpty: false,
|
||||
});
|
||||
const data = computed(() => {
|
||||
if (results.value.length) {
|
||||
return results.value.map(({ item }) => item);
|
||||
}
|
||||
switch (sort.value) {
|
||||
switch (sort) {
|
||||
case "all":
|
||||
return mostRecentContainers.value;
|
||||
return mostRecentContainers;
|
||||
case "running":
|
||||
return runningContainers.value;
|
||||
return runningContainers;
|
||||
default:
|
||||
throw `Invalid sort order: ${sort.value}`;
|
||||
throw `Invalid sort order: ${sort}`;
|
||||
}
|
||||
});
|
||||
|
||||
const mostRecentContainers = computed(() => [...containers.value].sort((a, b) => b.created - a.created));
|
||||
const runningContainers = computed(() => mostRecentContainers.value.filter((c) => c.state === "running"));
|
||||
const totalCpu = ref(0);
|
||||
let totalCpu = $ref(0);
|
||||
useIntervalFn(
|
||||
() => {
|
||||
totalCpu.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
|
||||
totalCpu = runningContainers.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
|
||||
},
|
||||
1000,
|
||||
{ immediate: true }
|
||||
);
|
||||
const totalMem = ref(0);
|
||||
|
||||
let totalMem = $ref(0);
|
||||
useIntervalFn(
|
||||
() => {
|
||||
totalMem.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
|
||||
totalMem = runningContainers.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
|
||||
},
|
||||
1000,
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function onEnter() {
|
||||
if (results.value.length == 1) {
|
||||
const [item] = results.value;
|
||||
if (data.value.length > 0) {
|
||||
const item = data.value[0];
|
||||
router.push({ name: "container-id", params: { id: item.id } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
class="input"
|
||||
type="text"
|
||||
:placeholder="$t('placeholder.search-containers')"
|
||||
v-model="search"
|
||||
@keyup.esc="search.value = null"
|
||||
v-model="query"
|
||||
@keyup.esc="query = ''"
|
||||
@keyup.enter="onEnter()"
|
||||
/>
|
||||
<span class="icon is-left">
|
||||
@@ -63,13 +63,13 @@
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="panel-tabs" v-if="!search">
|
||||
<p class="panel-tabs" v-if="query === ''">
|
||||
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
|
||||
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
|
||||
</p>
|
||||
<router-link
|
||||
:to="{ name: 'container-id', params: { id: item.id } }"
|
||||
v-for="item in results.slice(0, 10)"
|
||||
v-for="item in data.slice(0, 10)"
|
||||
:key="item.id"
|
||||
class="panel-block"
|
||||
>
|
||||
@@ -86,54 +86,59 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import fuzzysort from "fuzzysort";
|
||||
import SearchIcon from "~icons/mdi-light/magnify";
|
||||
import { useFuse } from "@vueuse/integrations/useFuse";
|
||||
|
||||
const { base, version, secured } = config;
|
||||
const containerStore = useContainerStore();
|
||||
const { containers } = storeToRefs(containerStore);
|
||||
const router = useRouter();
|
||||
|
||||
const sort = ref("running");
|
||||
const search = ref();
|
||||
const sort = $ref("running");
|
||||
const query = ref("");
|
||||
|
||||
const results = computed(() => {
|
||||
if (search.value) {
|
||||
return fuzzysort.go(search.value, containers.value, { key: "name" }).map((i) => i.obj);
|
||||
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
|
||||
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
|
||||
|
||||
const { results } = useFuse(query, containers, {
|
||||
fuseOptions: { keys: ["name"] },
|
||||
matchAllWhenSearchEmpty: false,
|
||||
});
|
||||
const data = computed(() => {
|
||||
if (results.value.length) {
|
||||
return results.value.map(({ item }) => item);
|
||||
}
|
||||
switch (sort.value) {
|
||||
switch (sort) {
|
||||
case "all":
|
||||
return mostRecentContainers.value;
|
||||
return mostRecentContainers;
|
||||
case "running":
|
||||
return runningContainers.value;
|
||||
return runningContainers;
|
||||
default:
|
||||
throw `Invalid sort order: ${sort.value}`;
|
||||
throw `Invalid sort order: ${sort}`;
|
||||
}
|
||||
});
|
||||
|
||||
const mostRecentContainers = computed(() => [...containers.value].sort((a, b) => b.created - a.created));
|
||||
const runningContainers = computed(() => mostRecentContainers.value.filter((c) => c.state === "running"));
|
||||
const totalCpu = ref(0);
|
||||
let totalCpu = $ref(0);
|
||||
useIntervalFn(
|
||||
() => {
|
||||
totalCpu.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
|
||||
totalCpu = runningContainers.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
|
||||
},
|
||||
1000,
|
||||
{ immediate: true }
|
||||
);
|
||||
const totalMem = ref(0);
|
||||
|
||||
let totalMem = $ref(0);
|
||||
useIntervalFn(
|
||||
() => {
|
||||
totalMem.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
|
||||
totalMem = runningContainers.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
|
||||
},
|
||||
1000,
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function onEnter() {
|
||||
if (results.value.length == 1) {
|
||||
const [item] = results.value;
|
||||
if (data.value.length > 0) {
|
||||
const item = data.value[0];
|
||||
router.push({ name: "container-id", params: { id: item.id } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ context("Dozzle default mode", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () =>
|
||||
it("shortcut for fuzzy search works", () => {
|
||||
cy.get("body").type("{ctrl}k");
|
||||
|
||||
cy.get("input[placeholder='Search containers using ⌘ + k or ctrl + k']").should("be.visible");
|
||||
cy.get("input[placeholder='Search containers (⌘ + k, ⌃k)']").should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ button:
|
||||
logout: Logout
|
||||
login: Login
|
||||
placeholder:
|
||||
search-containers: Search Containers
|
||||
search-containers: Search containers (⌘ + k, ⌃k)
|
||||
settings:
|
||||
display: Display
|
||||
small-scrollbars: Use smaller scrollbars
|
||||
|
||||
@@ -27,7 +27,7 @@ button:
|
||||
logout: Cerrar la sesión
|
||||
login: Iniciar sesión
|
||||
placeholder:
|
||||
search-containers: Buscar Contenedores
|
||||
search-containers: Buscar contenedores (⌘ + K, CTRL + K)
|
||||
settings:
|
||||
display: Vista
|
||||
small-scrollbars: Utilizar barras de desplazamiento más pequeñas
|
||||
|
||||
@@ -27,7 +27,7 @@ button:
|
||||
logout: Terminar sessão
|
||||
login: Iniciar sessão
|
||||
placeholder:
|
||||
search-containers: Pesquisar Contentores
|
||||
search-containers: Pesquisar contentores (⌘ + K, CTRL + K)
|
||||
settings:
|
||||
display: Visão
|
||||
small-scrollbars: Usar barras de rolagem mais pequenas
|
||||
|
||||
@@ -33,11 +33,12 @@
|
||||
"@vitejs/plugin-vue": "3.1.0",
|
||||
"@vue/compiler-sfc": "^3.2.39",
|
||||
"@vueuse/core": "^9.2.0",
|
||||
"@vueuse/integrations": "^9.2.0",
|
||||
"@vueuse/router": "^9.2.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"bulma": "^0.9.4",
|
||||
"date-fns": "^2.29.3",
|
||||
"fuzzysort": "^2.0.1",
|
||||
"fuse.js": "^6.6.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"pinia": "^2.0.22",
|
||||
"sass": "^1.54.9",
|
||||
|
||||
55
pnpm-lock.yaml
generated
55
pnpm-lock.yaml
generated
@@ -17,13 +17,14 @@ specifiers:
|
||||
'@vue/compiler-sfc': ^3.2.39
|
||||
'@vue/test-utils': ^2.0.2
|
||||
'@vueuse/core': ^9.2.0
|
||||
'@vueuse/integrations': ^9.2.0
|
||||
'@vueuse/router': ^9.2.0
|
||||
ansi-to-html: ^0.7.2
|
||||
bulma: ^0.9.4
|
||||
c8: ^7.12.0
|
||||
date-fns: ^2.29.3
|
||||
eventsourcemock: ^2.0.0
|
||||
fuzzysort: ^2.0.1
|
||||
fuse.js: ^6.6.2
|
||||
husky: ^8.0.1
|
||||
jest-serializer-vue: ^2.0.2
|
||||
jsdom: ^20.0.0
|
||||
@@ -62,11 +63,12 @@ dependencies:
|
||||
'@vitejs/plugin-vue': 3.1.0_vite@3.1.3+vue@3.2.39
|
||||
'@vue/compiler-sfc': 3.2.39
|
||||
'@vueuse/core': 9.2.0_vue@3.2.39
|
||||
'@vueuse/integrations': 9.2.0_fuse.js@6.6.2+vue@3.2.39
|
||||
'@vueuse/router': 9.2.0_u55wpvgoaskkfdnhr4v2vlugdi
|
||||
ansi-to-html: 0.7.2
|
||||
bulma: 0.9.4
|
||||
date-fns: 2.29.3
|
||||
fuzzysort: 2.0.1
|
||||
fuse.js: 6.6.2
|
||||
lodash.debounce: 4.0.8
|
||||
pinia: 2.0.22_arz4dztosvwy2ghjrlh2wdhejm
|
||||
sass: 1.54.9
|
||||
@@ -820,6 +822,50 @@ packages:
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/integrations/9.2.0_fuse.js@6.6.2+vue@3.2.39:
|
||||
resolution: {integrity: sha512-0NerkCPUUWnbEb0ZZaJyrO8YKPPClR9+aLLF8yBbG/XRsoEo7pcpVq8d+uMhfHrXABoUpKD+9FZ+Tz/aRb7yFg==}
|
||||
peerDependencies:
|
||||
async-validator: '*'
|
||||
axios: '*'
|
||||
change-case: '*'
|
||||
drauu: '*'
|
||||
focus-trap: '*'
|
||||
fuse.js: '*'
|
||||
jwt-decode: '*'
|
||||
nprogress: '*'
|
||||
qrcode: '*'
|
||||
universal-cookie: '*'
|
||||
peerDependenciesMeta:
|
||||
async-validator:
|
||||
optional: true
|
||||
axios:
|
||||
optional: true
|
||||
change-case:
|
||||
optional: true
|
||||
drauu:
|
||||
optional: true
|
||||
focus-trap:
|
||||
optional: true
|
||||
fuse.js:
|
||||
optional: true
|
||||
jwt-decode:
|
||||
optional: true
|
||||
nprogress:
|
||||
optional: true
|
||||
qrcode:
|
||||
optional: true
|
||||
universal-cookie:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@vueuse/core': 9.2.0_vue@3.2.39
|
||||
'@vueuse/shared': 9.2.0_vue@3.2.39
|
||||
fuse.js: 6.6.2
|
||||
vue-demi: 0.13.11_vue@3.2.39
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/metadata/9.2.0:
|
||||
resolution: {integrity: sha512-exN4KE6iquxDCdt72BgEhb3tlOpECtD61AUdXnUqBTIUCl70x1Ar/QXo3bYcvxmdMS2/peQyfeTzBjRTpvL5xw==}
|
||||
dev: false
|
||||
@@ -2195,8 +2241,9 @@ packages:
|
||||
/functions-have-names/1.2.3:
|
||||
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
||||
|
||||
/fuzzysort/2.0.1:
|
||||
resolution: {integrity: sha512-SlgbPAq0eQ6JQ1h3l4MNeGH/t9DHKH8GGM0RD/6RhmJrNnSoWt3oIVaiQm9g9BPB+wAhRMeMqlUTbhbd7+Ufcg==}
|
||||
/fuse.js/6.6.2:
|
||||
resolution: {integrity: sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/get-caller-file/2.0.5:
|
||||
|
||||
Reference in New Issue
Block a user