Compare commits
2
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 => {
|
export const isNullOrWhitespace = (value: string | null): value is null => {
|
||||||
return value == null || value.trim() === "";
|
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 = [
|
const capitalizeTestCases = [
|
||||||
["hello", "Hello"],
|
["hello", "Hello"],
|
||||||
@@ -12,8 +12,45 @@ const capitalizeTestCases = [
|
|||||||
|
|
||||||
describe("capitalize should capitalize the first letter of a string", () => {
|
describe("capitalize should capitalize the first letter of a string", () => {
|
||||||
capitalizeTestCases.forEach(([input, expected]) => {
|
capitalizeTestCases.forEach(([input, expected]) => {
|
||||||
it(`should capitalize ${input} to ${expected}`, () => {
|
test(`should capitalize ${input} to ${expected}`, () => {
|
||||||
expect(capitalize(input)).toEqual(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 { ContainerInfo, ContainerStats } from "dockerode";
|
||||||
import type Dockerode from "dockerode";
|
import type Dockerode from "dockerode";
|
||||||
|
|
||||||
|
import { bestMatch } from "@homarr/common";
|
||||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||||
import { db, like, or } from "@homarr/db";
|
import { db, like, or } from "@homarr/db";
|
||||||
@@ -82,12 +83,7 @@ async function getContainersWithStatsAsync() {
|
|||||||
name: container.Names[0]?.split("/")[1] ?? "Unknown",
|
name: container.Names[0]?.split("/")[1] ?? "Unknown",
|
||||||
host: container.instance,
|
host: container.instance,
|
||||||
state: container.State as ContainerState,
|
state: container.State as ContainerState,
|
||||||
iconUrl:
|
iconUrl: bestMatch(extractImage(container), dbIcons, (icon) => icon.name)?.url ?? null,
|
||||||
dbIcons.find((icon) => {
|
|
||||||
const extractedImage = extractImage(container);
|
|
||||||
if (!extractedImage) return false;
|
|
||||||
return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
|
|
||||||
})?.url ?? null,
|
|
||||||
cpuUsage,
|
cpuUsage,
|
||||||
memoryUsage,
|
memoryUsage,
|
||||||
image: container.Image,
|
image: container.Image,
|
||||||
|
|||||||
Reference in new issue
Block a user