fixes and improvements
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Stack,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { DialogFooter } from "./DialogFooter";
|
||||||
|
|
||||||
|
export function ConfirmDialog({
|
||||||
|
open,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
confirmLabel = "Delete",
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
busy,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
confirmLabel?: string;
|
||||||
|
onCancel: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
busy?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onCancel} fullWidth maxWidth="xs">
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{message}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter
|
||||||
|
onCancel={onCancel}
|
||||||
|
onConfirm={onConfirm}
|
||||||
|
confirmLabel={confirmLabel}
|
||||||
|
confirmColor="error"
|
||||||
|
confirmBusyLabel={confirmLabel}
|
||||||
|
confirmDisabled={busy}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
useMonitoringOverview,
|
useMonitoringOverview,
|
||||||
useSaveDashboardShortcut,
|
useSaveDashboardShortcut,
|
||||||
} from "../hooks/useDashboard";
|
} from "../hooks/useDashboard";
|
||||||
|
import { useMonitoringSettings } from "../hooks/useSettings";
|
||||||
import type { DashboardShortcut, DashboardShortcutInput } from "../types";
|
import type { DashboardShortcut, DashboardShortcutInput } from "../types";
|
||||||
import { NowPlaying } from "../components/NowPlaying";
|
import { NowPlaying } from "../components/NowPlaying";
|
||||||
import { MonitoringOverviewTable } from "../components/MonitoringOverviewTable";
|
import { MonitoringOverviewTable } from "../components/MonitoringOverviewTable";
|
||||||
@@ -310,7 +311,19 @@ function ShortcutCard({
|
|||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: activity } = useActivity();
|
const { data: machines = [] } = useMonitoringSettings();
|
||||||
|
const jellyfinMachines = useMemo(
|
||||||
|
() =>
|
||||||
|
machines.filter(
|
||||||
|
(machine) => machine.enabled && machine.services.includes("jellyfin"),
|
||||||
|
),
|
||||||
|
[machines],
|
||||||
|
);
|
||||||
|
const [activeJellyfinMachineId, setActiveJellyfinMachineId] =
|
||||||
|
useState<string>("");
|
||||||
|
const selectedJellyfinId =
|
||||||
|
activeJellyfinMachineId || jellyfinMachines[0]?.id || "";
|
||||||
|
const { data: activity } = useActivity(selectedJellyfinId || undefined);
|
||||||
const { data: monitoringOverview } = useMonitoringOverview();
|
const { data: monitoringOverview } = useMonitoringOverview();
|
||||||
const { data: shortcuts = [] } = useDashboardShortcuts();
|
const { data: shortcuts = [] } = useDashboardShortcuts();
|
||||||
const saveShortcut = useSaveDashboardShortcut();
|
const saveShortcut = useSaveDashboardShortcut();
|
||||||
@@ -319,6 +332,7 @@ export function Dashboard() {
|
|||||||
const [shortcutDraft, setShortcutDraft] = useState<DashboardShortcutInput>(
|
const [shortcutDraft, setShortcutDraft] = useState<DashboardShortcutInput>(
|
||||||
emptyShortcut(),
|
emptyShortcut(),
|
||||||
);
|
);
|
||||||
|
const [deleteShortcutId, setDeleteShortcutId] = useState<string | null>(null);
|
||||||
|
|
||||||
const openCreateShortcut = () => {
|
const openCreateShortcut = () => {
|
||||||
setShortcutDraft(emptyShortcut());
|
setShortcutDraft(emptyShortcut());
|
||||||
@@ -373,7 +387,7 @@ export function Dashboard() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onEdit={() => openEditShortcut(shortcut)}
|
onEdit={() => openEditShortcut(shortcut)}
|
||||||
onDelete={() => deleteShortcut.mutate(shortcut.id)}
|
onDelete={() => setDeleteShortcutId(shortcut.id)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
@@ -389,6 +403,29 @@ export function Dashboard() {
|
|||||||
<SectionCard
|
<SectionCard
|
||||||
title="Jellyfin activity"
|
title="Jellyfin activity"
|
||||||
description="Live sessions and idle users from Jellyfin."
|
description="Live sessions and idle users from Jellyfin."
|
||||||
|
action={
|
||||||
|
jellyfinMachines.length > 1 ? (
|
||||||
|
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||||
|
<Select
|
||||||
|
value={selectedJellyfinId}
|
||||||
|
onChange={(e) => setActiveJellyfinMachineId(e.target.value)}
|
||||||
|
sx={{ fontSize: "0.8rem" }}
|
||||||
|
>
|
||||||
|
{jellyfinMachines.map((m) => (
|
||||||
|
<MenuItem key={m.id} value={m.id}>
|
||||||
|
{m.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
) : jellyfinMachines.length === 1 ? (
|
||||||
|
<Chip
|
||||||
|
label={jellyfinMachines[0].name}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{activity ? (
|
{activity ? (
|
||||||
<NowPlaying
|
<NowPlaying
|
||||||
@@ -414,6 +451,31 @@ export function Dashboard() {
|
|||||||
onClose={() => setShortcutDialogOpen(false)}
|
onClose={() => setShortcutDialogOpen(false)}
|
||||||
onSave={saveShortcutDraft}
|
onSave={saveShortcutDraft}
|
||||||
/>
|
/>
|
||||||
|
<Dialog
|
||||||
|
open={Boolean(deleteShortcutId)}
|
||||||
|
onClose={() => setDeleteShortcutId(null)}
|
||||||
|
fullWidth
|
||||||
|
maxWidth="xs"
|
||||||
|
>
|
||||||
|
<DialogTitle>Delete shortcut?</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
This cannot be undone. The shortcut will be removed from the
|
||||||
|
dashboard.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter
|
||||||
|
onCancel={() => setDeleteShortcutId(null)}
|
||||||
|
onConfirm={() => {
|
||||||
|
if (deleteShortcutId) {
|
||||||
|
deleteShortcut.mutate(deleteShortcutId);
|
||||||
|
}
|
||||||
|
setDeleteShortcutId(null);
|
||||||
|
}}
|
||||||
|
confirmLabel="Delete"
|
||||||
|
confirmColor="error"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { HoverEditButton } from "../components/HoverEditButton";
|
|||||||
import { SectionCard } from "../components/SectionCard";
|
import { SectionCard } from "../components/SectionCard";
|
||||||
import { SelectionRailCard } from "../components/SelectionRailCard";
|
import { SelectionRailCard } from "../components/SelectionRailCard";
|
||||||
import { TabbedCard } from "../components/TabbedCard";
|
import { TabbedCard } from "../components/TabbedCard";
|
||||||
|
import { ConfirmDialog } from "../components/ConfirmDialog";
|
||||||
const SERVICE_OPTIONS = [
|
const SERVICE_OPTIONS = [
|
||||||
{ value: "monitoring", label: "Monitoring" },
|
{ value: "monitoring", label: "Monitoring" },
|
||||||
{ value: "files", label: "Files" },
|
{ value: "files", label: "Files" },
|
||||||
@@ -996,6 +997,7 @@ export function Settings() {
|
|||||||
const deleteMachine = useDeleteMonitoringMachine();
|
const deleteMachine = useDeleteMonitoringMachine();
|
||||||
const testMachineSSH = useTestMonitoringMachineSSH();
|
const testMachineSSH = useTestMonitoringMachineSSH();
|
||||||
const [tab, setTab] = useState<SettingsTab>("machines");
|
const [tab, setTab] = useState<SettingsTab>("machines");
|
||||||
|
const [deleteMachineId, setDeleteMachineId] = useState<string | null>(null);
|
||||||
const [sshValidationMessage, setSSHValidationMessage] = useState("");
|
const [sshValidationMessage, setSSHValidationMessage] = useState("");
|
||||||
const [sshValidationError, setSSHValidationError] = useState("");
|
const [sshValidationError, setSSHValidationError] = useState("");
|
||||||
const [sshValidationStatus, setSSHValidationStatus] = useState("");
|
const [sshValidationStatus, setSSHValidationStatus] = useState("");
|
||||||
@@ -1384,7 +1386,7 @@ export function Settings() {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="error"
|
color="error"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
deleteMachine.mutate(selectedMachine.id)
|
setDeleteMachineId(selectedMachine.id)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -1448,8 +1450,8 @@ export function Settings() {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="error"
|
color="error"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteMachine.mutate(machineDraft.id as string);
|
setDeleteMachineId(machineDraft.id as string);
|
||||||
closeMachineDialog();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -1457,6 +1459,20 @@ export function Settings() {
|
|||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
open={Boolean(deleteMachineId)}
|
||||||
|
title="Delete machine?"
|
||||||
|
message="The machine will be removed. Action history will be deleted."
|
||||||
|
onCancel={() => setDeleteMachineId(null)}
|
||||||
|
onConfirm={() => {
|
||||||
|
if (deleteMachineId) {
|
||||||
|
deleteMachine.mutate(deleteMachineId);
|
||||||
|
closeMachineDialog();
|
||||||
|
}
|
||||||
|
setDeleteMachineId(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user