EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
WordPressSiteModal.jsx
Go to the documentation of this file.
1import { useState, useEffect } from 'react';
2import { apiFetch } from '../lib/api';
3
4export default function WordPressSiteModal({ domain, onClose }) {
5 const [loading, setLoading] = useState(true);
6 const [site, setSite] = useState(null);
7 const [error, setError] = useState('');
8 const [loginUrl, setLoginUrl] = useState('');
9 const [generating, setGenerating] = useState(false);
10
11 useEffect(() => {
12 if (domain) {
13 fetchSiteDetails();
14 }
15 }, [domain]);
16
17 const fetchSiteDetails = async () => {
18 setLoading(true);
19 setError('');
20 try {
21 const res = await apiFetch(`/wordpress/sites/${domain}`);
22 if (res.ok) {
23 const data = await res.json();
24 setSite(data.site);
25 } else {
26 setError('Failed to load site details');
27 }
28 } catch (err) {
29 setError(err.message || 'Failed to load site details');
30 } finally {
31 setLoading(false);
32 }
33 };
34
35 const handleLogin = async () => {
36 setGenerating(true);
37 setLoginUrl('');
38 try {
39 const res = await apiFetch(`/wordpress/sites/${domain}/login`, {
40 method: 'POST'
41 });
42 if (res.ok) {
43 const data = await res.json();
44 // Open login URL in new tab
45 window.open(`https://${domain}/wp-admin/`, '_blank');
46 setLoginUrl(data.loginUrl);
47 } else {
48 alert('Failed to generate login URL');
49 }
50 } catch (err) {
51 alert(err.message || 'Failed to generate login URL');
52 } finally {
53 setGenerating(false);
54 }
55 };
56
57 const handleUpdatePlugin = async (pluginName) => {
58 if (!confirm(`Update plugin "${pluginName}"?`)) return;
59 alert('Plugin update feature coming soon');
60 };
61
62 const handleUpdateCore = async () => {
63 if (!confirm(`Update WordPress core to ${site.core?.updates[0]?.version}?`)) return;
64 alert('Core update feature coming soon');
65 };
66
67 if (!domain) return null;
68
69 return (
70 <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
71 <div className="bg-white rounded-lg shadow-xl max-w-5xl w-full max-h-[90vh] overflow-hidden flex flex-col">
72 {/* Header */}
73 <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between bg-gradient-to-r from-blue-500 to-blue-600 text-white">
74 <div>
75 <h2 className="text-2xl font-bold">{domain}</h2>
76 {site && (
77 <p className="text-blue-100 text-sm mt-1">
78 WordPress {site.wp_version} • {site.plugin_count} plugins • {site.disk_usage}
79 </p>
80 )}
81 </div>
82 <button
83 onClick={onClose}
84 className="text-white hover:text-gray-200 text-2xl font-bold"
85 >
86 ×
87 </button>
88 </div>
89
90 {/* Content */}
91 <div className="flex-1 overflow-y-auto p-6">
92 {loading ? (
93 <div className="flex items-center justify-center h-64">
94 <div className="text-gray-500">Loading site details...</div>
95 </div>
96 ) : error ? (
97 <div className="text-red-500 p-4 bg-red-50 rounded">{error}</div>
98 ) : site ? (
99 <div className="space-y-6">
100 {/* Quick Actions */}
101 <div className="flex gap-3">
102 <button
103 onClick={handleLogin}
104 disabled={generating}
105 className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50 flex items-center gap-2"
106 >
107 <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
108 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
109 </svg>
110 {generating ? 'Generating...' : 'WP Admin Login'}
111 </button>
112 <a
113 href={`https://${domain}`}
114 target="_blank"
115 rel="noopener noreferrer"
116 className="bg-gray-100 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-200 flex items-center gap-2"
117 >
118 <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
119 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
120 </svg>
121 Visit Site
122 </a>
123 </div>
124
125 {/* Security Issues */}
126 {site.malware_issues > 0 && (
127 <div className="bg-red-50 border border-red-200 rounded-lg p-4">
128 <div className="flex items-center gap-2 text-red-800 font-semibold mb-2">
129 <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
130 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
131 </svg>
132 Security Issues Detected
133 </div>
134 <p className="text-red-700">
135 {site.malware_issues} suspicious file{site.malware_issues !== 1 ? 's' : ''} found
136 </p>
137 {site.security && (
138 <div className="mt-3 text-sm text-red-600 space-y-1">
139 {site.security.php_in_uploads > 0 && <div>• {site.security.php_in_uploads} PHP files in uploads directory</div>}
140 {site.security.suspicious_files > 0 && <div>• {site.security.suspicious_files} suspicious files</div>}
141 {site.security.modified_core_files > 0 && <div>• {site.security.modified_core_files} modified core files</div>}
142 </div>
143 )}
144 <button className="mt-3 bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 text-sm">
145 Clean Threats (Coming Soon)
146 </button>
147 </div>
148 )}
149
150 {/* WordPress Core */}
151 {site.core && (
152 <div className="bg-white border border-gray-200 rounded-lg p-4">
153 <h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
154 <svg className="w-5 h-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
155 <path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z" />
156 </svg>
157 WordPress Core
158 </h3>
159 <div className="flex items-center justify-between">
160 <div>
161 <div className="text-sm text-gray-600">Current Version</div>
162 <div className="font-medium">{site.core.version}</div>
163 </div>
164 {site.core.update_available && site.core.updates.length > 0 && (
165 <div>
166 <button
167 onClick={handleUpdateCore}
168 className="bg-orange-500 text-white px-4 py-2 rounded-lg hover:bg-orange-600 text-sm flex items-center gap-2"
169 >
170 <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
171 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
172 </svg>
173 Update to {site.core.updates[0].version}
174 </button>
175 </div>
176 )}
177 </div>
178 </div>
179 )}
180
181 {/* Plugins */}
182 <div className="bg-white border border-gray-200 rounded-lg p-4">
183 <h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
184 <svg className="w-5 h-5 text-purple-600" fill="currentColor" viewBox="0 0 20 20">
185 <path fillRule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clipRule="evenodd" />
186 </svg>
187 Plugins ({site.plugin_count})
188 </h3>
189 {site.plugins && site.plugins.length > 0 ? (
190 <div className="space-y-2 max-h-64 overflow-y-auto">
191 {site.plugins.map((plugin, idx) => (
192 <div key={idx} className="flex items-center justify-between p-3 bg-gray-50 rounded">
193 <div>
194 <div className="font-medium">{plugin.title || plugin.name}</div>
195 <div className="text-sm text-gray-600">
196 v{plugin.version}
197 <span className={`ml-2 px-2 py-0.5 rounded text-xs ${
198 plugin.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-200 text-gray-700'
199 }`}>
200 {plugin.status}
201 </span>
202 </div>
203 </div>
204 {plugin.update_available === 'available' && (
205 <button
206 onClick={() => handleUpdatePlugin(plugin.name)}
207 className="text-orange-600 hover:text-orange-700 text-sm font-medium"
208 >
209 Update to v{plugin.new_version}
210 </button>
211 )}
212 </div>
213 ))}
214 </div>
215 ) : (
216 <div className="text-gray-500 text-sm">
217 {site.ssh_error ? `Unable to fetch plugin details: ${site.ssh_error}` : `${site.plugin_count} plugins installed (details loading...)`}
218 </div>
219 )}
220 </div>
221
222 {/* Themes */}
223 <div className="bg-white border border-gray-200 rounded-lg p-4">
224 <h3 className="font-semibold text-gray-900 mb-3 flex items-center gap-2">
225 <svg className="w-5 h-5 text-pink-600" fill="currentColor" viewBox="0 0 20 20">
226 <path fillRule="evenodd" d="M4 2a2 2 0 00-2 2v11a3 3 0 106 0V4a2 2 0 00-2-2H4zm1 14a1 1 0 100-2 1 1 0 000 2zm5-1.757l4.9-4.9a2 2 0 000-2.828L13.485 5.1a2 2 0 00-2.828 0L10 5.757v8.486zM16 18H9.071l6-6H16a2 2 0 012 2v2a2 2 0 01-2 2z" clipRule="evenodd" />
227 </svg>
228 Themes
229 </h3>
230 {site.themes && site.themes.length > 0 ? (
231 <div className="space-y-2">
232 {site.themes.map((theme, idx) => (
233 <div key={idx} className="flex items-center justify-between p-3 bg-gray-50 rounded">
234 <div>
235 <div className="font-medium">{theme.title || theme.name}</div>
236 <div className="text-sm text-gray-600">
237 v{theme.version}
238 <span className={`ml-2 px-2 py-0.5 rounded text-xs ${
239 theme.status === 'active' ? 'bg-blue-100 text-blue-800' : 'bg-gray-200 text-gray-700'
240 }`}>
241 {theme.status}
242 </span>
243 </div>
244 </div>
245 {theme.update_available === 'available' && (
246 <button className="text-orange-600 hover:text-orange-700 text-sm font-medium">
247 Update to v{theme.new_version}
248 </button>
249 )}
250 </div>
251 ))}
252 </div>
253 ) : (
254 <div className="text-gray-500 text-sm">No theme details available</div>
255 )}
256 </div>
257
258 {/* Server Info */}
259 <div className="bg-gray-50 border border-gray-200 rounded-lg p-4">
260 <h3 className="font-semibold text-gray-900 mb-3">Server Information</h3>
261 <div className="grid grid-cols-2 gap-4 text-sm">
262 <div>
263 <div className="text-gray-600">Server IP</div>
264 <div className="font-medium">{site.server_ip}</div>
265 </div>
266 <div>
267 <div className="text-gray-600">Hostname</div>
268 <div className="font-medium">{site.server_hostname}</div>
269 </div>
270 <div>
271 <div className="text-gray-600">Disk Usage</div>
272 <div className="font-medium">{site.disk_usage}</div>
273 </div>
274 <div>
275 <div className="text-gray-600">Status</div>
276 <div className={`font-medium ${site.status === 'online' ? 'text-green-600' : 'text-red-600'}`}>
277 {site.status}
278 </div>
279 </div>
280 {site.database && (
281 <>
282 <div>
283 <div className="text-gray-600">Database</div>
284 <div className="font-medium text-xs">{site.database.db_name}</div>
285 </div>
286 <div>
287 <div className="text-gray-600">DB User</div>
288 <div className="font-medium text-xs">{site.database.db_user}</div>
289 </div>
290 </>
291 )}
292 </div>
293 </div>
294 </div>
295 ) : null}
296 </div>
297
298 {/* Footer */}
299 <div className="px-6 py-4 border-t border-gray-200 bg-gray-50 flex justify-between items-center">
300 <div className="text-sm text-gray-600">
301 {site && site.last_updated && (
302 <>Last updated: {new Date(site.last_updated).toLocaleString()}</>
303 )}
304 </div>
305 <button
306 onClick={onClose}
307 className="bg-gray-600 text-white px-6 py-2 rounded-lg hover:bg-gray-700"
308 >
309 Close
310 </button>
311 </div>
312 </div>
313 </div>
314 );
315}