Skip to content

Commit c2f18dd

Browse files
Merge pull request #45 from oidebrett/dev
Updated ConfirmationModal to support alerts and changed alerts in PluginContext
2 parents c629337 + 1ce0400 commit c2f18dd

File tree

5 files changed

+89
-18
lines changed

5 files changed

+89
-18
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,14 +390,27 @@ Switch `active_data_source` and update URLs/credentials via the **Settings** pan
390390
go run main.go
391391
# For build:
392392
# go build -o middleware-manager main.go
393+
394+
```
395+
Tip: Use this command if you need to set a hostname record in /etc/hosts for pangolin based on the internal IP for the pangolin container
396+
```
397+
docker network inspect -v pangolin
393398
```
394399

395400
### Frontend
396401

397402
```bash
398403
cd ui
399-
# If node_modules is missing: npm install (or yarn install)
400-
npm start # or yarn start
404+
cp src/package.json .
405+
npm install
406+
npm start
407+
```
408+
Note: if you are getting an error such as `options.allowedHosts[0] should be a non-empty string`
409+
410+
you should create a .env file in /ui folder. Do not do this in production (development only)
411+
412+
```sh
413+
DANGEROUSLY_DISABLE_HOST_CHECK=true
401414
```
402415

403416
## License

ui/src/components/common/ConfirmationModal.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,50 @@ import React from 'react';
1212
* @param {Function} props.onConfirm - Function to call when confirmed
1313
* @param {Function} props.onCancel - Function to call when cancelled
1414
* @param {boolean} props.show - Whether to show the modal
15+
* @param {string} props.mode - Whether this is a confirmation or an alert
1516
* @returns {JSX.Element}
1617
*/
1718
const ConfirmationModal = ({
1819
title,
1920
message,
2021
details,
21-
confirmText = "Confirm",
22-
cancelText = "Cancel",
22+
confirmText,
23+
cancelText,
2324
onConfirm,
2425
onCancel,
25-
show
26+
show,
27+
mode = "confirm" // new prop, default to "confirm"
2628
}) => {
2729
if (!show) return null;
28-
30+
31+
// Use different button texts and visibility based on mode
32+
const isAlert = mode === "alert";
33+
const confirmButtonText = confirmText || (isAlert ? "OK" : "Confirm");
34+
const cancelButtonText = cancelText || "Cancel";
35+
2936
return (
3037
<div className="modal-overlay">
31-
<div className="modal-content max-w-md"> {/* Standard width */}
38+
<div className="modal-content max-w-md">
3239
<div className="modal-header">
33-
<h3 className="modal-title text-red-600 dark:text-red-400">{title}</h3>
34-
<button onClick={onCancel} className="modal-close-button">&times;</button>
40+
<h3 className={`modal-title ${isAlert ? 'text-blue-600 dark:text-blue-400' : 'text-red-600 dark:text-red-400'}`}>
41+
{title}
42+
</h3>
43+
{/* For alerts, closing the modal can just call onConfirm or onCancel or be hidden */}
44+
<button onClick={isAlert ? onConfirm : onCancel} className="modal-close-button">&times;</button>
3545
</div>
3646
<div className="modal-body">
3747
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">{message}</p>
3848
{details && <p className="text-xs text-gray-500 dark:text-gray-400 mb-4">{details}</p>}
3949
</div>
4050
<div className="modal-footer">
41-
<button onClick={onCancel} className="btn btn-secondary">{cancelText}</button>
42-
<button onClick={onConfirm} className="btn btn-danger">{confirmText}</button>
51+
{!isAlert && (
52+
<button onClick={onCancel} className="btn btn-secondary">
53+
{cancelButtonText}
54+
</button>
55+
)}
56+
<button onClick={onConfirm} className={isAlert ? "btn btn-primary" : "btn btn-danger"}>
57+
{confirmButtonText}
58+
</button>
4359
</div>
4460
</div>
4561
</div>

ui/src/components/plugins/PluginCard.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const PluginCard = ({ plugin }) => {
104104
<div className="flex-shrink-0 flex flex-col items-end space-y-1"> {/* Badge and Stars container */}
105105
{isActuallyInstalled && (
106106
<span className="badge badge-success text-xs whitespace-nowrap py-0.5 px-1.5">
107-
Installed {plugin.installedVersion && `(v${plugin.installedVersion})`}
107+
Installed {plugin.installedVersion && `(${plugin.installedVersion})`}
108108
</span>
109109
)}
110110
{plugin.stars !== undefined && ( // Show stars always if available, or conditionally if space is an issue

ui/src/components/plugins/PluginHub.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// ui/src/components/plugins/PluginHub.js
22
import React, { useEffect, useState } from 'react';
33
import { usePlugins } from '../../contexts/PluginContext';
4-
import { LoadingSpinner, ErrorMessage as GlobalErrorMessage } from '../common'; // Renamed to avoid conflict
4+
import { LoadingSpinner, ErrorMessage as GlobalErrorMessage, ConfirmationModal } from '../common'; // Renamed to avoid conflict
55
import PluginCard from './PluginCard';
66

77
const PluginHub = () => {
@@ -20,6 +20,11 @@ const PluginHub = () => {
2020
const [currentPath, setCurrentPath] = useState('');
2121
const [pathError, setPathError] = useState('');
2222
const [pathSaving, setPathSaving] = useState(false);
23+
const [showWarningModal, setShowWarningModal] = useState(false);
24+
25+
const cancelWarning = () => {
26+
setShowWarningModal(false);
27+
};
2328

2429
useEffect(() => {
2530
if (traefikConfigPath) {
@@ -40,8 +45,18 @@ const PluginHub = () => {
4045
// Error is set in context, this is for immediate feedback
4146
setPathError(error || 'Failed to update path. Check console.');
4247
} else {
43-
alert('Path updated. This change is in-memory and will be lost on application restart unless persisted in the backend configuration (e.g., environment variable or config file).');
44-
}
48+
{/* Warning Confirmation Modal */}
49+
<ConfirmationModal
50+
show={showWarningModal}
51+
title="Warning"
52+
message={`Path updated. `}
53+
details="This change is in-memory and will be lost on application restart unless persisted in the backend configuration (e.g., environment variable or config file)."
54+
cancelText="Cancel"
55+
onCancel={cancelWarning}
56+
/>
57+
// alert('Path updated. This change is in-memory and will be lost on application restart unless persisted in the backend configuration (e.g., environment variable or config file).');
58+
59+
}
4560
setPathSaving(false);
4661
};
4762

ui/src/contexts/PluginContext.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import React, { createContext, useState, useContext, useCallback, useEffect } from 'react';
33
// Ensure GlobalErrorMessage is imported correctly if you use it elsewhere in this file.
44
// For now, local error display via alert/setError should suffice for these new functions.
5+
import { ConfirmationModal } from '../components/common';
56

67
const API_URL = '/api/plugins';
78

@@ -13,6 +14,7 @@ export const PluginProvider = ({ children }) => {
1314
const [error, setError] = useState(null);
1415
const [traefikConfigPath, setTraefikConfigPath] = useState('');
1516
const [fetchingPath, setFetchingPath] = useState(true);
17+
const [alert, setAlert] = useState({ show: false, title: '', message: '' });
1618

1719
const fetchPlugins = useCallback(async () => {
1820
setLoading(true);
@@ -48,7 +50,12 @@ export const PluginProvider = ({ children }) => {
4850
throw new Error(errData.message);
4951
}
5052
const result = await response.json();
51-
alert(result.message || 'Plugin installation initiated successfully! Restart Traefik to apply.');
53+
//alert(result.message || 'Plugin installation initiated successfully! Restart Traefik to apply.'); // Alert is now in the Confirmation Dialog component
54+
setAlert({
55+
show: true,
56+
title: 'Success',
57+
message: result.message || 'Plugin installation initiated successfully! Restart Traefik to apply.',
58+
});
5259
fetchPlugins(); // Refresh plugin list to show installed status
5360
return true;
5461
} catch (err) {
@@ -75,7 +82,12 @@ export const PluginProvider = ({ children }) => {
7582
throw new Error(errData.message);
7683
}
7784
const result = await response.json();
78-
alert(result.message || 'Plugin removal initiated successfully! Restart Traefik to apply.');
85+
//alert(result.message || 'Plugin removal initiated successfully! Restart Traefik to apply.'); // Alert is now in the Confirmation Dialog component
86+
setAlert({
87+
show: true,
88+
title: 'Success',
89+
message: result.message || 'Plugin removal initiated successfully! Restart Traefik to apply.',
90+
});
7991
fetchPlugins(); // Refresh plugin list
8092
return true;
8193
} catch (err) {
@@ -124,7 +136,12 @@ export const PluginProvider = ({ children }) => {
124136
}
125137
const data = await response.json();
126138
setTraefikConfigPath(data.path || '');
127-
alert(data.message || 'Traefik config path updated successfully!');
139+
//alert(data.message || 'Traefik config path updated successfully!'); // Alert is now in the Confirmation Dialog component
140+
setAlert({
141+
show: true,
142+
title: 'Success',
143+
message: data.message || 'Traefik config path updated successfully!',
144+
});
128145
return true;
129146
} catch (err) {
130147
console.error('Failed to update Traefik config path:', err);
@@ -158,6 +175,16 @@ export const PluginProvider = ({ children }) => {
158175
return (
159176
<PluginContext.Provider value={value}>
160177
{children}
178+
179+
{/* Alert modal rendered here */}
180+
<ConfirmationModal
181+
show={alert.show}
182+
mode="alert"
183+
title={alert.title}
184+
message={alert.message}
185+
onConfirm={() => setAlert(prev => ({ ...prev, show: false }))}
186+
/>
187+
161188
</PluginContext.Provider>
162189
);
163190
};

0 commit comments

Comments
 (0)