plugin-cluster-manager 1.1.10 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/client.js +1 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/914.5dc1105cf3ada6a6.js +10 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/externalVersion.js +6 -5
- package/dist/locale/en-US.json +138 -28
- package/dist/locale/vi-VN.json +139 -28
- package/dist/locale/zh-CN.json +140 -28
- package/dist/server/actions/cache-monitor.js +301 -0
- package/dist/server/actions/cluster-nodes.js +391 -11
- package/dist/server/actions/doctor.js +1246 -0
- package/dist/server/actions/orchestrator.js +37 -0
- package/dist/server/actions/queue-mappings.js +107 -0
- package/dist/server/collections/cluster-manager-doctor-runs.js +52 -0
- package/dist/server/collections/cluster-manager-doctor.js +44 -0
- package/dist/server/collections/worker-queue-mappings.js +106 -0
- package/dist/server/hooks/cacheInvalidationHooks.js +81 -0
- package/dist/server/middlewares/listMetaCacheMiddleware.js +79 -0
- package/dist/server/orchestrator/PackageManager.js +21 -24
- package/dist/server/orchestrator/docker-adapter.js +49 -27
- package/dist/server/plugin.js +71 -16
- package/dist/server/queue-scanner.js +141 -0
- package/dist/server/utils/node.js +30 -2
- package/dist/server/utils/versionManager.js +91 -0
- package/package.json +9 -5
- package/server.js +1 -0
- package/src/client/AclCacheManager.tsx +292 -287
- package/src/client/CacheMonitor.tsx +166 -179
- package/src/client/ClusterManagerLayout.tsx +54 -42
- package/src/client/ClusterNodes.tsx +698 -418
- package/src/client/ContainerOrchestrator.tsx +184 -102
- package/src/client/Doctor.tsx +559 -0
- package/src/client/NginxCacheManager.tsx +415 -0
- package/src/client/PluginOperations.tsx +234 -234
- package/src/client/QueueAssignment.tsx +355 -0
- package/src/client/TaskManager.tsx +194 -187
- package/src/client/WorkflowExecutions.tsx +243 -238
- package/src/client/index.tsx +22 -14
- package/src/client/utils/clientSafeCache.ts +41 -0
- package/src/client/utils/requestDedupInterceptor.ts +213 -0
- package/src/client-v2/plugin.tsx +24 -0
- package/src/locale/en-US.json +138 -28
- package/src/locale/vi-VN.json +139 -28
- package/src/locale/zh-CN.json +140 -28
- package/src/server/__tests__/doctor.test.ts +53 -0
- package/src/server/actions/acl-cache.ts +272 -272
- package/src/server/actions/cache-monitor.ts +453 -116
- package/src/server/actions/cluster-nodes.ts +878 -378
- package/src/server/actions/doctor.ts +1536 -0
- package/src/server/actions/orchestrator.ts +54 -2
- package/src/server/actions/queue-mappings.ts +94 -0
- package/src/server/collections/cluster-manager-doctor-runs.ts +23 -0
- package/src/server/collections/cluster-manager-doctor.ts +19 -0
- package/src/server/collections/worker-queue-mappings.ts +85 -0
- package/src/server/hooks/cacheInvalidationHooks.ts +58 -0
- package/src/server/middlewares/listMetaCacheMiddleware.ts +55 -0
- package/src/server/orchestrator/PackageManager.ts +20 -24
- package/src/server/orchestrator/docker-adapter.ts +74 -37
- package/src/server/plugin.ts +347 -270
- package/src/server/queue-scanner.ts +154 -0
- package/src/server/utils/node.ts +48 -0
- package/src/server/utils/versionManager.ts +69 -0
- package/dist/client/AclCacheManager.d.ts +0 -2
- package/dist/client/CacheMonitor.d.ts +0 -2
- package/dist/client/ClusterManagerLayout.d.ts +0 -2
- package/dist/client/ClusterNodes.d.ts +0 -2
- package/dist/client/ContainerOrchestrator.d.ts +0 -2
- package/dist/client/EventQueueMonitor.d.ts +0 -2
- package/dist/client/LockMonitor.d.ts +0 -2
- package/dist/client/PackageInstaller.d.ts +0 -2
- package/dist/client/PluginOperations.d.ts +0 -2
- package/dist/client/RedisMonitor.d.ts +0 -2
- package/dist/client/TaskManager.d.ts +0 -2
- package/dist/client/WorkflowExecutions.d.ts +0 -2
- package/dist/client/index.d.ts +0 -5
- package/dist/client/utils.d.ts +0 -12
- package/dist/index.d.ts +0 -2
- package/dist/server/actions/acl-cache.d.ts +0 -53
- package/dist/server/actions/cache-monitor.d.ts +0 -23
- package/dist/server/actions/cluster-nodes.d.ts +0 -49
- package/dist/server/actions/event-queue-monitor.d.ts +0 -13
- package/dist/server/actions/lock-monitor.d.ts +0 -19
- package/dist/server/actions/orchestrator.d.ts +0 -58
- package/dist/server/actions/package-manager.d.ts +0 -6
- package/dist/server/actions/plugin-operations.d.ts +0 -6
- package/dist/server/actions/redis-monitor.d.ts +0 -12
- package/dist/server/actions/tasks.d.ts +0 -7
- package/dist/server/actions/workflow-executions.d.ts +0 -7
- package/dist/server/adapters/redis-lock-adapter.d.ts +0 -15
- package/dist/server/adapters/redis-node-registry.d.ts +0 -12
- package/dist/server/adapters/redis-pubsub-adapter.d.ts +0 -16
- package/dist/server/collections/app.d.ts +0 -8
- package/dist/server/collections/cluster-manager-acl-cache.d.ts +0 -22
- package/dist/server/collections/cluster-manager-cache-mgr.d.ts +0 -22
- package/dist/server/collections/cluster-manager-cluster.d.ts +0 -22
- package/dist/server/collections/cluster-manager-lock.d.ts +0 -22
- package/dist/server/collections/cluster-manager-plugins.d.ts +0 -18
- package/dist/server/collections/cluster-manager-queue.d.ts +0 -22
- package/dist/server/collections/cluster-manager-redis.d.ts +0 -22
- package/dist/server/collections/cluster-manager-workflow.d.ts +0 -22
- package/dist/server/collections/cluster-manager.d.ts +0 -22
- package/dist/server/collections/orchestrator-settings.d.ts +0 -59
- package/dist/server/collections/orchestrator-stacks.d.ts +0 -102
- package/dist/server/collections/worker-orchestrator.d.ts +0 -22
- package/dist/server/collections/worker-packages-configs.d.ts +0 -3
- package/dist/server/collections/worker-packages.d.ts +0 -22
- package/dist/server/orchestrator/PackageManager.d.ts +0 -39
- package/dist/server/orchestrator/docker-adapter.d.ts +0 -41
- package/dist/server/orchestrator/index.d.ts +0 -4
- package/dist/server/orchestrator/k8s-adapter.d.ts +0 -50
- package/dist/server/orchestrator/leader-election.d.ts +0 -48
- package/dist/server/orchestrator/types.d.ts +0 -84
- package/dist/server/plugin.d.ts +0 -26
- package/dist/server/utils/node.d.ts +0 -6
- package/dist/server/utils/redis.d.ts +0 -29
- package/dist/shared/packages.d.ts +0 -23
- /package/{dist/server/index.d.ts → src/client-v2/index.tsx} +0 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
Space,
|
|
5
|
+
Alert,
|
|
6
|
+
Radio,
|
|
7
|
+
Select,
|
|
8
|
+
Input,
|
|
9
|
+
Button,
|
|
10
|
+
Popconfirm,
|
|
11
|
+
message,
|
|
12
|
+
Form,
|
|
13
|
+
Tag,
|
|
14
|
+
Typography,
|
|
15
|
+
Row,
|
|
16
|
+
Col,
|
|
17
|
+
} from 'antd';
|
|
18
|
+
import {
|
|
19
|
+
ReloadOutlined,
|
|
20
|
+
DeleteOutlined,
|
|
21
|
+
CheckCircleOutlined,
|
|
22
|
+
CloseCircleOutlined,
|
|
23
|
+
SendOutlined,
|
|
24
|
+
} from '@ant-design/icons';
|
|
25
|
+
import { useAPIClient } from '@nocobase/client';
|
|
26
|
+
import { useT } from './utils';
|
|
27
|
+
|
|
28
|
+
const { TextArea } = Input;
|
|
29
|
+
const { Title, Text } = Typography;
|
|
30
|
+
|
|
31
|
+
export function NginxCacheManager() {
|
|
32
|
+
const t = useT();
|
|
33
|
+
const api = useAPIClient();
|
|
34
|
+
const [loading, setLoading] = useState(false);
|
|
35
|
+
const [clearing, setClearing] = useState(false);
|
|
36
|
+
const [status, setStatus] = useState<any>(null);
|
|
37
|
+
|
|
38
|
+
const [method, setMethod] = useState<'directory' | 'purgeRequest'>('directory');
|
|
39
|
+
const [selectedPath, setSelectedPath] = useState<string>('custom');
|
|
40
|
+
const [customPath, setCustomPath] = useState<string>('');
|
|
41
|
+
const [purgeUrl, setPurgeUrl] = useState<string>('');
|
|
42
|
+
const [httpMethod, setHttpMethod] = useState<string>('PURGE');
|
|
43
|
+
const [headersStr, setHeadersStr] = useState<string>('{\n "X-Purge": "1"\n}');
|
|
44
|
+
const [logs, setLogs] = useState<string>('');
|
|
45
|
+
|
|
46
|
+
const fetchNginxStatus = React.useCallback(async () => {
|
|
47
|
+
setLoading(true);
|
|
48
|
+
try {
|
|
49
|
+
const res = await api.request({ url: 'clusterManagerCacheMgr:nginxCacheStatus' });
|
|
50
|
+
setStatus(res?.data?.data);
|
|
51
|
+
} catch {
|
|
52
|
+
message.error(t('Failed to load Nginx status'));
|
|
53
|
+
} finally {
|
|
54
|
+
setLoading(false);
|
|
55
|
+
}
|
|
56
|
+
}, [api, t]);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
fetchNginxStatus();
|
|
60
|
+
}, [fetchNginxStatus]);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (status?.detectedPaths?.length > 0) {
|
|
64
|
+
setSelectedPath(status.detectedPaths[0]);
|
|
65
|
+
} else {
|
|
66
|
+
setSelectedPath('custom');
|
|
67
|
+
}
|
|
68
|
+
}, [status]);
|
|
69
|
+
|
|
70
|
+
const handleClearCache = async () => {
|
|
71
|
+
setClearing(true);
|
|
72
|
+
setLogs('');
|
|
73
|
+
try {
|
|
74
|
+
const values: any = { method };
|
|
75
|
+
|
|
76
|
+
if (method === 'directory') {
|
|
77
|
+
const targetDir = selectedPath === 'custom' ? customPath : selectedPath;
|
|
78
|
+
if (!targetDir) {
|
|
79
|
+
message.error(t('Nginx cache path is required'));
|
|
80
|
+
setClearing(false);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
values.directory = targetDir;
|
|
84
|
+
setLogs(`[INFO] Clearing physical cache files in: ${targetDir}\n`);
|
|
85
|
+
} else {
|
|
86
|
+
if (!purgeUrl) {
|
|
87
|
+
message.error(t('Please enter a valid Purge URL'));
|
|
88
|
+
setClearing(false);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
values.url = purgeUrl;
|
|
92
|
+
values.httpMethod = httpMethod;
|
|
93
|
+
|
|
94
|
+
let headers = {};
|
|
95
|
+
try {
|
|
96
|
+
if (headersStr.trim()) {
|
|
97
|
+
headers = JSON.parse(headersStr);
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
message.error(t('Invalid headers JSON structure'));
|
|
101
|
+
setClearing(false);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
values.headers = headers;
|
|
105
|
+
setLogs(
|
|
106
|
+
`[INFO] Sending HTTP Purge request:\n[INFO] Method: ${httpMethod}\n[INFO] URL: ${purgeUrl}\n[INFO] Headers: ${JSON.stringify(
|
|
107
|
+
headers,
|
|
108
|
+
null,
|
|
109
|
+
2,
|
|
110
|
+
)}\n`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const res = await api.request({
|
|
115
|
+
url: 'clusterManagerCacheMgr:clearNginxCache',
|
|
116
|
+
method: 'post',
|
|
117
|
+
data: { values },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const data = res?.data?.data;
|
|
121
|
+
if (method === 'directory') {
|
|
122
|
+
const msg = t('Cache cleared successfully. Cleared {count} items.').replace(
|
|
123
|
+
'{count}',
|
|
124
|
+
String(data?.clearedCount || 0),
|
|
125
|
+
);
|
|
126
|
+
message.success(msg);
|
|
127
|
+
setLogs((prev) => prev + `[SUCCESS] ${msg}\n`);
|
|
128
|
+
} else {
|
|
129
|
+
const msg = t('HTTP Purge request completed. Status: {status}').replace(
|
|
130
|
+
'{status}',
|
|
131
|
+
String(data?.status || 200),
|
|
132
|
+
);
|
|
133
|
+
message.success(msg);
|
|
134
|
+
setLogs(
|
|
135
|
+
(prev) =>
|
|
136
|
+
prev +
|
|
137
|
+
`[SUCCESS] ${msg}\n[RESPONSE BODY]\n${
|
|
138
|
+
typeof data?.data === 'object' ? JSON.stringify(data.data, null, 2) : String(data?.data || '')
|
|
139
|
+
}\n`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
} catch (err: any) {
|
|
143
|
+
const errMsg = err?.response?.data?.message || err?.message || String(err);
|
|
144
|
+
message.error(errMsg);
|
|
145
|
+
setLogs((prev) => prev + `[ERROR] Failed: ${errMsg}\n`);
|
|
146
|
+
} finally {
|
|
147
|
+
setClearing(false);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const detectedPaths = status?.detectedPaths || [];
|
|
152
|
+
const nginxInstalled = status?.nginxInstalled || false;
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Card bordered={false} loading={loading}>
|
|
156
|
+
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
|
157
|
+
{/* Nginx Detection Status */}
|
|
158
|
+
<Alert
|
|
159
|
+
type={nginxInstalled ? 'success' : 'warning'}
|
|
160
|
+
showIcon
|
|
161
|
+
icon={nginxInstalled ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
|
|
162
|
+
message={<span style={{ fontWeight: 600 }}>{t('Nginx Cache Status')}</span>}
|
|
163
|
+
description={
|
|
164
|
+
<div style={{ marginTop: 8 }}>
|
|
165
|
+
<div>
|
|
166
|
+
<span style={{ fontWeight: 500 }}>{t('Status')}:</span>{' '}
|
|
167
|
+
{nginxInstalled ? (
|
|
168
|
+
<Tag color="success">{t('Nginx is installed')}</Tag>
|
|
169
|
+
) : (
|
|
170
|
+
<Tag color="warning">{t('Nginx is NOT installed')}</Tag>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
{status?.mainConfigPath && (
|
|
174
|
+
<div style={{ marginTop: 4 }}>
|
|
175
|
+
<span style={{ fontWeight: 500 }}>{t('Nginx configuration file found at')}:</span>{' '}
|
|
176
|
+
<code style={{ fontSize: 12 }}>{status.mainConfigPath}</code>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
{detectedPaths.length > 0 && (
|
|
180
|
+
<div style={{ marginTop: 4 }}>
|
|
181
|
+
<span style={{ fontWeight: 500 }}>{t('Nginx cache paths detected')}:</span>
|
|
182
|
+
<div style={{ marginTop: 4, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
|
183
|
+
{detectedPaths.map((p: string) => (
|
|
184
|
+
<Tag key={p} color="blue" style={{ fontSize: 11 }}>
|
|
185
|
+
{p}
|
|
186
|
+
</Tag>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
{!nginxInstalled && (
|
|
192
|
+
<div style={{ marginTop: 4, fontStyle: 'italic', opacity: 0.8 }}>
|
|
193
|
+
{t('Nginx is not detected on this node. You can still input a custom cache directory.')}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
}
|
|
198
|
+
action={
|
|
199
|
+
<Button size="small" icon={<ReloadOutlined />} onClick={fetchNginxStatus}>
|
|
200
|
+
{t('Refresh')}
|
|
201
|
+
</Button>
|
|
202
|
+
}
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
{/* Method selection */}
|
|
206
|
+
<div>
|
|
207
|
+
<Title level={5} style={{ fontSize: 14 }}>
|
|
208
|
+
{t('Nginx cache clearing method')}
|
|
209
|
+
</Title>
|
|
210
|
+
<Radio.Group
|
|
211
|
+
value={method}
|
|
212
|
+
onChange={(e) => setMethod(e.target.value)}
|
|
213
|
+
style={{ width: '100%', marginTop: 8 }}
|
|
214
|
+
>
|
|
215
|
+
<Row gutter={16}>
|
|
216
|
+
<Col span={12}>
|
|
217
|
+
<Radio.Button
|
|
218
|
+
value="directory"
|
|
219
|
+
style={{
|
|
220
|
+
width: '100%',
|
|
221
|
+
height: 'auto',
|
|
222
|
+
padding: '16px 24px',
|
|
223
|
+
display: 'flex',
|
|
224
|
+
flexDirection: 'column',
|
|
225
|
+
borderRadius: 8,
|
|
226
|
+
textAlign: 'left',
|
|
227
|
+
boxShadow: 'none',
|
|
228
|
+
lineHeight: 'normal',
|
|
229
|
+
border: method === 'directory' ? '2px solid #1677ff' : '1px solid #d9d9d9',
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
<Space align="start" size="middle">
|
|
233
|
+
<DeleteOutlined
|
|
234
|
+
style={{ fontSize: 24, color: method === 'directory' ? '#1677ff' : 'inherit', marginTop: 4 }}
|
|
235
|
+
/>
|
|
236
|
+
<div>
|
|
237
|
+
<div
|
|
238
|
+
style={{ fontWeight: 600, fontSize: 15, color: method === 'directory' ? '#1677ff' : 'inherit' }}
|
|
239
|
+
>
|
|
240
|
+
{t('Physical Files')}
|
|
241
|
+
</div>
|
|
242
|
+
<div style={{ fontSize: 12, opacity: 0.65, marginTop: 4 }}>
|
|
243
|
+
{t('Directly deletes all cache files inside the nginx cache directory on this server node.')}
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</Space>
|
|
247
|
+
</Radio.Button>
|
|
248
|
+
</Col>
|
|
249
|
+
<Col span={12}>
|
|
250
|
+
<Radio.Button
|
|
251
|
+
value="purgeRequest"
|
|
252
|
+
style={{
|
|
253
|
+
width: '100%',
|
|
254
|
+
height: 'auto',
|
|
255
|
+
padding: '16px 24px',
|
|
256
|
+
display: 'flex',
|
|
257
|
+
flexDirection: 'column',
|
|
258
|
+
borderRadius: 8,
|
|
259
|
+
textAlign: 'left',
|
|
260
|
+
boxShadow: 'none',
|
|
261
|
+
lineHeight: 'normal',
|
|
262
|
+
border: method === 'purgeRequest' ? '2px solid #1677ff' : '1px solid #d9d9d9',
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
<Space align="start" size="middle">
|
|
266
|
+
<SendOutlined
|
|
267
|
+
style={{ fontSize: 24, color: method === 'purgeRequest' ? '#1677ff' : 'inherit', marginTop: 4 }}
|
|
268
|
+
/>
|
|
269
|
+
<div>
|
|
270
|
+
<div
|
|
271
|
+
style={{
|
|
272
|
+
fontWeight: 600,
|
|
273
|
+
fontSize: 15,
|
|
274
|
+
color: method === 'purgeRequest' ? '#1677ff' : 'inherit',
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
{t('HTTP Purge Request')}
|
|
278
|
+
</div>
|
|
279
|
+
<div style={{ fontSize: 12, opacity: 0.65, marginTop: 4 }}>
|
|
280
|
+
{t(
|
|
281
|
+
'Sends a custom HTTP request (like PURGE or bypass headers) to let Nginx clear specific URLs.',
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</Space>
|
|
286
|
+
</Radio.Button>
|
|
287
|
+
</Col>
|
|
288
|
+
</Row>
|
|
289
|
+
</Radio.Group>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Input fields based on method */}
|
|
293
|
+
<Card bordered size="small" style={{ backgroundColor: '#fafafa' }}>
|
|
294
|
+
{method === 'directory' ? (
|
|
295
|
+
<Form layout="vertical">
|
|
296
|
+
<Form.Item label={<span style={{ fontWeight: 500 }}>{t('Select Cache Path')}</span>} required>
|
|
297
|
+
<Select
|
|
298
|
+
value={selectedPath}
|
|
299
|
+
onChange={(val) => setSelectedPath(val)}
|
|
300
|
+
style={{ width: '100%' }}
|
|
301
|
+
options={[
|
|
302
|
+
...detectedPaths.map((p: string) => ({ label: p, value: p })),
|
|
303
|
+
{ label: t('Custom Path'), value: 'custom' },
|
|
304
|
+
]}
|
|
305
|
+
/>
|
|
306
|
+
</Form.Item>
|
|
307
|
+
|
|
308
|
+
{selectedPath === 'custom' && (
|
|
309
|
+
<Form.Item label={<span style={{ fontWeight: 500 }}>{t('Custom Cache Path')}</span>} required>
|
|
310
|
+
<Input
|
|
311
|
+
placeholder="/var/cache/nginx"
|
|
312
|
+
value={customPath}
|
|
313
|
+
onChange={(e) => setCustomPath(e.target.value)}
|
|
314
|
+
/>
|
|
315
|
+
<Text type="secondary" style={{ fontSize: 11, display: 'block', marginTop: 4 }}>
|
|
316
|
+
{t(
|
|
317
|
+
'Provide the full absolute path of the directory. Restricted system paths will be rejected for safety.',
|
|
318
|
+
)}
|
|
319
|
+
</Text>
|
|
320
|
+
</Form.Item>
|
|
321
|
+
)}
|
|
322
|
+
</Form>
|
|
323
|
+
) : (
|
|
324
|
+
<Form layout="vertical">
|
|
325
|
+
<Row gutter={16}>
|
|
326
|
+
<Col span={18}>
|
|
327
|
+
<Form.Item label={<span style={{ fontWeight: 500 }}>{t('Purge URL')}</span>} required>
|
|
328
|
+
<Input
|
|
329
|
+
placeholder="http://127.0.0.1/purge/*"
|
|
330
|
+
value={purgeUrl}
|
|
331
|
+
onChange={(e) => setPurgeUrl(e.target.value)}
|
|
332
|
+
/>
|
|
333
|
+
</Form.Item>
|
|
334
|
+
</Col>
|
|
335
|
+
<Col span={6}>
|
|
336
|
+
<Form.Item label={<span style={{ fontWeight: 500 }}>{t('HTTP Method')}</span>} required>
|
|
337
|
+
<Select
|
|
338
|
+
value={httpMethod}
|
|
339
|
+
onChange={(val) => setHttpMethod(val)}
|
|
340
|
+
options={[
|
|
341
|
+
{ label: 'PURGE', value: 'PURGE' },
|
|
342
|
+
{ label: 'GET', value: 'GET' },
|
|
343
|
+
{ label: 'POST', value: 'POST' },
|
|
344
|
+
{ label: 'DELETE', value: 'DELETE' },
|
|
345
|
+
]}
|
|
346
|
+
/>
|
|
347
|
+
</Form.Item>
|
|
348
|
+
</Col>
|
|
349
|
+
</Row>
|
|
350
|
+
<Form.Item label={<span style={{ fontWeight: 500 }}>{t('Headers (JSON)')}</span>}>
|
|
351
|
+
<TextArea
|
|
352
|
+
rows={4}
|
|
353
|
+
value={headersStr}
|
|
354
|
+
onChange={(e) => setHeadersStr(e.target.value)}
|
|
355
|
+
placeholder={'{\n "X-Purge": "1"\n}'}
|
|
356
|
+
style={{ fontFamily: 'monospace', fontSize: 12 }}
|
|
357
|
+
/>
|
|
358
|
+
</Form.Item>
|
|
359
|
+
</Form>
|
|
360
|
+
)}
|
|
361
|
+
</Card>
|
|
362
|
+
|
|
363
|
+
{/* Big Action Button */}
|
|
364
|
+
<div>
|
|
365
|
+
<Popconfirm
|
|
366
|
+
title={t('Clear Nginx Cache?')}
|
|
367
|
+
description={
|
|
368
|
+
method === 'directory'
|
|
369
|
+
? t(
|
|
370
|
+
'Are you sure you want to clear Nginx cache? This will permanently delete all files in this directory.',
|
|
371
|
+
)
|
|
372
|
+
: t('Are you sure you want to trigger this HTTP Purge request?')
|
|
373
|
+
}
|
|
374
|
+
onConfirm={handleClearCache}
|
|
375
|
+
okButtonProps={{ loading: clearing }}
|
|
376
|
+
>
|
|
377
|
+
<Button
|
|
378
|
+
type="primary"
|
|
379
|
+
danger={method === 'directory'}
|
|
380
|
+
size="large"
|
|
381
|
+
icon={method === 'directory' ? <DeleteOutlined /> : <SendOutlined />}
|
|
382
|
+
loading={clearing}
|
|
383
|
+
>
|
|
384
|
+
{method === 'directory' ? t('Clear Nginx Cache') : t('Send Purge Request')}
|
|
385
|
+
</Button>
|
|
386
|
+
</Popconfirm>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
{/* Execution Logs */}
|
|
390
|
+
{logs && (
|
|
391
|
+
<div>
|
|
392
|
+
<Title level={5} style={{ fontSize: 13, marginBottom: 8 }}>
|
|
393
|
+
{t('Execution Logs')}
|
|
394
|
+
</Title>
|
|
395
|
+
<pre
|
|
396
|
+
style={{
|
|
397
|
+
backgroundColor: '#1e1e1e',
|
|
398
|
+
color: '#d4d4d4',
|
|
399
|
+
padding: '12px 16px',
|
|
400
|
+
borderRadius: 6,
|
|
401
|
+
fontFamily: 'monospace',
|
|
402
|
+
fontSize: 12,
|
|
403
|
+
overflowX: 'auto',
|
|
404
|
+
whiteSpace: 'pre-wrap',
|
|
405
|
+
maxHeight: '300px',
|
|
406
|
+
}}
|
|
407
|
+
>
|
|
408
|
+
{logs}
|
|
409
|
+
</pre>
|
|
410
|
+
</div>
|
|
411
|
+
)}
|
|
412
|
+
</Space>
|
|
413
|
+
</Card>
|
|
414
|
+
);
|
|
415
|
+
}
|