🔀 Merge branch 'dev' into feature/dashdot-consistency-changes

This commit is contained in:
Manuel
2023-02-17 23:41:49 +01:00
28 changed files with 1558 additions and 165 deletions

View File

@@ -1,5 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
yarn-path ".yarn/releases/yarn-1.22.19.cjs"

View File

@@ -29,6 +29,7 @@
"@ctrl/transmission": "^4.1.1", "@ctrl/transmission": "^4.1.1",
"@emotion/react": "^11.10.5", "@emotion/react": "^11.10.5",
"@emotion/server": "^11.10.0", "@emotion/server": "^11.10.0",
"@jellyfin/sdk": "^0.7.0",
"@mantine/core": "^5.9.3", "@mantine/core": "^5.9.3",
"@mantine/dates": "^5.9.3", "@mantine/dates": "^5.9.3",
"@mantine/dropzone": "^5.9.3", "@mantine/dropzone": "^5.9.3",
@@ -49,6 +50,7 @@
"dockerode": "^3.3.2", "dockerode": "^3.3.2",
"fily-publish-gridstack": "^0.0.13", "fily-publish-gridstack": "^0.0.13",
"framer-motion": "^9.0.2", "framer-motion": "^9.0.2",
"html-entities": "^2.3.3",
"i18next": "^21.9.1", "i18next": "^21.9.1",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"next": "^13.1.6", "next": "^13.1.6",
@@ -58,8 +60,10 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-simple-code-editor": "^0.13.1", "react-simple-code-editor": "^0.13.1",
"rss-parser": "^3.12.0",
"sabnzbd-api": "^1.5.0", "sabnzbd-api": "^1.5.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"xml-js": "^1.6.11",
"yarn": "^1.22.19", "yarn": "^1.22.19",
"zustand": "^4.1.4" "zustand": "^4.1.4"
}, },

View File

@@ -0,0 +1,23 @@
{
"descriptor": {
"name": "IFrame",
"description": "Embed any content from the internet. Some websites may restrict access.",
"settings": {
"title": "IFrame settings",
"embedUrl": {
"label": "Embed URL"
},
"allowFullScreen": {
"label": "Allow full screen"
}
}
},
"card": {
"errors": {
"noUrl": {
"title": "Enter an URL",
"text": "Ensure that you've entered a valid address in the configuration of your widget"
}
}
}
}

View File

@@ -0,0 +1,24 @@
{
"descriptor": {
"name": "Media Server",
"description": "Interact with your Jellyfin or Plex media server",
"settings": {
"title": "Settings for media server widget"
}
},
"card": {
"table": {
"header": {
"session": "Session",
"user": "User",
"currentlyPlaying": "Currently playing"
}
},
"errors": {
"general": {
"title": "Unable to load content",
"text": "Unable to retrieve information from the server. Please check the logs for more details"
}
}
}
}

View File

@@ -0,0 +1,20 @@
{
"descriptor": {
"name": "RSS Widget",
"description": "Grabs the items from a RSS feed and displays them. Commonly used for online news",
"settings": {
"title": "Settings for RSS widget",
"rssFeedUrl": {
"label": "RSS feed url"
}
}
},
"card": {
"errors": {
"general": {
"title": "Unable to retrieve RSS feed",
"text": "There was a problem reaching out the the RSS feed. Make sure that you've configured the feed correctly and use a valid RSS url, that matches the official standard specification. After updating the feed, you may need to save your dashboard and refresh the page."
}
}
}
}

View File

@@ -0,0 +1,26 @@
import { Avatar, DefaultMantineColor, useMantineTheme } from '@mantine/core';
export const AppAvatar = ({
iconUrl,
color,
}: {
iconUrl: string;
color?: DefaultMantineColor | undefined;
}) => {
const { colors, colorScheme } = useMantineTheme();
return (
<Avatar
src={iconUrl}
bg={colorScheme === 'dark' ? colors.gray[8] : colors.gray[2]}
size="sm"
radius="xl"
p={4}
styles={{
root: {
borderColor: color !== undefined ? colors[color] : undefined,
},
}}
/>
);
};

View File

@@ -75,6 +75,16 @@ export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => {
image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/readarr.png', image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/readarr.png',
label: 'Readarr', label: 'Readarr',
}, },
{
value: 'jellyfin',
image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/jellyfin.png',
label: 'Jellyfin',
},
{
value: 'plex',
image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/plex.png',
label: 'Plex',
},
].filter((x) => Object.keys(integrationFieldProperties).includes(x.value)); ].filter((x) => Object.keys(integrationFieldProperties).includes(x.value));
const getNewProperties = (value: string | null): AppIntegrationPropertyType[] => { const getNewProperties = (value: string | null): AppIntegrationPropertyType[] => {

View File

@@ -0,0 +1,17 @@
import { useQuery } from '@tanstack/react-query';
import { MediaServersResponseType } from '../../../types/api/media-server/response';
interface GetMediaServersParams {
enabled: boolean;
}
export const useGetMediaServers = ({ enabled }: GetMediaServersParams) =>
useQuery({
queryKey: ['media-servers'],
queryFn: async (): Promise<MediaServersResponseType> => {
const response = await fetch('/api/modules/media-server');
return response.json();
},
enabled,
refetchInterval: 10 * 1000,
});

View File

@@ -0,0 +1,10 @@
import { useQuery } from '@tanstack/react-query';
export const useGetRssFeed = (feedUrl: string) =>
useQuery({
queryKey: ['rss-feed', feedUrl],
queryFn: async () => {
const response = await fetch('/api/modules/rss');
return response.json();
},
});

View File

@@ -0,0 +1,227 @@
import { Jellyfin } from '@jellyfin/sdk';
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 { getCookie } from 'cookies-next';
import { NextApiRequest, NextApiResponse } from 'next';
import { BaseItemKind, ProgramAudio } from '@jellyfin/sdk/lib/generated-client/models';
import { getConfig } from '../../../../tools/config/getConfig';
import { PlexClient } from '../../../../tools/server/sdk/plex/plexClient';
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';
const jellyfin = new Jellyfin({
clientInfo: {
name: 'Homarr',
version: '0.0.1',
},
deviceInfo: {
name: 'Homarr Jellyfin Widget',
id: 'homarr-jellyfin-widget',
},
});
const Get = async (request: NextApiRequest, response: NextApiResponse) => {
const configName = getCookie('config-name', { req: request });
const config = getConfig(configName?.toString() ?? 'default');
const apps = config.apps.filter((app) =>
['jellyfin', 'plex'].includes(app.integration?.type ?? '')
);
const servers = await Promise.all(
apps.map(async (app): Promise<GenericMediaServer | undefined> => {
try {
return await handleServer(app);
} catch (error) {
Consola.error(
`failed to communicate with media server '${app.name}' (${app.id}): ${error}`
);
return {
serverAddress: app.url,
sessions: [],
success: false,
version: undefined,
type: undefined,
appId: app.id,
};
}
})
);
return response.status(200).json({
servers: servers.filter((server) => server !== undefined),
} as MediaServersResponseType);
};
const handleServer = async (app: ConfigAppType): Promise<GenericMediaServer | undefined> => {
switch (app.integration?.type) {
case 'jellyfin': {
const username = app.integration.properties.find((x) => x.field === 'username');
if (!username || !username.value) {
return {
appId: app.id,
serverAddress: app.url,
sessions: [],
type: 'jellyfin',
version: undefined,
success: false,
};
}
const password = app.integration.properties.find((x) => x.field === 'password');
if (!password || !password.value) {
return {
appId: app.id,
serverAddress: app.url,
sessions: [],
type: 'jellyfin',
version: undefined,
success: false,
};
}
const api = jellyfin.createApi(app.url);
const infoApi = await getSystemApi(api).getPublicSystemInfo();
await api.authenticateUserByName(username.value, password.value);
const sessionApi = await getSessionApi(api);
const sessions = await sessionApi.getSessions();
return {
type: 'jellyfin',
appId: app.id,
serverAddress: app.url,
version: infoApi.data.Version ?? undefined,
sessions: sessions.data.map(
(session): GenericSessionInfo => ({
id: session.Id ?? '?',
username: session.UserName ?? undefined,
sessionName: `${session.Client} (${session.DeviceName})`,
supportsMediaControl: session.SupportsMediaControl ?? false,
currentlyPlaying: session.NowPlayingItem
? {
name: session.NowPlayingItem.Name as string,
seasonName: session.NowPlayingItem.SeasonName as string,
albumName: session.NowPlayingItem.Album as string,
episodeCount: session.NowPlayingItem.EpisodeCount ?? undefined,
metadata: {
video:
session.NowPlayingItem &&
session.NowPlayingItem.Width &&
session.NowPlayingItem.Height
? {
videoCodec: undefined,
width: session.NowPlayingItem.Width ?? undefined,
height: session.NowPlayingItem.Height ?? undefined,
bitrate: undefined,
videoFrameRate: session.TranscodingInfo?.Framerate
? String(session.TranscodingInfo?.Framerate)
: undefined,
}
: undefined,
audio: session.TranscodingInfo
? {
audioChannels: session.TranscodingInfo.AudioChannels ?? undefined,
audioCodec: session.TranscodingInfo.AudioCodec ?? undefined,
}
: undefined,
transcoding: session.TranscodingInfo
? {
audioChannels: session.TranscodingInfo.AudioChannels ?? -1,
audioCodec: session.TranscodingInfo.AudioCodec ?? undefined,
container: session.TranscodingInfo.Container ?? undefined,
width: session.TranscodingInfo.Width ?? undefined,
height: session.TranscodingInfo.Height ?? undefined,
videoCodec: session.TranscodingInfo?.VideoCodec ?? undefined,
audioDecision: undefined,
context: undefined,
duration: undefined,
error: undefined,
sourceAudioCodec: undefined,
sourceVideoCodec: undefined,
timeStamp: undefined,
transcodeHwRequested: undefined,
videoDecision: undefined,
}
: undefined,
},
type: convertJellyfinType(session.NowPlayingItem.Type),
}
: undefined,
userProfilePicture: undefined,
})
),
success: true,
};
}
case 'plex': {
const apiKey = app.integration.properties.find((x) => x.field === 'apiKey');
if (!apiKey || !apiKey.value) {
return {
serverAddress: app.url,
sessions: [],
type: 'plex',
appId: app.id,
version: undefined,
success: false,
};
}
const plexClient = new PlexClient(app.url, apiKey.value);
const sessions = await plexClient.getSessions();
return {
serverAddress: app.url,
sessions,
type: 'plex',
version: undefined,
appId: app.id,
success: true,
};
}
default: {
Consola.warn(
`media-server api entered a fallback case. This should normally not happen and must be reported. Cause: '${app.name}' (${app.id})`
);
return undefined;
}
}
};
const convertJellyfinType = (kind: BaseItemKind | undefined): GenericCurrentlyPlaying['type'] => {
switch (kind) {
case BaseItemKind.Audio:
case BaseItemKind.MusicVideo:
return 'audio';
case BaseItemKind.Episode:
case BaseItemKind.Video:
return 'video';
case BaseItemKind.Movie:
return 'movie';
case BaseItemKind.TvChannel:
case BaseItemKind.TvProgram:
case BaseItemKind.LiveTvChannel:
case BaseItemKind.LiveTvProgram:
return 'tv';
default:
return undefined;
}
};
export default async (request: NextApiRequest, response: NextApiResponse) => {
if (request.method === 'GET') {
return Get(request, response);
}
return response.status(405);
};

View File

@@ -0,0 +1,93 @@
import Consola from 'consola';
import { getCookie } from 'cookies-next';
import { decode } from 'html-entities';
import { NextApiRequest, NextApiResponse } from 'next';
import Parser from 'rss-parser';
import { getConfig } from '../../../../tools/config/getConfig';
import { Stopwatch } from '../../../../tools/shared/stopwatch';
import { IRssWidget } from '../../../../widgets/rss/RssWidgetTile';
type CustomItem = {
'media:content': string;
enclosure: {
url: string;
};
};
const parser: Parser<any, CustomItem> = new Parser({
customFields: {
item: ['media:content', 'enclosure'],
},
});
export const Get = async (request: NextApiRequest, response: NextApiResponse) => {
const configName = getCookie('config-name', { req: request });
const config = getConfig(configName?.toString() ?? 'default');
const rssWidget = config.widgets.find((x) => x.id === 'rss') as IRssWidget | undefined;
if (
!rssWidget ||
!rssWidget.properties.rssFeedUrl ||
rssWidget.properties.rssFeedUrl.length < 1
) {
response.status(400).json({ message: 'required widget does not exist' });
return;
}
Consola.info('Requesting RSS feed...');
const stopWatch = new Stopwatch();
const feed = await parser.parseURL(rssWidget.properties.rssFeedUrl);
Consola.info(`Retrieved RSS feed after ${stopWatch.getEllapsedMilliseconds()} milliseconds`);
const orderedFeed = {
...feed,
items: feed.items
.map((item: { title: any; content: any }) => ({
...item,
title: item.title ? decode(item.title) : undefined,
content: decode(item.content),
enclosure: createEnclosure(item),
}))
.sort((a: { pubDate: number }, b: { pubDate: number }) => {
if (!a.pubDate || !b.pubDate) {
return 0;
}
return a.pubDate - b.pubDate;
})
.slice(0, 20),
};
response.status(200).json({
feed: orderedFeed,
success: orderedFeed?.items !== undefined,
});
};
const createEnclosure = (item: any) => {
if (item.enclosure) {
return item.enclosure;
}
if (item['media:content']) {
return {
url: item['media:content'].$.url,
};
}
return undefined;
};
export default async (request: NextApiRequest, response: NextApiResponse) => {
if (request.method === 'GET') {
return Get(request, response);
}
return response.status(405);
};

View File

@@ -92,3 +92,8 @@
height: 0px; height: 0px;
min-height: 0px !important; min-height: 0px !important;
} }
.scroll-area-w100 .mantine-ScrollArea-viewport > div:nth-of-type(1) {
width: 100%;
display: inherit !important;
}

