feat: user setting ping icons (#1277)
* feat: user setting ping icons * fix: format issues * test: adjust test to match expectations
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, Group, Stack, Switch } from "@mantine/core";
|
||||||
|
|
||||||
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
|
import { useZodForm } from "@homarr/form";
|
||||||
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
import type { z } from "@homarr/validation";
|
||||||
|
import { validation } from "@homarr/validation";
|
||||||
|
|
||||||
|
interface PingIconsEnabledProps {
|
||||||
|
user: RouterOutputs["user"]["getById"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PingIconsEnabled = ({ user }: PingIconsEnabledProps) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const { mutate, isPending } = clientApi.user.changePingIconsEnabled.useMutation({
|
||||||
|
async onSettled() {
|
||||||
|
await revalidatePathActionAsync(`/manage/users/${user.id}`);
|
||||||
|
},
|
||||||
|
onSuccess(_, variables) {
|
||||||
|
form.setInitialValues({
|
||||||
|
pingIconsEnabled: variables.pingIconsEnabled,
|
||||||
|
});
|
||||||
|
showSuccessNotification({
|
||||||
|
message: t("user.action.changePingIconsEnabled.notification.success.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
showErrorNotification({
|
||||||
|
message: t("user.action.changePingIconsEnabled.notification.error.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const form = useZodForm(validation.user.pingIconsEnabled, {
|
||||||
|
initialValues: {
|
||||||
|
pingIconsEnabled: user.pingIconsEnabled,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = (values: FormType) => {
|
||||||
|
mutate({
|
||||||
|
id: user.id,
|
||||||
|
...values,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Switch {...form.getInputProps("pingIconsEnabled")} label={t("user.field.pingIconsEnabled.label")} />
|
||||||
|
|
||||||
|
<Group justify="end">
|
||||||
|
<Button type="submit" color="teal" loading={isPending}>
|
||||||
|
{t("common.action.save")}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type FormType = z.infer<typeof validation.user.pingIconsEnabled>;
|
||||||
@@ -14,6 +14,7 @@ import { canAccessUserEditPage } from "../access";
|
|||||||
import { ChangeHomeBoardForm } from "./_components/_change-home-board";
|
import { ChangeHomeBoardForm } from "./_components/_change-home-board";
|
||||||
import { DeleteUserButton } from "./_components/_delete-user-button";
|
import { DeleteUserButton } from "./_components/_delete-user-button";
|
||||||
import { FirstDayOfWeek } from "./_components/_first-day-of-week";
|
import { FirstDayOfWeek } from "./_components/_first-day-of-week";
|
||||||
|
import { PingIconsEnabled } from "./_components/_ping-icons-enabled";
|
||||||
import { UserProfileAvatarForm } from "./_components/_profile-avatar-form";
|
import { UserProfileAvatarForm } from "./_components/_profile-avatar-form";
|
||||||
import { UserProfileForm } from "./_components/_profile-form";
|
import { UserProfileForm } from "./_components/_profile-form";
|
||||||
|
|
||||||
@@ -99,6 +100,11 @@ export default async function EditUserPage({ params }: Props) {
|
|||||||
<FirstDayOfWeek user={user} />
|
<FirstDayOfWeek user={user} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<Stack mb="lg">
|
||||||
|
<Title order={2}>{tGeneral("item.accessibility")}</Title>
|
||||||
|
<PingIconsEnabled user={user} />
|
||||||
|
</Stack>
|
||||||
|
|
||||||
{isCredentialsUser && (
|
{isCredentialsUser && (
|
||||||
<DangerZoneRoot>
|
<DangerZoneRoot>
|
||||||
<DangerZoneItem
|
<DangerZoneItem
|
||||||
|
|||||||
@@ -248,18 +248,11 @@ describe("editProfile shoud update user", () => {
|
|||||||
const user = await db.select().from(schema.users).where(eq(schema.users.id, defaultOwnerId));
|
const user = await db.select().from(schema.users).where(eq(schema.users.id, defaultOwnerId));
|
||||||
|
|
||||||
expect(user).toHaveLength(1);
|
expect(user).toHaveLength(1);
|
||||||
expect(user[0]).toStrictEqual({
|
expect(user[0]).containSubset({
|
||||||
id: defaultOwnerId,
|
id: defaultOwnerId,
|
||||||
name: "ABC",
|
name: "ABC",
|
||||||
email: "abc@gmail.com",
|
email: "abc@gmail.com",
|
||||||
emailVerified,
|
emailVerified,
|
||||||
salt: null,
|
|
||||||
password: null,
|
|
||||||
image: null,
|
|
||||||
homeBoardId: null,
|
|
||||||
provider: "credentials",
|
|
||||||
colorScheme: "auto",
|
|
||||||
firstDayOfWeek: 1,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -289,18 +282,11 @@ describe("editProfile shoud update user", () => {
|
|||||||
const user = await db.select().from(schema.users).where(eq(schema.users.id, defaultOwnerId));
|
const user = await db.select().from(schema.users).where(eq(schema.users.id, defaultOwnerId));
|
||||||
|
|
||||||
expect(user).toHaveLength(1);
|
expect(user).toHaveLength(1);
|
||||||
expect(user[0]).toStrictEqual({
|
expect(user[0]).containSubset({
|
||||||
id: defaultOwnerId,
|
id: defaultOwnerId,
|
||||||
name: "ABC",
|
name: "ABC",
|
||||||
email: "myNewEmail@gmail.com",
|
email: "myNewEmail@gmail.com",
|
||||||
emailVerified: null,
|
emailVerified: null,
|
||||||
salt: null,
|
|
||||||
password: null,
|
|
||||||
image: null,
|
|
||||||
homeBoardId: null,
|
|
||||||
provider: "credentials",
|
|
||||||
colorScheme: "auto",
|
|
||||||
firstDayOfWeek: 1,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -317,40 +303,14 @@ describe("delete should delete user", () => {
|
|||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
name: "User 1",
|
name: "User 1",
|
||||||
email: null,
|
|
||||||
emailVerified: null,
|
|
||||||
image: null,
|
|
||||||
password: null,
|
|
||||||
salt: null,
|
|
||||||
homeBoardId: null,
|
|
||||||
provider: "ldap" as const,
|
|
||||||
colorScheme: "auto" as const,
|
|
||||||
firstDayOfWeek: 1 as const,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: defaultOwnerId,
|
id: defaultOwnerId,
|
||||||
name: "User 2",
|
name: "User 2",
|
||||||
email: null,
|
|
||||||
emailVerified: null,
|
|
||||||
image: null,
|
|
||||||
password: null,
|
|
||||||
salt: null,
|
|
||||||
homeBoardId: null,
|
|
||||||
colorScheme: "auto" as const,
|
|
||||||
firstDayOfWeek: 1 as const,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: createId(),
|
id: createId(),
|
||||||
name: "User 3",
|
name: "User 3",
|
||||||
email: null,
|
|
||||||
emailVerified: null,
|
|
||||||
image: null,
|
|
||||||
password: null,
|
|
||||||
salt: null,
|
|
||||||
homeBoardId: null,
|
|
||||||
provider: "oidc" as const,
|
|
||||||
colorScheme: "auto" as const,
|
|
||||||
firstDayOfWeek: 1 as const,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -359,6 +319,8 @@ describe("delete should delete user", () => {
|
|||||||
await caller.delete(defaultOwnerId);
|
await caller.delete(defaultOwnerId);
|
||||||
|
|
||||||
const usersInDb = await db.select().from(schema.users);
|
const usersInDb = await db.select().from(schema.users);
|
||||||
expect(usersInDb).toStrictEqual([initialUsers[0], initialUsers[2]]);
|
expect(usersInDb).toHaveLength(2);
|
||||||
|
expect(usersInDb[0]).containSubset(initialUsers[0]);
|
||||||
|
expect(usersInDb[1]).containSubset(initialUsers[2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
provider: true,
|
provider: true,
|
||||||
homeBoardId: true,
|
homeBoardId: true,
|
||||||
firstDayOfWeek: true,
|
firstDayOfWeek: true,
|
||||||
|
pingIconsEnabled: true,
|
||||||
},
|
},
|
||||||
where: eq(users.id, input.userId),
|
where: eq(users.id, input.userId),
|
||||||
});
|
});
|
||||||
@@ -376,6 +377,39 @@ export const userRouter = createTRPCRouter({
|
|||||||
})
|
})
|
||||||
.where(eq(users.id, ctx.session.user.id));
|
.where(eq(users.id, ctx.session.user.id));
|
||||||
}),
|
}),
|
||||||
|
getPingIconsEnabledOrDefault: publicProcedure.query(async ({ ctx }) => {
|
||||||
|
if (!ctx.session?.user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await ctx.db.query.users.findFirst({
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
pingIconsEnabled: true,
|
||||||
|
},
|
||||||
|
where: eq(users.id, ctx.session.user.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
return user?.pingIconsEnabled ?? false;
|
||||||
|
}),
|
||||||
|
changePingIconsEnabled: protectedProcedure
|
||||||
|
.input(validation.user.pingIconsEnabled.and(validation.common.byId))
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
// Only admins can change other users ping icons enabled
|
||||||
|
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "User not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.db
|
||||||
|
.update(users)
|
||||||
|
.set({
|
||||||
|
pingIconsEnabled: input.pingIconsEnabled,
|
||||||
|
})
|
||||||
|
.where(eq(users.id, ctx.session.user.id));
|
||||||
|
}),
|
||||||
getFirstDayOfWeekForUserOrDefault: publicProcedure.query(async ({ ctx }) => {
|
getFirstDayOfWeekForUserOrDefault: publicProcedure.query(async ({ ctx }) => {
|
||||||
if (!ctx.session?.user) {
|
if (!ctx.session?.user) {
|
||||||
return 1 as const;
|
return 1 as const;
|
||||||
@@ -394,7 +428,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
changeFirstDayOfWeek: protectedProcedure
|
changeFirstDayOfWeek: protectedProcedure
|
||||||
.input(validation.user.firstDayOfWeek.and(validation.common.byId))
|
.input(validation.user.firstDayOfWeek.and(validation.common.byId))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
// Only admins can change other users' passwords
|
// Only admins can change other users first day of week
|
||||||
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {
|
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
|
|||||||
1
packages/db/migrations/mysql/0011_freezing_banshee.sql
Normal file
1
packages/db/migrations/mysql/0011_freezing_banshee.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `user` ADD `pingIconsEnabled` boolean DEFAULT false NOT NULL;
|
||||||
1497
packages/db/migrations/mysql/meta/0011_snapshot.json
Normal file
1497
packages/db/migrations/mysql/meta/0011_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -78,6 +78,13 @@
|
|||||||
"when": 1728142597094,
|
"when": 1728142597094,
|
||||||
"tag": "0010_melted_pestilence",
|
"tag": "0010_melted_pestilence",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 11,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1728490046896,
|
||||||
|
"tag": "0011_freezing_banshee",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1
packages/db/migrations/sqlite/0011_classy_angel.sql
Normal file
1
packages/db/migrations/sqlite/0011_classy_angel.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `user` ADD `pingIconsEnabled` integer DEFAULT false NOT NULL;
|
||||||
1430
packages/db/migrations/sqlite/meta/0011_snapshot.json
Normal file
1430
packages/db/migrations/sqlite/meta/0011_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -78,6 +78,13 @@
|
|||||||
"when": 1728142590232,
|
"when": 1728142590232,
|
||||||
"tag": "0010_gorgeous_stingray",
|
"tag": "0010_gorgeous_stingray",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 11,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1728490026154,
|
||||||
|
"tag": "0011_classy_angel",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,10 @@
|
|||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"migration:mysql:generate": "drizzle-kit generate --config ./configs/mysql.config.ts",
|
"migration:mysql:generate": "drizzle-kit generate --config ./configs/mysql.config.ts",
|
||||||
"migration:mysql:run": "drizzle-kit migrate --config ./configs/mysql.config.ts",
|
"migration:mysql:run": "drizzle-kit migrate --config ./configs/mysql.config.ts",
|
||||||
|
"migration:mysql:drop": "drizzle-kit drop --config ./configs/mysql.config.ts",
|
||||||
"migration:sqlite:generate": "drizzle-kit generate --config ./configs/sqlite.config.ts",
|
"migration:sqlite:generate": "drizzle-kit generate --config ./configs/sqlite.config.ts",
|
||||||
"migration:sqlite:run": "drizzle-kit migrate --config ./configs/sqlite.config.ts",
|
"migration:sqlite:run": "drizzle-kit migrate --config ./configs/sqlite.config.ts",
|
||||||
|
"migration:sqlite:drop": "drizzle-kit drop --config ./configs/sqlite.config.ts",
|
||||||
"push:mysql": "drizzle-kit push --config ./configs/mysql.config.ts",
|
"push:mysql": "drizzle-kit push --config ./configs/mysql.config.ts",
|
||||||
"push:sqlite": "drizzle-kit push --config ./configs/sqlite.config.ts",
|
"push:sqlite": "drizzle-kit push --config ./configs/sqlite.config.ts",
|
||||||
"studio": "drizzle-kit studio --config ./configs/sqlite.config.ts",
|
"studio": "drizzle-kit studio --config ./configs/sqlite.config.ts",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export const users = mysqlTable("user", {
|
|||||||
}),
|
}),
|
||||||
colorScheme: varchar("colorScheme", { length: 5 }).$type<ColorScheme>().default("auto").notNull(),
|
colorScheme: varchar("colorScheme", { length: 5 }).$type<ColorScheme>().default("auto").notNull(),
|
||||||
firstDayOfWeek: tinyint("firstDayOfWeek").$type<DayOfWeek>().default(1).notNull(), // Defaults to Monday
|
firstDayOfWeek: tinyint("firstDayOfWeek").$type<DayOfWeek>().default(1).notNull(), // Defaults to Monday
|
||||||
|
pingIconsEnabled: boolean("pingIconsEnabled").default(false).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const accounts = mysqlTable(
|
export const accounts = mysqlTable(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const users = sqliteTable("user", {
|
|||||||
}),
|
}),
|
||||||
colorScheme: text("colorScheme").$type<ColorScheme>().default("auto").notNull(),
|
colorScheme: text("colorScheme").$type<ColorScheme>().default("auto").notNull(),
|
||||||
firstDayOfWeek: int("firstDayOfWeek").$type<DayOfWeek>().default(1).notNull(), // Defaults to Monday
|
firstDayOfWeek: int("firstDayOfWeek").$type<DayOfWeek>().default(1).notNull(), // Defaults to Monday
|
||||||
|
pingIconsEnabled: int("pingIconsEnabled", { mode: "boolean" }).default(false).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const accounts = sqliteTable(
|
export const accounts = sqliteTable(
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ export default {
|
|||||||
homeBoard: {
|
homeBoard: {
|
||||||
label: "Home board",
|
label: "Home board",
|
||||||
},
|
},
|
||||||
|
pingIconsEnabled: {
|
||||||
|
label: "Use icons for pings",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
usernameTaken: "Username already taken",
|
usernameTaken: "Username already taken",
|
||||||
@@ -116,6 +119,16 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
changePingIconsEnabled: {
|
||||||
|
notification: {
|
||||||
|
success: {
|
||||||
|
message: "Ping icons toggled successfully",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
message: "Unable to toggle ping icons",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
manageAvatar: {
|
manageAvatar: {
|
||||||
changeImage: {
|
changeImage: {
|
||||||
label: "Change image",
|
label: "Change image",
|
||||||
@@ -1703,6 +1716,7 @@ export default {
|
|||||||
language: "Language & Region",
|
language: "Language & Region",
|
||||||
board: "Home board",
|
board: "Home board",
|
||||||
firstDayOfWeek: "First day of the week",
|
firstDayOfWeek: "First day of the week",
|
||||||
|
accessibility: "Accessibility",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
security: {
|
security: {
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ const firstDayOfWeekSchema = z.object({
|
|||||||
firstDayOfWeek: z.custom<DayOfWeek>((value) => z.number().min(0).max(6).safeParse(value).success),
|
firstDayOfWeek: z.custom<DayOfWeek>((value) => z.number().min(0).max(6).safeParse(value).success),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pingIconsEnabledSchema = z.object({
|
||||||
|
pingIconsEnabled: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
export const userSchemas = {
|
export const userSchemas = {
|
||||||
signIn: signInSchema,
|
signIn: signInSchema,
|
||||||
registration: registrationSchema,
|
registration: registrationSchema,
|
||||||
@@ -121,4 +125,5 @@ export const userSchemas = {
|
|||||||
changePasswordApi: changePasswordApiSchema,
|
changePasswordApi: changePasswordApiSchema,
|
||||||
changeColorScheme: changeColorSchemeSchema,
|
changeColorScheme: changeColorSchemeSchema,
|
||||||
firstDayOfWeek: firstDayOfWeekSchema,
|
firstDayOfWeek: firstDayOfWeekSchema,
|
||||||
|
pingIconsEnabled: pingIconsEnabledSchema,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { Flex, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
import { Flex, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
||||||
|
import { IconLoader } from "@tabler/icons-react";
|
||||||
import combineClasses from "clsx";
|
import combineClasses from "clsx";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
@@ -59,7 +60,7 @@ export default function AppWidget({ options, isEditMode }: WidgetComponentProps<
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Tooltip.Floating>
|
</Tooltip.Floating>
|
||||||
{options.pingEnabled && app.href ? (
|
{options.pingEnabled && app.href ? (
|
||||||
<Suspense fallback={<PingDot color="blue" tooltip={`${t("common.action.loading")}…`} />}>
|
<Suspense fallback={<PingDot icon={IconLoader} color="blue" tooltip={`${t("common.action.loading")}…`} />}>
|
||||||
<PingIndicator href={app.href} />
|
<PingIndicator href={app.href} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -1,23 +1,33 @@
|
|||||||
import type { MantineColor } from "@mantine/core";
|
import type { MantineColor } from "@mantine/core";
|
||||||
import { Box, Tooltip } from "@mantine/core";
|
import { Box, Tooltip } from "@mantine/core";
|
||||||
|
|
||||||
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import type { TablerIcon } from "@homarr/ui";
|
||||||
|
|
||||||
interface PingDotProps {
|
interface PingDotProps {
|
||||||
|
icon: TablerIcon;
|
||||||
color: MantineColor;
|
color: MantineColor;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PingDot = ({ color, tooltip }: PingDotProps) => {
|
export const PingDot = ({ color, tooltip, ...props }: PingDotProps) => {
|
||||||
|
const [pingIconsEnabled] = clientApi.user.getPingIconsEnabledOrDefault.useSuspenseQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box bottom="2.5cqmin" right="2.5cqmin" pos="absolute">
|
<Box bottom="2.5cqmin" right="2.5cqmin" pos="absolute">
|
||||||
<Tooltip label={tooltip}>
|
<Tooltip label={tooltip}>
|
||||||
<Box
|
{pingIconsEnabled ? (
|
||||||
bg={color}
|
<props.icon style={{ width: "10cqmin", height: "10cqmin" }} color={color} />
|
||||||
style={{
|
) : (
|
||||||
borderRadius: "100%",
|
<Box
|
||||||
}}
|
bg={color}
|
||||||
w="10cqmin"
|
style={{
|
||||||
h="10cqmin"
|
borderRadius: "100%",
|
||||||
></Box>
|
}}
|
||||||
|
w="10cqmin"
|
||||||
|
h="10cqmin"
|
||||||
|
></Box>
|
||||||
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { IconCheck, IconX } from "@tabler/icons-react";
|
||||||
|
|
||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
@@ -32,9 +33,12 @@ export const PingIndicator = ({ href }: PingIndicatorProps) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isError = "error" in pingResult || pingResult.statusCode >= 500;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PingDot
|
<PingDot
|
||||||
color={"error" in pingResult || pingResult.statusCode >= 500 ? "red" : "green"}
|
icon={isError ? IconX : IconCheck}
|
||||||
|
color={isError ? "red" : "green"}
|
||||||
tooltip={"statusCode" in pingResult ? pingResult.statusCode.toString() : pingResult.error}
|
tooltip={"statusCode" in pingResult ? pingResult.statusCode.toString() : pingResult.error}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user