fix: reset service editor on switch + add service-page settings shortcut
Settings.tsx: ServiceConfigEditor derived editable state (name, config,
secrets) from the instance prop via useState, but the parent rendered it
without a key. Switching services in the rail reused the same component, so
name/config stayed pinned to the previously selected service while
instance.id/service_type (read live from props) pointed at the new one —
saving then wrote the stale values onto the wrong row (e.g. saving qBittorrent
renamed it "Jellyfin" with Jellyfin's URL). Add key={selectedService.id} so
the editor remounts and resets on switch.
ServicePage: add a Settings shortcut in the header that deep-links to
/settings?tab=services&service=<id>. Settings now reads tab + service query
params (useSearchParams) to open the Services tab with that service
pre-selected, via a new initialServiceId prop on ServicesAdminCard.
Tests: new Settings.services.test.tsx regression test (fails without the key,
passes with it); wrap existing Settings tests in MemoryRouter since Settings
now uses useSearchParams. 166/166 frontend tests pass; typecheck + ESLint clean.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import type {
|
||||
MonitoringMachine,
|
||||
MonitoringMachineInput,
|
||||
@@ -893,7 +894,11 @@ export function Settings() {
|
||||
const saveMachine = useSaveMonitoringMachine();
|
||||
const deleteMachine = useDeleteMonitoringMachine();
|
||||
const testMachineSSH = useTestMonitoringMachineSSH();
|
||||
const [tab, setTab] = useState<SettingsTab>("machines");
|
||||
const [searchParams] = useSearchParams();
|
||||
const [tab, setTab] = useState<SettingsTab>(
|
||||
(searchParams.get("tab") as SettingsTab | null) ?? "machines",
|
||||
);
|
||||
const initialServiceId = searchParams.get("service") ?? "";
|
||||
const [deleteMachineId, setDeleteMachineId] = useState<string | null>(null);
|
||||
const [selectedSSHKeyId, setSelectedSSHKeyId] = useState("");
|
||||
const [sshValidationMessage, setSSHValidationMessage] = useState("");
|
||||
@@ -1195,7 +1200,9 @@ export function Settings() {
|
||||
onSelectKeyId={setSelectedSSHKeyId}
|
||||
/>
|
||||
)}
|
||||
{tab === "services" && <ServicesAdminCard />}
|
||||
{tab === "services" && (
|
||||
<ServicesAdminCard initialServiceId={initialServiceId} />
|
||||
)}
|
||||
{tab === "danger" && <ResetLocalDatabaseCard />}
|
||||
</TabbedCard>
|
||||
{isMobile ? (
|
||||
@@ -1338,10 +1345,14 @@ export function Settings() {
|
||||
* old ServicePage ConfigBody — the service page is now a pure operational
|
||||
* view; all administration lives here.
|
||||
*/
|
||||
function ServicesAdminCard() {
|
||||
function ServicesAdminCard({
|
||||
initialServiceId = "",
|
||||
}: {
|
||||
initialServiceId?: string;
|
||||
}) {
|
||||
const { data: services = [] } = useServiceInstances();
|
||||
const { data: types = [] } = useServiceTypes();
|
||||
const [selectedServiceId, setSelectedServiceId] = useState("");
|
||||
const [selectedServiceId, setSelectedServiceId] = useState(initialServiceId);
|
||||
|
||||
const sortedServices = useMemo(
|
||||
() =>
|
||||
@@ -1416,6 +1427,11 @@ function ServicesAdminCard() {
|
||||
>
|
||||
{selectedService ? (
|
||||
<ServiceConfigEditor
|
||||
// Remount on service switch so useState initializers (name, config,
|
||||
// secrets) re-run for the new instance. Without this key, switching
|
||||
// services in the rail keeps the previous service's editable state
|
||||
// and a save writes stale name/config onto the new service's row.
|
||||
key={selectedService.id}
|
||||
instance={selectedService}
|
||||
typeInfo={selectedTypeInfo}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user