mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-13 10:36:35 +00:00
Merge audit16-core/misccode into the September 16 core audit fixes
# Conflicts: # CHANGELOG.md
This commit is contained in:
@@ -522,10 +522,14 @@ def save_main_config():
|
||||
if 'plugin_system' not in current_config:
|
||||
current_config['plugin_system'] = {}
|
||||
|
||||
# Handle plugin system checkboxes - always set to handle unchecked state
|
||||
# HTML checkboxes omit the key when unchecked, so missing key = unchecked = False
|
||||
for checkbox in ['auto_discover', 'auto_load_enabled', 'development_mode']:
|
||||
current_config['plugin_system'][checkbox] = _coerce_to_bool(data.get(checkbox))
|
||||
# auto_discover / auto_load_enabled / development_mode are read by
|
||||
# nothing and no longer have General-tab toggles. The form still
|
||||
# posts plugins_directory, so treating a missing key as an
|
||||
# unchecked box would rewrite stored values to false on every
|
||||
# save; only store what a client actually sends.
|
||||
for legacy_flag in ['auto_discover', 'auto_load_enabled', 'development_mode']:
|
||||
if legacy_flag in data:
|
||||
current_config['plugin_system'][legacy_flag] = _coerce_to_bool(data.get(legacy_flag))
|
||||
|
||||
# Handle plugins_directory
|
||||
if 'plugins_directory' in data:
|
||||
|
||||
@@ -988,6 +988,7 @@ def update_plugin():
|
||||
)
|
||||
|
||||
current_last_updated = None
|
||||
current_version = None
|
||||
current_commit = None
|
||||
current_branch = None
|
||||
|
||||
@@ -997,6 +998,7 @@ def update_plugin():
|
||||
with open(manifest_path, 'r', encoding='utf-8') as f:
|
||||
manifest = json.load(f)
|
||||
current_last_updated = manifest.get('last_updated')
|
||||
current_version = manifest.get('version')
|
||||
if manifest.get('local_only'):
|
||||
logger.debug("Skipping update for local-only plugin: %s", plugin_id)
|
||||
if api_v3.operation_history:
|
||||
@@ -1006,7 +1008,9 @@ def update_plugin():
|
||||
status="skipped",
|
||||
details={"reason": "local_only"}
|
||||
)
|
||||
return success_response(message=f'Plugin {plugin_id} is managed locally and does not receive registry updates')
|
||||
return success_response(
|
||||
data={'update_status': 'local_only'},
|
||||
message=f'Plugin {plugin_id} is managed locally and does not receive registry updates')
|
||||
except Exception as e:
|
||||
logger.debug("Could not read local manifest for plugin: %s", e)
|
||||
|
||||
@@ -1036,12 +1040,14 @@ def update_plugin():
|
||||
|
||||
if success:
|
||||
updated_last_updated = current_last_updated
|
||||
updated_version = current_version
|
||||
try:
|
||||
if manifest_path.exists():
|
||||
import json
|
||||
with open(manifest_path, 'r', encoding='utf-8') as f:
|
||||
manifest = json.load(f)
|
||||
updated_last_updated = manifest.get('last_updated', current_last_updated)
|
||||
updated_version = manifest.get('version', current_version)
|
||||
except Exception as e:
|
||||
logger.debug("Could not read updated manifest after update: %s", e)
|
||||
|
||||
@@ -1053,15 +1059,28 @@ def update_plugin():
|
||||
updated_commit = git_info_after.get('sha')
|
||||
updated_branch = git_info_after.get('branch') or updated_branch
|
||||
|
||||
# update_plugin() answers True for "nothing to do" as well as for
|
||||
# a real update (a ZIP-installed monorepo plugin already at the
|
||||
# registry version, a bundled plugin), so what changed is read off
|
||||
# the plugin itself: its git commit, else its manifest.
|
||||
update_status = 'updated'
|
||||
message = f'Plugin {plugin_id} updated successfully'
|
||||
if current_commit and updated_commit and current_commit == updated_commit:
|
||||
update_status = 'up_to_date'
|
||||
message = f'Plugin {plugin_id} already up to date (commit {updated_commit[:7]})'
|
||||
elif updated_commit:
|
||||
message = f'Plugin {plugin_id} updated to commit {updated_commit[:7]}'
|
||||
if updated_branch:
|
||||
message += f' on branch {updated_branch}'
|
||||
elif updated_version and updated_version != current_version:
|
||||
message = f'Plugin {plugin_id} updated to version {updated_version}'
|
||||
elif updated_last_updated and updated_last_updated != current_last_updated:
|
||||
message = f'Plugin {plugin_id} refreshed (Last Updated {updated_last_updated})'
|
||||
elif not current_commit:
|
||||
update_status = 'up_to_date'
|
||||
message = f'Plugin {plugin_id} already up to date'
|
||||
if updated_version:
|
||||
message += f' (version {updated_version})'
|
||||
|
||||
remote_commit_short = remote_commit[:7] if remote_commit else None
|
||||
if remote_commit_short and updated_commit and remote_commit_short != updated_commit[:7]:
|
||||
@@ -1093,14 +1112,16 @@ def update_plugin():
|
||||
"version": version,
|
||||
"previous_commit": current_commit[:7] if current_commit else None,
|
||||
"commit": updated_commit[:7] if updated_commit else None,
|
||||
"branch": updated_branch
|
||||
"branch": updated_branch,
|
||||
"update_status": update_status
|
||||
}
|
||||
)
|
||||
|
||||
return success_response(
|
||||
data={
|
||||
'last_updated': updated_last_updated,
|
||||
'commit': updated_commit
|
||||
'commit': updated_commit,
|
||||
'update_status': update_status
|
||||
},
|
||||
message=message
|
||||
)
|
||||
|
||||
@@ -153,31 +153,54 @@ const PluginAPI = {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
// NETWORK_ERROR means only that fetch() itself rejected: no HTTP
|
||||
// answer arrived (connection refused/reset, e.g. the web service
|
||||
// restarting). Callers retry that (install_manager.js updateAll).
|
||||
// Any HTTP response is the server's -- or a proxy's -- answer, so
|
||||
// a 502 HTML page or a JSON error without error_code is API_ERROR
|
||||
// and is not retried.
|
||||
let response;
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
const responseData = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
// Handle structured errors
|
||||
if (responseData.error_code) {
|
||||
throw responseData;
|
||||
}
|
||||
throw new Error(responseData.message || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
response = await fetch(url, options);
|
||||
} catch (error) {
|
||||
// Re-throw structured errors
|
||||
if (error.error_code) {
|
||||
throw error;
|
||||
}
|
||||
// Wrap network errors
|
||||
throw {
|
||||
error_code: 'NETWORK_ERROR',
|
||||
message: error.message || 'Network error',
|
||||
message: (error && error.message) || 'Network error',
|
||||
original_error: error
|
||||
};
|
||||
}
|
||||
|
||||
let responseData = null;
|
||||
let parseError = null;
|
||||
try {
|
||||
responseData = await response.json();
|
||||
} catch (error) {
|
||||
parseError = error;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
// Handle structured errors
|
||||
if (responseData && responseData.error_code) {
|
||||
throw responseData;
|
||||
}
|
||||
throw {
|
||||
error_code: 'API_ERROR',
|
||||
status: response.status,
|
||||
message: (responseData && responseData.message) || `HTTP ${response.status}`,
|
||||
original_error: parseError || undefined
|
||||
};
|
||||
}
|
||||
|
||||
if (parseError) {
|
||||
throw {
|
||||
error_code: 'API_ERROR',
|
||||
status: response.status,
|
||||
message: `Unreadable response from the server (HTTP ${response.status})`,
|
||||
original_error: parseError
|
||||
};
|
||||
}
|
||||
|
||||
return responseData;
|
||||
};
|
||||
|
||||
// Use throttling for GET requests, immediate execution for POST/PUT/DELETE
|
||||
|
||||
@@ -168,6 +168,58 @@ const PluginInstallManager = {
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Classify one POST /plugins/update answer.
|
||||
*
|
||||
* The route reports what actually happened in `data.update_status`
|
||||
* (`updated`, `up_to_date`, `local_only`). A plugin the updater had
|
||||
* nothing to do for -- e.g. a ZIP-installed monorepo plugin already at the
|
||||
* registry version -- is still a success response, so it must not be
|
||||
* counted as updated. Older servers only say so in the message.
|
||||
*
|
||||
* @param {Object} entry - One element of updateAll()'s results
|
||||
* @returns {string} 'failed' | 'updated' | 'up_to_date' | 'local_only'
|
||||
*/
|
||||
updateOutcome(entry) {
|
||||
if (!entry || !entry.success) return 'failed';
|
||||
const result = entry.result || {};
|
||||
const status = result.data && result.data.update_status;
|
||||
if (status === 'up_to_date' || status === 'local_only' || status === 'updated') {
|
||||
return status;
|
||||
}
|
||||
const message = typeof result.message === 'string' ? result.message : '';
|
||||
if (message.includes('already up to date')) return 'up_to_date';
|
||||
if (message.includes('managed locally')) return 'local_only';
|
||||
return 'updated';
|
||||
},
|
||||
|
||||
/**
|
||||
* Summarise updateAll()'s results for the Check & Update All toast.
|
||||
*
|
||||
* @param {Array} results - updateAll()'s results
|
||||
* @returns {{updated: number, upToDate: number, localOnly: number, failed: number, text: string, type: string}}
|
||||
*/
|
||||
summarizeUpdateResults(results) {
|
||||
const counts = { updated: 0, up_to_date: 0, local_only: 0, failed: 0 };
|
||||
for (const entry of (Array.isArray(results) ? results : [])) {
|
||||
counts[this.updateOutcome(entry)]++;
|
||||
}
|
||||
const parts = [];
|
||||
if (counts.updated > 0) parts.push(`${counts.updated} updated`);
|
||||
if (counts.up_to_date > 0) parts.push(`${counts.up_to_date} already up to date`);
|
||||
if (counts.local_only > 0) parts.push(`${counts.local_only} managed locally`);
|
||||
if (counts.failed > 0) parts.push(`${counts.failed} failed`);
|
||||
const type = counts.failed > 0 ? (counts.updated > 0 ? 'warning' : 'error') : 'success';
|
||||
return {
|
||||
updated: counts.updated,
|
||||
upToDate: counts.up_to_date,
|
||||
localOnly: counts.local_only,
|
||||
failed: counts.failed,
|
||||
text: parts.join(', '),
|
||||
type
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1999,22 +1999,21 @@ function runUpdateAllPlugins() {
|
||||
showNotification('No plugins to update.', 'info');
|
||||
return;
|
||||
}
|
||||
let updated = 0, upToDate = 0, failed = 0;
|
||||
for (const r of results) {
|
||||
if (!r.success) {
|
||||
failed++;
|
||||
} else if (r.result && r.result.message && r.result.message.includes('already up to date')) {
|
||||
upToDate++;
|
||||
} else {
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
const parts = [];
|
||||
if (updated > 0) parts.push(`${updated} updated`);
|
||||
if (upToDate > 0) parts.push(`${upToDate} already up to date`);
|
||||
if (failed > 0) parts.push(`${failed} failed`);
|
||||
const type = failed > 0 ? (updated > 0 ? 'warning' : 'error') : 'success';
|
||||
showNotification(parts.join(', '), type);
|
||||
// Counted by install_manager.js from each answer's update_status:
|
||||
// a no-op update is "already up to date", not "updated". A cached
|
||||
// install_manager.js from before that helper gets a plain count.
|
||||
const manager = window.PluginInstallManager;
|
||||
const summary = (manager && typeof manager.summarizeUpdateResults === 'function')
|
||||
? manager.summarizeUpdateResults(results)
|
||||
: (() => {
|
||||
const failed = results.filter(r => !r.success).length;
|
||||
const checked = results.length - failed;
|
||||
return {
|
||||
text: `${checked} checked` + (failed ? `, ${failed} failed` : ''),
|
||||
type: failed ? (checked ? 'warning' : 'error') : 'success'
|
||||
};
|
||||
})();
|
||||
showNotification(summary.text, summary.type);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error updating all plugins:', error);
|
||||
|
||||
@@ -166,44 +166,10 @@
|
||||
<p class="text-sm text-gray-600 mb-4">Configure the core plugin system behavior.</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Auto Discover -->
|
||||
<div class="form-group" id="setting-general-auto_discover" data-setting-key="plugin_system.auto_discover">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="auto_discover"
|
||||
value="true"
|
||||
{% if main_config.get('plugin_system', {}).get('auto_discover', True) %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Auto Discover Plugins</span>
|
||||
{{ ui.help_tip('Scan the plugins directory for installed plugins each time the service starts.\nDefault: on. Leave on unless you manage plugins manually.', 'Auto Discover Plugins') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Auto Load Enabled -->
|
||||
<div class="form-group" id="setting-general-auto_load_enabled" data-setting-key="plugin_system.auto_load_enabled">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="auto_load_enabled"
|
||||
value="true"
|
||||
{% if main_config.get('plugin_system', {}).get('auto_load_enabled', True) %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Auto Load Enabled Plugins</span>
|
||||
{{ ui.help_tip('Load every plugin marked enabled in the configuration at startup.\nDefault: on. Turn off to keep plugins installed but dormant.', 'Auto Load Enabled Plugins') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Development Mode -->
|
||||
<div class="form-group" id="setting-general-development_mode" data-setting-key="plugin_system.development_mode">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
name="development_mode"
|
||||
value="true"
|
||||
{% if main_config.get('plugin_system', {}).get('development_mode', False) %}checked{% endif %}
|
||||
class="form-control h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">Development Mode</span>
|
||||
{{ ui.help_tip('Enable verbose logging and developer features for plugin debugging.\nDefault: off. Keep off for normal use — it increases log volume.', 'Development Mode') }}
|
||||
</label>
|
||||
</div>
|
||||
<!-- plugin_system.auto_discover, auto_load_enabled and
|
||||
development_mode used to be toggles here. Nothing reads
|
||||
them: plugins are always discovered and every enabled
|
||||
plugin is loaded. Stored values are left alone. -->
|
||||
|
||||
<!-- Plugins Directory -->
|
||||
<div class="form-group" id="setting-general-plugins_directory" data-setting-key="plugin_system.plugins_directory">
|
||||
|
||||
Reference in New Issue
Block a user