Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da320c113d | ||
|
|
559a666589 |
No files matched your search
@@ -5,3 +5,26 @@ export const capitalize = <T extends string>(str: T) => {
|
||||
export const isNullOrWhitespace = (value: string | null): value is null => {
|
||||
return value == null || value.trim() === "";
|
||||
};
|
||||
|
||||
export const bestMatch = <TItem extends Record<string, unknown>>(
|
||||
search: string,
|
||||
options: TItem[],
|
||||
by: (item: TItem) => string,
|
||||
) => {
|
||||
if (options.length === 0) return null;
|
||||
|
||||
const normalizedSearch = search.toLowerCase().trim();
|
||||
|
||||
return options.reduce<TItem | null>((best, current) => {
|
||||
const currentMatchIndex = by(current).toLowerCase().indexOf(normalizedSearch);
|
||||
|
||||
if (currentMatchIndex === -1) return best;
|
||||
if (best === null) return current;
|
||||
|
||||
const bestMatchIndex = by(best).toLowerCase().indexOf(normalizedSearch);
|
||||
|
||||
if (currentMatchIndex < bestMatchIndex) return current;
|
||||
if (by(current).length < by(best).length && currentMatchIndex === bestMatchIndex) return current;
|
||||
return best;
|
||||
}, null);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { capitalize } from "../string";
|
||||
import { bestMatch, capitalize } from "../string";
|
||||
|
||||
const capitalizeTestCases = [
|
||||
["hello", "Hello"],
|
||||
@@ -12,8 +12,45 @@ const capitalizeTestCases = [
|
||||
|
||||
describe("capitalize should capitalize the first letter of a string", () => {
|
||||
capitalizeTestCases.forEach(([input, expected]) => {
|
||||
it(`should capitalize ${input} to ${expected}`, () => {
|
||||
test(`should capitalize ${input} to ${expected}`, () => {
|
||||
expect(capitalize(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("bestMatch should find the best match in an array of options", () => {
|
||||
test("should find exact match for 'nginx'", () => {
|
||||
const options = [
|
||||
{ name: "my-nginx" },
|
||||
{ name: "nginx-proxy" },
|
||||
{ name: "nginx" },
|
||||
{ name: "redis" },
|
||||
{ name: "postgres" },
|
||||
];
|
||||
|
||||
const result = bestMatch("nginx", options, ({ name }) => name);
|
||||
|
||||
expect(result).toEqual({ name: "nginx" });
|
||||
});
|
||||
test("should find closest match for 'nginx' when exact match is not available", () => {
|
||||
const options = [{ name: "redis" }, { name: "nginx-proxy" }, { name: "my-nginx" }, { name: "postgres" }];
|
||||
|
||||
const result = bestMatch("nginx", options, ({ name }) => name);
|
||||
|
||||
expect(result).toEqual({ name: "nginx-proxy" });
|
||||
});
|
||||
test("should return null if no match is found", () => {
|
||||
const options = [{ name: "redis" }, { name: "postgres" }];
|
||||
|
||||
const result = bestMatch("nginx", options, ({ name }) => name);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
test("should return null if options array is empty", () => {
|
||||
const options: { name: string }[] = [];
|
||||
|
||||
const result = bestMatch("nginx", options, ({ name }) => name);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import dayjs from "dayjs";
|
||||
import type { ContainerInfo, ContainerStats } from "dockerode";
|
||||
import type Dockerode from "dockerode";
|
||||
|
||||
import { bestMatch } from "@homarr/common";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
import { db, like, or } from "@homarr/db";
|
||||
@@ -82,12 +83,7 @@ async function getContainersWithStatsAsync() {
|
||||
name: container.Names[0]?.split("/")[1] ?? "Unknown",
|
||||
host: container.instance,
|
||||
state: container.State as ContainerState,
|
||||
iconUrl:
|
||||
dbIcons.find((icon) => {
|
||||
const extractedImage = extractImage(container);
|
||||
if (!extractedImage) return false;
|
||||
return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
|
||||
})?.url ?? null,
|
||||
iconUrl: bestMatch(extractImage(container), dbIcons, (icon) => icon.name)?.url ?? null,
|
||||
cpuUsage,
|
||||
memoryUsage,
|
||||
image: container.Image,
|
||||
|
||||
Reference in new issue
Block a user