Merge branch 'dev' into board-in-database

This commit is contained in:
Meier Lukas
2023-11-12 20:51:24 +01:00
142 changed files with 797 additions and 514 deletions

View File

@@ -41,7 +41,7 @@ export const StepOnboardingFinished = () => {
<Divider />
<NavLink
component={Link}
href="/b/default"
href="/b"
rightSection={<IconChevronRight size="0.8rem" stroke={1.5} />}
className={classes.link}
icon={<IconDashboard />}

View File

@@ -1,3 +1,5 @@
import Consola from 'consola';
import fs from 'fs/promises';
import { NextRequest, NextResponse } from 'next/server';
import { env } from 'process';
import { v4 } from 'uuid';
@@ -37,11 +39,8 @@ export async function middleware(req: NextRequest) {
return HomarrResponse.next(deviceId);
}
// is only called from when there were no users in the database in this session (Since the app started)
cachedUserCount = await client.user.count.query();
// Do not redirect if there are users in the database
if (cachedUserCount > 0) {
if (!(await shouldRedirectToOnboard())) {
return HomarrResponse.next(deviceId);
}
@@ -63,3 +62,25 @@ const HomarrResponse = {
return response;
},
};
const shouldRedirectToOnboard = async (): Promise<boolean> => {
const cacheAndGetUserCount = async () => {
cachedUserCount = await client.user.count.query();
return cachedUserCount === 0;
};
if (!env.DATABASE_URL?.startsWith('file:')) {
return await cacheAndGetUserCount();
}
const fileUri = env.DATABASE_URL.substring(4);
try {
await fs.access(fileUri, fs.constants.W_OK);
return await cacheAndGetUserCount();
} catch {
Consola.warn(
`detected that the path ${fileUri} was not readable. Showing onboarding page for setup...`
);
return true;
}
};

View File

@@ -59,6 +59,11 @@ export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = a
if (!board.allowGuests && !session?.user) {
return {
notFound: true,
props: {
primaryColor: board.primaryColor,
secondaryColor: board.secondaryColor,
primaryShade: board.primaryShade,
},
};
}

View File

@@ -62,6 +62,12 @@ export const getServerSideProps: GetServerSideProps = async ({ locale, req, res
};
}
const caller = dockerRouter.createCaller({
session: session,
cookies: req.cookies,
headers: req.headers,
});
const translations = await getServerSideTranslations(
[...boardNamespaces, 'layout/manage', 'tools/docker'],
locale,

View File

@@ -4,7 +4,6 @@ import { and, eq, inArray } from 'drizzle-orm';
import fs from 'fs';
import { z } from 'zod';
import { db } from '~/server/db';
import { WidgetSort } from '~/server/db/items';
import { getDefaultBoardAsync } from '~/server/db/queries/userSettings';
import {
appStatusCodes,

View File

@@ -67,12 +67,6 @@ export const configRouter = createTRPCRouter({
})
)
.mutation(async ({ input }) => {
if (process.env.DISABLE_EDIT_MODE?.toLowerCase() === 'true') {
throw new TRPCError({
code: 'METHOD_NOT_SUPPORTED',
message: 'Edit is not allowed, because edit mode is disabled',
});
}
Consola.info(`Saving updated configuration of '${input.name}' config.`);
const previousConfig = getConfig(input.name);

View File

@@ -62,22 +62,24 @@ export const dnsHoleRouter = createTRPCRouter({
)
);
const data = result.reduce(
(prev: AdStatistics, curr) => ({
domainsBeingBlocked: prev.domainsBeingBlocked + curr.domainsBeingBlocked,
adsBlockedToday: prev.adsBlockedToday + curr.adsBlockedToday,
dnsQueriesToday: prev.dnsQueriesToday + curr.dnsQueriesToday,
status: [...prev.status, curr.status],
adsBlockedTodayPercentage: 0,
}),
{
domainsBeingBlocked: 0,
adsBlockedToday: 0,
adsBlockedTodayPercentage: 0,
dnsQueriesToday: 0,
status: [],
}
);
const data = result
.filter((x) => x !== null)
.reduce(
(prev: AdStatistics, curr) => ({
domainsBeingBlocked: prev.domainsBeingBlocked + curr!.domainsBeingBlocked,
adsBlockedToday: prev.adsBlockedToday + curr!.adsBlockedToday,
dnsQueriesToday: prev.dnsQueriesToday + curr!.dnsQueriesToday,
status: [...prev.status, curr!.status],
adsBlockedTodayPercentage: 0,
}),
{
domainsBeingBlocked: 0,
adsBlockedToday: 0,
adsBlockedTodayPercentage: 0,
dnsQueriesToday: 0,
status: [],
}
);
data.adsBlockedTodayPercentage = data.adsBlockedToday / data.dnsQueriesToday;
if (Number.isNaN(data.adsBlockedTodayPercentage)) {
@@ -131,7 +133,13 @@ const processPiHole = async (app: ConfigAppType, enable: boolean) => {
const collectPiHoleSummary = async (app: ConfigAppType) => {
const piHole = new PiHoleClient(app.url, findAppProperty(app, 'apiKey'));
const summary = await piHole.getSummary();
const summary = await piHole.getSummary().catch(() => {
return null;
});
if (!summary) {
return null;
}
return {
domainsBeingBlocked: summary.domains_being_blocked,
@@ -152,7 +160,14 @@ const collectAdGuardSummary = async (app: ConfigAppType) => {
findAppProperty(app, 'password')
);
const stats = await adGuard.getStats();
const stats = await adGuard.getStats().catch(() => {
return null;
});
if (!stats) {
return null;
}
const status = await adGuard.getStatus();
const countFilteredDomains = await adGuard.getCountFilteringDomains();

View File

@@ -2,17 +2,21 @@ import { Jellyfin } from '@jellyfin/sdk';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models';
import { getSessionApi } from '@jellyfin/sdk/lib/utils/api/session-api';
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
import Consola from 'consola';
import { z } from 'zod';
import { createTRPCRouter, publicProcedure } from '../trpc';
import { ConfigAppType } from '~/types/app';
import { checkIntegrationsType, findAppProperty } from '~/tools/client/app-properties';
import { getConfig } from '~/tools/config/getConfig';
import { PlexClient } from '~/tools/server/sdk/plex/plexClient';
import { trimStringEnding } from '~/tools/shared/strings';
import { GenericMediaServer } from '~/types/api/media-server/media-server';
import { MediaServersResponseType } from '~/types/api/media-server/response';
import { GenericCurrentlyPlaying, GenericSessionInfo } from '~/types/api/media-server/session-info';
import { ConfigAppType } from '~/types/app';
import { createTRPCRouter, publicProcedure } from '../trpc';
import { PlexClient } from '~/tools/server/sdk/plex/plexClient';
const jellyfin = new Jellyfin({
clientInfo: {
@@ -104,7 +108,7 @@ const handleServer = async (app: ConfigAppType): Promise<GenericMediaServer | un
return {
type: 'jellyfin',
appId: app.id,
serverAddress: app.url,
serverAddress: trimStringEnding(app.url, ['/web/index.html#!/home.html', '/web', '/web/index.html', '/web/', '/web/index.html#']),
version: infoApi.data.Version ?? undefined,
sessions: sessions
.filter((session) => session.NowPlayingItem)
@@ -176,7 +180,7 @@ const handleServer = async (app: ConfigAppType): Promise<GenericMediaServer | un
if (!apiKey) {
return {
serverAddress: app.url,
serverAddress: trimStringEnding(app.url, ['/web', '/web/index.html', '/web/index.html#!', '/web/index.html#!/settings/web/general', '/web/']),
sessions: [],
type: 'plex',
appId: app.id,

View File

@@ -4,10 +4,10 @@ import { z } from 'zod';
import { db } from '~/server/db';
import { items, widgetOptions } from '~/server/db/schema';
import { createTRPCRouter, publicProcedure } from '../trpc';
import { adminProcedure, createTRPCRouter, publicProcedure } from '../trpc';
export const notebookRouter = createTRPCRouter({
update: publicProcedure
update: adminProcedure
.input(z.object({ widgetId: z.string(), content: z.string(), boardName: z.string() }))
.mutation(async ({ input }) => {
const item = await db.query.items.findFirst({

View File

@@ -32,7 +32,7 @@ export const adGuardApiStatusResponseSchema = z.object({
export const adGuardApiFilteringStatusSchema = z.object({
filters: z.array(
z.object({
url: z.string().url(),
url: z.string(),
name: z.string(),
last_updated: z.string().optional(),
id: z.number().nonnegative(),

View File

@@ -254,6 +254,7 @@ function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
);
case 'horizontal':
case 'vertical':
const flexDirection = widget.options.layout === 'vertical' ? 'column' : 'row';
return (
<Stack h="100%" spacing={0}>
<Title size="h4" px="0.25rem">
@@ -282,15 +283,16 @@ function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
w="100%"
>
{widget.options.items.map((item: BookmarkItem, index) => (
<>
{index > 0 && (
<Divider
m="3px"
orientation={widget.options.layout !== 'vertical' ? 'vertical' : 'horizontal'}
/>
)}
<div
key={index}
style={{ display: 'flex', flex: '1', flexDirection: flexDirection }}
>
<Divider
m="3px"
orientation={widget.options.layout !== 'vertical' ? 'vertical' : 'horizontal'}
color={index === 0 ? 'transparent' : undefined}
/>
<Card
key={index}
px="md"
py="1px"
component="a"
@@ -307,7 +309,7 @@ function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) {
>
<BookmarkItemContent item={item} />
</Card>
</>
</div>
))}
</Flex>
</ScrollArea>

View File

@@ -98,7 +98,6 @@ function CalendarTile({ widget }: CalendarTileProps) {
style={{ position: 'relative' }}
date={month}
maxLevel="month"
minLevel="month"
styles={{
calendarHeader: {
maxWidth: 'inherit',

View File

@@ -3,11 +3,13 @@ import {
Box,
Button,
Card,
Center,
Group,
Image,
SimpleGrid,
Stack,
Text,
Title,
UnstyledButton,
} from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
@@ -71,7 +73,7 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
const { isInitialLoading, data, isFetching: fetchingDnsSummary } = useDnsHoleSummeryQuery();
const { mutateAsync, isLoading: changingStatus } = useDnsHoleControlMutation();
const { width, ref } = useElementSize();
const { t } = useTranslation('common');
const { t } = useTranslation(['common', 'modules/dns-hole-controls']);
const { name: configName, config } = useConfigContext();
@@ -81,6 +83,20 @@ function DnsHoleControlsWidgetTile({ widget }: DnsHoleControlsWidgetProps) {
return <WidgetLoading />;
}
if (data.status.length === 0) {
return(
<Center h="100%">
<Stack align="center">
<IconDeviceGamepad size={40} strokeWidth={1}/>
<Title align="center" order={6}>{t('modules/dns-hole-controls:descriptor.errors.general.title')}</Title>
<Text align="center">{t('modules/dns-hole-controls:descriptor.errors.general.text')}</Text>
</Stack>
</Center>
)
}
console.log(data);
type getDnsStatusAcc = {
enabled: string[];
disabled: string[];

View File

@@ -145,8 +145,8 @@ function MediaRequestListTile({ widget }: MediaRequestListWidgetProps) {
) : (
<Text>{t('nonePending')}</Text>
)}
{sortedData.map((item) => (
<Card radius="md" withBorder>
{sortedData.map((item, index) => (
<Card radius="md" withBorder key={index}>
<Flex wrap="wrap" justify="space-between" gap="md">
<Flex gap="md">
<Image

View File

@@ -100,7 +100,7 @@ function MediaServerTile({ widget }: MediaServerWidgetProps) {
<Group pos="absolute" bottom="15" right="15" mt="auto">
<Avatar.Group>
{data?.servers.map((server) => {
{data?.servers.map((server, index) => {
const app = config?.apps.find((x) => x.id === server.appId);
if (!app) {
@@ -109,6 +109,7 @@ function MediaServerTile({ widget }: MediaServerWidgetProps) {
return (
<AppAvatar
key={index}
iconUrl={app.appearance.iconUrl}
// If success, the color is undefined, otherwise it's red but if isFetching is true, it's yellow
color={server.success ? (isFetching ? 'yellow' : undefined) : 'red'}

View File

@@ -30,8 +30,8 @@ const definition = defineWidget({
},
},
gridstack: {
minWidth: 3,
minHeight: 2,
minWidth: 1,
minHeight: 1,
maxWidth: 12,
maxHeight: 12,
},