fix(web-ui): Fix GitHub token warning persistence and improve UX (#154)

* fix(web): Resolve font display and config API error handling issues

- Fix font catalog display error where path.startsWith fails
  (path is object, not string)
- Update save_main_config to use error_response() helper
- Improve save_raw_main_config error handling consistency
- Add proper error codes and traceback details to API responses

* fix(web): Prevent fontCatalog redeclaration error on HTMX reload

- Use window object to store global font variables
- Check if script has already loaded before declaring variables
- Update both window properties and local references on assignment
- Fixes 'Identifier fontCatalog has already been declared' error

* fix(web): Wrap fonts script in IIFE to prevent all redeclaration errors

- Wrap entire script in IIFE that only runs once
- Check if script already loaded before declaring variables/functions
- Expose initializeFontsTab to window for re-initialization
- Prevents 'Identifier has already been declared' errors on HTMX reload

* fix(web): Exempt config save API endpoints from CSRF protection

- Exempt save_raw_main_config, save_raw_secrets_config, and save_main_config from CSRF
- These endpoints are called via fetch from JavaScript and don't include CSRF tokens
- Fixes 500 error when saving config via raw JSON editor

* fix(web): Exempt system action endpoint from CSRF protection

- Exempt execute_system_action from CSRF
- Fixes 500 error when using system action buttons (restart display, restart Pi, etc.)
- These endpoints are called via HTMX and don't include CSRF tokens

* fix(web): Exempt all API v3 endpoints from CSRF protection

- Add before_request handler to exempt all api_v3.* endpoints
- All API endpoints are programmatic (HTMX/fetch) and don't include CSRF tokens
- Prevents future CSRF errors on any API endpoint
- Cleaner than exempting individual endpoints

* refactor(web): Remove CSRF protection for local-only application

- CSRF is designed for internet-facing apps to prevent cross-site attacks
- For local-only Raspberry Pi app, threat model is different
- All endpoints were exempted anyway, so it wasn't protecting anything
- Forms use HTMX without CSRF tokens
- If exposing to internet later, can re-enable with proper token implementation

* fix(web): Fix font path double-prefixing in font catalog display

- Only prefix with 'assets/fonts/' if path is a bare filename
- If path starts with '/' (absolute) or 'assets/' (already prefixed), use as-is
- Fixes double-prefixing when get_fonts_catalog returns relative paths like 'assets/fonts/press_start.ttf'

* fix(web): Remove fontsTabInitialized guard to allow re-initialization on HTMX reload

- Remove fontsTabInitialized check that prevented re-initialization on HTMX content swap
- The window._fontsScriptLoaded guard is sufficient to prevent function redeclaration
- Allow initializeFontsTab() to run on each HTMX swap to attach listeners to new DOM elements
- Fixes fonts UI breaking after HTMX reload (buttons, upload dropzone, etc. not working)

* fix(api): Preserve empty strings for optional string fields in plugin config

- Add _is_field_required() helper to check if fields are required in schema
- Update _parse_form_value_with_schema() to preserve empty strings for optional string fields
- Fixes 400 error when saving MQTT plugin config with empty username/password
- Resolves validation error: 'Expected type string, got NoneType'

* fix(config): Add defaults to schemas and fix None value handling

- Updated merge_with_defaults to replace None values with defaults
- Fixed form processing to skip empty optional fields without defaults
- Added script to automatically add defaults to all plugin config schemas
- Added defaults to 89 fields across 10 plugin schemas
- Prevents validation errors from None values in configs

Changes:
- schema_manager.py: Enhanced merge_with_defaults to replace None with defaults
- api_v3.py: Added _SKIP_FIELD sentinel to skip optional fields without defaults
- add_defaults_to_schemas.py: Script to add sensible defaults to schemas
- Plugin schemas: Added defaults for number, boolean, and array fields

* fix(config): Fix save button spinner by checking HTTP status code

- Fixed handleConfigSave to check xhr.status instead of event.detail.successful
- With hx-swap="none", HTMX doesn't set event.detail.successful
- Now properly detects successful saves (status 200-299) and stops spinner
- Improved error message extraction from API responses
- Also fixed handleToggleResponse for consistency

* fix(web-ui): Resolve GitHub token warning persistence after save

- Made checkGitHubAuthStatus() return Promise for proper async handling
- Clear sessionStorage dismissal flag when token is saved
- Add delay before status check to ensure backend token reload
- Wait for status check completion before hiding settings panel

Fixes issue where GitHub token warnings and pop-ups would not
disappear after successfully saving a token in the web UI.

* fix(web-ui): Add token validation and improve GitHub token warning behavior

- Add token validation to backend API endpoint to check if token is valid/expired
- Implement _validate_github_token() method in PluginStoreManager with caching
- Update frontend to show warning only when token is missing or invalid
- Keep settings panel accessible (collapsible) when token is configured
- Collapse settings panel content after successful token save instead of hiding
- Display specific error messages for invalid/expired tokens
- Clear sessionStorage dismissal flag when token becomes valid

Fixes issue where GitHub token warnings and settings panel would not
properly hide/show based on token status. Now validates token validity
and provides better UX with collapsible settings panel.

* fix(web-ui): Fix CSS/display issue for GitHub token warning and settings

- Update all hide/show operations to use both classList and style.display
- Fix checkGitHubAuthStatus() to properly hide/show warning and settings
- Fix dismissGithubWarning() to use both methods
- Fix toggleGithubTokenSettings() with improved state checking
- Fix collapse button handler with improved state checking
- Fix saveGithubToken() to properly show/collapse settings panel

This ensures elements actually hide/show when status changes, matching
the pattern used elsewhere in the codebase (like toggleSection). All
buttons (dismiss, close, collapse) should now work correctly.

* fix(web-ui): Fix GitHub token expand button functionality

- Convert collapse button handler to named function (toggleGithubTokenContent)
- Improve state checking using class, inline style, and computed style
- Re-attach event listener after saving token to ensure it works
- Add console logging for debugging
- Make function globally accessible for better reliability

Fixes issue where expand button didn't work after saving token.

* fix(web-ui): Remove X button and improve GitHub token panel behavior

- Remove X (close) button from GitHub token configuration panel
- Replace toggleGithubTokenSettings() with openGithubTokenSettings() that only opens
- Auto-collapse panel when token is valid (user must click expand to edit)
- Auto-detect token status on page load (no need to click save)
- Simplify saveGithubToken() to rely on checkGitHubAuthStatus() for UI updates
- Ensure expand button works correctly with proper event listener attachment

The panel now remains visible but collapsed when a token is configured,
allowing users to expand it when needed without the ability to completely hide it.

* refactor(web-ui): Improve GitHub token collapse button code quality

- Update comment to reflect actual behavior (prevent parent click handlers)
- Use empty string for display to defer to CSS instead of hard-coding block/none
- Extract duplicate clone-and-attach logic into attachGithubTokenCollapseHandler() helper
- Make helper function globally accessible for reuse in checkGitHubAuthStatus()

Improves maintainability and makes code more future-proof for layout changes.

* fix(web-ui): Fix collapse/expand button by using removeProperty for display

- Use style.removeProperty('display') instead of style.display = ''
- This properly removes inline styles and defers to CSS classes
- Fixes issue where collapse/expand button stopped working after refactor

* fix(web-ui): Make display handling consistent for token collapse

- Use removeProperty('display') consistently in all places
- Fix checkGitHubAuthStatus() to use removeProperty instead of inline style
- Simplify state checking to rely on hidden class with computed style fallback
- Ensures collapse/expand button works correctly by deferring to CSS classes

* fix(web-ui): Fix token collapse button and simplify state detection

- Simplify state checking to rely on hidden class only (element has class='block')
- Only remove inline display style if it exists (check before removing)
- Add console logging to debug handler attachment
- Ensure collapse/expand works by relying on CSS classes

Fixes issues where:
- Collapse button did nothing
- Auto-detection of token status wasn't working

* debug(web-ui): Add extensive debugging for token collapse button

- Add console logs to track function calls and element detection
- Improve state detection to use computed style as fallback
- Add wrapper function for click handler to ensure it's called
- Better error messages to identify why handler might not attach

This will help identify why the collapse button isn't working.

* debug(web-ui): Add comprehensive debugging for GitHub token features

- Add console logs to checkGitHubAuthStatus() to track execution
- Re-attach collapse handler after plugin store is rendered
- Add error stack traces for better debugging
- Ensure handler is attached when content is dynamically loaded

This will help identify why:
- Auto-detection of token status isn't working
- Collapse button isn't functioning

* fix(web-ui): Move checkGitHubAuthStatus before IIFE to fix scope issue

- Move checkGitHubAuthStatus function definition before IIFE starts
- Function was defined after IIFE but called inside it, causing it to be undefined
- Now function is available when called during initialization
- This should fix auto-detection of token status on page load

* debug(web-ui): Add extensive logging to GitHub token functions

- Add logging when checkGitHubAuthStatus is defined
- Add logging when function is called during initialization
- Add logging in attachGithubTokenCollapseHandler
- Add logging in store render callback
- This will help identify why functions aren't executing

* fix(web-ui): Move GitHub token functions outside IIFE for availability

- Move attachGithubTokenCollapseHandler and toggleGithubTokenContent outside IIFE
- These functions need to be available when store renders, before IIFE completes
- Add logging to initializePlugins to track when it's called
- This should fix the 'undefined' error when store tries to attach handlers

* fix(web-ui): Fix GitHub token content collapse/expand functionality

- Element has 'block' class in HTML which conflicts with 'hidden' class
- When hiding: add 'hidden', remove 'block', set display:none inline
- When showing: remove 'hidden', add 'block', remove inline display
- This ensures proper visibility toggle for the GitHub API Configuration section

* perf(web-ui): Optimize GitHub token detection speed

- Call checkGitHubAuthStatus immediately when script loads (if elements exist)
- Call it early in initPluginsPage (before full initialization completes)
- Use requestAnimationFrame instead of setTimeout(100ms) for store render callback
- Reduce save token delay from 300ms to 100ms
- Token detection now happens in parallel with other initialization tasks
- This makes token status visible much faster on page load

* fix(web-ui): Fix all collapse/expand buttons on plugins page

- Fix Installed Plugins section collapse/expand button
- Fix Plugin Store section collapse/expand button
- Fix GitHub Install section collapse/expand button
- Apply same fixes as GitHub token button:
  * Clone buttons to remove existing listeners
  * Handle block/hidden class conflicts properly
  * Add proper event prevention (stopPropagation/preventDefault)
  * Add logging for debugging
- All collapse/expand buttons should now work correctly

* fix(web-ui): Fix syntax error in setupGitHubInstallHandlers

- Ensure all handler setup code is inside the function
- Add comment to mark function end clearly

* refactor(web-ui): Remove collapse buttons from Installed Plugins and Plugin Store

- Remove collapse/expand buttons from Installed Plugins section
- Remove collapse/expand buttons from Plugin Store section
- Remove related JavaScript handler code
- These sections are now always visible for better UX
- GitHub token section still has collapse functionality

---------

Co-authored-by: Chuck <chuck@example.com>
This commit is contained in:
Chuck
2025-12-28 14:18:51 -05:00
committed by GitHub
parent f7e8266e64
commit 97a301a1a9
4 changed files with 493 additions and 170 deletions

View File

@@ -449,6 +449,215 @@ if (_PLUGIN_DEBUG_EARLY) {
}
}
// GitHub Token Collapse Handler - Define early so it's available before IIFE
console.log('[DEFINE] Defining attachGithubTokenCollapseHandler function...');
window.attachGithubTokenCollapseHandler = function() {
console.log('[attachGithubTokenCollapseHandler] Starting...');
const toggleTokenCollapseBtn = document.getElementById('toggle-github-token-collapse');
console.log('[attachGithubTokenCollapseHandler] Button found:', !!toggleTokenCollapseBtn);
if (!toggleTokenCollapseBtn) {
console.warn('[attachGithubTokenCollapseHandler] GitHub token collapse button not found');
return;
}
console.log('[attachGithubTokenCollapseHandler] Checking toggleGithubTokenContent...', {
exists: typeof window.toggleGithubTokenContent
});
if (!window.toggleGithubTokenContent) {
console.warn('[attachGithubTokenCollapseHandler] toggleGithubTokenContent function not defined');
return;
}
// Remove any existing listeners by cloning the button
const parent = toggleTokenCollapseBtn.parentNode;
if (!parent) {
console.warn('[attachGithubTokenCollapseHandler] Button parent not found');
return;
}
const newBtn = toggleTokenCollapseBtn.cloneNode(true);
parent.replaceChild(newBtn, toggleTokenCollapseBtn);
// Attach listener to the new button
newBtn.addEventListener('click', function(e) {
console.log('[attachGithubTokenCollapseHandler] Button clicked, calling toggleGithubTokenContent');
window.toggleGithubTokenContent(e);
});
console.log('[attachGithubTokenCollapseHandler] Handler attached to button:', newBtn.id);
};
// Toggle GitHub Token Settings section
console.log('[DEFINE] Defining toggleGithubTokenContent function...');
window.toggleGithubTokenContent = function(e) {
console.log('[toggleGithubTokenContent] called', e);
if (e) {
e.stopPropagation();
e.preventDefault();
}
const tokenContent = document.getElementById('github-token-content');
const tokenIconCollapse = document.getElementById('github-token-icon-collapse');
const toggleTokenCollapseBtn = document.getElementById('toggle-github-token-collapse');
console.log('[toggleGithubTokenContent] Elements found:', {
tokenContent: !!tokenContent,
tokenIconCollapse: !!tokenIconCollapse,
toggleTokenCollapseBtn: !!toggleTokenCollapseBtn
});
if (!tokenContent || !toggleTokenCollapseBtn) {
console.warn('[toggleGithubTokenContent] GitHub token content or button not found');
return;
}
const hasHiddenClass = tokenContent.classList.contains('hidden');
const computedDisplay = window.getComputedStyle(tokenContent).display;
console.log('[toggleGithubTokenContent] Current state:', {
hasHiddenClass,
computedDisplay,
buttonText: toggleTokenCollapseBtn.querySelector('span')?.textContent
});
if (hasHiddenClass || computedDisplay === 'none') {
// Show content - remove hidden class, add block class, remove inline display
tokenContent.classList.remove('hidden');
tokenContent.classList.add('block');
tokenContent.style.removeProperty('display');
if (tokenIconCollapse) {
tokenIconCollapse.classList.remove('fa-chevron-down');
tokenIconCollapse.classList.add('fa-chevron-up');
}
const span = toggleTokenCollapseBtn.querySelector('span');
if (span) span.textContent = 'Collapse';
console.log('[toggleGithubTokenContent] Content shown - removed hidden, added block');
} else {
// Hide content - add hidden class, remove block class, ensure display is none
tokenContent.classList.add('hidden');
tokenContent.classList.remove('block');
tokenContent.style.display = 'none';
if (tokenIconCollapse) {
tokenIconCollapse.classList.remove('fa-chevron-up');
tokenIconCollapse.classList.add('fa-chevron-down');
}
const span = toggleTokenCollapseBtn.querySelector('span');
if (span) span.textContent = 'Expand';
console.log('[toggleGithubTokenContent] Content hidden - added hidden, removed block, set display:none');
}
};
// GitHub Authentication Status - Define early so it's available in IIFE
// Shows warning banner only when token is missing or invalid
// The token itself is never exposed to the frontend for security
// Returns a Promise so it can be awaited
console.log('[DEFINE] Defining checkGitHubAuthStatus function...');
window.checkGitHubAuthStatus = function checkGitHubAuthStatus() {
console.log('[checkGitHubAuthStatus] Starting...');
return fetch('/api/v3/plugins/store/github-status')
.then(response => {
console.log('checkGitHubAuthStatus: Response status:', response.status);
return response.json();
})
.then(data => {
console.log('checkGitHubAuthStatus: Data received:', data);
if (data.status === 'success') {
const authData = data.data;
const tokenStatus = authData.token_status || (authData.authenticated ? 'valid' : 'none');
console.log('checkGitHubAuthStatus: Token status:', tokenStatus);
const warning = document.getElementById('github-auth-warning');
const settings = document.getElementById('github-token-settings');
const rateLimit = document.getElementById('rate-limit-count');
console.log('checkGitHubAuthStatus: Elements found:', {
warning: !!warning,
settings: !!settings,
rateLimit: !!rateLimit
});
// Show warning only when token is missing ('none') or invalid ('invalid')
if (tokenStatus === 'none' || tokenStatus === 'invalid') {
// Check if user has dismissed the warning (stored in session storage)
const dismissed = sessionStorage.getItem('github-auth-warning-dismissed');
if (!dismissed) {
if (warning && rateLimit) {
rateLimit.textContent = authData.rate_limit;
// Update warning message for invalid tokens
if (tokenStatus === 'invalid' && authData.error) {
const warningText = warning.querySelector('p.text-sm.text-yellow-700');
if (warningText) {
// Preserve the structure but update the message
const errorMsg = authData.message || authData.error;
warningText.innerHTML = `<strong>Token Invalid:</strong> ${errorMsg}. Please update your GitHub token to increase API rate limits to 5,000 requests/hour.`;
}
}
// For 'none' status, use the default message from HTML template
// Show warning using both classList and style.display
warning.classList.remove('hidden');
warning.style.display = '';
console.log(`GitHub token status: ${tokenStatus} - showing API limit warning`);
}
}
// Ensure settings panel is accessible when token is missing or invalid
// Panel can be opened via "Configure Token" link in warning
// Don't force it to be visible, but don't prevent it from being shown
} else if (tokenStatus === 'valid') {
// Token is valid - hide warning and ensure settings panel is visible but collapsed
if (warning) {
// Hide warning using both classList and style.display
warning.classList.add('hidden');
warning.style.display = 'none';
console.log('GitHub token is valid - hiding API limit warning');
}
// Make settings panel visible but collapsed (accessible for token management)
if (settings) {
// Remove hidden class from panel itself - make it visible using both methods
settings.classList.remove('hidden');
settings.style.display = '';
// Always collapse the content when token is valid (user must click expand)
const tokenContent = document.getElementById('github-token-content');
if (tokenContent) {
// Collapse the content - add hidden, remove block, set display none
tokenContent.classList.add('hidden');
tokenContent.classList.remove('block');
tokenContent.style.display = 'none';
}
// Update collapse button state to show "Expand"
const tokenIconCollapse = document.getElementById('github-token-icon-collapse');
if (tokenIconCollapse) {
tokenIconCollapse.classList.remove('fa-chevron-up');
tokenIconCollapse.classList.add('fa-chevron-down');
}
const toggleTokenCollapseBtn = document.getElementById('toggle-github-token-collapse');
if (toggleTokenCollapseBtn) {
const span = toggleTokenCollapseBtn.querySelector('span');
if (span) span.textContent = 'Expand';
// Ensure event listener is attached
if (window.attachGithubTokenCollapseHandler) {
window.attachGithubTokenCollapseHandler();
}
}
}
// Clear dismissal flag when token becomes valid
sessionStorage.removeItem('github-auth-warning-dismissed');
}
}
})
.catch(error => {
console.error('Error checking GitHub auth status:', error);
console.error('Error stack:', error.stack || 'No stack trace');
});
};
(function() {
'use strict';
@@ -567,6 +776,13 @@ window.initPluginsPage = function() {
window.pluginManager.initializing = true;
window.__pluginDomReady = true;
// Check GitHub auth status immediately (don't wait for full initialization)
// This can run in parallel with other initialization
if (window.checkGitHubAuthStatus) {
console.log('[INIT] Checking GitHub auth status immediately...');
window.checkGitHubAuthStatus();
}
// If we fetched data before the DOM existed, render it now
if (window.__pendingInstalledPlugins) {
console.log('[RENDER] Applying pending installed plugins data');
@@ -706,17 +922,33 @@ function initializePluginPageWhenReady() {
let pluginsInitialized = false;
function initializePlugins() {
console.log('[initializePlugins] Called, pluginsInitialized:', pluginsInitialized);
// Guard against multiple initializations
if (pluginsInitialized) {
console.log('[initializePlugins] Already initialized, skipping');
pluginLog('[INIT] Plugins already initialized, skipping');
return;
}
pluginsInitialized = true;
console.log('[initializePlugins] Starting initialization...');
pluginLog('[INIT] Initializing plugins...');
// Check GitHub authentication status
checkGitHubAuthStatus();
console.log('[INIT] Checking for checkGitHubAuthStatus function...', {
exists: typeof window.checkGitHubAuthStatus,
type: typeof window.checkGitHubAuthStatus
});
if (window.checkGitHubAuthStatus) {
console.log('[INIT] Calling checkGitHubAuthStatus...');
try {
window.checkGitHubAuthStatus();
} catch (error) {
console.error('[INIT] Error calling checkGitHubAuthStatus:', error);
}
} else {
console.warn('[INIT] checkGitHubAuthStatus not available yet');
}
// Load both installed plugins and plugin store
loadInstalledPlugins();
@@ -3826,6 +4058,37 @@ function searchPluginStore(fetchCommitInfo = true) {
} catch (e) {
console.warn('Could not update store count:', e);
}
// Ensure GitHub token collapse handler is attached after store is rendered
// The button might not exist until the store content is loaded
console.log('[STORE] Checking for attachGithubTokenCollapseHandler...', {
exists: typeof window.attachGithubTokenCollapseHandler,
checkGitHubAuthStatus: typeof window.checkGitHubAuthStatus
});
if (window.attachGithubTokenCollapseHandler) {
// Use requestAnimationFrame for faster execution (runs on next frame, ~16ms)
requestAnimationFrame(() => {
console.log('[STORE] Re-attaching GitHub token collapse handler after store render');
try {
window.attachGithubTokenCollapseHandler();
} catch (error) {
console.error('[STORE] Error attaching collapse handler:', error);
}
// Also check auth status to update UI (already checked earlier, but refresh to be sure)
if (window.checkGitHubAuthStatus) {
console.log('[STORE] Refreshing GitHub auth status after store render...');
try {
window.checkGitHubAuthStatus();
} catch (error) {
console.error('[STORE] Error calling checkGitHubAuthStatus:', error);
}
} else {
console.warn('[STORE] checkGitHubAuthStatus not available');
}
});
} else {
console.warn('[STORE] attachGithubTokenCollapseHandler not available');
}
} else {
showError('Failed to search plugin store: ' + data.message);
try {
@@ -3977,78 +4240,17 @@ window.installPlugin = function(pluginId, branch = null) {
}
function setupCollapsibleSections() {
// Toggle Installed Plugins section
const toggleInstalledBtn = document.getElementById('toggle-installed-plugins');
const installedContent = document.getElementById('installed-plugins-content');
const installedIcon = document.getElementById('installed-plugins-icon');
console.log('[setupCollapsibleSections] Setting up collapsible sections...');
if (toggleInstalledBtn && installedContent) {
toggleInstalledBtn.addEventListener('click', function() {
const isHidden = installedContent.style.display === 'none' || installedContent.classList.contains('hidden');
if (isHidden) {
installedContent.style.display = 'block';
installedContent.classList.remove('hidden');
installedIcon.classList.remove('fa-chevron-down');
installedIcon.classList.add('fa-chevron-up');
toggleInstalledBtn.querySelector('span').textContent = 'Collapse';
} else {
installedContent.style.display = 'none';
installedContent.classList.add('hidden');
installedIcon.classList.remove('fa-chevron-up');
installedIcon.classList.add('fa-chevron-down');
toggleInstalledBtn.querySelector('span').textContent = 'Expand';
}
});
// Installed Plugins and Plugin Store sections no longer have collapse buttons
// They are always visible
// Functions are now defined outside IIFE, just attach the handler
if (window.attachGithubTokenCollapseHandler) {
window.attachGithubTokenCollapseHandler();
}
// Toggle Plugin Store section
const toggleStoreBtn = document.getElementById('toggle-plugin-store');
const storeContent = document.getElementById('plugin-store-content');
const storeIcon = document.getElementById('plugin-store-icon');
if (toggleStoreBtn && storeContent) {
toggleStoreBtn.addEventListener('click', function() {
const isHidden = storeContent.style.display === 'none' || storeContent.classList.contains('hidden');
if (isHidden) {
storeContent.style.display = 'block';
storeContent.classList.remove('hidden');
storeIcon.classList.remove('fa-chevron-down');
storeIcon.classList.add('fa-chevron-up');
toggleStoreBtn.querySelector('span').textContent = 'Collapse';
} else {
storeContent.style.display = 'none';
storeContent.classList.add('hidden');
storeIcon.classList.remove('fa-chevron-up');
storeIcon.classList.add('fa-chevron-down');
toggleStoreBtn.querySelector('span').textContent = 'Expand';
}
});
}
// Toggle GitHub Token Settings section
const toggleTokenCollapseBtn = document.getElementById('toggle-github-token-collapse');
const tokenContent = document.getElementById('github-token-content');
const tokenIconCollapse = document.getElementById('github-token-icon-collapse');
if (toggleTokenCollapseBtn && tokenContent) {
toggleTokenCollapseBtn.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent triggering the close button
const isHidden = tokenContent.style.display === 'none' || tokenContent.classList.contains('hidden');
if (isHidden) {
tokenContent.style.display = 'block';
tokenContent.classList.remove('hidden');
tokenIconCollapse.classList.remove('fa-chevron-down');
tokenIconCollapse.classList.add('fa-chevron-up');
toggleTokenCollapseBtn.querySelector('span').textContent = 'Collapse';
} else {
tokenContent.style.display = 'none';
tokenContent.classList.add('hidden');
tokenIconCollapse.classList.remove('fa-chevron-up');
tokenIconCollapse.classList.add('fa-chevron-down');
toggleTokenCollapseBtn.querySelector('span').textContent = 'Expand';
}
});
}
console.log('[setupCollapsibleSections] Collapsible sections setup complete');
}
function loadSavedRepositories() {
@@ -4135,26 +4337,66 @@ window.removeSavedRepository = function(repoUrl) {
}
function setupGitHubInstallHandlers() {
console.log('[setupGitHubInstallHandlers] Setting up GitHub install handlers...');
// Toggle GitHub install section visibility
const toggleBtn = document.getElementById('toggle-github-install');
const installSection = document.getElementById('github-install-section');
const icon = document.getElementById('github-install-icon');
console.log('[setupGitHubInstallHandlers] Elements found:', {
button: !!toggleBtn,
section: !!installSection,
icon: !!icon
});
if (toggleBtn && installSection) {
toggleBtn.addEventListener('click', function() {
const isHidden = installSection.classList.contains('hidden');
if (isHidden) {
installSection.classList.remove('hidden');
icon.classList.remove('fa-chevron-down');
icon.classList.add('fa-chevron-up');
toggleBtn.querySelector('span').textContent = 'Hide';
} else {
installSection.classList.add('hidden');
icon.classList.remove('fa-chevron-up');
icon.classList.add('fa-chevron-down');
toggleBtn.querySelector('span').textContent = 'Show';
}
});
// Clone button to remove any existing listeners
const parent = toggleBtn.parentNode;
if (parent) {
const newBtn = toggleBtn.cloneNode(true);
parent.replaceChild(newBtn, toggleBtn);
newBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
console.log('[setupGitHubInstallHandlers] GitHub install toggle clicked');
const section = document.getElementById('github-install-section');
const iconEl = document.getElementById('github-install-icon');
const btn = document.getElementById('toggle-github-install');
if (!section || !btn) return;
const hasHiddenClass = section.classList.contains('hidden');
const computedDisplay = window.getComputedStyle(section).display;
if (hasHiddenClass || computedDisplay === 'none') {
// Show section - remove hidden, ensure visible
section.classList.remove('hidden');
section.style.removeProperty('display');
if (iconEl) {
iconEl.classList.remove('fa-chevron-down');
iconEl.classList.add('fa-chevron-up');
}
const span = btn.querySelector('span');
if (span) span.textContent = 'Hide';
} else {
// Hide section - add hidden, set display none
section.classList.add('hidden');
section.style.display = 'none';
if (iconEl) {
iconEl.classList.remove('fa-chevron-up');
iconEl.classList.add('fa-chevron-down');
}
const span = btn.querySelector('span');
if (span) span.textContent = 'Show';
}
});
console.log('[setupGitHubInstallHandlers] Handler attached');
}
} else {
console.warn('[setupGitHubInstallHandlers] Required elements not found');
}
// Install single plugin from URL
@@ -4669,30 +4911,46 @@ function togglePasswordVisibility(fieldId) {
}
// GitHub Token Configuration Functions
window.toggleGithubTokenSettings = function() {
// Open GitHub Token Settings panel (only opens, doesn't close)
// Used when user clicks "Configure Token" link
window.openGithubTokenSettings = function() {
const settings = document.getElementById('github-token-settings');
const warning = document.getElementById('github-auth-warning');
const tokenContent = document.getElementById('github-token-content');
if (settings) {
// Remove inline style if present to avoid conflicts
if (settings.style.display !== undefined) {
settings.style.display = '';
// Show settings panel using both methods
settings.classList.remove('hidden');
settings.style.display = '';
// Expand the content when opening
if (tokenContent) {
tokenContent.style.removeProperty('display');
tokenContent.classList.remove('hidden');
// Update collapse button state
const tokenIconCollapse = document.getElementById('github-token-icon-collapse');
const toggleTokenCollapseBtn = document.getElementById('toggle-github-token-collapse');
if (tokenIconCollapse) {
tokenIconCollapse.classList.remove('fa-chevron-down');
tokenIconCollapse.classList.add('fa-chevron-up');
}
if (toggleTokenCollapseBtn) {
const span = toggleTokenCollapseBtn.querySelector('span');
if (span) span.textContent = 'Collapse';
}
}
// Toggle Tailwind hidden class
settings.classList.toggle('hidden');
const isOpening = !settings.classList.contains('hidden');
// When opening settings, hide the warning banner (they're now combined)
if (isOpening && warning) {
// When opening settings, hide the warning banner
if (warning) {
warning.classList.add('hidden');
warning.style.display = 'none';
// Clear any dismissal state since user is actively configuring
sessionStorage.removeItem('github-auth-warning-dismissed');
}
// Load token when opening the panel
if (isOpening) {
loadGithubToken();
}
loadGithubToken();
}
}
@@ -4845,13 +5103,17 @@ window.saveGithubToken = function() {
// Clear input field for security (user can reload if needed)
input.value = '';
// Refresh GitHub auth status to update UI (this will hide warning and settings)
checkGitHubAuthStatus();
// Clear the dismissal flag so warning can properly hide/show based on token status
sessionStorage.removeItem('github-auth-warning-dismissed');
// Hide the settings panel after successful save
// Small delay to ensure backend has reloaded the token, then refresh status
// checkGitHubAuthStatus() will handle collapsing the panel automatically
// Reduced delay from 300ms to 100ms - backend should reload quickly
setTimeout(() => {
toggleGithubTokenSettings();
}, 1500);
if (window.checkGitHubAuthStatus) {
window.checkGitHubAuthStatus();
}
}, 100);
} else {
throw new Error(data.message || 'Failed to save token');
}
@@ -4870,60 +5132,18 @@ window.saveGithubToken = function() {
});
}
// GitHub Authentication Status
// Only shows the warning banner if no GitHub token is configured
// The token itself is never exposed to the frontend for security
function checkGitHubAuthStatus() {
fetch('/api/v3/plugins/store/github-status')
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
const authData = data.data;
// Only show the banner if no GitHub token is configured
if (!authData.authenticated) {
// Check if user has dismissed the warning (stored in session storage)
const dismissed = sessionStorage.getItem('github-auth-warning-dismissed');
if (!dismissed) {
const warning = document.getElementById('github-auth-warning');
const rateLimit = document.getElementById('rate-limit-count');
if (warning && rateLimit) {
rateLimit.textContent = authData.rate_limit;
warning.classList.remove('hidden');
console.log('GitHub token not configured - showing API limit warning');
}
}
} else {
// Token is configured - hide both warning and settings
const warning = document.getElementById('github-auth-warning');
const settings = document.getElementById('github-token-settings');
if (warning) {
warning.classList.add('hidden');
console.log('GitHub token is configured - API limit warning hidden');
}
if (settings) {
settings.classList.add('hidden');
console.log('GitHub token is configured - hiding settings panel');
}
}
}
})
.catch(error => {
console.error('Error checking GitHub auth status:', error);
});
}
window.dismissGithubWarning = function() {
const warning = document.getElementById('github-auth-warning');
const settings = document.getElementById('github-token-settings');
if (warning) {
// Hide warning using both classList and style.display
warning.classList.add('hidden');
warning.style.display = 'none';
// Also hide settings if it's open (since they're combined now)
if (settings && !settings.classList.contains('hidden')) {
settings.classList.add('hidden');
settings.style.display = 'none';
}
// Remember dismissal for this session
sessionStorage.setItem('github-auth-warning-dismissed', 'true');
@@ -5639,6 +5859,12 @@ if (_PLUGIN_DEBUG_EARLY) {
});
}
// Check GitHub auth status immediately if elements exist (don't wait for full initialization)
if (window.checkGitHubAuthStatus && document.getElementById('github-auth-warning')) {
console.log('[EARLY] Checking GitHub auth status immediately on script load...');
window.checkGitHubAuthStatus();
}
setTimeout(function() {
const installedGrid = document.getElementById('installed-plugins-grid');
if (installedGrid) {