fix: address PR review feedback on mock services
- Restore `await Promise.resolve()` in all async mock methods to satisfy require-await lint rule (download, calendar, media-releases, media-server, network-controller-summary) - Fix Docker mock container ports to include required `IP` field and use proper `Port` type from dockerode - Add app widgets for bookmarked apps to the demo dashboard - Reposition bookmarks widget (6 wide, after Proxmox) and allow edit mode with discard-on-exit in demo mode
This commit is contained in:
9 files changed
+413
-83
No files matched your search
@@ -61,7 +61,11 @@ export function TRPCReactProvider(props: PropsWithChildren) {
|
||||
},
|
||||
mutations: {
|
||||
onError(error) {
|
||||
if (error instanceof TRPCClientError && error.data?.code === "FORBIDDEN" && error.message === "Mutations are disabled in demo mode") {
|
||||
if (
|
||||
error instanceof TRPCClientError &&
|
||||
error.data?.code === "FORBIDDEN" &&
|
||||
error.message === "Mutations are disabled in demo mode"
|
||||
) {
|
||||
showWarningNotification({
|
||||
title: "Demo mode",
|
||||
message: "This action is disabled in demo mode.",
|
||||
|
||||
@@ -23,7 +23,6 @@ import { SpotlightProvider } from "@homarr/spotlight";
|
||||
import type { SupportedLanguage } from "@homarr/translation";
|
||||
import { isLocaleRTL, isLocaleSupported } from "@homarr/translation";
|
||||
|
||||
|
||||
import { Analytics } from "~/components/layout/analytics";
|
||||
import { CrowdinLiveTranslation } from "~/components/layout/crowdin-live-translation";
|
||||
|
||||
|
||||
@@ -226,18 +226,66 @@ interface DemoWidget {
|
||||
}
|
||||
|
||||
const demoApps = [
|
||||
{ name: "Sonarr", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/sonarr.svg", href: "https://sonarr.tv" },
|
||||
{ name: "Radarr", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/radarr.svg", href: "https://radarr.video" },
|
||||
{ name: "Plex", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/plex.svg", href: "https://plex.tv" },
|
||||
{ name: "Jellyfin", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/jellyfin.svg", href: "https://jellyfin.org" },
|
||||
{ name: "Proxmox", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/proxmox.svg", href: "https://proxmox.com" },
|
||||
{ name: "Pi-hole", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pi-hole.svg", href: "https://pi-hole.net" },
|
||||
{ name: "Grafana", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg", href: "https://grafana.com" },
|
||||
{ name: "Portainer", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/portainer.svg", href: "https://portainer.io" },
|
||||
{ name: "Home Assistant", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/home-assistant.svg", href: "https://home-assistant.io" },
|
||||
{ name: "Nextcloud", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg", href: "https://nextcloud.com" },
|
||||
{ name: "qBittorrent", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/qbittorrent.svg", href: "https://qbittorrent.org" },
|
||||
{ name: "Overseerr", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/overseerr.svg", href: "https://overseerr.dev" },
|
||||
{
|
||||
name: "Sonarr",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/sonarr.svg",
|
||||
href: "https://sonarr.tv",
|
||||
},
|
||||
{
|
||||
name: "Radarr",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/radarr.svg",
|
||||
href: "https://radarr.video",
|
||||
},
|
||||
{
|
||||
name: "Plex",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/plex.svg",
|
||||
href: "https://plex.tv",
|
||||
},
|
||||
{
|
||||
name: "Jellyfin",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/jellyfin.svg",
|
||||
href: "https://jellyfin.org",
|
||||
},
|
||||
{
|
||||
name: "Proxmox",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/proxmox.svg",
|
||||
href: "https://proxmox.com",
|
||||
},
|
||||
{
|
||||
name: "Pi-hole",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pi-hole.svg",
|
||||
href: "https://pi-hole.net",
|
||||
},
|
||||
{
|
||||
name: "Grafana",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg",
|
||||
href: "https://grafana.com",
|
||||
},
|
||||
{
|
||||
name: "Portainer",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/portainer.svg",
|
||||
href: "https://portainer.io",
|
||||
},
|
||||
{
|
||||
name: "Home Assistant",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/home-assistant.svg",
|
||||
href: "https://home-assistant.io",
|
||||
},
|
||||
{
|
||||
name: "Nextcloud",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg",
|
||||
href: "https://nextcloud.com",
|
||||
},
|
||||
{
|
||||
name: "qBittorrent",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/qbittorrent.svg",
|
||||
href: "https://qbittorrent.org",
|
||||
},
|
||||
{
|
||||
name: "Overseerr",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/overseerr.svg",
|
||||
href: "https://overseerr.dev",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const buildDemoWidgets = (appIds: string[]): DemoWidget[] => [
|
||||
@@ -248,7 +296,10 @@ const buildDemoWidgets = (appIds: string[]): DemoWidget[] => [
|
||||
// Row 2: healthMonitoring + bookmarks = 12
|
||||
{ kind: "healthMonitoring", width: 6, height: 3, needsIntegration: true },
|
||||
{
|
||||
kind: "bookmarks", width: 6, height: 3, needsIntegration: false,
|
||||
kind: "bookmarks",
|
||||
width: 6,
|
||||
height: 3,
|
||||
needsIntegration: false,
|
||||
options: { title: "Homelab", items: appIds, layout: "grid", openNewTab: true },
|
||||
},
|
||||
// Row 3: mediaServer + mediaTranscoding + dnsHoleSummary = 12
|
||||
@@ -267,22 +318,27 @@ const buildDemoWidgets = (appIds: string[]): DemoWidget[] => [
|
||||
{ kind: "networkControllerSummary", width: 3, height: 2, needsIntegration: true },
|
||||
{ kind: "networkControllerStatus", width: 3, height: 2, needsIntegration: true },
|
||||
{
|
||||
kind: "rssFeed", width: 6, height: 2, needsIntegration: false,
|
||||
kind: "rssFeed",
|
||||
width: 6,
|
||||
height: 2,
|
||||
needsIntegration: false,
|
||||
options: {
|
||||
feedUrls: [
|
||||
"https://selfh.st/rss/",
|
||||
"https://hnrss.org/newest?q=self-hosted",
|
||||
],
|
||||
feedUrls: ["https://selfh.st/rss/", "https://hnrss.org/newest?q=self-hosted"],
|
||||
maximumAmountPosts: 20,
|
||||
textLinesClamp: 2,
|
||||
hideDescription: false,
|
||||
},
|
||||
},
|
||||
// Row 7-8: app widgets (2x1 each, 6 per row = 12)
|
||||
...appIds.map((appId): DemoWidget => ({
|
||||
kind: "app", width: 2, height: 1, needsIntegration: false,
|
||||
options: { appId, openInNewTab: true, showTitle: true, pingEnabled: false },
|
||||
})),
|
||||
...appIds.map(
|
||||
(appId): DemoWidget => ({
|
||||
kind: "app",
|
||||
width: 2,
|
||||
height: 1,
|
||||
needsIntegration: false,
|
||||
options: { appId, openInNewTab: true, showTitle: true, pingEnabled: false },
|
||||
}),
|
||||
),
|
||||
// Row 9: notebook + dockerContainers + weather = 12
|
||||
{ kind: "notebook", width: 4, height: 4, needsIntegration: false },
|
||||
{ kind: "dockerContainers", width: 6, height: 2, needsIntegration: false },
|
||||
|
||||
@@ -27,7 +27,15 @@ const eventTemplates: EventTemplate[] = [
|
||||
badge: { content: "S4/E3", color: "red" },
|
||||
},
|
||||
indicatorColor: "blue",
|
||||
links: [{ href: "https://www.imdb.com/title/tt8111088/", name: "IMDb", color: "#f5c518", isDark: false, logo: "/images/apps/imdb.svg" }],
|
||||
links: [
|
||||
{
|
||||
href: "https://www.imdb.com/title/tt8111088/",
|
||||
name: "IMDb",
|
||||
color: "#f5c518",
|
||||
isDark: false,
|
||||
logo: "/images/apps/imdb.svg",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Silo",
|
||||
@@ -41,7 +49,15 @@ const eventTemplates: EventTemplate[] = [
|
||||
badge: { content: "S2/E8", color: "teal" },
|
||||
},
|
||||
indicatorColor: "teal",
|
||||
links: [{ href: "https://www.imdb.com/title/tt14688458/", name: "IMDb", color: "#f5c518", isDark: false, logo: "/images/apps/imdb.svg" }],
|
||||
links: [
|
||||
{
|
||||
href: "https://www.imdb.com/title/tt14688458/",
|
||||
name: "IMDb",
|
||||
color: "#f5c518",
|
||||
isDark: false,
|
||||
logo: "/images/apps/imdb.svg",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Dune: Part Two",
|
||||
@@ -55,7 +71,15 @@ const eventTemplates: EventTemplate[] = [
|
||||
},
|
||||
metadata: { type: "radarr", releaseType: "digitalRelease" },
|
||||
indicatorColor: "cyan",
|
||||
links: [{ href: "https://www.imdb.com/title/tt15239678/", name: "IMDb", color: "#f5c518", isDark: false, logo: "/images/apps/imdb.svg" }],
|
||||
links: [
|
||||
{
|
||||
href: "https://www.imdb.com/title/tt15239678/",
|
||||
name: "IMDb",
|
||||
color: "#f5c518",
|
||||
isDark: false,
|
||||
logo: "/images/apps/imdb.svg",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Severance",
|
||||
@@ -83,7 +107,15 @@ const eventTemplates: EventTemplate[] = [
|
||||
},
|
||||
metadata: { type: "radarr", releaseType: "physicalRelease" },
|
||||
indicatorColor: "orange",
|
||||
links: [{ href: "https://www.imdb.com/title/tt15398776/", name: "IMDb", color: "#f5c518", isDark: false, logo: "/images/apps/imdb.svg" }],
|
||||
links: [
|
||||
{
|
||||
href: "https://www.imdb.com/title/tt15398776/",
|
||||
name: "IMDb",
|
||||
color: "#f5c518",
|
||||
isDark: false,
|
||||
logo: "/images/apps/imdb.svg",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Homarr Meetup",
|
||||
@@ -94,7 +126,15 @@ const eventTemplates: EventTemplate[] = [
|
||||
location: "Mountains",
|
||||
image: null,
|
||||
indicatorColor: "#fa5252",
|
||||
links: [{ href: "https://homarr.dev", name: "Homarr", logo: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg", color: "#000000", isDark: true }],
|
||||
links: [
|
||||
{
|
||||
href: "https://homarr.dev",
|
||||
name: "Homarr",
|
||||
logo: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
|
||||
color: "#000000",
|
||||
isDark: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "The Bear",
|
||||
@@ -144,28 +184,30 @@ const buildEventsForRange = (start: Date, end: Date): CalendarEvent[] => {
|
||||
const rangeStart = new Date(start);
|
||||
rangeStart.setDate(rangeStart.getDate() - 3);
|
||||
|
||||
return eventTemplates.map((tpl) => {
|
||||
const startDate = new Date(rangeStart);
|
||||
startDate.setDate(startDate.getDate() + tpl.dayOffset);
|
||||
startDate.setHours(tpl.hours, 0, 0, 0);
|
||||
return eventTemplates
|
||||
.map((tpl) => {
|
||||
const startDate = new Date(rangeStart);
|
||||
startDate.setDate(startDate.getDate() + tpl.dayOffset);
|
||||
startDate.setHours(tpl.hours, 0, 0, 0);
|
||||
|
||||
return {
|
||||
title: tpl.title,
|
||||
subTitle: tpl.subTitle,
|
||||
description: tpl.description,
|
||||
startDate,
|
||||
endDate: tpl.location ? new Date(startDate.getTime() + 2 * 60 * 60 * 1000) : null,
|
||||
image: tpl.image,
|
||||
location: tpl.location ?? null,
|
||||
indicatorColor: tpl.indicatorColor,
|
||||
links: tpl.links,
|
||||
...(tpl.metadata ? { metadata: tpl.metadata } : {}),
|
||||
};
|
||||
}).filter((event) => event.startDate >= start && event.startDate <= end);
|
||||
return {
|
||||
title: tpl.title,
|
||||
subTitle: tpl.subTitle,
|
||||
description: tpl.description,
|
||||
startDate,
|
||||
endDate: tpl.location ? new Date(startDate.getTime() + 2 * 60 * 60 * 1000) : null,
|
||||
image: tpl.image,
|
||||
location: tpl.location ?? null,
|
||||
indicatorColor: tpl.indicatorColor,
|
||||
links: tpl.links,
|
||||
...(tpl.metadata ? { metadata: tpl.metadata } : {}),
|
||||
};
|
||||
})
|
||||
.filter((event) => event.startDate >= start && event.startDate <= end);
|
||||
};
|
||||
|
||||
export class CalendarMockService implements ICalendarIntegration {
|
||||
public async getCalendarEventsAsync(start: Date, end: Date, _includeUnmonitored: boolean): Promise<CalendarEvent[]> {
|
||||
return buildEventsForRange(start, end);
|
||||
return await Promise.resolve(buildEventsForRange(start, end));
|
||||
}
|
||||
}
|
||||
@@ -125,30 +125,40 @@ const mockItems: Omit<DownloadClientItem, "id" | "index">[] = [
|
||||
|
||||
export class DownloadClientMockService implements IDownloadClientIntegration {
|
||||
public async getClientJobsAndStatusAsync(input: { limit: number }): Promise<DownloadClientJobsAndStatus> {
|
||||
return {
|
||||
return await Promise.resolve({
|
||||
status: {
|
||||
paused: false,
|
||||
rates: {
|
||||
down: 121_100_000,
|
||||
up: 9_340_000,
|
||||
},
|
||||
types: ["torrent", "usenet"],
|
||||
types: ["torrent", "usenet"] as const,
|
||||
},
|
||||
items: mockItems.slice(0, input.limit).map((item, index) => ({
|
||||
...item,
|
||||
id: `mock-${index}`,
|
||||
index,
|
||||
})),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public async pauseQueueAsync(): Promise<void> {}
|
||||
public async pauseQueueAsync(): Promise<void> {
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
public async pauseItemAsync(_item: DownloadClientItem): Promise<void> {}
|
||||
public async pauseItemAsync(_item: DownloadClientItem): Promise<void> {
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
public async resumeQueueAsync(): Promise<void> {}
|
||||
public async resumeQueueAsync(): Promise<void> {
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
public async resumeItemAsync(_item: DownloadClientItem): Promise<void> {}
|
||||
public async resumeItemAsync(_item: DownloadClientItem): Promise<void> {
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
public async deleteItemAsync(_item: DownloadClientItem, _fromDisk: boolean): Promise<void> {}
|
||||
public async deleteItemAsync(_item: DownloadClientItem, _fromDisk: boolean): Promise<void> {
|
||||
await Promise.resolve();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { IMediaReleasesIntegration, MediaRelease } from "../../interfaces/m
|
||||
|
||||
export class MediaReleasesMockService implements IMediaReleasesIntegration {
|
||||
public async getMediaReleasesAsync(): Promise<MediaRelease[]> {
|
||||
return mockMediaReleases;
|
||||
return await Promise.resolve(mockMediaReleases);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ export const mockMediaReleases: MediaRelease[] = [
|
||||
type: "movie",
|
||||
title: "Inception",
|
||||
subtitle: "A mind-bending thriller",
|
||||
description: "A thief who steals corporate secrets through dream-sharing technology is given the task of planting an idea into the mind of a CEO.",
|
||||
description:
|
||||
"A thief who steals corporate secrets through dream-sharing technology is given the task of planting an idea into the mind of a CEO.",
|
||||
releaseDate: new Date("2010-07-16"),
|
||||
imageUrls: {
|
||||
poster: "https://image.tmdb.org/t/p/w300/ljsZTbVsrQSqZgWeep2B1QiDKuh.jpg",
|
||||
@@ -63,7 +64,8 @@ export const mockMediaReleases: MediaRelease[] = [
|
||||
type: "movie",
|
||||
title: "The Dark Knight",
|
||||
subtitle: "Theatrical Release",
|
||||
description: "Batman raises the stakes in his war on crime. With the help of Lt. Gordon and DA Harvey Dent, Batman sets out to dismantle organized crime.",
|
||||
description:
|
||||
"Batman raises the stakes in his war on crime. With the help of Lt. Gordon and DA Harvey Dent, Batman sets out to dismantle organized crime.",
|
||||
releaseDate: new Date("2008-07-18"),
|
||||
imageUrls: {
|
||||
poster: "https://image.tmdb.org/t/p/w300/qJ2tW6WMUDux911BTUgMe1nV8Is.jpg",
|
||||
@@ -99,7 +101,8 @@ export const mockMediaReleases: MediaRelease[] = [
|
||||
type: "tv",
|
||||
title: "Severance",
|
||||
subtitle: "S2E1 - Premiere",
|
||||
description: "Mark leads a team of office workers whose memories are surgically divided between work and personal lives.",
|
||||
description:
|
||||
"Mark leads a team of office workers whose memories are surgically divided between work and personal lives.",
|
||||
releaseDate: new Date("2025-01-17"),
|
||||
imageUrls: {
|
||||
poster: "https://image.tmdb.org/t/p/w300/pJAtR4gMFnalHCmT9cByPFgkZ6B.jpg",
|
||||
@@ -115,7 +118,8 @@ export const mockMediaReleases: MediaRelease[] = [
|
||||
type: "movie",
|
||||
title: "Dune: Part Two",
|
||||
subtitle: "Theatrical Release",
|
||||
description: "Paul Atreides unites with the Fremen to seek revenge against the conspirators who destroyed his family.",
|
||||
description:
|
||||
"Paul Atreides unites with the Fremen to seek revenge against the conspirators who destroyed his family.",
|
||||
releaseDate: new Date("2024-03-01"),
|
||||
imageUrls: {
|
||||
poster: "https://image.tmdb.org/t/p/w300/8b8R8l88Qje9dn9OE8PY05Nez7.jpg",
|
||||
|
||||
@@ -2,16 +2,94 @@ import type { IMediaServerIntegration } from "../../interfaces/media-server/medi
|
||||
import type { CurrentSessionsInput, StreamSession } from "../../interfaces/media-server/media-server-types";
|
||||
|
||||
const sessions: StreamSession[] = [
|
||||
{ sessionId: "s-0", sessionName: "Living Room TV", user: { userId: "u-0", username: "thomas", profilePictureUrl: null }, currentlyPlaying: { type: "movie", name: "Interstellar", seasonName: undefined, episodeName: null, albumName: null, episodeCount: null, metadata: null } },
|
||||
{ sessionId: "s-1", sessionName: "Bedroom Chromecast", user: { userId: "u-1", username: "sarah", profilePictureUrl: null }, currentlyPlaying: { type: "video", name: "Breaking Bad", seasonName: "Season 5", episodeName: "Ozymandias", albumName: null, episodeCount: null, metadata: null } },
|
||||
{ sessionId: "s-2", sessionName: "Office Desktop", user: { userId: "u-2", username: "mike", profilePictureUrl: null }, currentlyPlaying: { type: "movie", name: "The Dark Knight", seasonName: undefined, episodeName: null, albumName: null, episodeCount: null, metadata: null } },
|
||||
{ sessionId: "s-3", sessionName: "iPad", user: { userId: "u-3", username: "emily", profilePictureUrl: null }, currentlyPlaying: { type: "video", name: "Severance", seasonName: "Season 2", episodeName: "The We We Are", albumName: null, episodeCount: null, metadata: null } },
|
||||
{ sessionId: "s-4", sessionName: "Mobile", user: { userId: "u-4", username: "alex", profilePictureUrl: null }, currentlyPlaying: { type: "audio", name: "Daft Punk", seasonName: undefined, episodeName: null, albumName: "Random Access Memories", episodeCount: null, metadata: null } },
|
||||
{ sessionId: "s-5", sessionName: "Guest Room", user: { userId: "u-5", username: "guest", profilePictureUrl: null }, currentlyPlaying: { type: "movie", name: "Oppenheimer", seasonName: undefined, episodeName: null, albumName: null, episodeCount: null, metadata: null } },
|
||||
{
|
||||
sessionId: "s-0",
|
||||
sessionName: "Living Room TV",
|
||||
user: { userId: "u-0", username: "thomas", profilePictureUrl: null },
|
||||
currentlyPlaying: {
|
||||
type: "movie",
|
||||
name: "Interstellar",
|
||||
seasonName: undefined,
|
||||
episodeName: null,
|
||||
albumName: null,
|
||||
episodeCount: null,
|
||||
metadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
sessionId: "s-1",
|
||||
sessionName: "Bedroom Chromecast",
|
||||
user: { userId: "u-1", username: "sarah", profilePictureUrl: null },
|
||||
currentlyPlaying: {
|
||||
type: "video",
|
||||
name: "Breaking Bad",
|
||||
seasonName: "Season 5",
|
||||
episodeName: "Ozymandias",
|
||||
albumName: null,
|
||||
episodeCount: null,
|
||||
metadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
sessionId: "s-2",
|
||||
sessionName: "Office Desktop",
|
||||
user: { userId: "u-2", username: "mike", profilePictureUrl: null },
|
||||
currentlyPlaying: {
|
||||
type: "movie",
|
||||
name: "The Dark Knight",
|
||||
seasonName: undefined,
|
||||
episodeName: null,
|
||||
albumName: null,
|
||||
episodeCount: null,
|
||||
metadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
sessionId: "s-3",
|
||||
sessionName: "iPad",
|
||||
user: { userId: "u-3", username: "emily", profilePictureUrl: null },
|
||||
currentlyPlaying: {
|
||||
type: "video",
|
||||
name: "Severance",
|
||||
seasonName: "Season 2",
|
||||
episodeName: "The We We Are",
|
||||
albumName: null,
|
||||
episodeCount: null,
|
||||
metadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
sessionId: "s-4",
|
||||
sessionName: "Mobile",
|
||||
user: { userId: "u-4", username: "alex", profilePictureUrl: null },
|
||||
currentlyPlaying: {
|
||||
type: "audio",
|
||||
name: "Daft Punk",
|
||||
seasonName: undefined,
|
||||
episodeName: null,
|
||||
albumName: "Random Access Memories",
|
||||
episodeCount: null,
|
||||
metadata: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
sessionId: "s-5",
|
||||
sessionName: "Guest Room",
|
||||
user: { userId: "u-5", username: "guest", profilePictureUrl: null },
|
||||
currentlyPlaying: {
|
||||
type: "movie",
|
||||
name: "Oppenheimer",
|
||||
seasonName: undefined,
|
||||
episodeName: null,
|
||||
albumName: null,
|
||||
episodeCount: null,
|
||||
metadata: null,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export class MediaServerMockService implements IMediaServerIntegration {
|
||||
public async getCurrentSessionsAsync(_options: CurrentSessionsInput): Promise<StreamSession[]> {
|
||||
return sessions;
|
||||
return await Promise.resolve(sessions);
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,12 @@ import type { NetworkControllerSummary } from "../../types";
|
||||
|
||||
export class NetworkControllerSummaryMockService implements NetworkControllerSummaryIntegration {
|
||||
public async getNetworkSummaryAsync(): Promise<NetworkControllerSummary> {
|
||||
return {
|
||||
return await Promise.resolve({
|
||||
lan: { guests: 5, users: 18, status: "enabled" },
|
||||
vpn: { users: 3, status: "enabled" },
|
||||
wanStatus: "enabled",
|
||||
wifi: { status: "enabled", guests: 2, users: 12 },
|
||||
www: { latency: 22, status: "enabled", ping: 14, uptime: 864000 },
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
import { db, like, or } from "@homarr/db";
|
||||
import { icons } from "@homarr/db/schema";
|
||||
import type { ContainerState } from "@homarr/docker";
|
||||
import type { ContainerState, Port } from "@homarr/docker";
|
||||
import { dockerLabels, DockerSingleton } from "@homarr/docker";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
@@ -16,19 +16,156 @@ const logger = createLogger({ module: "dockerRequestHandler" });
|
||||
|
||||
const isDemoMode = ["1", "yes", "t", "true"].includes((process.env.DEMO_MODE ?? "").toLowerCase());
|
||||
|
||||
const mockContainers = [
|
||||
{ id: "a1b2c3d4e5f6", name: "sonarr", host: "local", state: "running" as ContainerState, image: "lscr.io/linuxserver/sonarr:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/sonarr.svg", cpuUsage: 2.3, memoryUsage: 256 * 1024 * 1024, ports: [{ PrivatePort: 8989, PublicPort: 8989, Type: "tcp" }] },
|
||||
{ id: "b2c3d4e5f6a7", name: "radarr", host: "local", state: "running" as ContainerState, image: "lscr.io/linuxserver/radarr:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/radarr.svg", cpuUsage: 1.8, memoryUsage: 220 * 1024 * 1024, ports: [{ PrivatePort: 7878, PublicPort: 7878, Type: "tcp" }] },
|
||||
{ id: "c3d4e5f6a7b8", name: "plex", host: "local", state: "running" as ContainerState, image: "lscr.io/linuxserver/plex:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/plex.svg", cpuUsage: 12.5, memoryUsage: 1024 * 1024 * 1024, ports: [{ PrivatePort: 32400, PublicPort: 32400, Type: "tcp" }] },
|
||||
{ id: "d4e5f6a7b8c9", name: "qbittorrent", host: "local", state: "running" as ContainerState, image: "lscr.io/linuxserver/qbittorrent:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/qbittorrent.svg", cpuUsage: 5.1, memoryUsage: 380 * 1024 * 1024, ports: [{ PrivatePort: 8080, PublicPort: 8080, Type: "tcp" }] },
|
||||
{ id: "e5f6a7b8c9d0", name: "prowlarr", host: "local", state: "running" as ContainerState, image: "lscr.io/linuxserver/prowlarr:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/prowlarr.svg", cpuUsage: 0.8, memoryUsage: 120 * 1024 * 1024, ports: [{ PrivatePort: 9696, PublicPort: 9696, Type: "tcp" }] },
|
||||
{ id: "f6a7b8c9d0e1", name: "overseerr", host: "local", state: "running" as ContainerState, image: "lscr.io/linuxserver/overseerr:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/overseerr.svg", cpuUsage: 1.2, memoryUsage: 180 * 1024 * 1024, ports: [{ PrivatePort: 5055, PublicPort: 5055, Type: "tcp" }] },
|
||||
{ id: "a7b8c9d0e1f2", name: "homarr", host: "local", state: "running" as ContainerState, image: "ghcr.io/homarr-labs/homarr:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg", cpuUsage: 3.4, memoryUsage: 290 * 1024 * 1024, ports: [{ PrivatePort: 7575, PublicPort: 7575, Type: "tcp" }] },
|
||||
{ id: "b8c9d0e1f2a3", name: "pihole", host: "local", state: "running" as ContainerState, image: "pihole/pihole:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pi-hole.svg", cpuUsage: 0.5, memoryUsage: 95 * 1024 * 1024, ports: [{ PrivatePort: 80, PublicPort: 80, Type: "tcp" }, { PrivatePort: 53, PublicPort: 53, Type: "udp" }] },
|
||||
{ id: "c9d0e1f2a3b4", name: "nginx-proxy", host: "local", state: "running" as ContainerState, image: "jc21/nginx-proxy-manager:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nginx-proxy-manager.svg", cpuUsage: 0.3, memoryUsage: 65 * 1024 * 1024, ports: [{ PrivatePort: 443, PublicPort: 443, Type: "tcp" }, { PrivatePort: 81, PublicPort: 81, Type: "tcp" }] },
|
||||
{ id: "d0e1f2a3b4c5", name: "watchtower", host: "local", state: "running" as ContainerState, image: "containrrr/watchtower:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/watchtower.svg", cpuUsage: 0.1, memoryUsage: 30 * 1024 * 1024, ports: [] },
|
||||
{ id: "e1f2a3b4c5d6", name: "tdarr", host: "local", state: "exited" as ContainerState, image: "ghcr.io/haveagitgat/tdarr:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/tdarr.svg", cpuUsage: 0, memoryUsage: 0, ports: [] },
|
||||
{ id: "f2a3b4c5d6e7", name: "bazarr", host: "local", state: "running" as ContainerState, image: "lscr.io/linuxserver/bazarr:latest", iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/bazarr.svg", cpuUsage: 0.6, memoryUsage: 110 * 1024 * 1024, ports: [{ PrivatePort: 6767, PublicPort: 6767, Type: "tcp" }] },
|
||||
const port = (privatePort: number, publicPort: number, type: string): Port => ({
|
||||
IP: "0.0.0.0",
|
||||
PrivatePort: privatePort,
|
||||
PublicPort: publicPort,
|
||||
Type: type,
|
||||
});
|
||||
|
||||
const mockContainers: {
|
||||
id: string;
|
||||
name: string;
|
||||
host: string;
|
||||
state: ContainerState;
|
||||
image: string;
|
||||
iconUrl: string;
|
||||
cpuUsage: number;
|
||||
memoryUsage: number;
|
||||
ports: Port[];
|
||||
}[] = [
|
||||
{
|
||||
id: "a1b2c3d4e5f6",
|
||||
name: "sonarr",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "lscr.io/linuxserver/sonarr:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/sonarr.svg",
|
||||
cpuUsage: 2.3,
|
||||
memoryUsage: 256 * 1024 * 1024,
|
||||
ports: [port(8989, 8989, "tcp")],
|
||||
},
|
||||
{
|
||||
id: "b2c3d4e5f6a7",
|
||||
name: "radarr",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "lscr.io/linuxserver/radarr:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/radarr.svg",
|
||||
cpuUsage: 1.8,
|
||||
memoryUsage: 220 * 1024 * 1024,
|
||||
ports: [port(7878, 7878, "tcp")],
|
||||
},
|
||||
{
|
||||
id: "c3d4e5f6a7b8",
|
||||
name: "plex",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "lscr.io/linuxserver/plex:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/plex.svg",
|
||||
cpuUsage: 12.5,
|
||||
memoryUsage: 1024 * 1024 * 1024,
|
||||
ports: [port(32400, 32400, "tcp")],
|
||||
},
|
||||
{
|
||||
id: "d4e5f6a7b8c9",
|
||||
name: "qbittorrent",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "lscr.io/linuxserver/qbittorrent:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/qbittorrent.svg",
|
||||
cpuUsage: 5.1,
|
||||
memoryUsage: 380 * 1024 * 1024,
|
||||
ports: [port(8080, 8080, "tcp")],
|
||||
},
|
||||
{
|
||||
id: "e5f6a7b8c9d0",
|
||||
name: "prowlarr",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "lscr.io/linuxserver/prowlarr:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/prowlarr.svg",
|
||||
cpuUsage: 0.8,
|
||||
memoryUsage: 120 * 1024 * 1024,
|
||||
ports: [port(9696, 9696, "tcp")],
|
||||
},
|
||||
{
|
||||
id: "f6a7b8c9d0e1",
|
||||
name: "overseerr",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "lscr.io/linuxserver/overseerr:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/overseerr.svg",
|
||||
cpuUsage: 1.2,
|
||||
memoryUsage: 180 * 1024 * 1024,
|
||||
ports: [port(5055, 5055, "tcp")],
|
||||
},
|
||||
{
|
||||
id: "a7b8c9d0e1f2",
|
||||
name: "homarr",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "ghcr.io/homarr-labs/homarr:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
|
||||
cpuUsage: 3.4,
|
||||
memoryUsage: 290 * 1024 * 1024,
|
||||
ports: [port(7575, 7575, "tcp")],
|
||||
},
|
||||
{
|
||||
id: "b8c9d0e1f2a3",
|
||||
name: "pihole",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "pihole/pihole:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pi-hole.svg",
|
||||
cpuUsage: 0.5,
|
||||
memoryUsage: 95 * 1024 * 1024,
|
||||
ports: [port(80, 80, "tcp"), port(53, 53, "udp")],
|
||||
},
|
||||
{
|
||||
id: "c9d0e1f2a3b4",
|
||||
name: "nginx-proxy",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "jc21/nginx-proxy-manager:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nginx-proxy-manager.svg",
|
||||
cpuUsage: 0.3,
|
||||
memoryUsage: 65 * 1024 * 1024,
|
||||
ports: [port(443, 443, "tcp"), port(81, 81, "tcp")],
|
||||
},
|
||||
{
|
||||
id: "d0e1f2a3b4c5",
|
||||
name: "watchtower",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "containrrr/watchtower:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/watchtower.svg",
|
||||
cpuUsage: 0.1,
|
||||
memoryUsage: 30 * 1024 * 1024,
|
||||
ports: [],
|
||||
},
|
||||
{
|
||||
id: "e1f2a3b4c5d6",
|
||||
name: "tdarr",
|
||||
host: "local",
|
||||
state: "exited",
|
||||
image: "ghcr.io/haveagitgat/tdarr:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/tdarr.svg",
|
||||
cpuUsage: 0,
|
||||
memoryUsage: 0,
|
||||
ports: [],
|
||||
},
|
||||
{
|
||||
id: "f2a3b4c5d6e7",
|
||||
name: "bazarr",
|
||||
host: "local",
|
||||
state: "running",
|
||||
image: "lscr.io/linuxserver/bazarr:latest",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/bazarr.svg",
|
||||
cpuUsage: 0.6,
|
||||
memoryUsage: 110 * 1024 * 1024,
|
||||
ports: [port(6767, 6767, "tcp")],
|
||||
},
|
||||
];
|
||||
|
||||
export const dockerContainersRequestHandler = createCachedWidgetRequestHandler({
|
||||
|
||||
Reference in new issue
Block a user