mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 16:58:06 +00:00
chore(web): remove dead legacy client-side plugin-config generator (~2,300 lines)
Plugin config forms have been rendered server-side (plugin_config.html via GET /partials/plugin-config/<id>) since the HTMX migration; the old client-side generator survived as unreachable code. Verified dead by call graph, not by naming: showPluginConfigModal and showGithubTokenInstructions have zero callers anywhere in templates or JS, and everything removed here is reachable only from those two roots. Removed: - plugins_manager.js: showPluginConfigModal, generatePluginConfigForm, generateFormFromSchema, generateFieldHtml, generateSimpleConfigForm, handlePluginConfigSubmit, the modal's JSON-editor view (initJsonEditor, switchPluginConfigView, syncFormToJson/JsonToForm, saveConfigFromJsonEditor, resetPluginConfigToDefaults, displayValidationErrors, closePluginConfigModal, savePluginConfiguration, currentPluginConfigState), their exclusive helpers (getSchemaPropertyType, escapeCssSelector, dotToNested, collectBooleanFields, normalizeFormDataForConfig, flattenConfig, loadCustomHtmlWidget), the orphaned-modal cleanup block, the modal's listener wiring, and the never-invoked showGithubTokenInstructions/closeInstructionsModal pair. - plugins.html: the #plugin-config-modal markup those functions drove. - base.html: the deprecated pluginConfigData() component and the window.PluginConfigHelpers shim (only ever called by pluginConfigData). Deliberately kept, verified still live: - renderArrayObjectItem, getSchemaProperty, escapeHtml/escapeAttribute (window-exposed for the top-level array-of-objects handlers the server-rendered form uses), toggleNestedSection, addKeyValuePair/ addArrayObjectItem families, executePluginAction, and window.currentPluginConfig = null init (file-upload.js and executePluginAction read it, optional-chained). - app()'s internal generateConfigForm/generateSimpleConfigForm methods in base.html: unreachable now but embedded in the live Alpine component; excising methods from a live object is deferred to keep this change zero-risk. Validation: every deletion seam inspected line-by-line; Jinja parse of both templates passes; repo-wide sweep confirms zero remaining references to any deleted function or element id (deleted ranges contained no Jinja tags). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
66f9950a30
commit
d2b49c4ff2
@@ -1676,216 +1676,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ===== DEPRECATED: pluginConfigData =====
|
||||
// This function is no longer used - plugin configuration forms are now
|
||||
// rendered server-side and loaded via HTMX. Kept for backwards compatibility.
|
||||
// See: /v3/partials/plugin-config/<plugin_id> for the new implementation.
|
||||
function pluginConfigData(plugin) {
|
||||
if (!plugin) {
|
||||
console.error('pluginConfigData called with undefined plugin');
|
||||
return {
|
||||
plugin: { id: 'unknown', name: 'Unknown Plugin', enabled: false },
|
||||
loading: false,
|
||||
config: {},
|
||||
schema: {},
|
||||
webUiActions: [],
|
||||
onDemandRefreshing: false,
|
||||
onDemandStopping: false
|
||||
};
|
||||
}
|
||||
return {
|
||||
plugin: plugin,
|
||||
loading: true,
|
||||
config: {},
|
||||
schema: {},
|
||||
webUiActions: [],
|
||||
onDemandRefreshing: false,
|
||||
onDemandStopping: false,
|
||||
get onDemandStore() {
|
||||
if (window.Alpine && typeof Alpine.store === 'function' && Alpine.store('onDemand')) {
|
||||
return Alpine.store('onDemand');
|
||||
}
|
||||
return window.__onDemandStore || { loading: true, state: {}, service: {}, error: null, lastUpdated: null };
|
||||
},
|
||||
get isOnDemandLoading() {
|
||||
const store = this.onDemandStore || {};
|
||||
return !!store.loading;
|
||||
},
|
||||
get onDemandState() {
|
||||
const store = this.onDemandStore || {};
|
||||
return store.state || {};
|
||||
},
|
||||
get onDemandService() {
|
||||
const store = this.onDemandStore || {};
|
||||
return store.service || {};
|
||||
},
|
||||
get onDemandError() {
|
||||
const store = this.onDemandStore || {};
|
||||
return store.error || null;
|
||||
},
|
||||
get onDemandActive() {
|
||||
const state = this.onDemandState;
|
||||
return !!(state.active && state.plugin_id === plugin.id);
|
||||
},
|
||||
resolvePluginName() {
|
||||
return plugin.name || plugin.id;
|
||||
},
|
||||
resolvePluginDisplayName(id) {
|
||||
if (!id) {
|
||||
return 'Another plugin';
|
||||
}
|
||||
const list = window.installedPlugins || [];
|
||||
const match = Array.isArray(list) ? list.find(p => p.id === id) : null;
|
||||
return match ? (match.name || match.id) : id;
|
||||
},
|
||||
formatDuration(value) {
|
||||
if (value === undefined || value === null) {
|
||||
return '';
|
||||
}
|
||||
const total = Number(value);
|
||||
if (Number.isNaN(total)) {
|
||||
return '';
|
||||
}
|
||||
const seconds = Math.max(0, Math.round(total));
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
if (minutes > 0) {
|
||||
return `${minutes}m${remainingSeconds > 0 ? ` ${remainingSeconds}s` : ''}`;
|
||||
}
|
||||
return `${remainingSeconds}s`;
|
||||
},
|
||||
get onDemandStatusText() {
|
||||
if (this.isOnDemandLoading) {
|
||||
return 'Loading on-demand status...';
|
||||
}
|
||||
if (this.onDemandError) {
|
||||
return `On-demand error: ${this.onDemandError}`;
|
||||
}
|
||||
const state = this.onDemandState;
|
||||
if (state.active) {
|
||||
const activeName = this.resolvePluginDisplayName(state.plugin_id);
|
||||
if (state.plugin_id !== plugin.id) {
|
||||
return `${activeName} is running on-demand.`;
|
||||
}
|
||||
const modeLabel = state.mode ? ` (${state.mode})` : '';
|
||||
const remaining = this.formatDuration(state.remaining);
|
||||
const duration = this.formatDuration(state.duration);
|
||||
let message = `${this.resolvePluginName()}${modeLabel} is running on-demand`;
|
||||
if (remaining) {
|
||||
message += ` — ${remaining} remaining`;
|
||||
} else if (duration) {
|
||||
message += ` — duration ${duration}`;
|
||||
} else {
|
||||
message += ' — until stopped';
|
||||
}
|
||||
return message;
|
||||
}
|
||||
const lastEvent = state.last_event ? state.last_event.replace(/-/g, ' ') : null;
|
||||
if (lastEvent && lastEvent !== 'cleared') {
|
||||
return `No on-demand session active (last event: ${lastEvent})`;
|
||||
}
|
||||
return 'No on-demand session active.';
|
||||
},
|
||||
get onDemandStatusClass() {
|
||||
if (this.isOnDemandLoading) return 'text-blue-600';
|
||||
if (this.onDemandError) return 'text-red-600';
|
||||
if (this.onDemandActive) return 'text-green-600';
|
||||
return 'text-blue-600';
|
||||
},
|
||||
get onDemandServiceText() {
|
||||
if (this.isOnDemandLoading) {
|
||||
return 'Checking display service status...';
|
||||
}
|
||||
if (this.onDemandError) {
|
||||
return 'Display service status unavailable.';
|
||||
}
|
||||
if (this.onDemandService.active) {
|
||||
return 'Display service is running.';
|
||||
}
|
||||
const serviceError = this.onDemandService.stderr || this.onDemandService.error;
|
||||
return serviceError ? `Display service inactive (${serviceError})` : 'Display service is not running.';
|
||||
},
|
||||
get onDemandServiceClass() {
|
||||
if (this.isOnDemandLoading) return 'text-blue-500';
|
||||
if (this.onDemandError) return 'text-red-500';
|
||||
return this.onDemandService.active ? 'text-blue-500' : 'text-red-500';
|
||||
},
|
||||
get onDemandLastUpdated() {
|
||||
const store = this.onDemandStore || {};
|
||||
if (!store.lastUpdated) {
|
||||
return '';
|
||||
}
|
||||
const deltaSeconds = Math.round((Date.now() - store.lastUpdated) / 1000);
|
||||
if (deltaSeconds < 5) return 'Just now';
|
||||
if (deltaSeconds < 60) return `${deltaSeconds}s ago`;
|
||||
const minutes = Math.round(deltaSeconds / 60);
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
const hours = Math.round(minutes / 60);
|
||||
return `${hours}h ago`;
|
||||
},
|
||||
get canStopOnDemand() {
|
||||
if (this.isOnDemandLoading) return false;
|
||||
if (this.onDemandError) return true;
|
||||
return this.onDemandActive;
|
||||
},
|
||||
get disableRunButton() {
|
||||
return !plugin.enabled;
|
||||
},
|
||||
get showEnableHint() {
|
||||
return !plugin.enabled;
|
||||
},
|
||||
notify(message, type = 'info') {
|
||||
if (typeof showNotification === 'function') {
|
||||
showNotification(message, type);
|
||||
}
|
||||
},
|
||||
refreshOnDemandStatus() {
|
||||
if (typeof window.loadOnDemandStatus !== 'function') {
|
||||
this.notify('On-demand status controls unavailable. Refresh the Plugin Manager tab.', 'error');
|
||||
return;
|
||||
}
|
||||
this.onDemandRefreshing = true;
|
||||
Promise.resolve(window.loadOnDemandStatus(true))
|
||||
.finally(() => {
|
||||
this.onDemandRefreshing = false;
|
||||
});
|
||||
},
|
||||
runOnDemand() {
|
||||
// Note: On-demand can work with disabled plugins - the backend will temporarily enable them
|
||||
if (typeof window.openOnDemandModal === 'function') {
|
||||
window.openOnDemandModal(plugin.id);
|
||||
} else {
|
||||
this.notify('On-demand modal unavailable. Refresh the Plugin Manager tab.', 'error');
|
||||
}
|
||||
},
|
||||
stopOnDemandWithEvent(stopService = false) {
|
||||
if (typeof window.requestOnDemandStop !== 'function') {
|
||||
this.notify('Unable to stop on-demand mode. Refresh the Plugin Manager tab.', 'error');
|
||||
return;
|
||||
}
|
||||
this.onDemandStopping = true;
|
||||
Promise.resolve(window.requestOnDemandStop({ stopService }))
|
||||
.finally(() => {
|
||||
this.onDemandStopping = false;
|
||||
});
|
||||
},
|
||||
async loadPluginConfig(pluginId) {
|
||||
// Use PluginConfigHelpers to load config directly into this component
|
||||
if (window.PluginConfigHelpers) {
|
||||
await window.PluginConfigHelpers.loadPluginConfig(pluginId, this);
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
console.error('loadPluginConfig not available');
|
||||
this.loading = false;
|
||||
}
|
||||
// Note: generateConfigForm and savePluginConfig are now called via window.PluginConfigHelpers
|
||||
// to avoid delegation recursion and ensure proper access to app component.
|
||||
// See template usage:
|
||||
// x-html="window.PluginConfigHelpers.generateConfigForm(...)" and
|
||||
// x-on:submit.prevent="window.PluginConfigHelpers.savePluginConfig(...)"
|
||||
};
|
||||
}
|
||||
|
||||
// Alpine.js app function - full implementation
|
||||
function app() {
|
||||
@@ -3399,134 +3189,6 @@
|
||||
// Make app() available globally
|
||||
window.app = app;
|
||||
|
||||
// ===== DEPRECATED: Plugin Configuration Functions (Global Access) =====
|
||||
// These functions are no longer the primary method for loading plugin configs.
|
||||
// Plugin configuration forms are now rendered server-side via HTMX.
|
||||
// See: /v3/partials/plugin-config/<plugin_id> for the new implementation.
|
||||
// Kept for backwards compatibility with any remaining client-side code.
|
||||
window.PluginConfigHelpers = {
|
||||
loadPluginConfig: async function(pluginId, componentContext) {
|
||||
// This function can be called from inline components
|
||||
// It loads config, schema, and updates the component context
|
||||
if (!componentContext) {
|
||||
console.error('loadPluginConfig requires component context');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Loading config for plugin:', pluginId);
|
||||
componentContext.loading = true;
|
||||
|
||||
try {
|
||||
// Load config, schema, and installed plugins (for web_ui_actions) in parallel
|
||||
let configData, schemaData, pluginsData;
|
||||
|
||||
if (window.PluginAPI && window.PluginAPI.batch) {
|
||||
try {
|
||||
const results = await window.PluginAPI.batch([
|
||||
{endpoint: `/plugins/config?plugin_id=${pluginId}`, method: 'GET'},
|
||||
{endpoint: `/plugins/schema?plugin_id=${pluginId}`, method: 'GET'},
|
||||
{endpoint: '/plugins/installed', method: 'GET'}
|
||||
]);
|
||||
[configData, schemaData, pluginsData] = results;
|
||||
} catch (batchError) {
|
||||
console.error('Batch API request failed, falling back to individual requests:', batchError);
|
||||
// Fall back to individual requests
|
||||
const [configResponse, schemaResponse, pluginsResponse] = await Promise.all([
|
||||
fetch(`/api/v3/plugins/config?plugin_id=${pluginId}`).then(r => r.json()).catch(e => ({ status: 'error', message: e.message })),
|
||||
fetch(`/api/v3/plugins/schema?plugin_id=${pluginId}`).then(r => r.json()).catch(e => ({ status: 'error', message: e.message })),
|
||||
fetch(`/api/v3/plugins/installed`).then(r => r.json()).catch(e => ({ status: 'error', message: e.message }))
|
||||
]);
|
||||
configData = configResponse;
|
||||
schemaData = schemaResponse;
|
||||
pluginsData = pluginsResponse;
|
||||
}
|
||||
} else {
|
||||
const [configResponse, schemaResponse, pluginsResponse] = await Promise.all([
|
||||
fetch(`/api/v3/plugins/config?plugin_id=${pluginId}`).then(r => r.json()).catch(e => ({ status: 'error', message: e.message })),
|
||||
fetch(`/api/v3/plugins/schema?plugin_id=${pluginId}`).then(r => r.json()).catch(e => ({ status: 'error', message: e.message })),
|
||||
fetch(`/api/v3/plugins/installed`).then(r => r.json()).catch(e => ({ status: 'error', message: e.message }))
|
||||
]);
|
||||
configData = configResponse;
|
||||
schemaData = schemaResponse;
|
||||
pluginsData = pluginsResponse;
|
||||
}
|
||||
|
||||
if (configData && configData.status === 'success') {
|
||||
componentContext.config = configData.data;
|
||||
} else {
|
||||
console.warn('Config API returned non-success status:', configData);
|
||||
// Set defaults if config failed to load
|
||||
componentContext.config = { enabled: true, display_duration: 30 };
|
||||
}
|
||||
|
||||
if (schemaData && schemaData.status === 'success') {
|
||||
componentContext.schema = schemaData.data.schema || {};
|
||||
} else {
|
||||
console.warn('Schema API returned non-success status:', schemaData);
|
||||
// Set empty schema as fallback
|
||||
componentContext.schema = {};
|
||||
}
|
||||
|
||||
if (pluginsData && pluginsData.status === 'success' && pluginsData.data && pluginsData.data.plugins) {
|
||||
const pluginInfo = pluginsData.data.plugins.find(p => p.id === pluginId);
|
||||
componentContext.webUiActions = pluginInfo ? (pluginInfo.web_ui_actions || []) : [];
|
||||
} else {
|
||||
console.warn('Plugins API returned non-success status:', pluginsData);
|
||||
componentContext.webUiActions = [];
|
||||
}
|
||||
|
||||
console.log('Loaded config, schema, and actions for', pluginId);
|
||||
} catch (error) {
|
||||
console.error('Error loading plugin config:', error);
|
||||
componentContext.config = { enabled: true, display_duration: 30 };
|
||||
componentContext.schema = {};
|
||||
componentContext.webUiActions = [];
|
||||
} finally {
|
||||
componentContext.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
generateConfigForm: function(pluginId, config, schema, webUiActions, componentContext) {
|
||||
// Try to get the app component
|
||||
let appComponent = null;
|
||||
if (window.Alpine) {
|
||||
const appElement = document.querySelector('[x-data="app()"]');
|
||||
if (appElement && appElement._x_dataStack && appElement._x_dataStack[0]) {
|
||||
appComponent = appElement._x_dataStack[0];
|
||||
}
|
||||
}
|
||||
|
||||
// If we have access to the app component, use its method
|
||||
if (appComponent && typeof appComponent.generateConfigForm === 'function') {
|
||||
return appComponent.generateConfigForm(pluginId, config, schema, webUiActions);
|
||||
}
|
||||
|
||||
// Fallback: return loading message if function not available
|
||||
if (!pluginId || !config) {
|
||||
return '<div class="text-gray-500">Loading configuration...</div>';
|
||||
}
|
||||
return '<div class="text-gray-500">Configuration form not available yet...</div>';
|
||||
},
|
||||
|
||||
savePluginConfig: async function(pluginId, event, componentContext) {
|
||||
// Try to get the app component
|
||||
let appComponent = null;
|
||||
if (window.Alpine) {
|
||||
const appElement = document.querySelector('[x-data="app()"]');
|
||||
if (appElement && appElement._x_dataStack && appElement._x_dataStack[0]) {
|
||||
appComponent = appElement._x_dataStack[0];
|
||||
}
|
||||
}
|
||||
|
||||
// If we have access to the app component, use its method
|
||||
if (appComponent && typeof appComponent.savePluginConfig === 'function') {
|
||||
return appComponent.savePluginConfig(pluginId, event);
|
||||
}
|
||||
|
||||
console.error('savePluginConfig not available');
|
||||
throw new Error('Save configuration method not available');
|
||||
}
|
||||
};
|
||||
|
||||
// ===== Nested Section Toggle =====
|
||||
window.toggleNestedSection = function(sectionId, event) {
|
||||
|
||||
@@ -466,65 +466,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Configuration Modal -->
|
||||
<div id="plugin-config-modal" class="fixed inset-0 modal-backdrop flex items-center justify-center z-50" style="display: none;">
|
||||
<div class="modal-content p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 id="plugin-config-title" class="text-lg font-semibold">Plugin Configuration</h3>
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- View Toggle -->
|
||||
<div class="flex items-center bg-gray-100 rounded-lg p-1">
|
||||
<button id="view-toggle-form" class="view-toggle-btn active px-3 py-1 rounded text-sm font-medium transition-colors" data-view="form">
|
||||
<i class="fas fa-list mr-1"></i>Form
|
||||
</button>
|
||||
<button id="view-toggle-json" class="view-toggle-btn px-3 py-1 rounded text-sm font-medium transition-colors" data-view="json">
|
||||
<i class="fas fa-code mr-1"></i>JSON
|
||||
</button>
|
||||
</div>
|
||||
<!-- Reset Button -->
|
||||
<button id="reset-to-defaults-btn" class="px-3 py-1 text-sm bg-yellow-500 hover:bg-yellow-600 text-white rounded transition-colors" title="Reset to defaults">
|
||||
<i class="fas fa-undo mr-1"></i>Reset
|
||||
</button>
|
||||
<button id="close-plugin-config" class="text-gray-400 hover:text-gray-600">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Validation Errors Display -->
|
||||
<div id="plugin-config-validation-errors" class="hidden mb-4 p-3 bg-red-50 border border-red-200 rounded-md">
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-exclamation-circle text-red-600 mt-0.5 mr-2"></i>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium text-red-800 mb-2">Configuration Validation Errors</p>
|
||||
<ul id="validation-errors-list" class="text-sm text-red-700 list-disc list-inside space-y-1"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Form View -->
|
||||
<div id="plugin-config-form-view" class="plugin-config-view">
|
||||
<div id="plugin-config-content">
|
||||
<!-- Plugin config form will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- JSON Editor View -->
|
||||
<div id="plugin-config-json-view" class="plugin-config-view hidden">
|
||||
<div class="mb-2">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Configuration JSON</label>
|
||||
<textarea id="plugin-config-json-editor" class="w-full border border-gray-300 rounded-md font-mono text-sm" rows="20"></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-2 pt-2 border-t border-gray-200">
|
||||
<button type="button" onclick="closePluginConfigModal()" class="btn bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" id="save-json-config-btn" class="btn bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md">
|
||||
<i class="fas fa-save mr-2"></i>Save Configuration
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- On-Demand Modal moved to base.html so it's always available -->
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user