View File

@@ -0,0 +1,108 @@
import { Element, xml2js } from 'xml-js';
import {
GenericCurrentlyPlaying,
GenericSessionInfo,
} from '../../../../types/api/media-server/session-info';
export class PlexClient {
constructor(private readonly apiAddress: string, private readonly token: string) {}
async getSessions(): Promise<GenericSessionInfo[]> {
const response = await fetch(`${this.apiAddress}/status/sessions?X-Plex-Token=${this.token}`);
const body = await response.text();
// convert xml response to objects, as there is no JSON api
const data = xml2js(body);
// TODO: Investigate when there are no media containers
const mediaContainer = data.elements[0] as Element;
// no sessions are open or available
if (!mediaContainer.elements?.some((_) => true)) {
return [];
}
const videoElements = mediaContainer.elements as Element[];
const videos = videoElements
.map((videoElement): GenericSessionInfo | undefined => {
// extract the elements from the children
const userElement = this.findElement('User', videoElement.elements);
const playerElement = this.findElement('Player', videoElement.elements);
const mediaElement = this.findElement('Media', videoElement.elements);
const sessionElement = this.findElement('Session', videoElement.elements);
if (!userElement || !playerElement || !mediaElement || !sessionElement) {
return undefined;
}
const { videoCodec, videoFrameRate, audioCodec, audioChannels, height, width, bitrate } =
mediaElement;
const transcodingElement = this.findElement('TranscodeSession', videoElement.elements);
return {
id: sessionElement.id as string,
username: userElement.title as string,
userProfilePicture: userElement.thumb as string,
sessionName: `${playerElement.product} (${playerElement.title})`,
currentlyPlaying: {
name: videoElement.attributes?.title as string,
type: this.getCurrentlyPlayingType(videoElement.attributes?.type as string),
metadata: {
video: {
bitrate,
height,
videoCodec,
videoFrameRate,
width,
},
audio: {
audioChannels,
audioCodec,
},
transcoding:
transcodingElement === undefined
? undefined
: {
audioChannels: transcodingElement.audioChannels,
audioCodec: transcodingElement.audioCodec,
audioDecision: transcodingElement.audioDecision,
container: transcodingElement.container,
context: transcodingElement.context,
duration: transcodingElement.duration,
error: transcodingElement.error === 1,
height: transcodingElement.height,
sourceAudioCodec: transcodingElement.sourceAudioCodec,
sourceVideoCodec: transcodingElement.sourceVideoCodec,
timeStamp: transcodingElement.timeStamp,
transcodeHwRequested: transcodingElement.transcodeHwRequested === 1,
videoCodec: transcodingElement.videoCodec,
videoDecision: transcodingElement.videoDecision,
width: transcodingElement.width,
},
},
},
} as GenericSessionInfo;
})
.filter((x) => x !== undefined) as GenericSessionInfo[];
return videos;
}
private findElement(name: string, elements: Element[] | undefined) {
return elements?.find((x) => x.name === name)?.attributes;
}
private getCurrentlyPlayingType(type: string): GenericCurrentlyPlaying['type'] {
switch (type) {
case 'movie':
return 'movie';
case 'episode':
return 'video';
default:
return undefined;
}
}
}

View File

@@ -29,9 +29,12 @@ export const dashboardNamespaces = [
'modules/torrents-status', 'modules/torrents-status',
'modules/weather', 'modules/weather',
'modules/ping', 'modules/ping',
'modules/iframe',
'modules/rss',
'modules/docker', 'modules/docker',
'modules/dashdot', 'modules/dashdot',
'modules/overseerr', 'modules/overseerr',
'modules/media-server',
'modules/common-media-cards', 'modules/common-media-cards',
'modules/video-stream', 'modules/video-stream',
]; ];

View File

@@ -0,0 +1,11 @@
export class Stopwatch {
private startTime: Date;
constructor() {
this.startTime = new Date();
}
getEllapsedMilliseconds() {
return new Date().getTime() - this.startTime.getTime();
}
}

View File

@@ -0,0 +1,34 @@
import { GenericSessionInfo } from './session-info';
export type GenericMediaServer = {
/**
* The type of the media server.
* Undefined indicates, that the type is either unsupported or recognizing went wrong
*/
type: 'jellyfin' | 'plex' | undefined;
/**
* The address of the server
*/
serverAddress: string;
/**
* The current version of the server
*/
version: string | undefined;
/**
* The active sessions on the server
*/
sessions: GenericSessionInfo[];
/**
* The app id of the used app
*/
appId: string;
/**
* Indicates, wether the communication was successfull or not
*/
success: boolean;
};

View File

@@ -0,0 +1,5 @@
import { GenericMediaServer } from './media-server';
export type MediaServersResponseType = {
servers: GenericMediaServer[];
};

View File

@@ -0,0 +1,46 @@
export type GenericSessionInfo = {
supportsMediaControl: boolean;
username: string | undefined;
id: string;
sessionName: string;
userProfilePicture: string | undefined;
currentlyPlaying: GenericCurrentlyPlaying | undefined;
};
export type GenericCurrentlyPlaying = {
name: string;
seasonName: string | undefined;
albumName: string | undefined;
episodeCount: number | undefined;
type: 'audio' | 'video' | 'tv' | 'movie' | undefined;
metadata: {
video: {
videoCodec: string | undefined;
videoFrameRate: string | undefined;
height: number | undefined;
width: number | undefined;
bitrate: number | undefined;
} | undefined;
audio: {
audioCodec: string | undefined;
audioChannels: number | undefined;
} | undefined;
transcoding: {
context: string | undefined;
sourceVideoCodec: string | undefined;
sourceAudioCodec: string | undefined;
videoDecision: string | undefined;
audioDecision: string | undefined;
container: string | undefined;
videoCodec: string | undefined;
audioCodec: string | undefined;
error: boolean | undefined;
duration: number | undefined;
audioChannels: number | undefined;
width: number | undefined;
height: number | undefined;
transcodeHwRequested: boolean | undefined;
timeStamp: number | undefined;
} | undefined;
};
};

