Revert "feat: refine mobile dashboard experience"
This reverts commit 315520beca.
This commit is contained in:
1 parent
8ac35083e6
commit
85db534fa7
35 files changed
+110
-989
No files matched your search
@@ -6,7 +6,6 @@ import { optionsBuilder } from "../options";
|
||||
export const { definition, componentLoader } = createWidgetDefinition("bazarr", {
|
||||
supportsAdvancedFocus: true,
|
||||
icon: IconBadgeCc,
|
||||
queryKey: [["widget", "bazarr"]],
|
||||
supportedIntegrations: ["bazarr"],
|
||||
integrationsRequired: true,
|
||||
maxIntegrations: 1,
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { WidgetMobileLoading, WidgetMobileSummary } from "../../common/mobile-summary";
|
||||
import { useBeszelFilteredSystems } from "./hooks";
|
||||
|
||||
interface BeszelSystemsMobileSummaryProps {
|
||||
integrationIds: string[];
|
||||
label: string;
|
||||
statusFilter: string;
|
||||
}
|
||||
|
||||
export const BeszelSystemsMobileSummary = ({
|
||||
integrationIds,
|
||||
label,
|
||||
statusFilter,
|
||||
}: BeszelSystemsMobileSummaryProps) => {
|
||||
const t = useI18n();
|
||||
const { data: results = [], error, isPending } = clientApi.widget.beszel.getSystems.useQuery({ integrationIds });
|
||||
const systems = useBeszelFilteredSystems(results, statusFilter);
|
||||
const failedResults = results.filter((result) => "error" in result);
|
||||
|
||||
if (isPending) return <WidgetMobileLoading />;
|
||||
if (error) throw error;
|
||||
if (results.length > 0 && failedResults.length === results.length) {
|
||||
throw new Error(String(failedResults[0]?.error ?? "Unable to connect to Beszel"));
|
||||
}
|
||||
|
||||
const up = systems.filter((system) => system.status === "up").length;
|
||||
|
||||
return (
|
||||
<WidgetMobileSummary
|
||||
value={systems.length}
|
||||
label={label}
|
||||
description={t("board.mobile.systemStatus", {
|
||||
up,
|
||||
attention: systems.length - up,
|
||||
})}
|
||||
isStale={failedResults.length > 0}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,57 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { Center, Group, Loader, Stack, Text } from "@mantine/core";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { Center, Group, Stack, Text } from "@mantine/core";
|
||||
|
||||
interface WidgetMobileSummaryProps {
|
||||
value: number | string;
|
||||
label: string;
|
||||
description?: string;
|
||||
isStale?: boolean;
|
||||
}
|
||||
|
||||
export function WidgetMobileSummary({ value, label, description, isStale = false }: WidgetMobileSummaryProps) {
|
||||
const t = useI18n();
|
||||
|
||||
export function WidgetMobileSummary({ value, label, description }: WidgetMobileSummaryProps) {
|
||||
return (
|
||||
<Center h="100%" p="sm">
|
||||
<Stack align="center" gap={2} w="100%" style={{ minWidth: 0 }}>
|
||||
<Text fz="xl" fw={700} lh={1.15} ta="center" w="100%" lineClamp={2} style={{ overflowWrap: "anywhere" }}>
|
||||
{value}
|
||||
</Text>
|
||||
<Text size="sm" fw={600} lineClamp={1} ta="center" w="100%">
|
||||
{label}
|
||||
</Text>
|
||||
<Group gap="xs" justify="center" wrap="nowrap" w="100%">
|
||||
<Text fz="xl" fw={700} lh={1}>
|
||||
{value}
|
||||
</Text>
|
||||
<Text size="sm" fw={600} lineClamp={1}>
|
||||
{label}
|
||||
</Text>
|
||||
</Group>
|
||||
{description !== undefined && (
|
||||
<Text c="var(--mantine-color-text)" size="xs" lineClamp={1} ta="center" w="100%">
|
||||
<Text c="dimmed" size="xs" lineClamp={1} ta="center" w="100%">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
{isStale && (
|
||||
<Group component="output" gap={4} justify="center" wrap="nowrap" w="100%">
|
||||
<IconAlertTriangle
|
||||
size={14}
|
||||
color="light-dark(var(--mantine-color-yellow-9), var(--mantine-color-yellow-3))"
|
||||
aria-hidden
|
||||
/>
|
||||
<Text c="var(--mantine-color-text)" size="xs" lineClamp={1}>
|
||||
{t("board.mobile.dataWarning")}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
export function WidgetMobileLoading() {
|
||||
const t = useI18n();
|
||||
|
||||
return (
|
||||
<Center component="output" h="100%" aria-label={t("common.action.loading")}>
|
||||
<Loader size="sm" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
@@ -13,10 +13,9 @@ interface ApplicationsSectionProps {
|
||||
applications: CoolifyApplicationWithContext[];
|
||||
baseUrl: string;
|
||||
isTiny: boolean;
|
||||
isMobileDetail: boolean;
|
||||
}
|
||||
|
||||
export function ApplicationsSection({ applications, baseUrl, isTiny, isMobileDetail }: ApplicationsSectionProps) {
|
||||
export function ApplicationsSection({ applications, baseUrl, isTiny }: ApplicationsSectionProps) {
|
||||
const t = useScopedI18n("widget.coolify");
|
||||
const tCommon = useScopedI18n("common");
|
||||
const runningApps = applications.filter((app) => parseStatus(app.status ?? "") === "running").length;
|
||||
@@ -35,14 +34,7 @@ export function ApplicationsSection({ applications, baseUrl, isTiny, isMobileDet
|
||||
{applications.length > 0 ? (
|
||||
<Stack gap={4}>
|
||||
{applications.map((app) => (
|
||||
<ResourceRow
|
||||
key={app.uuid}
|
||||
item={app}
|
||||
baseUrl={baseUrl}
|
||||
isTiny={isTiny}
|
||||
resourceType="application"
|
||||
isMobileDetail={isMobileDetail}
|
||||
/>
|
||||
<ResourceRow key={app.uuid} item={app} baseUrl={baseUrl} isTiny={isTiny} resourceType="application" />
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
|
||||
@@ -6,8 +6,6 @@ import { optionsBuilder } from "../options";
|
||||
export const { definition, componentLoader } = createWidgetDefinition("coolify", {
|
||||
supportsAdvancedFocus: true,
|
||||
icon: IconCloud,
|
||||
mobile: { supportsDetailView: true },
|
||||
queryKey: [["widget", "coolify"]],
|
||||
createOptions() {
|
||||
return optionsBuilder.from((factory) => ({
|
||||
showServers: factory.switch({
|
||||
|
||||
@@ -6,8 +6,6 @@ import { optionsBuilder } from "../options";
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("customApi", {
|
||||
icon: IconApi,
|
||||
mobile: { supportsDetailView: true },
|
||||
queryKey: [["widget", "customApi"]],
|
||||
createOptions() {
|
||||
return optionsBuilder.from((factory) => ({
|
||||
definitionId: factory.customWidgetSelect({ defaultValue: "" }),
|
||||
|
||||
@@ -42,21 +42,12 @@ const TimerModal = ({ opened, close, selectedIntegrationIds, disableDns }: Timer
|
||||
setMinutes(0);
|
||||
}}
|
||||
title={t("widget.dnsHoleControls.controls.setTimer")}
|
||||
closeButtonProps={{
|
||||
"aria-label": t("common.action.close"),
|
||||
size: 44,
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<Stack align="flex-end">
|
||||
<Group>
|
||||
<Text>{t("widget.dnsHoleControls.controls.hours")}</Text>
|
||||
<ActionIcon
|
||||
size={44}
|
||||
variant="default"
|
||||
aria-label={`${t("widget.dnsHoleControls.controls.hours")} −`}
|
||||
onClick={() => hoursHandlers.current?.decrement()}
|
||||
>
|
||||
<ActionIcon size={35} variant="default" onClick={() => hoursHandlers.current?.decrement()}>
|
||||
–
|
||||
</ActionIcon>
|
||||
<NumberInput
|
||||
@@ -67,26 +58,15 @@ const TimerModal = ({ opened, close, selectedIntegrationIds, disableDns }: Timer
|
||||
max={999}
|
||||
min={0}
|
||||
step={1}
|
||||
aria-label={t("widget.dnsHoleControls.controls.hours")}
|
||||
styles={{ input: { width: rem(54), textAlign: "center" } }}
|
||||
/>
|
||||
<ActionIcon
|
||||
size={44}
|
||||
variant="default"
|
||||
aria-label={`${t("widget.dnsHoleControls.controls.hours")} +`}
|
||||
onClick={() => hoursHandlers.current?.increment()}
|
||||
>
|
||||
<ActionIcon size={35} variant="default" onClick={() => hoursHandlers.current?.increment()}>
|
||||
+
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
<Group>
|
||||
<Text>{t("widget.dnsHoleControls.controls.minutes")}</Text>
|
||||
<ActionIcon
|
||||
size={44}
|
||||
variant="default"
|
||||
aria-label={`${t("widget.dnsHoleControls.controls.minutes")} −`}
|
||||
onClick={() => minutesHandlers.current?.decrement()}
|
||||
>
|
||||
<ActionIcon size={35} variant="default" onClick={() => minutesHandlers.current?.decrement()}>
|
||||
–
|
||||
</ActionIcon>
|
||||
<NumberInput
|
||||
@@ -97,15 +77,9 @@ const TimerModal = ({ opened, close, selectedIntegrationIds, disableDns }: Timer
|
||||
max={59}
|
||||
min={0}
|
||||
step={1}
|
||||
aria-label={t("widget.dnsHoleControls.controls.minutes")}
|
||||
styles={{ input: { width: rem(54), textAlign: "center" } }}
|
||||
/>
|
||||
<ActionIcon
|
||||
size={44}
|
||||
variant="default"
|
||||
aria-label={`${t("widget.dnsHoleControls.controls.minutes")} +`}
|
||||
onClick={() => minutesHandlers.current?.increment()}
|
||||
>
|
||||
<ActionIcon size={35} variant="default" onClick={() => minutesHandlers.current?.increment()}>
|
||||
+
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
@@ -117,7 +91,7 @@ const TimerModal = ({ opened, close, selectedIntegrationIds, disableDns }: Timer
|
||||
variant="light"
|
||||
color="red"
|
||||
leftSection={<IconClockPause size={20} />}
|
||||
mih={44}
|
||||
h="2rem"
|
||||
w="12rem"
|
||||
onClick={handleSetTimer}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,6 @@ export const widgetKind = "dnsHoleControls";
|
||||
export const { definition, componentLoader } = createWidgetDefinition(widgetKind, {
|
||||
supportsAdvancedFocus: false,
|
||||
icon: IconDeviceGamepad,
|
||||
mobile: { supportsDetailView: true },
|
||||
queryKey: [["widget", "dnsHole"]],
|
||||
refetchInterval: 5,
|
||||
createOptions() {
|
||||
|
||||
@@ -25,13 +25,6 @@ const columnTranslationKeyMap = {
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("dockerContainers", {
|
||||
icon: IconBrandDocker,
|
||||
mobile: {
|
||||
width: 2,
|
||||
height: 1,
|
||||
supportsCompactSummary: true,
|
||||
supportsDetailView: true,
|
||||
unmountWhenOffscreen: true,
|
||||
},
|
||||
queryKey: [["docker", "getContainers"]],
|
||||
refetchInterval: 30,
|
||||
createOptions() {
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { WidgetMobileSummary } from "../common/mobile-summary";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
|
||||
const Notebook = dynamic(() => import("./notebook").then((module) => module.Notebook), {
|
||||
@@ -12,23 +9,5 @@ const Notebook = dynamic(() => import("./notebook").then((module) => module.Note
|
||||
});
|
||||
|
||||
export default function NotebookWidget(props: WidgetComponentProps<"notebook">) {
|
||||
const t = useScopedI18n("widget.notebook");
|
||||
|
||||
if (props.displayMode === "mobileSummary") {
|
||||
const excerpt = props.options.content
|
||||
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ")
|
||||
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ")
|
||||
.replace(/<[^>]+>/g, " ")
|
||||
.replace(/&(?:nbsp|#160);/gi, " ")
|
||||
.replace(/&(?:amp|#38);/gi, "&")
|
||||
.replace(/&(?:lt|#60);/gi, "<")
|
||||
.replace(/&(?:gt|#62);/gi, ">")
|
||||
.replace(/&(?:quot|#34);/gi, '"')
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
return <WidgetMobileSummary value={excerpt || "—"} label={t("name")} />;
|
||||
}
|
||||
|
||||
return <Notebook {...props} />;
|
||||
}
|
||||
@@ -6,12 +6,6 @@ import { defaultContent } from "./default-content";
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("notebook", {
|
||||
icon: IconNotes,
|
||||
mobile: {
|
||||
width: 2,
|
||||
height: 1,
|
||||
supportsCompactSummary: true,
|
||||
supportsDetailView: true,
|
||||
},
|
||||
createOptions() {
|
||||
return optionsBuilder.from(
|
||||
(factory) => ({
|
||||
|
||||
@@ -6,7 +6,6 @@ import { optionsBuilder } from "../options";
|
||||
export const { definition, componentLoader } = createWidgetDefinition("paperlessNgx", {
|
||||
supportsAdvancedFocus: false,
|
||||
icon: IconFileText,
|
||||
queryKey: [["widget", "paperlessNgx"]],
|
||||
refetchInterval: null,
|
||||
supportedIntegrations: ["paperlessNgx"],
|
||||
integrationsRequired: true,
|
||||
|
||||
@@ -9,8 +9,6 @@ import { patchmonOptionsSuperRefine } from "./threshold-validation";
|
||||
export const { definition, componentLoader } = createWidgetDefinition("patchmon", {
|
||||
supportsAdvancedFocus: true,
|
||||
icon: IconShieldCheck,
|
||||
mobile: { supportsDetailView: true },
|
||||
queryKey: [["widget", "patchmon"]],
|
||||
supportedIntegrations: ["patchmon"],
|
||||
integrationsRequired: true,
|
||||
maxIntegrations: 1,
|
||||
@@ -192,42 +190,18 @@ export const { definition, componentLoader } = createWidgetDefinition("patchmon"
|
||||
useCustomThresholds: {
|
||||
shouldHide: (options) => !options.enableThresholdColors,
|
||||
},
|
||||
hostsNeedingUpdatesThresholdMode: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
hostsNeedingUpdatesWarningAt: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
hostsNeedingUpdatesCriticalAt: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
securityUpdatesThresholdMode: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
securityUpdatesWarningAt: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
securityUpdatesCriticalAt: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
hostsWithSecurityUpdatesThresholdMode: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
hostsWithSecurityUpdatesWarningAt: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
hostsWithSecurityUpdatesCriticalAt: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
upToDateHostsThresholdMode: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
upToDateHostsWarningAt: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
upToDateHostsCriticalAt: {
|
||||
shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds,
|
||||
},
|
||||
hostsNeedingUpdatesThresholdMode: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
hostsNeedingUpdatesWarningAt: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
hostsNeedingUpdatesCriticalAt: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
securityUpdatesThresholdMode: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
securityUpdatesWarningAt: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
securityUpdatesCriticalAt: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
hostsWithSecurityUpdatesThresholdMode: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
hostsWithSecurityUpdatesWarningAt: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
hostsWithSecurityUpdatesCriticalAt: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
upToDateHostsThresholdMode: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
upToDateHostsWarningAt: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
upToDateHostsCriticalAt: { shouldHide: (options) => !options.enableThresholdColors || !options.useCustomThresholds },
|
||||
totalOutdatedPackagesThresholdMode: {
|
||||
shouldHide: (options) =>
|
||||
!options.enableThresholdColors || !options.useCustomThresholds || !options.showTotalOutdatedPackages,
|
||||
|
||||
@@ -7,13 +7,6 @@ import { createStorageVolumeMultiSelectOptions } from "../storage-volume-options
|
||||
export const { definition, componentLoader } = createWidgetDefinition("systemDisks", {
|
||||
supportsAdvancedFocus: true,
|
||||
icon: IconServer2,
|
||||
mobile: {
|
||||
width: 2,
|
||||
height: 1,
|
||||
supportsCompactSummary: true,
|
||||
supportsDetailView: true,
|
||||
unmountWhenOffscreen: true,
|
||||
},
|
||||
queryKey: [["widget", "healthMonitoring"]],
|
||||
refetchInterval: 5,
|
||||
supportedIntegrations: ["dashDot", "openmediavault", "truenas", "unraid", "synology"],
|
||||
|
||||
@@ -8,8 +8,6 @@ import { optionsBuilder } from "../options";
|
||||
export const { componentLoader, definition } = createWidgetDefinition("vpn", {
|
||||
supportsAdvancedFocus: false,
|
||||
icon: IconShieldLock,
|
||||
mobile: { supportsDetailView: true },
|
||||
queryKey: [["widget", "vpn"]],
|
||||
refetchInterval: null,
|
||||
createOptions() {
|
||||
return optionsBuilder.from(() => ({}));
|
||||
|
||||
Reference in new issue
Block a user