Minor frontend changes

This commit is contained in:
root
2025-10-08 19:06:19 +00:00
parent 6ed9b94d3a
commit 737495d186
3 changed files with 61 additions and 67 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ export default function App() {
<div className="max-w-6xl mx-auto px-6 py-6"> <div className="max-w-6xl mx-auto px-6 py-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between"> <div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div className="flex flex-col items-end gap-1"> <div className="flex flex-col items-end gap-1">
<span className="text-xs text-gray-500">v0.2</span> <span className="text-xs text-gray-500">v0.3 WIP</span>
<img src={logo} alt="StackPulse" className="h-10 w-auto" /> <img src={logo} alt="StackPulse" className="h-10 w-auto" />
</div> </div>
<nav className="flex gap-2 items-end"> <nav className="flex gap-2 items-end">
+14 -13
View File
@@ -547,22 +547,10 @@ export default function Logs() {
}; };
return ( return (
<div className="space-y-4"> <div className="mx-auto max-w-6xl space-y-4 p-6">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<h2 className="text-xl font-semibold text-gray-100">Redeploy-Logs</h2> <h2 className="text-xl font-semibold text-gray-100">Redeploy-Logs</h2>
<div className="flex flex-wrap items-center gap-3"> <div className="flex flex-wrap items-center gap-3">
<div className="flex items-center gap-2 text-sm text-gray-400">
<span>Einträge pro Seite</span>
<select
value={perPage}
onChange={handlePerPageChange}
className="rounded-md border border-gray-700 bg-gray-900/70 px-3 py-1.5 text-gray-200 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
{PER_PAGE_OPTIONS.map(({ value, label }) => (
<option key={value} value={value}>{label}</option>
))}
</select>
</div>
<button <button
onClick={() => handleExport('txt')} onClick={() => handleExport('txt')}
disabled={actionLoading || loading} disabled={actionLoading || loading}
@@ -830,6 +818,19 @@ export default function Logs() {
)} )}
</div> </div>
<div className="flex flex-wrap items-center justify-end gap-2 text-sm text-gray-300">
<span>Einträge pro Seite</span>
<select
value={perPage}
onChange={handlePerPageChange}
className="rounded-md border border-gray-700 bg-gray-900 px-2 py-1 text-sm text-gray-100 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500"
>
{PER_PAGE_OPTIONS.map(({ value, label }) => (
<option key={value} value={value}>{label}</option>
))}
</select>
</div>
<div className="overflow-x-auto bg-gray-800/60 rounded-xl border border-gray-700"> <div className="overflow-x-auto bg-gray-800/60 rounded-xl border border-gray-700">
<table className="min-w-full divide-y divide-gray-700"> <table className="min-w-full divide-y divide-gray-700">
<thead className="bg-gray-800"> <thead className="bg-gray-800">
+46 -53
View File
@@ -24,7 +24,6 @@ export default function Stacks() {
const [selectionPromptVisible, setSelectionPromptVisible] = useState(false); const [selectionPromptVisible, setSelectionPromptVisible] = useState(false);
const [rememberSelectionChoice, setRememberSelectionChoice] = useState(false); const [rememberSelectionChoice, setRememberSelectionChoice] = useState(false);
const [selectionPreferenceStored, setSelectionPreferenceStored] = useState(false); const [selectionPreferenceStored, setSelectionPreferenceStored] = useState(false);
const [showRedeployingOnly, setShowRedeployingOnly] = useState(false);
const [perPage, setPerPage] = useState(PER_PAGE_DEFAULT); const [perPage, setPerPage] = useState(PER_PAGE_DEFAULT);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
@@ -111,7 +110,6 @@ export default function Stacks() {
return stacks.filter((stack) => { return stacks.filter((stack) => {
if (statusFilter === "current" && stack.updateStatus !== '✅') return false; if (statusFilter === "current" && stack.updateStatus !== '✅') return false;
if (statusFilter === "outdated" && stack.updateStatus === '✅') return false; if (statusFilter === "outdated" && stack.updateStatus === '✅') return false;
if (showRedeployingOnly && !stack.redeploying) return false;
if (normalizedSearch) { if (normalizedSearch) {
const identifier = `${stack.Name ?? ""} ${stack.Id ?? ""}`.toLowerCase(); const identifier = `${stack.Name ?? ""} ${stack.Id ?? ""}`.toLowerCase();
@@ -120,7 +118,7 @@ export default function Stacks() {
return true; return true;
}); });
}, [stacks, statusFilter, normalizedSearch, showRedeployingOnly]); }, [stacks, statusFilter, normalizedSearch]);
const eligibleFilteredStacks = useMemo( const eligibleFilteredStacks = useMemo(
() => filteredStacks.filter((stack) => stack.updateStatus !== '✅' && !stack.redeployDisabled), () => filteredStacks.filter((stack) => stack.updateStatus !== '✅' && !stack.redeployDisabled),
@@ -154,7 +152,7 @@ export default function Stacks() {
); );
const selectionPreferenceRef = useRef({ action: 'keep', remember: false }); const selectionPreferenceRef = useRef({ action: 'keep', remember: false });
const previousFiltersRef = useRef({ status: statusFilter, search: normalizedSearch, redeploying: showRedeployingOnly }); const previousFiltersRef = useRef({ status: statusFilter, search: normalizedSearch });
const didMountRef = useRef(false); const didMountRef = useRef(false);
useEffect(() => { useEffect(() => {
@@ -177,28 +175,28 @@ export default function Stacks() {
const normalized = normalizedSearch; const normalized = normalizedSearch;
if (!didMountRef.current) { if (!didMountRef.current) {
didMountRef.current = true; didMountRef.current = true;
previousFiltersRef.current = { status: statusFilter, search: normalized, redeploying: showRedeployingOnly }; previousFiltersRef.current = { status: statusFilter, search: normalized };
return; return;
} }
const prev = previousFiltersRef.current; const prev = previousFiltersRef.current;
const filtersChanged = prev.status !== statusFilter || prev.search !== normalized || prev.redeploying !== showRedeployingOnly; const filtersChanged = prev.status !== statusFilter || prev.search !== normalized;
if (!filtersChanged) { if (!filtersChanged) {
if (statusFilter === 'all' && normalized.length === 0 && !showRedeployingOnly && selectionPromptVisible) { if (statusFilter === 'all' && normalized.length === 0 && selectionPromptVisible) {
setSelectionPromptVisible(false); setSelectionPromptVisible(false);
} }
return; return;
} }
previousFiltersRef.current = { status: statusFilter, search: normalized, redeploying: showRedeployingOnly }; previousFiltersRef.current = { status: statusFilter, search: normalized };
if (selectedStackIds.length === 0) { if (selectedStackIds.length === 0) {
setSelectionPromptVisible(false); setSelectionPromptVisible(false);
return; return;
} }
const hasFilters = statusFilter !== 'all' || normalized.length > 0 || showRedeployingOnly; const hasFilters = statusFilter !== 'all' || normalized.length > 0;
if (!hasFilters) { if (!hasFilters) {
setSelectionPromptVisible(false); setSelectionPromptVisible(false);
return; return;
@@ -223,11 +221,11 @@ export default function Stacks() {
} }
}, [selectedStackIds.length, selectionPromptVisible]); }, [selectedStackIds.length, selectionPromptVisible]);
const hasActiveFilters = statusFilter !== "all" || normalizedSearch.length > 0 || showRedeployingOnly; const hasActiveFilters = statusFilter !== "all" || normalizedSearch.length > 0;
useEffect(() => { useEffect(() => {
setPage(1); setPage(1);
}, [statusFilter, normalizedSearch, showRedeployingOnly]); }, [statusFilter, normalizedSearch]);
useEffect(() => { useEffect(() => {
if (perPage === 'all') { if (perPage === 'all') {
@@ -452,7 +450,7 @@ export default function Stacks() {
if (error) return <p className="text-red-400">{error}</p>; if (error) return <p className="text-red-400">{error}</p>;
return ( return (
<div className="p-6"> <div className="mx-auto max-w-6xl p-6">
<div className="mb-6 flex flex-col gap-4 md:flex-row md:items-end md:justify-between"> <div className="mb-6 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="flex flex-col gap-4 md:flex-row md:items-end md:gap-6"> <div className="flex flex-col gap-4 md:flex-row md:items-end md:gap-6">
<div> <div>
@@ -491,18 +489,9 @@ export default function Stacks() {
placeholder="Name oder ID" placeholder="Name oder ID"
className="w-full rounded-lg border border-gray-700 bg-gray-900 px-3 py-2 text-sm text-gray-100 placeholder-gray-500 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500" className="w-full rounded-lg border border-gray-700 bg-gray-900 px-3 py-2 text-sm text-gray-100 placeholder-gray-500 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500"
/> />
<label className="mt-2 flex items-center gap-2 text-sm text-gray-300">
<input
type="checkbox"
checked={showRedeployingOnly}
onChange={(event) => setShowRedeployingOnly(event.target.checked)}
className="h-4 w-4 rounded border border-purple-400 bg-gray-900 text-purple-400 focus:ring-purple-400"
/>
<span>Nur Redeploy läuft</span>
</label>
</div> </div>
</div> </div>
<div className="flex flex-col items-start gap-2 md:items-end"> <div className="flex flex-col items-end gap-2">
{selectionPreferenceStored && ( {selectionPreferenceStored && (
<button <button
type="button" type="button"
@@ -519,18 +508,8 @@ export default function Stacks() {
> >
{bulkButtonLabel} {bulkButtonLabel}
</button> </button>
</div> <label className="flex items-center gap-2 text-sm text-gray-300">
</div> <span>Einträge pro Seite</span>
<div className="mb-6 flex flex-col gap-3 text-sm text-gray-300 md:flex-row md:items-center md:justify-between">
<div>
{totalItems === 0
? 'Keine Stacks verfügbar'
: `Zeige ${pageStart}-${pageEnd} von ${totalItems} Stacks`}
</div>
<div className="flex flex-wrap items-center gap-3">
<label className="flex items-center gap-2">
<span>Einträge pro Seite:</span>
<select <select
value={perPage} value={perPage}
onChange={handlePerPageChange} onChange={handlePerPageChange}
@@ -543,25 +522,6 @@ export default function Stacks() {
))} ))}
</select> </select>
</label> </label>
<div className="flex items-center gap-2">
<button
type="button"
onClick={handlePrevPage}
disabled={page <= 1 || totalItems === 0 || perPage === 'all'}
className="rounded-md border border-gray-700 px-2 py-1 text-xs font-medium uppercase tracking-wide text-gray-200 transition hover:bg-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
>
Zurück
</button>
<span>{`Seite ${Math.min(page, totalPages)} / ${totalPages}`}</span>
<button
type="button"
onClick={handleNextPage}
disabled={perPage === 'all' || totalItems === 0 || page >= totalPages}
className="rounded-md border border-gray-700 px-2 py-1 text-xs font-medium uppercase tracking-wide text-gray-200 transition hover:bg-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
>
Weiter
</button>
</div>
</div> </div>
</div> </div>
@@ -716,6 +676,39 @@ export default function Stacks() {
</p> </p>
)} )}
</div> </div>
<div className="mt-6 flex flex-wrap items-center justify-between gap-3 text-sm text-gray-300">
<span>
{totalItems === 0
? 'Keine Stacks verfügbar'
: perPage === 'all'
? `Zeige alle ${totalItems} Stacks`
: `Zeige ${pageStart}-${pageEnd} von ${totalItems} Stacks`}
</span>
{perPage !== 'all' && totalItems > 0 && (
<div className="flex items-center gap-2">
<button
type="button"
onClick={handlePrevPage}
disabled={page <= 1}
className="rounded-md border border-gray-600 px-3 py-1 text-sm text-gray-200 transition hover:bg-gray-700 disabled:opacity-60"
>
Zurück
</button>
<span className="text-gray-400">
Seite {Math.min(page, totalPages)} / {totalPages}
</span>
<button
type="button"
onClick={handleNextPage}
disabled={page >= totalPages}
className="rounded-md border border-gray-600 px-3 py-1 text-sm text-gray-200 transition hover:bg-gray-700 disabled:opacity-60"
>
Weiter
</button>
</div>
)}
</div>
</div> </div>
); );
} }