View File

@@ -41,6 +41,8 @@ export type IntegrationType =
| 'deluge' | 'deluge'
| 'qBittorrent' | 'qBittorrent'
| 'transmission' | 'transmission'
| 'plex'
| 'jellyfin'
| 'nzbGet'; | 'nzbGet';
export type AppIntegrationType = { export type AppIntegrationType = {
@@ -79,6 +81,8 @@ export const integrationFieldProperties: {
nzbGet: ['username', 'password'], nzbGet: ['username', 'password'],
qBittorrent: ['username', 'password'], qBittorrent: ['username', 'password'],
transmission: ['username', 'password'], transmission: ['username', 'password'],
jellyfin: ['username', 'password'],
plex: ['apiKey'],
}; };
export type IntegrationFieldDefinitionType = { export type IntegrationFieldDefinitionType = {

View File

@@ -15,6 +15,7 @@ import { Serie, Datum, ResponsiveLine } from '@nivo/line';
import { IconDownload, IconUpload } from '@tabler/icons'; import { IconDownload, IconUpload } from '@tabler/icons';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { AppAvatar } from '../../components/AppAvatar';
import { useConfigContext } from '../../config/provider'; import { useConfigContext } from '../../config/provider';
import { useGetDownloadClientsQueue } from '../../hooks/widgets/download-speed/useGetNetworkSpeed'; import { useGetDownloadClientsQueue } from '../../hooks/widgets/download-speed/useGetNetworkSpeed';
import { useColorTheme } from '../../tools/color'; import { useColorTheme } from '../../tools/color';
@@ -258,17 +259,3 @@ export default function TorrentNetworkTrafficTile({ widget }: TorrentNetworkTraf
</Stack> </Stack>
); );
} }
const AppAvatar = ({ iconUrl }: { iconUrl: string }) => {
const { colors, colorScheme } = useMantineTheme();
return (
<Avatar
src={iconUrl}
bg={colorScheme === 'dark' ? colors.gray[8] : colors.gray[2]}
size="sm"
radius="xl"
p={4}
/>
);
};

View File

@@ -0,0 +1,82 @@
import { Center, createStyles, Stack, Title, Text, Container } from '@mantine/core';
import { IconBrowser, IconUnlink } from '@tabler/icons';
import { useTranslation } from 'next-i18next';
import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
const definition = defineWidget({
id: 'iframe',
icon: IconBrowser,
gridstack: {
maxHeight: 12,
maxWidth: 12,
minHeight: 1,
minWidth: 1,
},
options: {
embedUrl: {
type: 'text',
defaultValue: '',
},
allowFullScreen: {
type: 'switch',
defaultValue: false,
},
},
component: IFrameTile,
});
export type IIFrameWidget = IWidget<(typeof definition)['id'], typeof definition>;
interface IFrameTileProps {
widget: IIFrameWidget;
}
function IFrameTile({ widget }: IFrameTileProps) {
const { t } = useTranslation('modules/iframe');
const { classes } = useStyles();
if (!widget.properties.embedUrl) {
return (
<Center h="100%">
<Stack align="center">
<IconUnlink size={36} strokeWidth={1.2} />
<Stack align="center" spacing={0}>
<Title order={6} align="center">
{t('card.errors.noUrl.title')}
</Title>
<Text align="center" maw={200}>
{t('card.errors.noUrl.text')}
</Text>
</Stack>
</Stack>
</Center>
);
}
return (
<Container h="100%" w="100%" p={0}>
<iframe
className={classes.iframe}
src={widget.properties.embedUrl}
title="widget iframe"
allowFullScreen={widget.properties.allowFullScreen}
>
<Text>Your Browser does not support iframes. Please update your browser.</Text>
</iframe>
</Container>
);
}
const useStyles = createStyles(({ radius }) => ({
iframe: {
borderRadius: radius.sm,
width: '100%',
height: '100%',
border: 'none',
background: 'none',
backgroundColor: 'transparent',
},
}));
export default definition;

View File

