Merge pull request 'feat(ui): collapse connect instance panel when instances exist' (#27) from feature/ui-collapse-connect-panel into master

Reviewed-on: http://gitea.lan:3000/robbond/pleskSaas/pulls/27
This commit was merged in pull request #27.
This commit is contained in:
2026-03-05 18:31:15 +00:00

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import type { ChangeEvent } from "react"; import type { ChangeEvent } from "react";
import type { FormEvent } from "react"; import type { FormEvent } from "react";
import { formatDateTime } from "@/lib/dates"; import { formatDateTime } from "@/lib/dates";
@@ -167,6 +167,7 @@ export function DashboardControls({
autoSyncByInstance, autoSyncByInstance,
healthEventsByInstance, healthEventsByInstance,
}: Props) { }: Props) {
const hasInstances = instances.length > 0;
const autoSyncFrequencyOptions = [15, 30, 60, 180, 360, 1440]; const autoSyncFrequencyOptions = [15, 30, 60, 180, 360, 1440];
const [instanceName, setInstanceName] = useState(""); const [instanceName, setInstanceName] = useState("");
const [baseUrl, setBaseUrl] = useState(""); const [baseUrl, setBaseUrl] = useState("");
@@ -182,6 +183,39 @@ export function DashboardControls({
const [domainStatus, setDomainStatus] = useState("all"); const [domainStatus, setDomainStatus] = useState("all");
const [message, setMessage] = useState<string | null>(null); const [message, setMessage] = useState<string | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isConnectPanelOpen, setIsConnectPanelOpen] = useState(!hasInstances);
useEffect(() => {
if (!hasInstances) {
setIsConnectPanelOpen(true);
return;
}
const savedState = window.localStorage.getItem(
"dashboard_connect_panel_open",
);
if (savedState === "true") {
setIsConnectPanelOpen(true);
return;
}
if (savedState === "false") {
setIsConnectPanelOpen(false);
return;
}
setIsConnectPanelOpen(false);
}, [hasInstances]);
useEffect(() => {
if (!hasInstances) return;
window.localStorage.setItem(
"dashboard_connect_panel_open",
isConnectPanelOpen ? "true" : "false",
);
}, [hasInstances, isConnectPanelOpen]);
const healthCounts = instances.reduce( const healthCounts = instances.reduce(
(acc, instance) => { (acc, instance) => {
@@ -416,79 +450,102 @@ export function DashboardControls({
</div> </div>
<div className="grid gap-4 lg:grid-cols-2"> <div className="grid gap-4 lg:grid-cols-2">
<form <div className="rounded-xl border border-slate-200 bg-white p-4">
onSubmit={onCreateInstance} <div className="flex items-center justify-between gap-3">
className="space-y-3 rounded-xl border border-slate-200 bg-white p-4" <h2 className="text-lg font-semibold">
> {hasInstances
<h2 className="text-lg font-semibold">Connect Plesk Instance</h2> ? "Connect another Plesk instance"
<input : "Connect Plesk Instance"}
required </h2>
value={instanceName} {hasInstances ? (
onChange={(e: ChangeEvent<HTMLInputElement>) => <button
setInstanceName(e.target.value) type="button"
} onClick={() => setIsConnectPanelOpen((open) => !open)}
className="w-full rounded border border-slate-300 px-3 py-2 text-sm" className="rounded border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700"
placeholder="Instance name" >
/> {isConnectPanelOpen ? "Hide" : "Add instance"}
<input </button>
required ) : null}
value={baseUrl} </div>
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setBaseUrl(e.target.value) {!isConnectPanelOpen && hasInstances ? (
} <p className="mt-2 text-sm text-slate-500">
className="w-full rounded border border-slate-300 px-3 py-2 text-sm" Add another server when needed.
placeholder="https://plesk.example.com" </p>
/> ) : null}
<select
value={authType} {isConnectPanelOpen ? (
onChange={(e: ChangeEvent<HTMLSelectElement>) => <form onSubmit={onCreateInstance} className="mt-3 space-y-3">
setAuthType(e.target.value as "api_key" | "basic")
}
className="w-full rounded border border-slate-300 px-3 py-2 text-sm"
>
<option value="api_key">API Key</option>
<option value="basic">Basic</option>
</select>
{authType === "api_key" ? (
<input
required
value={apiKey}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setApiKey(e.target.value)
}
className="w-full rounded border border-slate-300 px-3 py-2 text-sm"
placeholder="Plesk API key"
/>
) : (
<div className="grid gap-2 sm:grid-cols-2">
<input <input
required required
value={username} value={instanceName}
onChange={(e: ChangeEvent<HTMLInputElement>) => onChange={(e: ChangeEvent<HTMLInputElement>) =>
setUsername(e.target.value) setInstanceName(e.target.value)
} }
className="w-full rounded border border-slate-300 px-3 py-2 text-sm" className="w-full rounded border border-slate-300 px-3 py-2 text-sm"
placeholder="Plesk username" placeholder="Instance name"
/> />
<input <input
required required
type="password" value={baseUrl}
value={password}
onChange={(e: ChangeEvent<HTMLInputElement>) => onChange={(e: ChangeEvent<HTMLInputElement>) =>
setPassword(e.target.value) setBaseUrl(e.target.value)
} }
className="w-full rounded border border-slate-300 px-3 py-2 text-sm" className="w-full rounded border border-slate-300 px-3 py-2 text-sm"
placeholder="Plesk password" placeholder="https://plesk.example.com"
/> />
</div> <select
)} value={authType}
<button onChange={(e: ChangeEvent<HTMLSelectElement>) =>
disabled={loading} setAuthType(e.target.value as "api_key" | "basic")
className="rounded bg-brand-600 px-3 py-2 text-sm text-white" }
> className="w-full rounded border border-slate-300 px-3 py-2 text-sm"
Save & Validate >
</button> <option value="api_key">API Key</option>
</form> <option value="basic">Basic</option>
</select>
{authType === "api_key" ? (
<input
required
value={apiKey}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setApiKey(e.target.value)
}
className="w-full rounded border border-slate-300 px-3 py-2 text-sm"
placeholder="Plesk API key"
/>
) : (
<div className="grid gap-2 sm:grid-cols-2">
<input
required
value={username}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setUsername(e.target.value)
}
className="w-full rounded border border-slate-300 px-3 py-2 text-sm"
placeholder="Plesk username"
/>
<input
required
type="password"
value={password}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setPassword(e.target.value)
}
className="w-full rounded border border-slate-300 px-3 py-2 text-sm"
placeholder="Plesk password"
/>
</div>
)}
<button
disabled={loading}
className="rounded bg-brand-600 px-3 py-2 text-sm text-white"
>
Save & Validate
</button>
</form>
) : null}
</div>
<div className="space-y-3 rounded-xl border border-slate-200 bg-white p-4"> <div className="space-y-3 rounded-xl border border-slate-200 bg-white p-4">
<h2 className="text-lg font-semibold">Plesk Instances</h2> <h2 className="text-lg font-semibold">Plesk Instances</h2>