Yay drop all these max fields

This commit is contained in:
shamoon
2026-08-21 12:33:35 -07:00
parent fabccceb3f
commit d11ca0d70e
17 changed files with 24 additions and 40 deletions
@@ -85,6 +85,7 @@ To ensure cohesiveness of various widgets, the following should be used as a gui
- Note that we reserve the right to decline widgets for projects that are very young (eg < ~1y) or those with a small reach (eg low GitHub stars). Again, this is in an effort to keep overall widget maintenance under control.
- Widgets should be only one row of blocks
- Widgets should be no more than 4 blocks wide and generally conform to the styling / design choices of other widgets
- Use `withWidgetFields(service, defaultFields)` from `utils/widget-fields` to apply default fields and cap configured fields without mutating the service. The helper limits widgets to 4 fields.
- Minimize the number of API calls
- Avoid the use of custom proxy unless absolutely necessary
- Widgets should be 'read-only', as in they should not make write changes using the relevant tool's API. Homepage widgets are designed to surface information, not to be a (usually worse) replacement for the tool itself.
+1 -2
View File
@@ -5,12 +5,11 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const MAX_FIELDS = 4;
const DEFAULT_FIELDS = ["running", "stopped", "total", "image_updates"];
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_FIELDS);
const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const envNotSet = widget.env == null || widget.env === "";
+1 -2
View File
@@ -6,12 +6,11 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const BACKREST_DEFAULT_FIELDS = ["num_success_latest", "num_failure_latest", "num_failure_30", "bytes_added_30"];
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, BACKREST_DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, BACKREST_DEFAULT_FIELDS);
const { widget } = service;
const { data, error } = useWidgetAPI(widget, "summary");
+1 -2
View File
@@ -5,12 +5,11 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const MAX_FIELDS = 4;
const DEFAULT_FIELDS = ["running", "total", "cpu", "memory"];
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_FIELDS);
const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const { data: stats, error: statsError } = useWidgetAPI(widget, "dashboard/stats");
+1 -2
View File
@@ -6,11 +6,10 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
export const fritzboxDefaultFields = ["connectionStatus", "uptime", "maxDown", "maxUp"];
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, fritzboxDefaultFields, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, fritzboxDefaultFields);
const { widget } = service;
const { data: fritzboxData, error: fritzboxError } = useWidgetAPI(widget, "status");
+1 -2
View File
@@ -6,11 +6,10 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
export const karakeepDefaultFields = ["bookmarks", "favorites", "archived", "highlights"];
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, karakeepDefaultFields, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, karakeepDefaultFields);
const { widget } = service;
const { data: statsData, error: statsError } = useWidgetAPI(widget, "stats");
+1 -2
View File
@@ -5,7 +5,6 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const MAX_ALLOWED_FIELDS = 4;
const SUMMARY_FIELDS = ["servers", "stacks", "containers"];
const STACK_FIELDS = ["total", "running", "down", "unhealthy"];
const CONTAINER_FIELDS = ["total", "running", "stopped", "unhealthy"];
@@ -18,7 +17,7 @@ export default function Component({ service: configuredService }) {
: configuredWidget.showStacks
? STACK_FIELDS
: CONTAINER_FIELDS;
const service = withWidgetFields(configuredService, defaultFields, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, defaultFields);
const { widget } = service;
const containersEndpoint = !(!widget.showSummary && widget.showStacks) ? "containers" : "";
const { data: containersData, error: containersError } = useWidgetAPI(widget, containersEndpoint);
+1 -2
View File
@@ -6,11 +6,10 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const DEFAULT_FIELDS = ["itemsHandled", "episodesHandled", "moviesHandled", "reclaimable"];
const MAX_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_FIELDS);
const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const { data, error } = useWidgetAPI(widget);
+1 -2
View File
@@ -5,12 +5,11 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const MAX_ALLOWED_FIELDS = 4;
const DEFAULT_FIELDS = ["sites", "resources", "targets", "traffic"];
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const { data: sitesData, error: sitesError } = useWidgetAPI(widget, "sites");
+1 -2
View File
@@ -6,10 +6,9 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const ROMM_DEFAULT_FIELDS = ["platforms", "totalRoms", "saves", "states"];
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const service = withWidgetFields(configuredService, ROMM_DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, ROMM_DEFAULT_FIELDS);
const { widget } = service;
const { t } = useTranslation();
const { data: response, error: responseError } = useWidgetAPI(widget, "statistics");
+1 -2
View File
@@ -5,10 +5,9 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
export const seerrDefaultFields = ["pending", "approved", "completed"];
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const service = withWidgetFields(configuredService, seerrDefaultFields, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, seerrDefaultFields);
const { widget } = service;
const isIssueEnabled = widget.fields.includes("issues");
+1 -2
View File
@@ -6,11 +6,10 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const slskdDefaultFields = ["slskStatus", "downloads", "uploads", "sharedFiles"];
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, slskdDefaultFields, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, slskdDefaultFields);
const { widget } = service;
const { data: appData, error: appError } = useWidgetAPI(widget, "application");
+1 -2
View File
@@ -6,12 +6,11 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const DEFAULT_FIELDS = ["address", "last_seen", "expires"];
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const { data: tailscaleData, error: tailscaleError } = useWidgetAPI(widget, "device");
+1 -3
View File
@@ -5,14 +5,12 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const MAX_ALLOWED_FIELDS = 4;
export const technitiumDefaultFields = ["totalQueries", "totalAuthoritative", "totalCached", "totalServerFailure"];
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, technitiumDefaultFields, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, technitiumDefaultFields);
const { widget } = service;
const params = {
+3 -8
View File
@@ -3,9 +3,9 @@ import Container from "components/services/widget/container";
import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const UNRAID_DEFAULT_FIELDS = ["status", "cpu", "memoryPercent", "notifications"];
const MAX_ALLOWED_FIELDS = 4;
const POOLS = ["pool1", "pool2", "pool3", "pool4"];
const POOL_FIELDS = [
@@ -14,8 +14,9 @@ const POOL_FIELDS = [
{ param: "UsedPercent", label: "poolUsed", valueKey: "fsUsedPercent", valueType: "common.percent" },
];
export default function Component({ service }) {
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, UNRAID_DEFAULT_FIELDS);
const { widget } = service;
const { data, error } = useWidgetAPI(widget);
@@ -24,12 +25,6 @@ export default function Component({ service }) {
return <Container service={service} error={error} />;
}
if (!widget.fields?.length) {
widget.fields = UNRAID_DEFAULT_FIELDS;
} else if (widget.fields.length > MAX_ALLOWED_FIELDS) {
widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
}
if (!data) {
return (
<Container service={service}>
+6 -3
View File
@@ -20,14 +20,17 @@ describe("widgets/unraid/component", () => {
vi.clearAllMocks();
});
it("defaults widget.fields and filters down to 4 visible blocks while loading", () => {
it("defaults fields and filters down to 4 visible blocks while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const service = { widget: { type: "unraid" } };
const { container } = renderWithProviders(<Component service={service} />, { settings: { hideErrors: false } });
// Component sets default fields
expect(service.widget.fields).toEqual(["status", "cpu", "memoryPercent", "notifications"]);
expect(service.widget.fields).toBeUndefined();
expect(useWidgetAPI).toHaveBeenCalledWith({
type: "unraid",
fields: ["status", "cpu", "memoryPercent", "notifications"],
});
// Container filters the many placeholder Blocks down to the selected fields.
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
+1 -2
View File
@@ -5,7 +5,6 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const MAX_ALLOWED_FIELDS = 4;
const DEFAULT_FIELDS = ["activeSubscriptions", "nextRenewingSubscription", "thisMonthlyCost", "nextMonthlyCost"];
const todayDate = new Date();
@@ -20,7 +19,7 @@ function toApiMonthYear(offset = 0) {
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const subscriptionsEndPoint =