@@ -1,11 +1,14 @@
import date from './date/DateTile';
import calendar from './calendar/CalendarTile'; import calendar from './calendar/CalendarTile';
import dashdot from './dashDot/DashDotTile'; import dashdot from './dashDot/DashDotTile';
import usenet from './useNet/UseNetTile'; import date from './date/DateTile';
import weather from './weather/WeatherTile';
import torrent from './torrent/TorrentTile';
import torrentNetworkTraffic from './download-speed/TorrentNetworkTrafficTile'; import torrentNetworkTraffic from './download-speed/TorrentNetworkTrafficTile';
import iframe from './iframe/IFrameTile';
import mediaServer from './media-server/MediaServerTile';
import rss from './rss/RssWidgetTile';
import torrent from './torrent/TorrentTile';
import usenet from './useNet/UseNetTile';
import videoStream from './video/VideoStreamTile'; import videoStream from './video/VideoStreamTile';
import weather from './weather/WeatherTile';
export default { export default {
calendar, calendar,
@@ -15,5 +18,8 @@ export default {
'torrents-status': torrent, 'torrents-status': torrent,
dlspeed: torrentNetworkTraffic, dlspeed: torrentNetworkTraffic,
date, date,
rss,
'video-stream': videoStream, 'video-stream': videoStream,
iframe,
'media-server': mediaServer,
}; };

View File

@@ -0,0 +1,128 @@
import { Card, Divider, Flex, Grid, Group, Text } from '@mantine/core';
import { IconDeviceMobile, IconId } from '@tabler/icons';
import { GenericSessionInfo } from '../../types/api/media-server/session-info';
export const DetailCollapseable = ({ session }: { session: GenericSessionInfo }) => {
let details: { title: string; metrics: { name: string; value: string | undefined }[] }[] = [];
if (session.currentlyPlaying) {
if (session.currentlyPlaying.metadata.video) {
details = [
...details,
{
title: 'Video',
metrics: [
{
name: 'Resolution',
value: `${session.currentlyPlaying.metadata.video.width}x${session.currentlyPlaying.metadata.video.height}`,
},
{
name: 'Framerate',
value: session.currentlyPlaying.metadata.video.videoFrameRate,
},
{
name: 'Codec',
value: session.currentlyPlaying.metadata.video.videoCodec,
},
{
name: 'Bitrate',
value: session.currentlyPlaying.metadata.video.bitrate
? String(session.currentlyPlaying.metadata.video.bitrate)
: undefined,
},
],
},
];
}
if (session.currentlyPlaying.metadata.audio) {
details = [
...details,
{
title: 'Audio',
metrics: [
{
name: 'Audio channels',
value: `${session.currentlyPlaying.metadata.audio.audioChannels}`,
},
{
name: 'Audio codec',
value: session.currentlyPlaying.metadata.audio.audioCodec,
},
],
},
];
}
if (session.currentlyPlaying.metadata.transcoding) {
details = [
...details,
{
title: 'Transcoding',
metrics: [
{
name: 'Resolution',
value: `${session.currentlyPlaying.metadata.transcoding.width}x${session.currentlyPlaying.metadata.transcoding.height}`,
},
{
name: 'Context',
value: session.currentlyPlaying.metadata.transcoding.context,
},
{
name: 'Hardware encoding requested',
value: session.currentlyPlaying.metadata.transcoding.transcodeHwRequested
? 'yes'
: 'no',
},
{
name: 'Source codec',
value:
session.currentlyPlaying.metadata.transcoding.sourceAudioCodec ||
session.currentlyPlaying.metadata.transcoding.sourceVideoCodec
? `${session.currentlyPlaying.metadata.transcoding.sourceVideoCodec} ${session.currentlyPlaying.metadata.transcoding.sourceAudioCodec}`
: undefined,
},
{
name: 'Target codec',
value: `${session.currentlyPlaying.metadata.transcoding.videoCodec} ${session.currentlyPlaying.metadata.transcoding.audioCodec}`,
},
],
},
];
}
}
return (
<Card>
<Flex justify="space-between" mb="xs">
<Group>
<IconId size={16} />
<Text>ID</Text>
</Group>
<Text>{session.id}</Text>
</Flex>
<Flex justify="space-between" mb="md">
<Group>
<IconDeviceMobile size={16} />
<Text>Device</Text>
</Group>
<Text>{session.sessionName}</Text>
</Flex>
{details.length > 0 && <Divider label="Stats for nerds" labelPosition="center" mt="lg" mb="sm" />}
<Grid>
{details.map((detail, index) => (
<Grid.Col xs={12} sm={6} key={index}>
<Text weight="bold">{detail.title}</Text>
{detail.metrics
.filter((x) => x.value !== undefined)
.map((metric, index2) => (
<Group position="apart" key={index2}>
<Text>{metric.name}</Text>
<Text>{metric.value}</Text>
</Group>
))}
</Grid.Col>
))}
</Grid>
</Card>
);
};

View File

@@ -0,0 +1,110 @@
import {
Avatar,
Center,
Group,
Loader,
ScrollArea,
Stack,
Table,
Text,
Title,
} from '@mantine/core';
import { IconAlertTriangle, IconMovie } from '@tabler/icons';
import { useTranslation } from 'next-i18next';
import { AppAvatar } from '../../components/AppAvatar';
import { useConfigContext } from '../../config/provider';
import { useGetMediaServers } from '../../hooks/widgets/media-servers/useGetMediaServers';
import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
import { TableRow } from './TableRow';
const definition = defineWidget({
id: 'media-server',
icon: IconMovie,
options: {},
component: MediaServerTile,
gridstack: {
minWidth: 3,
minHeight: 2,
maxWidth: 12,
maxHeight: 12,
},
});
export type MediaServerWidget = IWidget<(typeof definition)['id'], typeof definition>;
interface MediaServerWidgetProps {
widget: MediaServerWidget;
}
function MediaServerTile({ widget }: MediaServerWidgetProps) {
const { t } = useTranslation('modules/media-server');
const { config } = useConfigContext();
const { data, isError } = useGetMediaServers({
enabled: config !== undefined,
});
if (isError) {
return (
<Center>
<Stack align="center">
<IconAlertTriangle />
<Title order={6}>{t('card.errors.general.title')}</Title>
<Text>{t('card.errors.general.text')}</Text>
</Stack>
</Center>
);
}
if (!data) {
<Center h="100%">
<Loader />
</Center>;
}
return (
<Stack h="100%">
<ScrollArea offsetScrollbars>
<Table highlightOnHover striped>
<thead>
<tr>
<th>{t('card.table.header.session')}</th>
<th>{t('card.table.header.user')}</th>
<th>{t('card.table.header.currentlyPlaying')}</th>
</tr>
</thead>
<tbody>
{data?.servers.map((server) => {
const app = config?.apps.find((x) => x.id === server.appId);
return server.sessions.map((session, index) => (
<TableRow session={session} app={app} key={index} />
));
})}
</tbody>
</Table>
</ScrollArea>
<Group position="right" mt="auto">
<Avatar.Group>
{data?.servers.map((server) => {
const app = config?.apps.find((x) => x.id === server.appId);
if (!app) {
return null;
}
return (
<AppAvatar
iconUrl={app.appearance.iconUrl}
color={server.success === true ? undefined : 'red'}
/>
);
})}
</Avatar.Group>
</Group>
</Stack>
);
}
export default definition;

View File

@@ -0,0 +1,50 @@
import { Flex, Group, Stack, Text } from '@mantine/core';
import {
IconDeviceTv,
IconHeadphones,
IconQuestionMark,
IconVideo,
TablerIcon,
} from '@tabler/icons';
import { useTranslation } from 'next-i18next';
import { GenericSessionInfo } from '../../types/api/media-server/session-info';
export const NowPlayingDisplay = ({ session }: { session: GenericSessionInfo }) => {
const { t } = useTranslation();
if (!session.currentlyPlaying) {
return null;
}
const Icon = (): TablerIcon => {
switch (session.currentlyPlaying?.type) {
case 'audio':
return IconHeadphones;
case 'tv':
return IconDeviceTv;
case 'video':
return IconVideo;
default:
return IconQuestionMark;
}
};
const Test = Icon();
return (
<Flex wrap="nowrap" gap="sm" align="center">
<Test size={16} />
<Stack spacing={0}>
<Text lineClamp={1}>{session.currentlyPlaying.name}</Text>
{session.currentlyPlaying.albumName ? (
<Text lineClamp={1} color="dimmed" size="xs">{session.currentlyPlaying.albumName}</Text>
) : (
session.currentlyPlaying.seasonName && (
<Text lineClamp={1} color="dimmed" size="xs">{session.currentlyPlaying.seasonName}</Text>
)
)}
</Stack>
</Flex>
);
};

View File

@@ -0,0 +1,73 @@
import {
Avatar,
Card,
Collapse,
createStyles,
Flex,
Grid,
Group,
Stack,
Text,
Title,
} from '@mantine/core';
import { useState } from 'react';
import { AppAvatar } from '../../components/AppAvatar';
import { GenericSessionInfo } from '../../types/api/media-server/session-info';
import { AppType } from '../../types/app';
import { DetailCollapseable } from './DetailCollapseable';
import { NowPlayingDisplay } from './NowPlayingDisplay';
interface TableRowProps {
session: GenericSessionInfo;
app: AppType | undefined;
}
export const TableRow = ({ session, app }: TableRowProps) => {
const [collapseOpen, setCollapseOpen] = useState(false);
const hasUserThumb = session.userProfilePicture !== undefined;
const { classes } = useStyles();
return (
<>
<tr className={classes.dataRow} onClick={() => setCollapseOpen(!collapseOpen)}>
<td>
<Flex wrap="nowrap" gap="xs">
{app?.appearance.iconUrl && <AppAvatar iconUrl={app.appearance.iconUrl} />}
<Text lineClamp={1}>{session.sessionName}</Text>
</Flex>
</td>
<td>
<Flex wrap="nowrap" gap="sm">
{hasUserThumb ? (
<Avatar src={session.userProfilePicture} size="sm" />
) : (
<Avatar src={null} alt={session.username} size="sm">
{session.username?.at(0)?.toUpperCase()}
</Avatar>
)}
<Text>{session.username}</Text>
</Flex>
</td>
<td>
<NowPlayingDisplay session={session} />
</td>
</tr>
<tr>
<td className={classes.collapseTableDataCell} colSpan={3}>
<Collapse in={collapseOpen} w="100%">
<DetailCollapseable session={session} />
</Collapse>
</td>
</tr>
</>
);
};
const useStyles = createStyles(() => ({
dataRow: {
cursor: 'pointer',
},
collapseTableDataCell: {
border: 'none !important',
padding: '0 !important',
},
}));

View File

@@ -0,0 +1,236 @@
import {
ActionIcon,
Badge,
Card,
Center,
createStyles,
Flex,
Group,
Image,
Loader,
LoadingOverlay,
MediaQuery,
ScrollArea,
Stack,
Text,
Title,
UnstyledButton,
} from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import {
IconBulldozer,
IconCalendarTime,
IconClock,
IconCopyright,
IconRefresh,
IconRss,
IconSpeakerphone,
} from '@tabler/icons';
import { useTranslation } from 'next-i18next';
import Link from 'next/link';
import { useState } from 'react';
import { useGetRssFeed } from '../../hooks/widgets/rss/useGetRssFeed';
import { sleep } from '../../tools/client/time';
import { defineWidget } from '../helper';
import { IWidget } from '../widgets';
const definition = defineWidget({
id: 'rss',
icon: IconRss,
options: {
rssFeedUrl: {
type: 'text',
defaultValue: '',
},
},
gridstack: {
minWidth: 2,
minHeight: 2,
maxWidth: 12,
maxHeight: 12,
},
component: RssTile,
});
export type IRssWidget = IWidget<(typeof definition)['id'], typeof definition>;
interface RssTileProps {
widget: IRssWidget;
}
function RssTile({ widget }: RssTileProps) {
const { t } = useTranslation('modules/rss');
const { data, isLoading, isFetching, isError, refetch } = useGetRssFeed(
widget.properties.rssFeedUrl
);
const { classes } = useStyles();
const [loadingOverlayVisible, setLoadingOverlayVisible] = useState(false);
const { ref, height } = useElementSize();
if (!data || isLoading) {
return (
<Center>
<Loader />
</Center>
);
}
if (!data.success || isError) {
return (
<Center h="100%">
<Stack align="center">
<IconRss size={40} strokeWidth={1} />
<Title order={6}>{t('card.errors.general.title')}</Title>
<Text align="center">{t('card.errors.general.text')}</Text>
</Stack>
</Center>
);
}
return (
<Stack ref={ref} h="100%">
<LoadingOverlay visible={loadingOverlayVisible} />
<Flex gap="md">
{data.feed.image ? (
<Image
src={data.feed.image.url}
alt={data.feed.image.title}
width="auto"
height={40}
mx="auto"
/>
) : (
<Title order={6}>{data.feed.title}</Title>
)}
<UnstyledButton
onClick={async () => {
setLoadingOverlayVisible(true);
await Promise.all([sleep(1500), refetch()]);
setLoadingOverlayVisible(false);
}}
disabled={isFetching || isLoading}
>
<ActionIcon>
<IconRefresh />
</ActionIcon>
</UnstyledButton>
</Flex>
<ScrollArea className="scroll-area-w100" w="100%">
<Stack w="100%" spacing="xs">
{data.feed.items.map((item: any, index: number) => (
<Card
key={index}
withBorder
component={Link}
href={item.link}
radius="md"
target="_blank"
w="100%"
>
{item.enclosure && (
// eslint-disable-next-line @next/next/no-img-element
<img
className={classes.backgroundImage}
src={item.enclosure.url ?? undefined}
alt="backdrop"
/>
)}
<Flex gap="xs">
<MediaQuery query="(max-width: 1200px)" styles={{ display: 'none' }}>
<Image
src={item.enclosure?.url ?? undefined}
width={140}
height={140}
radius="md"
withPlaceholder
/>
</MediaQuery>
<Flex gap={2} direction="column">
{item.categories && (
<Flex gap="xs" wrap="wrap" h={20} style={{ overflow: 'hidden' }}>
{item.categories.map((category: any, categoryIndex: number) => (
<Badge key={categoryIndex}>{category._}</Badge>
))}
</Flex>
)}
<Text lineClamp={2}>{item.title}</Text>
<Text color="dimmed" size="xs" lineClamp={3}>
{item.content}
</Text>
{item.pubDate && <TimeDisplay date={item.pubDate} />}
</Flex>
</Flex>
</Card>
))}
</Stack>
</ScrollArea>
<Flex wrap="wrap" columnGap="md">
<Group spacing="sm">
<IconCopyright size={14} />
<Text color="dimmed" size="sm">
{data.feed.copyright}
</Text>
</Group>
<Group>
<IconCalendarTime size={14} />
<Text color="dimmed" size="sm">
{data.feed.pubDate}
</Text>
</Group>
<Group>
<IconBulldozer size={14} />
<Text color="dimmed" size="sm">
{data.feed.lastBuildDate}
</Text>
</Group>
{data.feed.feedUrl && (
<Group spacing="sm">
<IconSpeakerphone size={14} />
<Text
color="dimmed"
size="sm"
variant="link"
target="_blank"
component={Link}
href={data.feed.feedUrl}
>
Feed URL
</Text>
</Group>
)}
</Flex>
</Stack>
);
}
const TimeDisplay = ({ date }: { date: string }) => (
<Group mt="auto" spacing="xs">
<IconClock size={14} />
<Text size="xs" color="dimmed">
{date}
</Text>
</Group>
);
const useStyles = createStyles(({ colorScheme }) => ({
backgroundImage: {
position: 'absolute',
width: '100%',
height: '100%',
filter: colorScheme === 'dark' ? 'blur(30px)' : 'blur(15px)',
transform: 'scaleX(-1)',
opacity: colorScheme === 'dark' ? 0.3 : 0.2,
transition: 'ease-in-out 0.2s',
'&:hover': {
opacity: colorScheme === 'dark' ? 0.4 : 0.3,
filter: 'blur(40px) brightness(0.7)',
},
},
}));
export default definition;

340
yarn.lock
View File

@@ -131,7 +131,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.8.0": "@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.8.0":
version: 7.20.2 version: 7.20.2
resolution: "@babel/helper-plugin-utils@npm:7.20.2" resolution: "@babel/helper-plugin-utils@npm:7.20.2"
checksum: f6cae53b7fdb1bf3abd50fa61b10b4470985b400cc794d92635da1e7077bb19729f626adc0741b69403d9b6e411cddddb9c0157a709cc7c4eeb41e663be5d74b checksum: f6cae53b7fdb1bf3abd50fa61b10b4470985b400cc794d92635da1e7077bb19729f626adc0741b69403d9b6e411cddddb9c0157a709cc7c4eeb41e663be5d74b
@@ -263,17 +263,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/plugin-syntax-jsx@npm:^7.17.12":
version: 7.18.6
resolution: "@babel/plugin-syntax-jsx@npm:7.18.6"
dependencies:
"@babel/helper-plugin-utils": ^7.18.6
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: 6d37ea972970195f1ffe1a54745ce2ae456e0ac6145fae9aa1480f297248b262ea6ebb93010eddb86ebfacb94f57c05a1fc5d232b9a67325b09060299d515c67
languageName: node
linkType: hard
"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": "@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3":
version: 7.10.4 version: 7.10.4
resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4"
@@ -507,12 +496,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@emotion/babel-plugin@npm:^11.10.5": "@emotion/babel-plugin@npm:^11.10.6":
version: 11.10.5 version: 11.10.6
resolution: "@emotion/babel-plugin@npm:11.10.5" resolution: "@emotion/babel-plugin@npm:11.10.6"
dependencies: dependencies:
"@babel/helper-module-imports": ^7.16.7 "@babel/helper-module-imports": ^7.16.7
"@babel/plugin-syntax-jsx": ^7.17.12
"@babel/runtime": ^7.18.3 "@babel/runtime": ^7.18.3
"@emotion/hash": ^0.9.0 "@emotion/hash": ^0.9.0
"@emotion/memoize": ^0.8.0 "@emotion/memoize": ^0.8.0
@@ -523,9 +511,7 @@ __metadata:
find-root: ^1.1.0 find-root: ^1.1.0
source-map: ^0.5.7 source-map: ^0.5.7
stylis: 4.1.3 stylis: 4.1.3
peerDependencies: checksum: 3eed138932e8edf2598352e69ad949b9db3051a4d6fcff190dacbac9aa838d7ef708b9f3e6c48660625d9311dae82d73477ae4e7a31139feef5eb001a5528421
"@babel/core": ^7.0.0
checksum: e3353499c76c4422d6e900c0dfab73607056d9da86161a3f27c3459c193c4908050c5d252c68fcde231e13f02a9d8e0dc07d260317ae0e5206841e331cc4caae
languageName: node languageName: node
linkType: hard linkType: hard
@@ -573,11 +559,11 @@ __metadata:
linkType: hard linkType: hard
"@emotion/react@npm:^11.10.5": "@emotion/react@npm:^11.10.5":
version: 11.10.5 version: 11.10.6
resolution: "@emotion/react@npm:11.10.5" resolution: "@emotion/react@npm:11.10.6"
dependencies: dependencies:
"@babel/runtime": ^7.18.3 "@babel/runtime": ^7.18.3
"@emotion/babel-plugin": ^11.10.5 "@emotion/babel-plugin": ^11.10.6
"@emotion/cache": ^11.10.5 "@emotion/cache": ^11.10.5
"@emotion/serialize": ^1.1.1 "@emotion/serialize": ^1.1.1
"@emotion/use-insertion-effect-with-fallbacks": ^1.0.0 "@emotion/use-insertion-effect-with-fallbacks": ^1.0.0
@@ -585,14 +571,11 @@ __metadata:
"@emotion/weak-memoize": ^0.3.0 "@emotion/weak-memoize": ^0.3.0
hoist-non-react-statics: ^3.3.1 hoist-non-react-statics: ^3.3.1
peerDependencies: peerDependencies:
"@babel/core": ^7.0.0
react: ">=16.8.0" react: ">=16.8.0"
peerDependenciesMeta: peerDependenciesMeta:
"@babel/core":
optional: true
"@types/react": "@types/react":
optional: true optional: true
checksum: 32b67b28e9b6d6c53b970072680697f04c2521441050bdeb19a1a7f0164af549b4dad39ff375eda1b6a3cf1cc86ba2c6fa55460ec040e6ebbca3e9ec58353cf7 checksum: 4762042e39126ffaffe76052dc65c9bb0ba6b8893013687ba3cc13ed4dd834c31597f1230684c3c078e90aecc13ab6cd0e3cde0dec8b7761affd2571f4d80019
languageName: node languageName: node
linkType: hard linkType: hard
@@ -774,6 +757,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@jellyfin/sdk@npm:^0.7.0":
version: 0.7.0
resolution: "@jellyfin/sdk@npm:0.7.0"
dependencies:
axios: 0.27.2
compare-versions: 5.0.1
checksum: d7ddae88464e0f2276ec7a793f7e525098a56d96491a450d5e859bf46d74e473a4b4bd5dce2a64ef80484e12e23c14934a4cb20429e89a3c41e52e817e3f31d4
languageName: node
linkType: hard
"@jest/console@npm:^28.1.3": "@jest/console@npm:^28.1.3":
version: 28.1.3 version: 28.1.3
resolution: "@jest/console@npm:28.1.3" resolution: "@jest/console@npm:28.1.3"
@@ -1058,133 +1051,133 @@ __metadata:
linkType: hard linkType: hard
"@mantine/core@npm:^5.9.3": "@mantine/core@npm:^5.9.3":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/core@npm:5.10.3" resolution: "@mantine/core@npm:5.10.4"
dependencies: dependencies:
"@floating-ui/react": ^0.19.1 "@floating-ui/react": ^0.19.1
"@mantine/styles": 5.10.3 "@mantine/styles": 5.10.4
"@mantine/utils": 5.10.3 "@mantine/utils": 5.10.4
"@radix-ui/react-scroll-area": 1.0.2 "@radix-ui/react-scroll-area": 1.0.2
react-textarea-autosize: 8.3.4 react-textarea-autosize: 8.3.4
peerDependencies: peerDependencies:
"@mantine/hooks": 5.10.3 "@mantine/hooks": 5.10.4
react: ">=16.8.0" react: ">=16.8.0"
react-dom: ">=16.8.0" react-dom: ">=16.8.0"
checksum: 460e613e90ecd94a9414bb93a98046e076256fa54ec5cd0b43d20815755e7862826d912a516591d2a2e0356647d72b293564d2ccb9ca1fb8999da0e963d701bd checksum: 98cba720fa9764a7a45b13d167ad2dce1af1b639c202a25cf60c8ca439e386dfddefae262e770df62348af90647ba6cbc095c074a8f0582968059450fbbc2ff0
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/dates@npm:^5.9.3": "@mantine/dates@npm:^5.9.3":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/dates@npm:5.10.3" resolution: "@mantine/dates@npm:5.10.4"
dependencies: dependencies:
"@mantine/utils": 5.10.3 "@mantine/utils": 5.10.4
peerDependencies: peerDependencies:
"@mantine/core": 5.10.3 "@mantine/core": 5.10.4
"@mantine/hooks": 5.10.3 "@mantine/hooks": 5.10.4
dayjs: ">=1.0.0" dayjs: ">=1.0.0"
react: ">=16.8.0" react: ">=16.8.0"
checksum: 80e22b0eb2235a373f0d35979bea7aeea671515962f4d119811145da7086f2258663ceb2d19a6ad23a02b6f08e1c53047778ec37cf559d8e81c9061d9f83a159 checksum: 14a32aa4c16e030266629dfc5171e930d271682de742541b2298d448df11c9fab40e1d8003eabccfd5449a65ef14681993af6426197da4dcad1f509fb9fff932
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/dropzone@npm:^5.9.3": "@mantine/dropzone@npm:^5.9.3":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/dropzone@npm:5.10.3" resolution: "@mantine/dropzone@npm:5.10.4"
dependencies: dependencies:
"@mantine/utils": 5.10.3 "@mantine/utils": 5.10.4
react-dropzone: 14.2.3 react-dropzone: 14.2.3
peerDependencies: peerDependencies:
"@mantine/core": 5.10.3 "@mantine/core": 5.10.4
"@mantine/hooks": 5.10.3 "@mantine/hooks": 5.10.4
react: ">=16.8.0" react: ">=16.8.0"
react-dom: ">=16.8.0" react-dom: ">=16.8.0"
checksum: 550bf8a282220adb3f7564e2ffa7c617480a5ab9d2556c50072b614450ca1d7fc526e8755a635936c4cb76f9a090f91686f690b7e7704320972dd282e00ab1d6 checksum: 0b837b5bc7c982ad9832142c880c1b0f2827de5c2163b76895cac2dbe6204f7c67e466468a3fb00f72482bad957c4b1209820025c4dfc2c25650734df79e208b
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/form@npm:^5.9.3": "@mantine/form@npm:^5.9.3":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/form@npm:5.10.3" resolution: "@mantine/form@npm:5.10.4"
dependencies: dependencies:
fast-deep-equal: ^3.1.3 fast-deep-equal: ^3.1.3
klona: ^2.0.5 klona: ^2.0.5
peerDependencies: peerDependencies:
react: ">=16.8.0" react: ">=16.8.0"
checksum: 88bcf7b37d19e0648500e9da7111d961a3b130ef1164f4c4fc15f606cececdddad614887ff6659e0a0ce3896a4e52e62c2f72758f857e932e903cf412a69ca54 checksum: 00ebc0011981f8dae5e96e833f7e0102067a7bc38b1f6208b377f029fdc8bb703a492f40a688eb015dea3147c3b6e5a7cd26c584b93cc8d2567f0dcb10319759
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/hooks@npm:^5.9.3": "@mantine/hooks@npm:^5.9.3":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/hooks@npm:5.10.3" resolution: "@mantine/hooks@npm:5.10.4"
peerDependencies: peerDependencies:
react: ">=16.8.0" react: ">=16.8.0"
checksum: 629554658e910dec1c14ecbdaf8e48c1ce2af022044269e73ff069d719a4f9b68428bb75ade5108ace40bd29253658e663c594a237e080226de28705c10c871e checksum: 41ededb62ea9311303e4b8d577ec21c12ddb339e60e70c3a1f561cb1b2c66fb6e6f29a7a23a89322748779cc06797de749203afd546f2b13180781c0e2873fa6
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/modals@npm:^5.9.3": "@mantine/modals@npm:^5.9.3":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/modals@npm:5.10.3" resolution: "@mantine/modals@npm:5.10.4"
dependencies: dependencies:
"@mantine/utils": 5.10.3 "@mantine/utils": 5.10.4
peerDependencies: peerDependencies:
"@mantine/core": 5.10.3 "@mantine/core": 5.10.4
"@mantine/hooks": 5.10.3 "@mantine/hooks": 5.10.4
react: ">=16.8.0" react: ">=16.8.0"
react-dom: ">=16.8.0" react-dom: ">=16.8.0"
checksum: b013eb3c68ee1c0f163c1bd4b786c59e4a7f77d596f175ef2a88b1922153c24a154f12a36b65138af778cdd47944869056df5beb921e5d07801f5246b19b8da9 checksum: 82fce48fffbbce11526212b994e2763f0b36a276a86385f5b9ac1b5f1226f39f3f12aa171fe8b9181b755f95dbb869ef88f339e620bfd0ec28b7b0017f6b7fa7
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/next@npm:^5.9.3": "@mantine/next@npm:^5.9.3":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/next@npm:5.10.3" resolution: "@mantine/next@npm:5.10.4"
dependencies: dependencies:
"@mantine/ssr": 5.10.3 "@mantine/ssr": 5.10.4
"@mantine/styles": 5.10.3 "@mantine/styles": 5.10.4
peerDependencies: peerDependencies:
next: "*" next: "*"
react: ">=16.8.0" react: ">=16.8.0"
react-dom: ">=16.8.0" react-dom: ">=16.8.0"
checksum: 78ee3e7f7565318f40f367c8d53edfb164ae10c4fcc788b8792b27ae3f10fad81be8dbfaecd171fd66c43eafc50cc30ea5ab7f3da5421642b3a8c96d5a03d618 checksum: 344590d09cfef4194187f7ea430082d9f22379e264b04fa3a9261a137abf9b1e4681eb7c610b678822b2ad3001f5611e201a73a4612ee8b13d93460d68a28b0c
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/notifications@npm:^5.9.3": "@mantine/notifications@npm:^5.9.3":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/notifications@npm:5.10.3" resolution: "@mantine/notifications@npm:5.10.4"
dependencies: dependencies:
"@mantine/utils": 5.10.3 "@mantine/utils": 5.10.4
react-transition-group: 4.4.2 react-transition-group: 4.4.2
peerDependencies: peerDependencies:
"@mantine/core": 5.10.3 "@mantine/core": 5.10.4
"@mantine/hooks": 5.10.3 "@mantine/hooks": 5.10.4
react: ">=16.8.0" react: ">=16.8.0"
react-dom: ">=16.8.0" react-dom: ">=16.8.0"
checksum: e4b735725fc7c0558cf0b084f677d432d6bd49220621ff20e32371621ba454a9d8b8eba0a36db7e99a0951cc174cb51b7ec91a9e127cba13330c93f3bcaecb10 checksum: da439698331f09dd1f3efaa470df1f9717f7309c2a61b0bfaa14bd832185fbd0711fbaffe867b0d6978108db30d059909b59c2b7c0ed83d1bd55bc8b7a6d3e7f
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/ssr@npm:5.10.3": "@mantine/ssr@npm:5.10.4":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/ssr@npm:5.10.3" resolution: "@mantine/ssr@npm:5.10.4"
dependencies: dependencies:
"@mantine/styles": 5.10.3 "@mantine/styles": 5.10.4
html-react-parser: 1.4.12 html-react-parser: 1.4.12
peerDependencies: peerDependencies:
"@emotion/react": ">=11.9.0" "@emotion/react": ">=11.9.0"
"@emotion/server": ">=11.4.0" "@emotion/server": ">=11.4.0"
react: ">=16.8.0" react: ">=16.8.0"
react-dom: ">=16.8.0" react-dom: ">=16.8.0"
checksum: 8a6f0140f8fc853e3be8b0ec78908f71378ab76f36efe58ea2b1818139db60658bd12d3b1a56b9e61218262e85e2fbf9488393c3f0a64cb2d24125a27230397f checksum: 74e5c56b85ea731597aba876941086306abb5ae24faa86d37a5210cfd743aa164e97b29e547563f810ef92b51b3c90bd02ce1a39fbee84cbec2abd2e6ad45ca7
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/styles@npm:5.10.3": "@mantine/styles@npm:5.10.4":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/styles@npm:5.10.3" resolution: "@mantine/styles@npm:5.10.4"
dependencies: dependencies:
clsx: 1.1.1 clsx: 1.1.1
csstype: 3.0.9 csstype: 3.0.9
@@ -1192,16 +1185,16 @@ __metadata:
"@emotion/react": ">=11.9.0" "@emotion/react": ">=11.9.0"
react: ">=16.8.0" react: ">=16.8.0"
react-dom: ">=16.8.0" react-dom: ">=16.8.0"
checksum: 60fcfd5ccbfd7e1106a24a13c3f705ac4b995214ae038c2d8e7bf5e795cb7c1f5da9ee5e336caedf2a537e77272a05eb315e76df3edb4a466ec158ae6aeaf8b5 checksum: 54af835dca68a457be758570c82eab2d602da19a24da599b2f2c02f451be136a400b2f8efbbdc7d1a677188b57515d3ace23df0b8aa8e37c4ddf3a2fdbce1630
languageName: node languageName: node
linkType: hard linkType: hard
"@mantine/utils@npm:5.10.3": "@mantine/utils@npm:5.10.4":
version: 5.10.3 version: 5.10.4
resolution: "@mantine/utils@npm:5.10.3" resolution: "@mantine/utils@npm:5.10.4"
peerDependencies: peerDependencies:
react: ">=16.8.0" react: ">=16.8.0"
checksum: 063237e49f3c52e3bbcd99e7a8383ac881718d9e1e039d32249231370a463a19e070cea7f90c3c0fea2933c3c162f5509a0f2add015db2aeb90f7945cd954a12 checksum: 96e2602f8500c29b5979d4fe0b3456c8de911ff1bd2ef216d960b23a5370ff6828871aa859538a4004ad095fb63d7e0e76cdfb365bdb930f70f8076d730302c1
languageName: node languageName: node
linkType: hard linkType: hard
@@ -2080,9 +2073,9 @@ __metadata:
linkType: hard linkType: hard
"@types/node@npm:*, @types/node@npm:^18.11.18": "@types/node@npm:*, @types/node@npm:^18.11.18":
version: 18.13.0 version: 18.14.0
resolution: "@types/node@npm:18.13.0" resolution: "@types/node@npm:18.14.0"
checksum: 4ea10f8802848b01672bce938f678b6774ca2cee0c9774f12275ab064ae07818419c3e2e41d6257ce7ba846d1ea26c63214aa1dfa4166fa3746291752b8c6416 checksum: d83fcf5e4ed544755dd9028f5cbb6b9d46235043159111bb2ad62223729aee581c0144a9f6df8ba73d74011db9ed4ebd7af2fd5e0996714e3beb508a5da8ac5c
languageName: node languageName: node
linkType: hard linkType: hard
@@ -2674,7 +2667,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"axios@npm:^0.27.2": "axios@npm:0.27.2, axios@npm:^0.27.2":
version: 0.27.2 version: 0.27.2
resolution: "axios@npm:0.27.2" resolution: "axios@npm:0.27.2"
dependencies: dependencies:
@@ -3005,9 +2998,9 @@ __metadata:
linkType: hard linkType: hard
"caniuse-lite@npm:^1.0.30001406, caniuse-lite@npm:^1.0.30001449": "caniuse-lite@npm:^1.0.30001406, caniuse-lite@npm:^1.0.30001449":
version: 1.0.30001451 version: 1.0.30001456
resolution: "caniuse-lite@npm:1.0.30001451" resolution: "caniuse-lite@npm:1.0.30001456"
checksum: 48a06a7881093bb4d8a08ed5428f24a1cbdaa544b0a6f0c3614287d4f34b6c853e79a0f608a5bd901c27995f5e951825606fba11e7930251cc422bd61de9d849 checksum: c2cc479962149abd09a25b64699ee7484d9c433db2bad0a489f7b51b09a463c991f6efd7b8e201bc1a1ccf3294263f88503a3adf0a57db9046939ee7e58b76a6
languageName: node languageName: node
linkType: hard linkType: hard
@@ -3198,6 +3191,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"compare-versions@npm:5.0.1":
version: 5.0.1
resolution: "compare-versions@npm:5.0.1"
checksum: 302a4e46224b47b9280cf894c6c87d8df912671fa391dcdbf0e63438d9b0a69fe20dd747fb439e8d54c43af016ff4eaaf0a4c9d8e7ca358bcd12dadf4ad2935e
languageName: node
linkType: hard
"concat-map@npm:0.0.1": "concat-map@npm:0.0.1":
version: 0.0.1 version: 0.0.1
resolution: "concat-map@npm:0.0.1" resolution: "concat-map@npm:0.0.1"
@@ -3713,9 +3713,9 @@ __metadata:
linkType: hard linkType: hard
"electron-to-chromium@npm:^1.4.284": "electron-to-chromium@npm:^1.4.284":
version: 1.4.295 version: 1.4.302
resolution: "electron-to-chromium@npm:1.4.295" resolution: "electron-to-chromium@npm:1.4.302"
checksum: 66fff1341d3c94c2ccd1f2a39cffdb92118304f4b949d3194427e7022d6a6bd8c482b5c4afd9dce210117ba20cac01c1a1466089f5a862fe9c563113b86ff829 checksum: aa764494f9a5b6916ba9f311c0204b2c73449addba18cc55d43e84e8c4465732af9cd6560a8efeb32f3c5a928299030e41352e5b3a081e9e56b086d5be618f45
languageName: node languageName: node
linkType: hard linkType: hard
@@ -3758,7 +3758,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"entities@npm:^2.0.0": "entities@npm:^2.0.0, entities@npm:^2.0.3":
version: 2.2.0 version: 2.2.0
resolution: "entities@npm:2.2.0" resolution: "entities@npm:2.2.0"
checksum: 19010dacaf0912c895ea262b4f6128574f9ccf8d4b3b65c7e8334ad0079b3706376360e28d8843ff50a78aabcb8f08f0a32dbfacdc77e47ed77ca08b713669b3 checksum: 19010dacaf0912c895ea262b4f6128574f9ccf8d4b3b65c7e8334ad0079b3706376360e28d8843ff50a78aabcb8f08f0a32dbfacdc77e47ed77ca08b713669b3
@@ -4101,13 +4101,13 @@ __metadata:
linkType: hard linkType: hard
"eslint-plugin-testing-library@npm:^5.5.1": "eslint-plugin-testing-library@npm:^5.5.1":
version: 5.10.1 version: 5.10.2
resolution: "eslint-plugin-testing-library@npm:5.10.1" resolution: "eslint-plugin-testing-library@npm:5.10.2"
dependencies: dependencies:
"@typescript-eslint/utils": ^5.43.0 "@typescript-eslint/utils": ^5.43.0
peerDependencies: peerDependencies:
eslint: ^7.5.0 || ^8.0.0 eslint: ^7.5.0 || ^8.0.0
checksum: fbc24ce8cbd03bee283ae520f023bd52f10d21fb25bd66c5a09a0e0ad17997bae559450438ce0b4fcc38b4e14b6802f18482c41d2bc72bf1a8e1a12a91766572 checksum: 3b2b330e62f4a6dc438050006f0d0c97605f6861828b153271dc6d2fafb1e60f4e86fbaa8166c7afd452e3b6cad39413738fd4c8e2eb2def1915c678154676da
languageName: node languageName: node
linkType: hard linkType: hard
@@ -4249,11 +4249,11 @@ __metadata:
linkType: hard linkType: hard
"esquery@npm:^1.4.0": "esquery@npm:^1.4.0":
version: 1.4.0 version: 1.4.2
resolution: "esquery@npm:1.4.0" resolution: "esquery@npm:1.4.2"
dependencies: dependencies:
estraverse: ^5.1.0 estraverse: ^5.1.0
checksum: a0807e17abd7fbe5fbd4fab673038d6d8a50675cdae6b04fbaa520c34581be0c5fa24582990e8acd8854f671dd291c78bb2efb9e0ed5b62f33bac4f9cf820210 checksum: 2f4ad89c5aafaca61cc2c15e256190f0d6deb4791cae6552d3cb4b1eb8867958cdf27a56aaa3272ff17435e3eaa19ee0d4129fac336ca6373d7354d7b5da7966
languageName: node languageName: node
linkType: hard linkType: hard
@@ -4512,8 +4512,8 @@ __metadata:
linkType: hard linkType: hard
"framer-motion@npm:^9.0.2": "framer-motion@npm:^9.0.2":
version: 9.0.2 version: 9.0.4
resolution: "framer-motion@npm:9.0.2" resolution: "framer-motion@npm:9.0.4"
dependencies: dependencies:
"@emotion/is-prop-valid": ^0.8.2 "@emotion/is-prop-valid": ^0.8.2
"@motionone/dom": ^10.15.3 "@motionone/dom": ^10.15.3
@@ -4525,7 +4525,7 @@ __metadata:
dependenciesMeta: dependenciesMeta:
"@emotion/is-prop-valid": "@emotion/is-prop-valid":
optional: true optional: true
checksum: 5694d3a49acb9f753f0eed4a946c2b33efc237e0863b882cc4bed9a6022447ce2c9024134b5e9fd7b6f73602d9af046fa633c0f7235e8cbb24702b403299b55e checksum: 9ef23d81c78785dd2f7f278bad133d96c22fd3a9dcc310521ea220873b0a923ca927dc2d3e8000e221edfcc5bfd6272f752435472e3a8f9b1ef476202ede1ce2
languageName: node languageName: node
linkType: hard linkType: hard
@@ -4944,6 +4944,7 @@ __metadata:
"@ctrl/transmission": ^4.1.1 "@ctrl/transmission": ^4.1.1
"@emotion/react": ^11.10.5 "@emotion/react": ^11.10.5
"@emotion/server": ^11.10.0 "@emotion/server": ^11.10.0
"@jellyfin/sdk": ^0.7.0
"@mantine/core": ^5.9.3 "@mantine/core": ^5.9.3
"@mantine/dates": ^5.9.3 "@mantine/dates": ^5.9.3
"@mantine/dropzone": ^5.9.3 "@mantine/dropzone": ^5.9.3
@@ -4985,6 +4986,7 @@ __metadata:
eslint-plugin-unused-imports: ^2.0.0 eslint-plugin-unused-imports: ^2.0.0
fily-publish-gridstack: ^0.0.13 fily-publish-gridstack: ^0.0.13
framer-motion: ^9.0.2 framer-motion: ^9.0.2
html-entities: ^2.3.3
i18next: ^21.9.1 i18next: ^21.9.1
jest: ^28.1.3 jest: ^28.1.3
js-file-download: ^0.4.12 js-file-download: ^0.4.12
@@ -4996,12 +4998,14 @@ __metadata:
react: ^18.2.0 react: ^18.2.0
react-dom: ^18.2.0 react-dom: ^18.2.0
react-simple-code-editor: ^0.13.1 react-simple-code-editor: ^0.13.1
rss-parser: ^3.12.0
sabnzbd-api: ^1.5.0 sabnzbd-api: ^1.5.0
sass: ^1.56.1 sass: ^1.56.1
turbo: ^1.7.4 turbo: ^1.7.4
typescript: ^4.7.4 typescript: ^4.7.4
uuid: ^8.3.2 uuid: ^8.3.2
video.js: ^8.0.3 video.js: ^8.0.3
xml-js: ^1.6.11
yarn: ^1.22.19 yarn: ^1.22.19
zustand: ^4.1.4 zustand: ^4.1.4
languageName: unknown languageName: unknown
@@ -5017,6 +5021,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"html-entities@npm:^2.3.3":
version: 2.3.3
resolution: "html-entities@npm:2.3.3"
checksum: 92521501da8aa5f66fee27f0f022d6e9ceae62667dae93aa6a2f636afa71ad530b7fb24a18d4d6c124c9885970cac5f8a52dbf1731741161002816ae43f98196
languageName: node
linkType: hard
"html-escaper@npm:^2.0.0": "html-escaper@npm:^2.0.0":
version: 2.0.2 version: 2.0.2
resolution: "html-escaper@npm:2.0.2" resolution: "html-escaper@npm:2.0.2"
@@ -6318,9 +6329,9 @@ __metadata:
linkType: hard linkType: hard
"lru-cache@npm:^7.7.1": "lru-cache@npm:^7.7.1":
version: 7.14.1 version: 7.16.1
resolution: "lru-cache@npm:7.14.1" resolution: "lru-cache@npm:7.16.1"
checksum: d72c6713c6a6d86836a7a6523b3f1ac6764768cca47ec99341c3e76db06aacd4764620e5e2cda719a36848785a52a70e531822dc2b33fb071fa709683746c104 checksum: 64618e3ed4fd1203afedd9bbf5247921b1419f8e3100f20e58e5f04e741f8287bd7d04fefaad332411bb53b3a73445714b235de750cf5d310cba1fa23bd82795
languageName: node languageName: node
linkType: hard linkType: hard
@@ -7656,6 +7667,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"rss-parser@npm:^3.12.0":
version: 3.12.0
resolution: "rss-parser@npm:3.12.0"
dependencies:
entities: ^2.0.3
xml2js: ^0.4.19
checksum: aa0f0eb2e3a5c70677a1c7cb6c2e96420f12c8963a8bed922ec2ff1bb9dbbb725fc5783be31ca8140154c3d5589ccd31580ced7d32ebd0dda7572f78ce242a41
languageName: node
linkType: hard
"run-parallel@npm:^1.1.9": "run-parallel@npm:^1.1.9":
version: 1.2.0 version: 1.2.0
resolution: "run-parallel@npm:1.2.0" resolution: "run-parallel@npm:1.2.0"
@@ -7726,15 +7747,22 @@ __metadata:
linkType: hard linkType: hard
"sass@npm:^1.56.1": "sass@npm:^1.56.1":
version: 1.58.1 version: 1.58.2
resolution: "sass@npm:1.58.1" resolution: "sass@npm:1.58.2"
dependencies: dependencies:
chokidar: ">=3.0.0 <4.0.0" chokidar: ">=3.0.0 <4.0.0"
immutable: ^4.0.0 immutable: ^4.0.0
source-map-js: ">=0.6.2 <2.0.0" source-map-js: ">=0.6.2 <2.0.0"
bin: bin:
sass: sass.js sass: sass.js
checksum: ff079887d906b5c0dde99084d14ac36336d238c0c07935ff6381bad68f05de212c1ff12657ac1e8a0533523cd7a393126facdc2508d758e7d5700344a0e6ea51 checksum: e0febe4d274af7b9490b9207ff7f05762d60df6b2ad307f7a823432cb4e1604eced6784ae635a6b80e4a6177c047f5a9623c53a15aaec3b9bf981ea86e8937a9
languageName: node
linkType: hard
"sax@npm:>=0.6.0, sax@npm:^1.2.4":
version: 1.2.4
resolution: "sax@npm:1.2.4"
checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe
languageName: node languageName: node
linkType: hard linkType: hard
@@ -8166,9 +8194,9 @@ __metadata:
linkType: hard linkType: hard
"tabbable@npm:^6.0.1": "tabbable@npm:^6.0.1":
version: 6.0.1 version: 6.1.1
resolution: "tabbable@npm:6.0.1" resolution: "tabbable@npm:6.1.1"
checksum: 65e378ad69a97416f2fdce34ade11b8ff68b33d9b2d978920a9d285c77e1bb88cb35113a8f00af8c4f0163d788d451a48840a216fa918d6a3f0c554951deb984 checksum: 348639497262241ce8e0ccb0664ea582a386183107299ee8f27cf7b56bc84f36e09eaf667d3cb4201e789634012a91f7129bcbd49760abe874fbace35b4cf429
languageName: node languageName: node
linkType: hard linkType: hard
@@ -8335,58 +8363,58 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-darwin-64@npm:1.7.4": "turbo-darwin-64@npm:1.8.0":
version: 1.7.4 version: 1.8.0
resolution: "turbo-darwin-64@npm:1.7.4" resolution: "turbo-darwin-64@npm:1.8.0"
conditions: os=darwin & cpu=x64 conditions: os=darwin & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-darwin-arm64@npm:1.7.4": "turbo-darwin-arm64@npm:1.8.0":
version: 1.7.4 version: 1.8.0
resolution: "turbo-darwin-arm64@npm:1.7.4" resolution: "turbo-darwin-arm64@npm:1.8.0"
conditions: os=darwin & cpu=arm64 conditions: os=darwin & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-linux-64@npm:1.7.4": "turbo-linux-64@npm:1.8.0":
version: 1.7.4 version: 1.8.0
resolution: "turbo-linux-64@npm:1.7.4" resolution: "turbo-linux-64@npm:1.8.0"
conditions: os=linux & cpu=x64 conditions: os=linux & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-linux-arm64@npm:1.7.4": "turbo-linux-arm64@npm:1.8.0":
version: 1.7.4 version: 1.8.0
resolution: "turbo-linux-arm64@npm:1.7.4" resolution: "turbo-linux-arm64@npm:1.8.0"
conditions: os=linux & cpu=arm64 conditions: os=linux & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-windows-64@npm:1.7.4": "turbo-windows-64@npm:1.8.0":
version: 1.7.4 version: 1.8.0
resolution: "turbo-windows-64@npm:1.7.4" resolution: "turbo-windows-64@npm:1.8.0"
conditions: os=win32 & cpu=x64 conditions: os=win32 & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-windows-arm64@npm:1.7.4": "turbo-windows-arm64@npm:1.8.0":
version: 1.7.4 version: 1.8.0
resolution: "turbo-windows-arm64@npm:1.7.4" resolution: "turbo-windows-arm64@npm:1.8.0"
conditions: os=win32 & cpu=arm64 conditions: os=win32 & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo@npm:^1.7.4": "turbo@npm:^1.7.4":
version: 1.7.4 version: 1.8.0
resolution: "turbo@npm:1.7.4" resolution: "turbo@npm:1.8.0"
dependencies: dependencies:
turbo-darwin-64: 1.7.4 turbo-darwin-64: 1.8.0
turbo-darwin-arm64: 1.7.4 turbo-darwin-arm64: 1.8.0
turbo-linux-64: 1.7.4 turbo-linux-64: 1.8.0
turbo-linux-arm64: 1.7.4 turbo-linux-arm64: 1.8.0
turbo-windows-64: 1.7.4 turbo-windows-64: 1.8.0
turbo-windows-arm64: 1.7.4 turbo-windows-arm64: 1.8.0
dependenciesMeta: dependenciesMeta:
turbo-darwin-64: turbo-darwin-64:
optional: true optional: true
@@ -8402,7 +8430,7 @@ __metadata:
optional: true optional: true
bin: bin:
turbo: bin/turbo turbo: bin/turbo
checksum: c4387cfee36c57dd1490e9b2452888d8450faa390aec5bc148389b426bc079d03044891d61ad84a6a3a7454e66dfbe9e9cfba05f636b36fef7ece7af275ef9ce checksum: 7f97068d7f9a155e088d3575b1f9922e68fa3015aae0c92625238d44b4e6c275bec2a281907702dedb402fca29a6cd4690499e916cb334d7c24c98099bc3d8b0
languageName: node languageName: node
linkType: hard linkType: hard
@@ -8612,13 +8640,13 @@ __metadata:
linkType: hard linkType: hard
"v8-to-istanbul@npm:^9.0.1": "v8-to-istanbul@npm:^9.0.1":
version: 9.0.1 version: 9.1.0
resolution: "v8-to-istanbul@npm:9.0.1" resolution: "v8-to-istanbul@npm:9.1.0"
dependencies: dependencies:
"@jridgewell/trace-mapping": ^0.3.12 "@jridgewell/trace-mapping": ^0.3.12
"@types/istanbul-lib-coverage": ^2.0.1 "@types/istanbul-lib-coverage": ^2.0.1
convert-source-map: ^1.6.0 convert-source-map: ^1.6.0
checksum: a49c34bf0a3af0c11041a3952a2600913904a983bd1bc87148b5c033bc5c1d02d5a13620fcdbfa2c60bc582a2e2970185780f0c844b4c3a220abf405f8af6311 checksum: 2069d59ee46cf8d83b4adfd8a5c1a90834caffa9f675e4360f1157ffc8578ef0f763c8f32d128334424159bb6b01f3876acd39cd13297b2769405a9da241f8d1
languageName: node languageName: node
linkType: hard linkType: hard
@@ -8822,6 +8850,34 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"xml-js@npm:^1.6.11":
version: 1.6.11
resolution: "xml-js@npm:1.6.11"
dependencies:
sax: ^1.2.4
bin:
xml-js: ./bin/cli.js
checksum: 24a55479919413687105fc2d8ab05e613ebedb1c1bc12258a108e07cff5ef793779297db854800a4edf0281303ebd1f177bc4a588442f5344e62b3dddda26c2b
languageName: node
linkType: hard
"xml2js@npm:^0.4.19":
version: 0.4.23
resolution: "xml2js@npm:0.4.23"
dependencies:
sax: ">=0.6.0"
xmlbuilder: ~11.0.0
checksum: ca0cf2dfbf6deeaae878a891c8fbc0db6fd04398087084edf143cdc83d0509ad0fe199b890f62f39c4415cf60268a27a6aed0d343f0658f8779bd7add690fa98
languageName: node
linkType: hard
"xmlbuilder@npm:~11.0.0":
version: 11.0.1
resolution: "xmlbuilder@npm:11.0.1"
checksum: 7152695e16f1a9976658215abab27e55d08b1b97bca901d58b048d2b6e106b5af31efccbdecf9b07af37c8377d8e7e821b494af10b3a68b0ff4ae60331b415b0
languageName: node
linkType: hard
"xtend@npm:^4.0.0": "xtend@npm:^4.0.0":
version: 4.0.2 version: 4.0.2
resolution: "xtend@npm:4.0.2" resolution: "xtend@npm:4.0.2"
@@ -8874,8 +8930,8 @@ __metadata:
linkType: hard linkType: hard
"yargs@npm:^17.3.1": "yargs@npm:^17.3.1":
version: 17.6.2 version: 17.7.0
resolution: "yargs@npm:17.6.2" resolution: "yargs@npm:17.7.0"
dependencies: dependencies:
cliui: ^8.0.1 cliui: ^8.0.1
escalade: ^3.1.1 escalade: ^3.1.1
@@ -8884,7 +8940,7 @@ __metadata:
string-width: ^4.2.3 string-width: ^4.2.3
y18n: ^5.0.5 y18n: ^5.0.5
yargs-parser: ^21.1.1 yargs-parser: ^21.1.1
checksum: 47da1b0d854fa16d45a3ded57b716b013b2179022352a5f7467409da5a04a1eef5b3b3d97a2dfc13e8bbe5f2ffc0afe3bc6a4a72f8254e60f5a4bd7947138643 checksum: e7d5f5b60e63b04ded7c27c3d4b194565565cac3ea19fffcdbb183bed973a83106822a04dda28ebba4811ce92949a9d9858d3935186ff8f343548bf98aab2120
languageName: node languageName: node
linkType: hard linkType: hard