Skip to content

Commit 599638d

Browse files
committed
feat: Migrate legacy Slack settings to group-specific plugin settings and enhance plugin management
1 parent fdd7dc2 commit 599638d

6 files changed

Lines changed: 257 additions & 219 deletions

File tree

‎server/frontend/src/Components/Plugins/Plugins.jsx‎

Lines changed: 152 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -6,172 +6,210 @@ import { useNavigate } from "react-router-dom";
66

77
const Plugins = () => {
88
const navigate = useNavigate("");
9-
const clickAndNavigate = function (path) {
10-
navigate(`/${path}`);
11-
};
129

1310
const [pluginsArr, setPluginsArr] = useState([]);
1411
const [hostGroups, setHostGroups] = useState([]);
1512
const [addGroupOpen, setAddGroupOpen] = useState(false);
1613
const [newGroupName, setNewGroupName] = useState("");
14+
const [addPluginGroupId, setAddPluginGroupId] = useState(null);
1715

18-
const getPlugginsArr = async function () {
16+
const load = async () => {
1917
const data = await getData("plugins");
2018
if (data) {
2119
setPluginsArr(data.plugins);
2220
if (data.hostGroups) setHostGroups(data.hostGroups);
2321
}
2422
};
2523

26-
useEffect(() => {
27-
getPlugginsArr();
28-
}, []);
24+
useEffect(() => { load(); }, []);
2925

30-
const disablePlugin = async function (e) {
31-
const data = await apiFetch({ id: e.target.id }, `plugin_disable`);
32-
if (data.status === "success") {
33-
getPlugginsArr();
34-
}
26+
const disablePlugin = async (id) => {
27+
const data = await apiFetch({ id }, `plugin_disable`);
28+
if (data.status === "success") load();
3529
};
3630

3731
const createGroup = async () => {
3832
if (!newGroupName.trim()) return;
39-
await apiFetch({ name: newGroupName.trim(), slackWebhook: "" }, "host_groups");
33+
await apiFetch({ name: newGroupName.trim() }, "host_groups");
4034
setNewGroupName("");
4135
setAddGroupOpen(false);
42-
getPlugginsArr();
36+
load();
4337
};
4438

4539
const deleteGroup = async (id) => {
4640
if (!window.confirm("Delete this group? Hosts will become ungrouped.")) return;
4741
await apiFetch({}, `host_groups/${id}/delete`);
48-
getPlugginsArr();
42+
load();
43+
};
44+
45+
const removePluginFromGroup = async (groupId, pluginId) => {
46+
await apiFetch({}, `host_groups/${groupId}/plugin/${pluginId}/remove`);
47+
load();
4948
};
5049

51-
const slackPlugin = pluginsArr.find((p) => p.id === "slack-notifications");
50+
const availablePlugins = pluginsArr;
5251

5352
return (
5453
<div>
5554
<div className="container mx-auto flex justify-center px-4">
5655
<div className="min-w-2/3 p-4 my-5 bg-gray-100 rounded-lg shadow-md sm:p-8 dark:bg-gray-600 dark:border-gray-700">
5756
<div className="flex justify-between items-center mb-5">
58-
<div>
59-
<a
60-
onClick={() => { clickAndNavigate("home"); }}
61-
className="text-white dark:text-gray-800 bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none
62-
focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5
63-
text-center mr-3 md:mr-0 dark:bg-green-400 dark:hover:bg-green-500 dark:focus:ring-green-800 flex items-center"
64-
>
65-
<svg
66-
xmlns="http://www.w3.org/2000/svg"
67-
className="h-5 w-5 mr-2"
68-
viewBox="0 0 20 20"
69-
fill="currentColor"
70-
>
71-
<path
72-
fillRule="evenodd"
73-
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
74-
clipRule="evenodd"
75-
/>
76-
</svg>
77-
Back to hosts list
78-
</a>
79-
</div>
57+
<a
58+
onClick={() => navigate("/home")}
59+
className="text-white dark:text-gray-800 bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none
60+
focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5
61+
text-center mr-3 md:mr-0 dark:bg-green-400 dark:hover:bg-green-500 dark:focus:ring-green-800 flex items-center cursor-pointer"
62+
>
63+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
64+
<path fillRule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clipRule="evenodd" />
65+
</svg>
66+
Back to hosts list
67+
</a>
8068
</div>
8169

82-
<div className="flex justify-between items-center mb-4">
83-
<h5 className="mb-5 text-3xl font-bold leading-none text-gray-900 dark:text-white">
84-
Available plugins
85-
</h5>
86-
</div>
70+
{/* Plugins section */}
71+
<h5 className="mb-5 text-3xl font-bold leading-none text-gray-900 dark:text-white">
72+
Available plugins
73+
</h5>
8774

88-
<div className="max-w-2/3 gap-x-10 gap-y-10 divide-gray-200 dark:divide-gray-700 flex flex-wrap">
89-
{pluginsArr.map((pl) => {
90-
return (
91-
<div
92-
key={pl.id}
93-
className="w-60 bg-white rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700 flex flex-col"
94-
>
95-
<img
96-
className="w-40 h-40 my-6 mx-auto rounded-t-lg"
97-
src={pl.iconUrlOrBase64}
98-
alt="product image"
99-
/>
100-
<div className="px-6 pb-3"></div>
101-
<div className="flex justify-between items-center px-6 pb-6 mt-auto">
102-
{pl.pluginEnabled ? (
103-
<a
104-
onClick={() => { clickAndNavigate(`plugin/${pl.id}`); }}
105-
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 cursor-pointer"
106-
>
107-
Settings
108-
</a>
109-
) : (
110-
<a
111-
onClick={() => { clickAndNavigate(`plugin/${pl.id}`); }}
112-
className="text-white w-full bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800 cursor-pointer"
113-
>
114-
Enable
115-
</a>
116-
)}
117-
{pl.pluginEnabled ? (
118-
<button
119-
onClick={(e) => { disablePlugin(e); }}
120-
id={pl.id}
121-
className="ml-5 text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800 cursor-pointer"
122-
>
123-
Disable
124-
</button>
125-
) : null}
126-
</div>
75+
<div className="max-w-2/3 gap-x-10 gap-y-10 divide-gray-200 dark:divide-gray-700 flex flex-wrap mb-10">
76+
{pluginsArr.map((pl) => (
77+
<div
78+
key={pl.id}
79+
className="w-60 bg-white rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700 flex flex-col"
80+
>
81+
<img className="w-40 h-40 my-6 mx-auto rounded-t-lg" src={pl.iconUrlOrBase64} alt={pl.id} />
82+
<div className="flex justify-between items-center px-6 pb-6 mt-auto">
83+
{pl.pluginEnabled ? (
84+
<a
85+
onClick={() => navigate(`/plugin/${pl.id}`)}
86+
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 cursor-pointer"
87+
>
88+
Settings
89+
</a>
90+
) : (
91+
<a
92+
onClick={() => navigate(`/plugin/${pl.id}`)}
93+
className="text-white w-full bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800 cursor-pointer"
94+
>
95+
Enable
96+
</a>
97+
)}
98+
{pl.pluginEnabled && (
99+
<button
100+
onClick={() => disablePlugin(pl.id)}
101+
className="ml-5 text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800 cursor-pointer"
102+
>
103+
Disable
104+
</button>
105+
)}
127106
</div>
128-
);
129-
})}
107+
</div>
108+
))}
109+
</div>
110+
111+
{/* Groups section */}
112+
<h5 className="mb-5 text-3xl font-bold leading-none text-gray-900 dark:text-white">
113+
Groups
114+
</h5>
130115

131-
{/* Host group cards */}
116+
<div className="max-w-2/3 gap-x-10 gap-y-10 flex flex-wrap">
132117
{hostGroups.map((g) => (
133118
<div
134-
key={`group_${g.id}`}
119+
key={g.id}
135120
className="w-60 bg-white rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700 flex flex-col"
136121
>
137-
{slackPlugin && (
138-
<img
139-
className="w-40 h-40 my-6 mx-auto rounded-t-lg"
140-
src={slackPlugin.iconUrlOrBase64}
141-
alt="Slack"
142-
/>
122+
<div className="px-6 pt-5 pb-2">
123+
<p className="text-base font-semibold text-gray-900 dark:text-white">{g.name}</p>
124+
</div>
125+
126+
{/* Installed plugins */}
127+
{g.installedPlugins && g.installedPlugins.length > 0 && (
128+
<div className="px-6 pb-2 flex flex-col gap-2">
129+
{g.installedPlugins.map((pluginId) => {
130+
const pl = pluginsArr.find((p) => p.id === pluginId);
131+
return (
132+
<div key={pluginId} className="flex flex-col justify-center gap-2">
133+
<div className="flex gap-2">
134+
{pl && <img className="w-6 h-6 rounded" src={pl.iconUrlOrBase64} alt={pluginId} />}
135+
<span className="text-sm text-gray-700 dark:text-gray-300 flex-1">{pluginId}</span>
136+
</div>
137+
<div className="flex gap-2">
138+
<a
139+
onClick={() => navigate(`/plugin/${pluginId}?groupId=${g.id}`)}
140+
className="text-xs text-center w-full text-white bg-blue-600 hover:bg-blue-700 rounded px-2 py-1 cursor-pointer"
141+
>
142+
Settings
143+
</a>
144+
<button
145+
onClick={() => removePluginFromGroup(g.id, pluginId)}
146+
className="text-xs text-center w-full text-white bg-red-600 hover:bg-red-700 rounded px-2 py-1"
147+
>
148+
Remove
149+
</button>
150+
</div>
151+
</div>
152+
);
153+
})}
154+
</div>
143155
)}
144-
<div className="px-6 pb-3 text-center">
145-
<p className="text-sm font-semibold text-gray-900 dark:text-white">{g.name}</p>
146-
{g.channelName && (
147-
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">{g.channelName}</p>
156+
157+
{/* Add plugin button */}
158+
<div className="px-6 pb-4 my-auto pt-3">
159+
{addPluginGroupId === g.id ? (
160+
<div className="flex flex-col gap-2">
161+
<select
162+
className="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
163+
defaultValue=""
164+
onChange={(e) => {
165+
if (e.target.value) {
166+
navigate(`/plugin/${e.target.value}?groupId=${g.id}`);
167+
}
168+
}}
169+
>
170+
<option value="" disabled>Select plugin…</option>
171+
{availablePlugins
172+
.filter((p) => !g.installedPlugins?.includes(p.id))
173+
.map((p) => (
174+
<option key={p.id} value={p.id}>{p.id}</option>
175+
))}
176+
</select>
177+
<button
178+
onClick={() => setAddPluginGroupId(null)}
179+
className="text-xs text-gray-600 dark:text-gray-300 underline"
180+
>
181+
Cancel
182+
</button>
183+
</div>
184+
) : (
185+
<div className="flex flex-col gap-2 items-center">
186+
<button
187+
onClick={() => setAddPluginGroupId(g.id)}
188+
disabled={availablePlugins.filter((p) => !g.installedPlugins?.includes(p.id)).length === 0}
189+
className="text-white w-full bg-green-700 hover:bg-green-800 disabled:opacity-40 font-medium rounded-lg text-sm px-4 py-2 dark:bg-green-600 dark:hover:bg-green-700"
190+
>
191+
+ Add Plugin
192+
</button>
193+
<button
194+
onClick={() => deleteGroup(g.id)}
195+
className="text-white w-full bg-red-700 hover:bg-red-800 font-medium rounded-lg text-sm px-4 py-2 dark:bg-red-600 dark:hover:bg-red-700"
196+
>
197+
Delete Group
198+
</button>
199+
</div>
148200
)}
149201
</div>
150-
<div className="flex justify-between items-center px-6 pb-6 mt-auto">
151-
<a
152-
onClick={() => { navigate(`/plugin/slack-notifications?groupId=${g.id}`); }}
153-
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 cursor-pointer"
154-
>
155-
Settings
156-
</a>
157-
<button
158-
onClick={() => deleteGroup(g.id)}
159-
className="ml-5 text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800 cursor-pointer"
160-
>
161-
Delete
162-
</button>
163-
</div>
164202
</div>
165203
))}
166204

167205
{/* Add group card */}
168-
<div className="w-60 bg-white rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700 flex flex-col items-center justify-center min-h-[280px]">
206+
<div className="w-60 bg-white rounded-lg shadow-md dark:bg-gray-800 dark:border-gray-700 flex flex-col items-center justify-center min-h-[160px]">
169207
{!addGroupOpen ? (
170208
<button
171209
onClick={() => setAddGroupOpen(true)}
172-
className="text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800 cursor-pointer"
210+
className="text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-green-600 dark:hover:bg-green-700"
173211
>
174-
+ Add Slack Group
212+
+ Add Group
175213
</button>
176214
) : (
177215
<div className="p-4 w-full">
@@ -182,6 +220,7 @@ const Plugins = () => {
182220
className="w-full mb-3 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:text-white"
183221
placeholder="Group name"
184222
onKeyDown={(e) => e.key === "Enter" && createGroup()}
223+
autoFocus
185224
/>
186225
<div className="flex gap-2">
187226
<button
@@ -203,47 +242,6 @@ const Plugins = () => {
203242
</div>
204243
</div>
205244
</div>
206-
207-
<div
208-
id="toast-copied"
209-
className="fixed right-2 bottom-2 hidden flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-green-900 dark:bg-green-100"
210-
role="alert"
211-
>
212-
<div className="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-100 dark:text-green-900">
213-
<svg
214-
className="w-5 h-5"
215-
fill="currentColor"
216-
viewBox="0 0 20 20"
217-
xmlns="http://www.w3.org/2000/svg"
218-
>
219-
<path
220-
fillRule="evenodd"
221-
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
222-
clipRule="evenodd"
223-
></path>
224-
</svg>
225-
</div>
226-
<div className="ml-3 text-sm font-normal">Copied to clipboard!</div>
227-
<button
228-
type="button"
229-
className="bg-transparent ml-auto -mx-1.5 -my-1.5 text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 inline-flex h-8 w-8 dark:text-green-900 dark:hover:text-black"
230-
aria-label="Close"
231-
>
232-
<span className="sr-only">Close</span>
233-
<svg
234-
className="w-5 h-5"
235-
fill="currentColor"
236-
viewBox="0 0 20 20"
237-
xmlns="http://www.w3.org/2000/svg"
238-
>
239-
<path
240-
fillRule="evenodd"
241-
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
242-
clipRule="evenodd"
243-
></path>
244-
</svg>
245-
</button>
246-
</div>
247245
</div>
248246
);
249247
};

0 commit comments

Comments
 (0)