claude-code-templates 1.21.12 → 1.22.0
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.
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
// Plugin Dashboard Application - Enhanced UX
|
|
2
|
+
class PluginDashboard {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.data = {
|
|
5
|
+
marketplaces: [],
|
|
6
|
+
plugins: [],
|
|
7
|
+
summary: {}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
this.state = {
|
|
11
|
+
selectedMarketplace: null,
|
|
12
|
+
pluginStatusFilter: 'all',
|
|
13
|
+
marketplaceStatusFilter: 'all',
|
|
14
|
+
searchQuery: '',
|
|
15
|
+
sortBy: 'status',
|
|
16
|
+
viewMode: 'grid',
|
|
17
|
+
sidebarCollapsed: false
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async init() {
|
|
22
|
+
this.setupEventListeners();
|
|
23
|
+
await this.loadData();
|
|
24
|
+
this.restoreViewPreferences();
|
|
25
|
+
this.startAutoRefresh();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setupEventListeners() {
|
|
29
|
+
// Sidebar toggle
|
|
30
|
+
const sidebarToggle = document.getElementById('sidebarToggle');
|
|
31
|
+
if (sidebarToggle) {
|
|
32
|
+
sidebarToggle.addEventListener('click', () => this.toggleSidebar());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Refresh button
|
|
36
|
+
const refreshBtn = document.getElementById('refreshBtn');
|
|
37
|
+
if (refreshBtn) {
|
|
38
|
+
refreshBtn.addEventListener('click', () => this.refreshData());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Marketplace filter buttons in sidebar
|
|
42
|
+
document.querySelectorAll('.marketplace-filter-btn').forEach(btn => {
|
|
43
|
+
btn.addEventListener('click', (e) => {
|
|
44
|
+
const filter = e.target.dataset.filter;
|
|
45
|
+
this.setMarketplaceStatusFilter(filter);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Plugin status filter chips
|
|
50
|
+
document.querySelectorAll('.filter-chip').forEach(chip => {
|
|
51
|
+
chip.addEventListener('click', (e) => {
|
|
52
|
+
const filter = e.currentTarget.dataset.filter;
|
|
53
|
+
this.setPluginStatusFilter(filter);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Search input
|
|
58
|
+
const searchInput = document.getElementById('pluginSearch');
|
|
59
|
+
if (searchInput) {
|
|
60
|
+
searchInput.addEventListener('input', (e) => {
|
|
61
|
+
this.state.searchQuery = e.target.value.toLowerCase().trim();
|
|
62
|
+
this.renderPlugins();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Sort select
|
|
67
|
+
const sortSelect = document.getElementById('sortSelect');
|
|
68
|
+
if (sortSelect) {
|
|
69
|
+
sortSelect.addEventListener('change', (e) => {
|
|
70
|
+
this.state.sortBy = e.target.value;
|
|
71
|
+
this.renderPlugins();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// View mode toggle
|
|
76
|
+
document.querySelectorAll('.view-btn').forEach(btn => {
|
|
77
|
+
btn.addEventListener('click', (e) => {
|
|
78
|
+
const view = e.currentTarget.dataset.view;
|
|
79
|
+
this.setViewMode(view);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Clear filters button
|
|
84
|
+
const clearFiltersBtn = document.getElementById('clearFiltersBtn');
|
|
85
|
+
if (clearFiltersBtn) {
|
|
86
|
+
clearFiltersBtn.addEventListener('click', () => this.clearAllFilters());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Modal close handlers
|
|
90
|
+
const modalCloseBtn = document.getElementById('modalCloseBtn');
|
|
91
|
+
const modalOverlay = document.getElementById('pluginModal');
|
|
92
|
+
|
|
93
|
+
if (modalCloseBtn) {
|
|
94
|
+
modalCloseBtn.addEventListener('click', () => this.closeModal());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (modalOverlay) {
|
|
98
|
+
modalOverlay.addEventListener('click', (e) => {
|
|
99
|
+
if (e.target === modalOverlay) {
|
|
100
|
+
this.closeModal();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Keyboard shortcuts
|
|
106
|
+
document.addEventListener('keydown', (e) => {
|
|
107
|
+
if (e.key === 'Escape') {
|
|
108
|
+
this.closeModal();
|
|
109
|
+
}
|
|
110
|
+
if (e.key === '/' && e.target.tagName !== 'INPUT') {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
searchInput?.focus();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async loadData() {
|
|
118
|
+
try {
|
|
119
|
+
const [marketplacesRes, pluginsRes, summaryRes] = await Promise.all([
|
|
120
|
+
fetch('/api/marketplaces'),
|
|
121
|
+
fetch('/api/plugins'),
|
|
122
|
+
fetch('/api/summary')
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
const [marketplacesData, pluginsData, summaryData] = await Promise.all([
|
|
126
|
+
marketplacesRes.json(),
|
|
127
|
+
pluginsRes.json(),
|
|
128
|
+
summaryRes.json()
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
this.data.marketplaces = marketplacesData.marketplaces || [];
|
|
132
|
+
this.data.plugins = pluginsData.plugins || [];
|
|
133
|
+
this.data.summary = summaryData;
|
|
134
|
+
|
|
135
|
+
this.renderAll();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Error loading data:', error);
|
|
138
|
+
this.showError('Failed to load dashboard data');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
renderAll() {
|
|
143
|
+
this.updateSidebarStats();
|
|
144
|
+
this.renderMarketplaces();
|
|
145
|
+
this.renderPlugins();
|
|
146
|
+
this.updateFilterCounts();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
updateSidebarStats() {
|
|
150
|
+
const totalPlugins = this.data.plugins.length;
|
|
151
|
+
const enabledPlugins = this.data.plugins.filter(p => p.enabled).length;
|
|
152
|
+
|
|
153
|
+
document.getElementById('sidebarTotalPlugins').textContent = totalPlugins;
|
|
154
|
+
document.getElementById('sidebarEnabledPlugins').textContent = enabledPlugins;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getFilteredMarketplaces() {
|
|
158
|
+
let filtered = [...this.data.marketplaces];
|
|
159
|
+
|
|
160
|
+
if (this.state.marketplaceStatusFilter === 'enabled') {
|
|
161
|
+
filtered = filtered.filter(m => m.enabled);
|
|
162
|
+
} else if (this.state.marketplaceStatusFilter === 'disabled') {
|
|
163
|
+
filtered = filtered.filter(m => !m.enabled);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return filtered;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
renderMarketplaces() {
|
|
170
|
+
const nav = document.getElementById('marketplaceNav');
|
|
171
|
+
if (!nav) return;
|
|
172
|
+
|
|
173
|
+
const marketplaces = this.getFilteredMarketplaces();
|
|
174
|
+
|
|
175
|
+
document.getElementById('marketplaceCount').textContent = marketplaces.length;
|
|
176
|
+
|
|
177
|
+
if (marketplaces.length === 0) {
|
|
178
|
+
nav.innerHTML = `
|
|
179
|
+
<div class="sidebar-empty">
|
|
180
|
+
<p>No marketplaces found</p>
|
|
181
|
+
</div>
|
|
182
|
+
`;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Add "All Plugins" option
|
|
187
|
+
const allPluginsActive = this.state.selectedMarketplace === null ? 'active' : '';
|
|
188
|
+
let html = `
|
|
189
|
+
<button class="marketplace-item ${allPluginsActive}" data-marketplace="all">
|
|
190
|
+
<span class="marketplace-icon">🧩</span>
|
|
191
|
+
<div class="marketplace-info">
|
|
192
|
+
<span class="marketplace-name">All Plugins</span>
|
|
193
|
+
<span class="marketplace-count">${this.data.plugins.length} plugins</span>
|
|
194
|
+
</div>
|
|
195
|
+
</button>
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
// Add individual marketplaces
|
|
199
|
+
html += marketplaces.map(marketplace => {
|
|
200
|
+
const isActive = this.state.selectedMarketplace === marketplace.name ? 'active' : '';
|
|
201
|
+
const statusClass = marketplace.enabled ? 'enabled' : 'disabled';
|
|
202
|
+
const icon = this.getMarketplaceIcon(marketplace.type);
|
|
203
|
+
|
|
204
|
+
return `
|
|
205
|
+
<button class="marketplace-item ${isActive} ${statusClass}" data-marketplace="${this.escapeHtml(marketplace.name)}">
|
|
206
|
+
<span class="marketplace-icon">${icon}</span>
|
|
207
|
+
<div class="marketplace-info">
|
|
208
|
+
<span class="marketplace-name">${this.escapeHtml(marketplace.name)}</span>
|
|
209
|
+
<span class="marketplace-count">${marketplace.pluginCount} plugins</span>
|
|
210
|
+
</div>
|
|
211
|
+
${marketplace.enabled
|
|
212
|
+
? '<span class="marketplace-status enabled"></span>'
|
|
213
|
+
: '<span class="marketplace-status disabled"></span>'}
|
|
214
|
+
</button>
|
|
215
|
+
`;
|
|
216
|
+
}).join('');
|
|
217
|
+
|
|
218
|
+
nav.innerHTML = html;
|
|
219
|
+
|
|
220
|
+
// Add click handlers
|
|
221
|
+
nav.querySelectorAll('.marketplace-item').forEach(item => {
|
|
222
|
+
item.addEventListener('click', (e) => {
|
|
223
|
+
const marketplace = e.currentTarget.dataset.marketplace;
|
|
224
|
+
this.selectMarketplace(marketplace === 'all' ? null : marketplace);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
selectMarketplace(marketplaceName) {
|
|
230
|
+
this.state.selectedMarketplace = marketplaceName;
|
|
231
|
+
this.renderMarketplaces();
|
|
232
|
+
this.renderPlugins();
|
|
233
|
+
this.updatePageTitle();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
updatePageTitle() {
|
|
237
|
+
const nameEl = document.getElementById('currentMarketplaceName');
|
|
238
|
+
const iconEl = document.getElementById('currentMarketplaceIcon');
|
|
239
|
+
|
|
240
|
+
if (this.state.selectedMarketplace) {
|
|
241
|
+
const marketplace = this.data.marketplaces.find(m => m.name === this.state.selectedMarketplace);
|
|
242
|
+
nameEl.textContent = this.state.selectedMarketplace;
|
|
243
|
+
iconEl.textContent = marketplace ? this.getMarketplaceIcon(marketplace.type) : '📦';
|
|
244
|
+
} else {
|
|
245
|
+
nameEl.textContent = 'All Plugins';
|
|
246
|
+
iconEl.textContent = '🧩';
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
getMarketplaceIcon(type) {
|
|
251
|
+
const icons = {
|
|
252
|
+
'GitHub': '🐙',
|
|
253
|
+
'Git': '📁',
|
|
254
|
+
'Local': '💻',
|
|
255
|
+
'default': '📦'
|
|
256
|
+
};
|
|
257
|
+
return icons[type] || icons.default;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
setMarketplaceStatusFilter(filter) {
|
|
261
|
+
this.state.marketplaceStatusFilter = filter;
|
|
262
|
+
|
|
263
|
+
document.querySelectorAll('.marketplace-filter-btn').forEach(btn => {
|
|
264
|
+
btn.classList.toggle('active', btn.dataset.filter === filter);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
this.renderMarketplaces();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
setPluginStatusFilter(filter) {
|
|
271
|
+
this.state.pluginStatusFilter = filter;
|
|
272
|
+
|
|
273
|
+
document.querySelectorAll('.filter-chip').forEach(chip => {
|
|
274
|
+
chip.classList.toggle('active', chip.dataset.filter === filter);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
this.renderPlugins();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getFilteredPlugins() {
|
|
281
|
+
let filtered = [...this.data.plugins];
|
|
282
|
+
|
|
283
|
+
// Filter by marketplace
|
|
284
|
+
if (this.state.selectedMarketplace) {
|
|
285
|
+
filtered = filtered.filter(p => p.marketplace === this.state.selectedMarketplace);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Filter by status
|
|
289
|
+
if (this.state.pluginStatusFilter === 'enabled') {
|
|
290
|
+
filtered = filtered.filter(p => p.enabled);
|
|
291
|
+
} else if (this.state.pluginStatusFilter === 'disabled') {
|
|
292
|
+
filtered = filtered.filter(p => !p.enabled);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Filter by search query
|
|
296
|
+
if (this.state.searchQuery) {
|
|
297
|
+
filtered = filtered.filter(p => {
|
|
298
|
+
const searchStr = this.state.searchQuery;
|
|
299
|
+
return (
|
|
300
|
+
p.name.toLowerCase().includes(searchStr) ||
|
|
301
|
+
p.description.toLowerCase().includes(searchStr) ||
|
|
302
|
+
p.marketplace.toLowerCase().includes(searchStr) ||
|
|
303
|
+
(p.category && p.category.toLowerCase().includes(searchStr))
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Sort plugins
|
|
309
|
+
filtered = this.sortPlugins(filtered);
|
|
310
|
+
|
|
311
|
+
return filtered;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
sortPlugins(plugins) {
|
|
315
|
+
const sorted = [...plugins];
|
|
316
|
+
|
|
317
|
+
switch (this.state.sortBy) {
|
|
318
|
+
case 'name':
|
|
319
|
+
sorted.sort((a, b) => a.name.localeCompare(b.name));
|
|
320
|
+
break;
|
|
321
|
+
case 'components':
|
|
322
|
+
sorted.sort((a, b) => {
|
|
323
|
+
const aTotal = Object.values(a.components).reduce((sum, val) => sum + val, 0);
|
|
324
|
+
const bTotal = Object.values(b.components).reduce((sum, val) => sum + val, 0);
|
|
325
|
+
return bTotal - aTotal;
|
|
326
|
+
});
|
|
327
|
+
break;
|
|
328
|
+
case 'status':
|
|
329
|
+
sorted.sort((a, b) => {
|
|
330
|
+
if (a.enabled === b.enabled) return a.name.localeCompare(b.name);
|
|
331
|
+
return a.enabled ? -1 : 1;
|
|
332
|
+
});
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return sorted;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
updateFilterCounts() {
|
|
340
|
+
const allPlugins = this.getFilteredPluginsBase();
|
|
341
|
+
const enabled = allPlugins.filter(p => p.enabled).length;
|
|
342
|
+
const disabled = allPlugins.filter(p => !p.enabled).length;
|
|
343
|
+
|
|
344
|
+
document.getElementById('countAll').textContent = allPlugins.length;
|
|
345
|
+
document.getElementById('countEnabled').textContent = enabled;
|
|
346
|
+
document.getElementById('countDisabled').textContent = disabled;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
getFilteredPluginsBase() {
|
|
350
|
+
let filtered = [...this.data.plugins];
|
|
351
|
+
|
|
352
|
+
if (this.state.selectedMarketplace) {
|
|
353
|
+
filtered = filtered.filter(p => p.marketplace === this.state.selectedMarketplace);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (this.state.searchQuery) {
|
|
357
|
+
filtered = filtered.filter(p => {
|
|
358
|
+
const searchStr = this.state.searchQuery;
|
|
359
|
+
return (
|
|
360
|
+
p.name.toLowerCase().includes(searchStr) ||
|
|
361
|
+
p.description.toLowerCase().includes(searchStr) ||
|
|
362
|
+
p.marketplace.toLowerCase().includes(searchStr) ||
|
|
363
|
+
(p.category && p.category.toLowerCase().includes(searchStr))
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return filtered;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
renderPlugins() {
|
|
372
|
+
const container = document.getElementById('pluginsContainer');
|
|
373
|
+
const emptyState = document.getElementById('emptyState');
|
|
374
|
+
const clearFiltersBtn = document.getElementById('clearFiltersBtn');
|
|
375
|
+
|
|
376
|
+
if (!container) return;
|
|
377
|
+
|
|
378
|
+
const plugins = this.getFilteredPlugins();
|
|
379
|
+
this.updateFilterCounts();
|
|
380
|
+
|
|
381
|
+
if (plugins.length === 0) {
|
|
382
|
+
container.innerHTML = '';
|
|
383
|
+
emptyState.style.display = 'flex';
|
|
384
|
+
|
|
385
|
+
const hasFilters = this.state.selectedMarketplace ||
|
|
386
|
+
this.state.pluginStatusFilter !== 'all' ||
|
|
387
|
+
this.state.searchQuery;
|
|
388
|
+
|
|
389
|
+
clearFiltersBtn.style.display = hasFilters ? 'inline-block' : 'none';
|
|
390
|
+
|
|
391
|
+
document.getElementById('emptyDescription').textContent = hasFilters
|
|
392
|
+
? 'Try adjusting your filters or search terms'
|
|
393
|
+
: 'No plugins available in this marketplace';
|
|
394
|
+
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
emptyState.style.display = 'none';
|
|
399
|
+
|
|
400
|
+
const viewClass = this.state.viewMode === 'list' ? 'plugins-list' : 'plugins-grid';
|
|
401
|
+
container.className = `plugins-container ${viewClass}`;
|
|
402
|
+
|
|
403
|
+
if (this.state.viewMode === 'grid') {
|
|
404
|
+
container.innerHTML = plugins.map(plugin => this.renderPluginCard(plugin)).join('');
|
|
405
|
+
} else {
|
|
406
|
+
container.innerHTML = plugins.map(plugin => this.renderPluginListItem(plugin)).join('');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Add click handlers to cards
|
|
410
|
+
container.querySelectorAll('[data-plugin]').forEach(card => {
|
|
411
|
+
card.addEventListener('click', () => {
|
|
412
|
+
const pluginName = card.dataset.plugin;
|
|
413
|
+
this.showPluginDetails(pluginName);
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
renderPluginCard(plugin) {
|
|
419
|
+
const statusClass = plugin.enabled ? 'enabled' : 'disabled';
|
|
420
|
+
const totalComponents = Object.values(plugin.components).reduce((sum, val) => sum + val, 0);
|
|
421
|
+
|
|
422
|
+
return `
|
|
423
|
+
<article class="plugin-card ${statusClass}" data-plugin="${this.escapeHtml(plugin.name)}">
|
|
424
|
+
<div class="plugin-card-header">
|
|
425
|
+
<div class="plugin-card-status ${statusClass}"></div>
|
|
426
|
+
<h3 class="plugin-card-title">${this.escapeHtml(plugin.name)}</h3>
|
|
427
|
+
<span class="plugin-card-version">v${plugin.version}</span>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<p class="plugin-card-description">${this.escapeHtml(plugin.description)}</p>
|
|
431
|
+
|
|
432
|
+
<div class="plugin-card-components">
|
|
433
|
+
<div class="component-badge" title="Agents">
|
|
434
|
+
<span class="component-icon">🤖</span>
|
|
435
|
+
<span class="component-value">${plugin.components.agents || 0}</span>
|
|
436
|
+
</div>
|
|
437
|
+
<div class="component-badge" title="Commands">
|
|
438
|
+
<span class="component-icon">⚡</span>
|
|
439
|
+
<span class="component-value">${plugin.components.commands || 0}</span>
|
|
440
|
+
</div>
|
|
441
|
+
<div class="component-badge" title="Hooks">
|
|
442
|
+
<span class="component-icon">🪝</span>
|
|
443
|
+
<span class="component-value">${plugin.components.hooks || 0}</span>
|
|
444
|
+
</div>
|
|
445
|
+
<div class="component-badge" title="MCPs">
|
|
446
|
+
<span class="component-icon">🔌</span>
|
|
447
|
+
<span class="component-value">${plugin.components.mcps || 0}</span>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<div class="plugin-card-footer">
|
|
452
|
+
<span class="plugin-card-marketplace">${this.escapeHtml(plugin.marketplace)}</span>
|
|
453
|
+
<span class="plugin-card-total">${totalComponents} component${totalComponents !== 1 ? 's' : ''}</span>
|
|
454
|
+
</div>
|
|
455
|
+
</article>
|
|
456
|
+
`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
renderPluginListItem(plugin) {
|
|
460
|
+
const statusClass = plugin.enabled ? 'enabled' : 'disabled';
|
|
461
|
+
const totalComponents = Object.values(plugin.components).reduce((sum, val) => sum + val, 0);
|
|
462
|
+
|
|
463
|
+
return `
|
|
464
|
+
<article class="plugin-list-item ${statusClass}" data-plugin="${this.escapeHtml(plugin.name)}">
|
|
465
|
+
<div class="plugin-list-status ${statusClass}"></div>
|
|
466
|
+
|
|
467
|
+
<div class="plugin-list-main">
|
|
468
|
+
<div class="plugin-list-info">
|
|
469
|
+
<h3 class="plugin-list-title">${this.escapeHtml(plugin.name)}</h3>
|
|
470
|
+
<p class="plugin-list-description">${this.escapeHtml(plugin.description)}</p>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
473
|
+
<div class="plugin-list-meta">
|
|
474
|
+
<span class="plugin-list-version">v${plugin.version}</span>
|
|
475
|
+
<span class="plugin-list-marketplace">${this.escapeHtml(plugin.marketplace)}</span>
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
<div class="plugin-list-components">
|
|
480
|
+
<div class="component-badge-sm" title="Agents">
|
|
481
|
+
<span class="component-icon">🤖</span>
|
|
482
|
+
<span class="component-value">${plugin.components.agents || 0}</span>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="component-badge-sm" title="Commands">
|
|
485
|
+
<span class="component-icon">⚡</span>
|
|
486
|
+
<span class="component-value">${plugin.components.commands || 0}</span>
|
|
487
|
+
</div>
|
|
488
|
+
<div class="component-badge-sm" title="Hooks">
|
|
489
|
+
<span class="component-icon">🪝</span>
|
|
490
|
+
<span class="component-value">${plugin.components.hooks || 0}</span>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="component-badge-sm" title="MCPs">
|
|
493
|
+
<span class="component-icon">🔌</span>
|
|
494
|
+
<span class="component-value">${plugin.components.mcps || 0}</span>
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
|
|
498
|
+
<div class="plugin-list-action">
|
|
499
|
+
<span class="action-icon">→</span>
|
|
500
|
+
</div>
|
|
501
|
+
</article>
|
|
502
|
+
`;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
showPluginDetails(pluginName) {
|
|
506
|
+
const plugin = this.data.plugins.find(p => p.name === pluginName);
|
|
507
|
+
if (!plugin) return;
|
|
508
|
+
|
|
509
|
+
document.getElementById('modalPluginName').textContent = plugin.name;
|
|
510
|
+
document.getElementById('modalPluginVersion').textContent = `v${plugin.version}`;
|
|
511
|
+
|
|
512
|
+
const statusBadge = document.getElementById('modalPluginStatus');
|
|
513
|
+
statusBadge.textContent = plugin.enabled ? 'Enabled' : 'Disabled';
|
|
514
|
+
statusBadge.className = `modal-badge status-badge ${plugin.enabled ? 'enabled' : 'disabled'}`;
|
|
515
|
+
|
|
516
|
+
document.getElementById('modalPluginDescription').textContent = plugin.description;
|
|
517
|
+
|
|
518
|
+
// Components
|
|
519
|
+
document.getElementById('modalComponents').innerHTML = `
|
|
520
|
+
<div class="modal-component-card">
|
|
521
|
+
<div class="modal-component-icon">🤖</div>
|
|
522
|
+
<div class="modal-component-count">${plugin.components.agents || 0}</div>
|
|
523
|
+
<div class="modal-component-label">Agents</div>
|
|
524
|
+
</div>
|
|
525
|
+
<div class="modal-component-card">
|
|
526
|
+
<div class="modal-component-icon">⚡</div>
|
|
527
|
+
<div class="modal-component-count">${plugin.components.commands || 0}</div>
|
|
528
|
+
<div class="modal-component-label">Commands</div>
|
|
529
|
+
</div>
|
|
530
|
+
<div class="modal-component-card">
|
|
531
|
+
<div class="modal-component-icon">🪝</div>
|
|
532
|
+
<div class="modal-component-count">${plugin.components.hooks || 0}</div>
|
|
533
|
+
<div class="modal-component-label">Hooks</div>
|
|
534
|
+
</div>
|
|
535
|
+
<div class="modal-component-card">
|
|
536
|
+
<div class="modal-component-icon">🔌</div>
|
|
537
|
+
<div class="modal-component-count">${plugin.components.mcps || 0}</div>
|
|
538
|
+
<div class="modal-component-label">MCPs</div>
|
|
539
|
+
</div>
|
|
540
|
+
`;
|
|
541
|
+
|
|
542
|
+
// Details
|
|
543
|
+
document.getElementById('modalMarketplace').textContent = plugin.marketplace;
|
|
544
|
+
|
|
545
|
+
// Conditional fields
|
|
546
|
+
const authorCard = document.getElementById('modalAuthorCard');
|
|
547
|
+
if (plugin.author) {
|
|
548
|
+
authorCard.style.display = 'flex';
|
|
549
|
+
document.getElementById('modalAuthor').textContent =
|
|
550
|
+
typeof plugin.author === 'object' ? plugin.author.name : plugin.author;
|
|
551
|
+
} else {
|
|
552
|
+
authorCard.style.display = 'none';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const categoryCard = document.getElementById('modalCategoryCard');
|
|
556
|
+
if (plugin.category) {
|
|
557
|
+
categoryCard.style.display = 'flex';
|
|
558
|
+
document.getElementById('modalCategory').textContent = plugin.category;
|
|
559
|
+
} else {
|
|
560
|
+
categoryCard.style.display = 'none';
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const licenseCard = document.getElementById('modalLicenseCard');
|
|
564
|
+
if (plugin.license) {
|
|
565
|
+
licenseCard.style.display = 'flex';
|
|
566
|
+
document.getElementById('modalLicense').textContent =
|
|
567
|
+
typeof plugin.license === 'object' ? plugin.license.type : plugin.license;
|
|
568
|
+
} else {
|
|
569
|
+
licenseCard.style.display = 'none';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Keywords
|
|
573
|
+
const keywordsSection = document.getElementById('modalKeywordsSection');
|
|
574
|
+
if (plugin.keywords && plugin.keywords.length > 0) {
|
|
575
|
+
keywordsSection.style.display = 'block';
|
|
576
|
+
document.getElementById('modalKeywords').innerHTML = plugin.keywords
|
|
577
|
+
.map(kw => `<span class="keyword-tag">${this.escapeHtml(kw)}</span>`)
|
|
578
|
+
.join('');
|
|
579
|
+
} else {
|
|
580
|
+
keywordsSection.style.display = 'none';
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Homepage
|
|
584
|
+
const homepageSection = document.getElementById('modalHomepageSection');
|
|
585
|
+
if (plugin.homepage) {
|
|
586
|
+
homepageSection.style.display = 'block';
|
|
587
|
+
document.getElementById('modalHomepage').href = plugin.homepage;
|
|
588
|
+
} else {
|
|
589
|
+
homepageSection.style.display = 'none';
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Render plugin actions and update command references
|
|
593
|
+
this.renderPluginActions(plugin);
|
|
594
|
+
this.updateCommandReferences(plugin);
|
|
595
|
+
|
|
596
|
+
// Show modal
|
|
597
|
+
document.getElementById('pluginModal').classList.add('active');
|
|
598
|
+
document.body.style.overflow = 'hidden';
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// ==================== PLUGIN ACTIONS & COMMANDS ====================
|
|
602
|
+
|
|
603
|
+
renderPluginActions(plugin) {
|
|
604
|
+
const actionsContainer = document.getElementById('modalActions');
|
|
605
|
+
if (!actionsContainer) return;
|
|
606
|
+
|
|
607
|
+
const pluginIdentifier = `${plugin.name}@${plugin.marketplace}`;
|
|
608
|
+
|
|
609
|
+
// Determine which buttons to show based on plugin status
|
|
610
|
+
let buttons = [];
|
|
611
|
+
|
|
612
|
+
if (!plugin.enabled) {
|
|
613
|
+
// Plugin is disabled or not installed - show install button
|
|
614
|
+
buttons.push({
|
|
615
|
+
label: 'Install Plugin',
|
|
616
|
+
icon: '📥',
|
|
617
|
+
class: 'install-btn',
|
|
618
|
+
command: 'install'
|
|
619
|
+
});
|
|
620
|
+
} else {
|
|
621
|
+
// Plugin is enabled - show disable and uninstall buttons
|
|
622
|
+
buttons.push({
|
|
623
|
+
label: 'Disable Plugin',
|
|
624
|
+
icon: '⏸️',
|
|
625
|
+
class: 'disable-btn',
|
|
626
|
+
command: 'disable'
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Always show uninstall if enabled
|
|
631
|
+
if (plugin.enabled) {
|
|
632
|
+
buttons.push({
|
|
633
|
+
label: 'Uninstall Plugin',
|
|
634
|
+
icon: '🗑️',
|
|
635
|
+
class: 'uninstall-btn',
|
|
636
|
+
command: 'uninstall'
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// If disabled but installed, show enable button
|
|
641
|
+
if (!plugin.enabled && this.isPluginInstalled(plugin)) {
|
|
642
|
+
buttons.unshift({
|
|
643
|
+
label: 'Enable Plugin',
|
|
644
|
+
icon: '✅',
|
|
645
|
+
class: 'enable-btn',
|
|
646
|
+
command: 'enable'
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
actionsContainer.innerHTML = buttons.map(btn => `
|
|
651
|
+
<button class="action-button ${btn.class}" onclick="window.dashboard.copyPluginCommand('${btn.command}', '${this.escapeHtml(pluginIdentifier)}')">
|
|
652
|
+
<span class="action-icon">${btn.icon}</span>
|
|
653
|
+
${btn.label}
|
|
654
|
+
</button>
|
|
655
|
+
`).join('');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
isPluginInstalled(plugin) {
|
|
659
|
+
// This is a simple heuristic - in a real implementation, you'd check the actual installation status
|
|
660
|
+
return plugin.components.agents > 0 || plugin.components.commands > 0 ||
|
|
661
|
+
plugin.components.hooks > 0 || plugin.components.mcps > 0;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
updateCommandReferences(plugin) {
|
|
665
|
+
const pluginIdentifier = `${plugin.name}@${plugin.marketplace}`;
|
|
666
|
+
|
|
667
|
+
document.getElementById('installCommand').textContent = `/plugin install ${pluginIdentifier}`;
|
|
668
|
+
document.getElementById('enableCommand').textContent = `/plugin enable ${pluginIdentifier}`;
|
|
669
|
+
document.getElementById('disableCommand').textContent = `/plugin disable ${pluginIdentifier}`;
|
|
670
|
+
document.getElementById('uninstallCommand').textContent = `/plugin uninstall ${pluginIdentifier}`;
|
|
671
|
+
|
|
672
|
+
// Store current plugin for command copying
|
|
673
|
+
this.currentPlugin = plugin;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
copyCommand(commandType) {
|
|
677
|
+
if (!this.currentPlugin) return;
|
|
678
|
+
|
|
679
|
+
const pluginIdentifier = `${this.currentPlugin.name}@${this.currentPlugin.marketplace}`;
|
|
680
|
+
const command = `/plugin ${commandType} ${pluginIdentifier}`;
|
|
681
|
+
|
|
682
|
+
this.copyToClipboard(command);
|
|
683
|
+
this.showToast(`Command copied: ${command}`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
copyPluginCommand(commandType, pluginIdentifier) {
|
|
687
|
+
const command = `/plugin ${commandType} ${pluginIdentifier}`;
|
|
688
|
+
this.copyToClipboard(command);
|
|
689
|
+
this.showToast(`Command copied: ${command}`);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
copyToClipboard(text) {
|
|
693
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
694
|
+
navigator.clipboard.writeText(text)
|
|
695
|
+
.then(() => {
|
|
696
|
+
console.log('Command copied to clipboard:', text);
|
|
697
|
+
})
|
|
698
|
+
.catch(err => {
|
|
699
|
+
console.error('Failed to copy:', err);
|
|
700
|
+
this.fallbackCopy(text);
|
|
701
|
+
});
|
|
702
|
+
} else {
|
|
703
|
+
this.fallbackCopy(text);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
fallbackCopy(text) {
|
|
708
|
+
const textarea = document.createElement('textarea');
|
|
709
|
+
textarea.value = text;
|
|
710
|
+
textarea.style.position = 'fixed';
|
|
711
|
+
textarea.style.opacity = '0';
|
|
712
|
+
document.body.appendChild(textarea);
|
|
713
|
+
textarea.select();
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
document.execCommand('copy');
|
|
717
|
+
console.log('Command copied to clipboard (fallback):', text);
|
|
718
|
+
} catch (err) {
|
|
719
|
+
console.error('Fallback copy failed:', err);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
document.body.removeChild(textarea);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
showToast(message) {
|
|
726
|
+
const toast = document.getElementById('commandToast');
|
|
727
|
+
if (!toast) return;
|
|
728
|
+
|
|
729
|
+
toast.querySelector('.toast-message').textContent = message;
|
|
730
|
+
toast.classList.add('show');
|
|
731
|
+
|
|
732
|
+
setTimeout(() => {
|
|
733
|
+
toast.classList.remove('show');
|
|
734
|
+
}, 3000);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
closeModal() {
|
|
738
|
+
document.getElementById('pluginModal').classList.remove('active');
|
|
739
|
+
document.body.style.overflow = '';
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
setViewMode(mode) {
|
|
743
|
+
this.state.viewMode = mode;
|
|
744
|
+
|
|
745
|
+
document.querySelectorAll('.view-btn').forEach(btn => {
|
|
746
|
+
btn.classList.toggle('active', btn.dataset.view === mode);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
this.renderPlugins();
|
|
750
|
+
localStorage.setItem('pluginDashboardViewMode', mode);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
toggleSidebar() {
|
|
754
|
+
this.state.sidebarCollapsed = !this.state.sidebarCollapsed;
|
|
755
|
+
const sidebar = document.getElementById('sidebar');
|
|
756
|
+
sidebar.classList.toggle('collapsed', this.state.sidebarCollapsed);
|
|
757
|
+
localStorage.setItem('sidebarCollapsed', this.state.sidebarCollapsed);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
restoreViewPreferences() {
|
|
761
|
+
const savedViewMode = localStorage.getItem('pluginDashboardViewMode');
|
|
762
|
+
if (savedViewMode) {
|
|
763
|
+
this.setViewMode(savedViewMode);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const savedSidebarState = localStorage.getItem('sidebarCollapsed');
|
|
767
|
+
if (savedSidebarState === 'true') {
|
|
768
|
+
this.toggleSidebar();
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
clearAllFilters() {
|
|
773
|
+
this.state.selectedMarketplace = null;
|
|
774
|
+
this.state.pluginStatusFilter = 'all';
|
|
775
|
+
this.state.searchQuery = '';
|
|
776
|
+
|
|
777
|
+
document.getElementById('pluginSearch').value = '';
|
|
778
|
+
|
|
779
|
+
document.querySelectorAll('.filter-chip').forEach(chip => {
|
|
780
|
+
chip.classList.toggle('active', chip.dataset.filter === 'all');
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
this.renderMarketplaces();
|
|
784
|
+
this.renderPlugins();
|
|
785
|
+
this.updatePageTitle();
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
escapeHtml(text) {
|
|
789
|
+
if (typeof text !== 'string') return '';
|
|
790
|
+
const div = document.createElement('div');
|
|
791
|
+
div.textContent = text;
|
|
792
|
+
return div.innerHTML;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
showError(message) {
|
|
796
|
+
console.error(message);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Initialize dashboard
|
|
801
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
802
|
+
window.dashboard = new PluginDashboard();
|
|
803
|
+
window.dashboard.init();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// ==================== REFRESH FUNCTIONALITY ====================
|