🐳 Added Docker vars in the UI

This commit is contained in:
2025-07-14 12:06:04 -04:00
parent 0def39cd73
commit 80f0ced0b0
3 changed files with 125 additions and 95 deletions

View File

@@ -0,0 +1,38 @@
import React from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
export const DockerVars = (): React.ReactElement => {
const [environmentVariables, setEnvironmentVariables] = React.useState<
Record<string, string>
>({});
const { data } = useQuery({
queryKey: ["docker-vars"],
queryFn: async () => {
await axios({
method: "GET",
url: "http://localhost:3000/api/settings/getEnvironmentVariables",
}).then((response) => {
setEnvironmentVariables(response.data);
});
},
refetchOnWindowFocus: false,
refetchOnReconnect: false,
});
console.log("Docker Vars: ", environmentVariables);
return (
<div className="flex flex-col gap-4">
<h2 className="text-xl font-semibold">Docker Environment Variables</h2>
<p className="text-gray-600 dark:text-gray-400">
<pre>
{Object.entries(environmentVariables).length > 0
? JSON.stringify(environmentVariables, null, 2)
: "No environment variables found."}
</pre>
</p>
{/* Add your form or content for Docker environment variables here */}
</div>
);
};
export default DockerVars;

View File

@@ -4,6 +4,7 @@ import { AirDCPPHubsForm } from "./AirDCPPSettings/AirDCPPHubsForm";
import { QbittorrentConnectionForm } from "./QbittorrentSettings/QbittorrentConnectionForm"; import { QbittorrentConnectionForm } from "./QbittorrentSettings/QbittorrentConnectionForm";
import { SystemSettingsForm } from "./SystemSettings/SystemSettingsForm"; import { SystemSettingsForm } from "./SystemSettings/SystemSettingsForm";
import ProwlarrSettingsForm from "./ProwlarrSettings/ProwlarrSettingsForm"; import ProwlarrSettingsForm from "./ProwlarrSettings/ProwlarrSettingsForm";
import DockerVars from "./DockerVars/DockerVars";
import { ServiceStatuses } from "../ServiceStatuses/ServiceStatuses"; import { ServiceStatuses } from "../ServiceStatuses/ServiceStatuses";
import settingsObject from "../../constants/settings/settingsMenu.json"; import settingsObject from "../../constants/settings/settingsMenu.json";
import { isUndefined, map } from "lodash"; import { isUndefined, map } from "lodash";
@@ -12,139 +13,130 @@ interface ISettingsProps {}
export const Settings = (props: ISettingsProps): ReactElement => { export const Settings = (props: ISettingsProps): ReactElement => {
const [active, setActive] = useState("gen-db"); const [active, setActive] = useState("gen-db");
console.log(active); const [expanded, setExpanded] = useState<Record<string, boolean>>({});
const toggleExpanded = (id: string) => {
setExpanded((prev) => ({
...prev,
[id]: !prev[id],
}));
};
const settingsContent = [ const settingsContent = [
{ { id: "adc-hubs", content: <AirDCPPHubsForm /> },
id: "adc-hubs", { id: "adc-connection", content: <AirDCPPSettingsForm /> },
content: ( {id: "gen-docker-vars", content: <DockerVars />},
<div key="adc-hubs"> { id: "qbt-connection", content: <QbittorrentConnectionForm /> },
<AirDCPPHubsForm /> { id: "prwlr-connection", content: <ProwlarrSettingsForm /> },
</div> { id: "core-service", content: <>a</> },
), { id: "flushdb", content: <SystemSettingsForm /> },
},
{
id: "adc-connection",
content: (
<div key="adc-connection">
<AirDCPPSettingsForm />
</div>
),
},
{
id: "qbt-connection",
content: (
<div key="qbt-connection">
<QbittorrentConnectionForm />
</div>
),
},
{
id: "prwlr-connection",
content: (
<>
<ProwlarrSettingsForm />
</>
),
},
{
id: "core-service",
content: <>a</>,
},
{
id: "flushdb",
content: (
<div key="flushdb">
<SystemSettingsForm />
</div>
),
},
]; ];
return ( return (
<div> <div>
<section> <section>
{/* Header */}
<header className="bg-slate-200 dark:bg-slate-500"> <header className="bg-slate-200 dark:bg-slate-500">
<div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4"> <div className="mx-auto max-w-screen-xl px-4 py-6 sm:px-6 lg:px-8">
<div className="sm:flex sm:items-center sm:justify-between"> <div className="sm:flex sm:items-center sm:justify-between">
<div className="text-center sm:text-left"> <div className="text-center sm:text-left">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl"> <h1 className="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
Settings Settings
</h1> </h1>
<p className="mt-1 text-sm text-gray-500 dark:text-white">
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
Import comics into the ThreeTwo library. Import comics into the ThreeTwo library.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</header> </header>
<div className="flex flex-row">
<div className="inset-y-0 w-80 dark:bg-gray-800 bg-slate-300 text-white overflow-y-auto"> {/* Main Layout */}
<aside className="px-4 py-4 sm:px-6 sm:py-8 lg:px-8"> <div className="flex gap-8 px-12 py-6">
{map(settingsObject, (settingObject, idx) => { {/* Sidebar */}
return ( <div className="relative z-30">
<aside
className="sticky top-6 w-72 max-h-[90vh]
rounded-2xl shadow-xl backdrop-blur-md
bg-white/70 dark:bg-slate-800/60
border border-slate-200 dark:border-slate-700
overflow-hidden"
>
<div className="px-4 py-6 overflow-y-auto">
{map(settingsObject, (settingObject, idx) => (
<div <div
className="w-64 py-2 text-slate-700 dark:text-slate-400"
key={idx} key={idx}
className="mb-6 text-slate-700 dark:text-slate-300"
> >
<h3 className="text-l pb-2"> <h3 className="text-xs font-semibold text-slate-500 dark:text-slate-400 tracking-wide mb-3">
{settingObject.category.toUpperCase()} {settingObject.category.toUpperCase()}
</h3> </h3>
{/* First level children */}
{!isUndefined(settingObject.children) ? ( {!isUndefined(settingObject.children) && (
<ul key={settingObject.id}> <ul>
{map(settingObject.children, (item, idx) => { {map(settingObject.children, (item, idx) => {
const isOpen = expanded[item.id];
return ( return (
<li key={idx} className="mb-2"> <li key={idx} className="mb-1">
<a <div
className={ onClick={() => toggleExpanded(item.id)}
item.id.toString() === active className={`cursor-pointer flex justify-between items-center px-1 py-1 rounded-md transition-colors hover:bg-white/50 dark:hover:bg-slate-700 ${
? "is-active flex items-center" item.id === active
: "flex items-center" ? "font-semibold text-blue-600 dark:text-blue-400"
} : ""
onClick={() => setActive(item.id.toString())} }`}
> >
{item.displayName} <span
</a> onClick={() => setActive(item.id.toString())}
{/* Second level children */} className="flex-1"
{!isUndefined(item.children) ? ( >
<ul className="pl-4 mt-2"> {item.displayName}
{map(item.children, (item, idx) => ( </span>
<li key={item.id} className="mb-2"> {!isUndefined(item.children) && (
<span className="text-xs opacity-60">
{isOpen ? "" : "+"}
</span>
)}
</div>
{!isUndefined(item.children) && isOpen && (
<ul className="pl-4 mt-1">
{map(item.children, (subItem) => (
<li key={subItem.id} className="mb-1">
<a <a
className={
item.id.toString() === active
? "is-active flex items-center"
: "flex items-center"
}
onClick={() => onClick={() =>
setActive(item.id.toString()) setActive(subItem.id.toString())
} }
className={`cursor-pointer flex items-center px-1 py-1 rounded-md transition-colors hover:bg-white/50 dark:hover:bg-slate-700 ${
subItem.id.toString() === active
? "font-semibold text-blue-600 dark:text-blue-400"
: ""
}`}
> >
{item.displayName} {subItem.displayName}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
) : null} )}
</li> </li>
); );
})} })}
</ul> </ul>
) : null} )}
</div> </div>
); ))}
})} </div>
</aside> </aside>
</div> </div>
{/* content for settings */} {/* Content */}
<div className="flex mx-12"> <main className="flex-1 px-2 py-2">
<div className=""> {settingsContent.map(({ id, content }) =>
{map(settingsContent, ({ id, content }) => active === id ? <div key={id}>{content}</div> : null,
active === id ? content : null, )}
)} </main>
</div>
</div>
</div> </div>
</section> </section>
</div> </div>

View File

@@ -9,8 +9,8 @@
"displayName": "Dashboard" "displayName": "Dashboard"
}, },
{ {
"id": "gen-gls", "id": "gen-docker-vars",
"displayName": "Global Search" "displayName": "Docker ENV vars"
} }
] ]
}, },