plugin-cluster-manager 1.1.5 → 1.1.7
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/README.md +17 -5
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +5 -5
- package/dist/locale/en-US.json +28 -4
- package/dist/locale/vi-VN.json +28 -4
- package/dist/locale/zh-CN.json +28 -4
- package/dist/server/actions/event-queue-monitor.js +123 -1
- package/dist/server/actions/orchestrator.js +1 -1
- package/dist/server/actions/plugin-operations.js +171 -0
- package/dist/server/collections/cluster-manager-plugins.js +44 -0
- package/dist/server/plugin.js +8 -2
- package/package.json +9 -4
- package/src/client/ClusterManagerLayout.tsx +16 -10
- package/src/client/EventQueueMonitor.tsx +349 -202
- package/src/client/PluginOperations.tsx +234 -0
- package/src/locale/en-US.json +28 -4
- package/src/locale/vi-VN.json +28 -4
- package/src/locale/zh-CN.json +28 -4
- package/src/server/actions/event-queue-monitor.ts +234 -95
- package/src/server/actions/orchestrator.ts +1 -1
- package/src/server/actions/plugin-operations.ts +151 -0
- package/src/server/collections/cluster-manager-plugins.ts +19 -0
- package/src/server/orchestrator/PackageManager.ts +84 -17
- package/src/server/plugin.ts +28 -20
- 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/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/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-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/index.d.ts +0 -1
- package/dist/server/orchestrator/PackageManager.d.ts +0 -37
- 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
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { Alert, Button, Input, Popconfirm, Space, Spin, Table, Tag, Typography, message } from 'antd';
|
|
3
|
+
import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined, StopOutlined } from '@ant-design/icons';
|
|
4
|
+
import { useAPIClient } from '@nocobase/client';
|
|
5
|
+
import { useT } from './utils';
|
|
6
|
+
|
|
7
|
+
interface PluginRecord {
|
|
8
|
+
name: string;
|
|
9
|
+
packageName?: string;
|
|
10
|
+
displayName?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
version?: string;
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
installed?: boolean;
|
|
15
|
+
loaded?: boolean;
|
|
16
|
+
protected?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getErrorMessage(err: any, fallback: string) {
|
|
20
|
+
return err?.response?.data?.errors?.[0]?.message || err?.message || fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function PluginOperations() {
|
|
24
|
+
const t = useT();
|
|
25
|
+
const api = useAPIClient();
|
|
26
|
+
const [loading, setLoading] = useState(false);
|
|
27
|
+
const [actionKey, setActionKey] = useState<string | null>(null);
|
|
28
|
+
const [search, setSearch] = useState('');
|
|
29
|
+
const [plugins, setPlugins] = useState<PluginRecord[]>([]);
|
|
30
|
+
|
|
31
|
+
const fetchData = useCallback(async () => {
|
|
32
|
+
setLoading(true);
|
|
33
|
+
try {
|
|
34
|
+
const res = await api.request({ url: 'clusterManagerPlugins:list' });
|
|
35
|
+
const data = Array.isArray(res?.data?.data?.data)
|
|
36
|
+
? res.data.data.data
|
|
37
|
+
: Array.isArray(res?.data?.data)
|
|
38
|
+
? res.data.data
|
|
39
|
+
: Array.isArray(res?.data)
|
|
40
|
+
? res.data
|
|
41
|
+
: [];
|
|
42
|
+
setPlugins(data);
|
|
43
|
+
} catch (err: any) {
|
|
44
|
+
message.error(getErrorMessage(err, t('Failed to load plugins')));
|
|
45
|
+
} finally {
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
}, [api, t]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
fetchData();
|
|
52
|
+
}, [fetchData]);
|
|
53
|
+
|
|
54
|
+
const filteredPlugins = useMemo(() => {
|
|
55
|
+
const keyword = search.trim().toLowerCase();
|
|
56
|
+
const list = Array.isArray(plugins) ? plugins : [];
|
|
57
|
+
if (!keyword) return list;
|
|
58
|
+
return list.filter((plugin) =>
|
|
59
|
+
[plugin.name, plugin.packageName, plugin.displayName, plugin.description]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.some((value) => String(value).toLowerCase().includes(keyword)),
|
|
62
|
+
);
|
|
63
|
+
}, [plugins, search]);
|
|
64
|
+
|
|
65
|
+
const handleForceDisable = async (record: PluginRecord) => {
|
|
66
|
+
const key = `${record.name}:disable`;
|
|
67
|
+
setActionKey(key);
|
|
68
|
+
try {
|
|
69
|
+
const res = await api.request({
|
|
70
|
+
url: 'clusterManagerPlugins:forceDisable',
|
|
71
|
+
method: 'post',
|
|
72
|
+
data: { name: record.name },
|
|
73
|
+
});
|
|
74
|
+
message.success(res?.data?.data?.message || res?.data?.message || t('Plugin force disabled'));
|
|
75
|
+
await fetchData();
|
|
76
|
+
} catch (err: any) {
|
|
77
|
+
message.error(getErrorMessage(err, t('Failed to force disable plugin')));
|
|
78
|
+
} finally {
|
|
79
|
+
setActionKey(null);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleForceRemove = async (record: PluginRecord) => {
|
|
84
|
+
const key = `${record.name}:remove`;
|
|
85
|
+
setActionKey(key);
|
|
86
|
+
try {
|
|
87
|
+
const res = await api.request({
|
|
88
|
+
url: 'clusterManagerPlugins:forceRemove',
|
|
89
|
+
method: 'post',
|
|
90
|
+
data: { name: record.name },
|
|
91
|
+
});
|
|
92
|
+
message.success(res?.data?.data?.message || res?.data?.message || t('Plugin force removed'));
|
|
93
|
+
await fetchData();
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
message.error(getErrorMessage(err, t('Failed to force remove plugin')));
|
|
96
|
+
} finally {
|
|
97
|
+
setActionKey(null);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const columns = [
|
|
102
|
+
{
|
|
103
|
+
title: t('Plugin'),
|
|
104
|
+
dataIndex: 'displayName',
|
|
105
|
+
key: 'displayName',
|
|
106
|
+
render: (_: string, record: PluginRecord) => (
|
|
107
|
+
<Space direction="vertical" size={0}>
|
|
108
|
+
<Typography.Text strong>{record.displayName || record.name}</Typography.Text>
|
|
109
|
+
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
|
110
|
+
{record.packageName || record.name}
|
|
111
|
+
</Typography.Text>
|
|
112
|
+
</Space>
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
title: t('Name'),
|
|
117
|
+
dataIndex: 'name',
|
|
118
|
+
key: 'name',
|
|
119
|
+
width: 220,
|
|
120
|
+
render: (name: string) => <code style={{ fontSize: 12 }}>{name}</code>,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
title: t('Status'),
|
|
124
|
+
key: 'status',
|
|
125
|
+
width: 260,
|
|
126
|
+
render: (_: any, record: PluginRecord) => (
|
|
127
|
+
<Space wrap size={[4, 4]}>
|
|
128
|
+
<Tag color={record.enabled ? 'green' : 'default'}>
|
|
129
|
+
{record.enabled ? t('Enabled') : t('Disabled')}
|
|
130
|
+
</Tag>
|
|
131
|
+
<Tag color={record.installed ? 'blue' : 'default'}>
|
|
132
|
+
{record.installed ? t('Installed') : t('Not installed')}
|
|
133
|
+
</Tag>
|
|
134
|
+
<Tag color={record.loaded ? 'processing' : 'default'}>
|
|
135
|
+
{record.loaded ? t('Loaded') : t('Not loaded')}
|
|
136
|
+
</Tag>
|
|
137
|
+
{record.protected && <Tag color="red">{t('Protected')}</Tag>}
|
|
138
|
+
</Space>
|
|
139
|
+
),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
title: t('Version'),
|
|
143
|
+
dataIndex: 'version',
|
|
144
|
+
key: 'version',
|
|
145
|
+
width: 120,
|
|
146
|
+
render: (version: string) => version || '-',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
title: t('Description'),
|
|
150
|
+
dataIndex: 'description',
|
|
151
|
+
key: 'description',
|
|
152
|
+
ellipsis: true,
|
|
153
|
+
render: (description: string) => description || '-',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
title: t('Actions'),
|
|
157
|
+
key: 'actions',
|
|
158
|
+
width: 250,
|
|
159
|
+
render: (_: any, record: PluginRecord) => (
|
|
160
|
+
<Space>
|
|
161
|
+
<Popconfirm
|
|
162
|
+
title={t('Force disable this plugin?')}
|
|
163
|
+
description={t('This updates the plugin registry directly. Restart or reload is required to fully unload runtime hooks.')}
|
|
164
|
+
disabled={record.protected || !record.enabled}
|
|
165
|
+
onConfirm={() => handleForceDisable(record)}
|
|
166
|
+
okText={t('Force disable')}
|
|
167
|
+
cancelText={t('Cancel')}
|
|
168
|
+
>
|
|
169
|
+
<Button
|
|
170
|
+
size="small"
|
|
171
|
+
icon={<StopOutlined />}
|
|
172
|
+
disabled={record.protected || !record.enabled}
|
|
173
|
+
loading={actionKey === `${record.name}:disable`}
|
|
174
|
+
>
|
|
175
|
+
{t('Force disable')}
|
|
176
|
+
</Button>
|
|
177
|
+
</Popconfirm>
|
|
178
|
+
<Popconfirm
|
|
179
|
+
title={t('Force remove this plugin?')}
|
|
180
|
+
description={t('This removes the plugin registry record. Package files are not deleted. Restart or reload is required.')}
|
|
181
|
+
disabled={record.protected}
|
|
182
|
+
onConfirm={() => handleForceRemove(record)}
|
|
183
|
+
okText={t('Force remove')}
|
|
184
|
+
cancelText={t('Cancel')}
|
|
185
|
+
>
|
|
186
|
+
<Button
|
|
187
|
+
size="small"
|
|
188
|
+
danger
|
|
189
|
+
icon={<DeleteOutlined />}
|
|
190
|
+
disabled={record.protected}
|
|
191
|
+
loading={actionKey === `${record.name}:remove`}
|
|
192
|
+
>
|
|
193
|
+
{t('Force remove')}
|
|
194
|
+
</Button>
|
|
195
|
+
</Popconfirm>
|
|
196
|
+
</Space>
|
|
197
|
+
),
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<Spin spinning={loading}>
|
|
203
|
+
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
204
|
+
<Alert
|
|
205
|
+
type="warning"
|
|
206
|
+
showIcon
|
|
207
|
+
icon={<ExclamationCircleOutlined />}
|
|
208
|
+
message={t('Force operations bypass plugin lifecycle hooks')}
|
|
209
|
+
description={t('Use this only when the normal plugin manager cannot disable or remove a broken plugin. Restart or reload the app after a successful operation.')}
|
|
210
|
+
/>
|
|
211
|
+
<Space>
|
|
212
|
+
<Button icon={<ReloadOutlined />} onClick={fetchData}>
|
|
213
|
+
{t('Refresh')}
|
|
214
|
+
</Button>
|
|
215
|
+
<Input.Search
|
|
216
|
+
allowClear
|
|
217
|
+
placeholder={t('Search plugins')}
|
|
218
|
+
value={search}
|
|
219
|
+
onChange={(event) => setSearch(event.target.value)}
|
|
220
|
+
style={{ width: 320 }}
|
|
221
|
+
/>
|
|
222
|
+
</Space>
|
|
223
|
+
<Table
|
|
224
|
+
rowKey="name"
|
|
225
|
+
size="small"
|
|
226
|
+
dataSource={filteredPlugins}
|
|
227
|
+
columns={columns}
|
|
228
|
+
pagination={{ pageSize: 20 }}
|
|
229
|
+
scroll={{ x: 1100 }}
|
|
230
|
+
/>
|
|
231
|
+
</Space>
|
|
232
|
+
</Spin>
|
|
233
|
+
);
|
|
234
|
+
}
|
package/src/locale/en-US.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"Cluster Manager": "Cluster Manager",
|
|
3
3
|
"Async Tasks": "Async Tasks",
|
|
4
4
|
"Workflow Executions": "Workflow Executions",
|
|
5
5
|
"Redis Monitor": "Redis Monitor",
|
|
@@ -66,6 +66,30 @@
|
|
|
66
66
|
"This will clear all cached data across all stores.": "This will clear all cached data across all stores.",
|
|
67
67
|
"All caches flushed": "All caches flushed",
|
|
68
68
|
"Failed to flush caches": "Failed to flush caches",
|
|
69
|
-
"Sync Messages": "Sync Messages",
|
|
70
|
-
"Subscribers": "Subscribers"
|
|
71
|
-
|
|
69
|
+
"Sync Messages": "Sync Messages",
|
|
70
|
+
"Subscribers": "Subscribers",
|
|
71
|
+
"Plugins": "Plugins",
|
|
72
|
+
"Plugin": "Plugin",
|
|
73
|
+
"Enabled": "Enabled",
|
|
74
|
+
"Disabled": "Disabled",
|
|
75
|
+
"Installed": "Installed",
|
|
76
|
+
"Not installed": "Not installed",
|
|
77
|
+
"Loaded": "Loaded",
|
|
78
|
+
"Not loaded": "Not loaded",
|
|
79
|
+
"Protected": "Protected",
|
|
80
|
+
"Description": "Description",
|
|
81
|
+
"Force disable": "Force disable",
|
|
82
|
+
"Force remove": "Force remove",
|
|
83
|
+
"Force disable this plugin?": "Force disable this plugin?",
|
|
84
|
+
"Force remove this plugin?": "Force remove this plugin?",
|
|
85
|
+
"This updates the plugin registry directly. Restart or reload is required to fully unload runtime hooks.": "This updates the plugin registry directly. Restart or reload is required to fully unload runtime hooks.",
|
|
86
|
+
"This removes the plugin registry record. Package files are not deleted. Restart or reload is required.": "This removes the plugin registry record. Package files are not deleted. Restart or reload is required.",
|
|
87
|
+
"Force operations bypass plugin lifecycle hooks": "Force operations bypass plugin lifecycle hooks",
|
|
88
|
+
"Use this only when the normal plugin manager cannot disable or remove a broken plugin. Restart or reload the app after a successful operation.": "Use this only when the normal plugin manager cannot disable or remove a broken plugin. Restart or reload the app after a successful operation.",
|
|
89
|
+
"Search plugins": "Search plugins",
|
|
90
|
+
"Plugin force disabled": "Plugin force disabled",
|
|
91
|
+
"Plugin force removed": "Plugin force removed",
|
|
92
|
+
"Failed to load plugins": "Failed to load plugins",
|
|
93
|
+
"Failed to force disable plugin": "Failed to force disable plugin",
|
|
94
|
+
"Failed to force remove plugin": "Failed to force remove plugin"
|
|
95
|
+
}
|
package/src/locale/vi-VN.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"Cluster Manager": "Quản lý cụm",
|
|
3
3
|
"Async Tasks": "Tác vụ bất đồng bộ",
|
|
4
4
|
"Workflow Executions": "Thực thi Workflow",
|
|
5
5
|
"Redis Monitor": "Giám sát Redis",
|
|
@@ -66,6 +66,30 @@
|
|
|
66
66
|
"This will clear all cached data across all stores.": "Điều này sẽ xóa tất cả dữ liệu đệm trong mọi kho.",
|
|
67
67
|
"All caches flushed": "Đã xóa tất cả bộ nhớ đệm",
|
|
68
68
|
"Failed to flush caches": "Xóa bộ nhớ đệm thất bại",
|
|
69
|
-
"Sync Messages": "Tin nhắn đồng bộ",
|
|
70
|
-
"Subscribers": "Người đăng ký"
|
|
71
|
-
|
|
69
|
+
"Sync Messages": "Tin nhắn đồng bộ",
|
|
70
|
+
"Subscribers": "Người đăng ký",
|
|
71
|
+
"Plugins": "Plugin",
|
|
72
|
+
"Plugin": "Plugin",
|
|
73
|
+
"Enabled": "Đã bật",
|
|
74
|
+
"Disabled": "Đã tắt",
|
|
75
|
+
"Installed": "Đã cài đặt",
|
|
76
|
+
"Not installed": "Chưa cài đặt",
|
|
77
|
+
"Loaded": "Đã tải",
|
|
78
|
+
"Not loaded": "Chưa tải",
|
|
79
|
+
"Protected": "Được bảo vệ",
|
|
80
|
+
"Description": "Mô tả",
|
|
81
|
+
"Force disable": "Buộc tắt",
|
|
82
|
+
"Force remove": "Buộc xóa",
|
|
83
|
+
"Force disable this plugin?": "Buộc tắt plugin này?",
|
|
84
|
+
"Force remove this plugin?": "Buộc xóa plugin này?",
|
|
85
|
+
"This updates the plugin registry directly. Restart or reload is required to fully unload runtime hooks.": "Thao tác này cập nhật trực tiếp registry plugin. Cần restart hoặc reload để gỡ hoàn toàn các hook runtime.",
|
|
86
|
+
"This removes the plugin registry record. Package files are not deleted. Restart or reload is required.": "Thao tác này xóa bản ghi plugin trong registry. File package không bị xóa. Cần restart hoặc reload.",
|
|
87
|
+
"Force operations bypass plugin lifecycle hooks": "Thao tác force bỏ qua lifecycle hook của plugin",
|
|
88
|
+
"Use this only when the normal plugin manager cannot disable or remove a broken plugin. Restart or reload the app after a successful operation.": "Chỉ dùng khi plugin manager bình thường không thể tắt hoặc xóa plugin lỗi. Restart hoặc reload ứng dụng sau khi thao tác thành công.",
|
|
89
|
+
"Search plugins": "Tìm plugin",
|
|
90
|
+
"Plugin force disabled": "Đã buộc tắt plugin",
|
|
91
|
+
"Plugin force removed": "Đã buộc xóa plugin",
|
|
92
|
+
"Failed to load plugins": "Tải danh sách plugin thất bại",
|
|
93
|
+
"Failed to force disable plugin": "Buộc tắt plugin thất bại",
|
|
94
|
+
"Failed to force remove plugin": "Buộc xóa plugin thất bại"
|
|
95
|
+
}
|
package/src/locale/zh-CN.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"Cluster Manager": "集群管理",
|
|
3
3
|
"Async Tasks": "异步任务",
|
|
4
4
|
"Workflow Executions": "工作流执行",
|
|
5
5
|
"Redis Monitor": "Redis 监控",
|
|
@@ -66,6 +66,30 @@
|
|
|
66
66
|
"This will clear all cached data across all stores.": "这将清除所有存储中的缓存数据。",
|
|
67
67
|
"All caches flushed": "所有缓存已清空",
|
|
68
68
|
"Failed to flush caches": "清空缓存失败",
|
|
69
|
-
"Sync Messages": "同步消息",
|
|
70
|
-
"Subscribers": "订阅者"
|
|
71
|
-
|
|
69
|
+
"Sync Messages": "同步消息",
|
|
70
|
+
"Subscribers": "订阅者",
|
|
71
|
+
"Plugins": "插件",
|
|
72
|
+
"Plugin": "插件",
|
|
73
|
+
"Enabled": "已启用",
|
|
74
|
+
"Disabled": "已禁用",
|
|
75
|
+
"Installed": "已安装",
|
|
76
|
+
"Not installed": "未安装",
|
|
77
|
+
"Loaded": "已加载",
|
|
78
|
+
"Not loaded": "未加载",
|
|
79
|
+
"Protected": "受保护",
|
|
80
|
+
"Description": "描述",
|
|
81
|
+
"Force disable": "强制禁用",
|
|
82
|
+
"Force remove": "强制移除",
|
|
83
|
+
"Force disable this plugin?": "强制禁用此插件?",
|
|
84
|
+
"Force remove this plugin?": "强制移除此插件?",
|
|
85
|
+
"This updates the plugin registry directly. Restart or reload is required to fully unload runtime hooks.": "此操作会直接更新插件注册表。需要重启或重新加载后才能完全卸载运行时钩子。",
|
|
86
|
+
"This removes the plugin registry record. Package files are not deleted. Restart or reload is required.": "此操作会移除插件注册记录。不会删除 package 文件。需要重启或重新加载。",
|
|
87
|
+
"Force operations bypass plugin lifecycle hooks": "强制操作会绕过插件生命周期钩子",
|
|
88
|
+
"Use this only when the normal plugin manager cannot disable or remove a broken plugin. Restart or reload the app after a successful operation.": "仅在普通插件管理器无法禁用或移除异常插件时使用。操作成功后请重启或重新加载应用。",
|
|
89
|
+
"Search plugins": "搜索插件",
|
|
90
|
+
"Plugin force disabled": "插件已强制禁用",
|
|
91
|
+
"Plugin force removed": "插件已强制移除",
|
|
92
|
+
"Failed to load plugins": "加载插件失败",
|
|
93
|
+
"Failed to force disable plugin": "强制禁用插件失败",
|
|
94
|
+
"Failed to force remove plugin": "强制移除插件失败"
|
|
95
|
+
}
|