diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c463b3201..16a98de895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ # Changelog +## 0.1.57 – 2025-01-16 +### Fixed +- SOLR setup now uses tenant-specific configSets and collections instead of base resources +- Enhanced SOLR setup error reporting with detailed progress tracking and troubleshooting steps +- Fixed dashboard stats availability inconsistency with connection test +- Centralized all SOLR operations through GuzzleSolrService for consistency +- Improved multi-tenant isolation in SOLR infrastructure + +### Added +- Visual progress bar in SOLR setup dialog showing completed vs total steps +- Detailed error information display with primary error, error type, and operation context +- Configuration details shown during failed SOLR setup attempts +- Numbered troubleshooting steps section with actionable items +- Enhanced step timeline with individual step status and timestamps +- Color-coded progress indicators for better user experience + ## 0.1.5 – 2024-09-07 ### Added - First version for the Nextcloud store diff --git a/lib/Setup/SolrSetup.php b/lib/Setup/SolrSetup.php index d8419db59e..375b806bf2 100644 --- a/lib/Setup/SolrSetup.php +++ b/lib/Setup/SolrSetup.php @@ -276,13 +276,15 @@ public function setupSolr(): bool return false; } - // Step 2: Ensure base configSet exists - $this->trackStep(2, 'ConfigSet Creation', 'started', 'Checking and creating base configSet "openregister"'); + // Step 2: Ensure tenant configSet exists + $tenantConfigSetName = $this->getTenantConfigSetName(); + $this->trackStep(2, 'ConfigSet Creation', 'started', 'Checking and creating tenant configSet "' . $tenantConfigSetName . '"'); try { - if (!$this->ensureBaseConfigSet()) { - $this->trackStep(2, 'ConfigSet Creation', 'failed', 'Failed to create base configSet', [ - 'configSet' => 'openregister', + if (!$this->ensureTenantConfigSet()) { + $this->trackStep(2, 'ConfigSet Creation', 'failed', 'Failed to create tenant configSet', [ + 'configSet' => $tenantConfigSetName, + 'template' => '_default', 'error_details' => $this->lastErrorDetails ]); @@ -290,12 +292,13 @@ public function setupSolr(): bool // Enhanced error details for configSet failure if ($this->lastErrorDetails === null) { $this->lastErrorDetails = [ - 'operation' => 'ensureBaseConfigSet', + 'operation' => 'ensureTenantConfigSet', 'step' => 2, 'step_name' => 'ConfigSet Creation', 'error_type' => 'configset_creation_failure', - 'error_message' => 'Failed to create base configSet "openregister"', - 'configSet' => 'openregister', + 'error_message' => 'Failed to create tenant configSet "' . $tenantConfigSetName . '"', + 'configSet' => $tenantConfigSetName, + 'template' => '_default', 'troubleshooting' => [ 'Check if SOLR server has write permissions for config directory', @@ -309,22 +312,25 @@ public function setupSolr(): bool return false; } - $this->trackStep(2, 'ConfigSet Creation', 'completed', 'Base configSet "openregister" is available'); + $this->trackStep(2, 'ConfigSet Creation', 'completed', 'Tenant configSet "' . $tenantConfigSetName . '" is available'); + $this->setupProgress['completed_steps']++; } catch (\Exception $e) { $this->trackStep(2, 'ConfigSet Creation', 'failed', $e->getMessage(), [ 'exception_type' => get_class($e), - 'configSet' => 'openregister' + + 'configSet' => $tenantConfigSetName ]); $this->lastErrorDetails = [ - 'operation' => 'ensureBaseConfigSet', + 'operation' => 'ensureTenantConfigSet', + 'step' => 2, 'step_name' => 'ConfigSet Creation', 'error_type' => 'configset_exception', 'error_message' => $e->getMessage(), 'exception_type' => get_class($e), - 'configSet' => 'openregister' + 'configSet' => $tenantConfigSetName ]; return false; } @@ -334,16 +340,12 @@ public function setupSolr(): bool $this->trackStep(3, 'Collection Creation', 'started', 'Checking and creating tenant collection "' . $tenantCollectionName . '"'); try { - // First ensure base collection exists (as template) - if (!$this->ensureBaseCollectionExists()) { - $this->logger->warning('Base collection creation failed, but continuing with tenant collection'); - } - - // Then ensure tenant collection exists (primary collection for this instance) + // Ensure tenant collection exists (using tenant-specific configSet) if (!$this->ensureTenantCollectionExists()) { + $tenantConfigSetName = $this->getTenantConfigSetName(); $this->trackStep(3, 'Collection Creation', 'failed', 'Failed to create tenant collection', [ 'collection' => $tenantCollectionName, - 'configSet' => 'openregister', + 'configSet' => $tenantConfigSetName, 'error_details' => $this->lastErrorDetails ]); @@ -356,9 +358,9 @@ public function setupSolr(): bool 'error_type' => 'collection_creation_failure', 'error_message' => 'Failed to create tenant collection "' . $tenantCollectionName . '"', 'collection' => $tenantCollectionName, - 'configSet' => 'openregister', + 'configSet' => $tenantConfigSetName, 'troubleshooting' => [ - 'Verify configSet "openregister" was created in previous step', + 'Verify configSet "' . $tenantConfigSetName . '" was created in previous step', 'Check SOLR permissions to create collections', 'Verify ZooKeeper coordination in SolrCloud', 'Check available disk space and memory on SOLR server', @@ -388,7 +390,6 @@ public function setupSolr(): bool ]; return false; } - // Step 4: Configure schema fields $this->trackStep(4, 'Schema Configuration', 'started', 'Configuring schema fields for ObjectEntity metadata'); @@ -476,9 +477,9 @@ public function setupSolr(): bool $this->setupProgress['success'] = true; $tenantCollectionName = $this->getTenantCollectionName(); + $tenantConfigSetName = $this->getTenantConfigSetName(); $this->logger->info('✅ SOLR setup completed successfully (SolrCloud mode)', [ - 'configSet_created' => 'openregister', - 'base_collection_created' => 'openregister', + 'tenant_configSet_created' => $tenantConfigSetName, 'tenant_collection_created' => $tenantCollectionName, 'schema_fields_configured' => true, 'setup_validated' => true, @@ -567,23 +568,30 @@ private function verifySolrConnectivity(): bool } /** - * Ensure the base configSet exists for creating tenant cores - * - * Creates the 'openregister' configSet if it doesn't exist, using + * Ensures the tenant-specific configSet exists in SOLR. + * Creates the tenant configSet if it doesn't exist, using * the default '_default' configSet as a template. * * @return bool True if configSet exists or was created successfully */ - private function ensureBaseConfigSet(): bool + private function ensureTenantConfigSet(): bool { + $tenantConfigSetName = $this->getTenantConfigSetName(); + // Check if configSet already exists - if ($this->configSetExists('openregister')) { - $this->logger->info('Base configSet already exists'); + if ($this->configSetExists($tenantConfigSetName)) { + $this->logger->info('Tenant configSet already exists', [ + 'configSet' => $tenantConfigSetName + ]); return true; } // Create configSet using _default as template - return $this->createConfigSet('openregister', '_default'); + $this->logger->info('Creating tenant configSet', [ + 'configSet' => $tenantConfigSetName, + 'template' => '_default' + ]); + return $this->createConfigSet($tenantConfigSetName, '_default'); } /** @@ -902,31 +910,12 @@ private function createConfigSet(string $newConfigSetName, string $templateConfi return false; } - /** - * Ensure the base collection exists as a template for tenant collections - * - * The base 'openregister' collection serves as both a working collection and - * a template for creating tenant-specific collections in SolrCloud. - * - * @return bool True if base collection exists or was created successfully - */ - private function ensureBaseCollectionExists(): bool - { - // Check if base collection already exists - if ($this->solrService->collectionExists('openregister')) { - $this->logger->info('Base collection already exists'); - return true; - } - - // Create base collection using the openregister configSet - return $this->solrService->createCollection('openregister', 'openregister'); - } /** * Ensure the tenant-specific collection exists for this instance * * Creates a tenant-specific collection (e.g., "openregister_nc_f0e53393") - * using the base "openregister" configSet. + * using the tenant-specific configSet (e.g., "openregister_nc_f0e53393"). * * @return bool True if tenant collection exists or was created successfully */ @@ -942,13 +931,15 @@ private function ensureTenantCollectionExists(): bool return true; } - // Create tenant collection using the openregister configSet + // Create tenant collection using the tenant-specific configSet + $tenantConfigSetName = $this->getTenantConfigSetName(); $this->logger->info('Creating tenant collection', [ 'collection' => $tenantCollectionName, - 'configSet' => 'openregister' + 'configSet' => $tenantConfigSetName ]); - return $this->solrService->createCollection($tenantCollectionName, 'openregister'); + return $this->solrService->createCollection($tenantCollectionName, $tenantConfigSetName); + } /** @@ -1898,9 +1889,13 @@ private function validateSetup(): bool { $tenantCollectionName = $this->getTenantCollectionName(); - // Check configSet exists - if (!$this->configSetExists('openregister')) { - $this->logger->error('Validation failed: configSet missing'); + // Check tenant configSet exists + $tenantConfigSetName = $this->getTenantConfigSetName(); + if (!$this->configSetExists($tenantConfigSetName)) { + $this->logger->error('Validation failed: tenant configSet missing', [ + 'configSet' => $tenantConfigSetName + ]); + return false; } diff --git a/src/views/settings/sections/SolrConfiguration.vue b/src/views/settings/sections/SolrConfiguration.vue index 4728259c05..56a3b00961 100644 --- a/src/views/settings/sections/SolrConfiguration.vue +++ b/src/views/settings/sections/SolrConfiguration.vue @@ -605,27 +605,76 @@

{{ setupResults.message }}

⏱️ {{ setupResults.timestamp }} - {{ setupResults.mode }}
- + +
+

📊 Setup Progress

+
+
+
+
+
+
+
+ {{ setupResults.progress.completed_steps }} / {{ setupResults.progress.total_steps }} steps completed +
+
+
+ ❌ Failed at Step {{ setupResults.progress.failed_at_step }} + {{ setupResults.progress.failed_step_name }} +
+
+
+ + +
+

🔍 Error Details

+
+
+
{{ setupResults.error_details.primary_error }}
+
+ {{ setupResults.error_details.error_type }} + {{ setupResults.error_details.operation }} +
+
+ +
+
Configuration Used:
+
+
+ {{ key }}: + {{ value }} +
+
+
+
+
+ +

🔧 Setup Process

-
+
- {{ step.step }} + {{ step.step_number }} {{ step.status === 'completed' ? '✅' : step.status === 'failed' ? '❌' : '⏳' }}
-
{{ step.name }}
+
{{ step.step_name }}

{{ step.description }}

-
+
+ ⏱️ {{ step.timestamp }} +
+
{{ formatDetailLabel(key) }}: {{ formatDetailValue(value) }} @@ -636,6 +685,17 @@
+ +
+

🛠️ Troubleshooting Steps

+
+
+ {{ index + 1 }} + {{ step }} +
+
+
+

🏗️ Infrastructure Created

@@ -1394,6 +1454,229 @@ export default { overflow-y: auto; } +/* Progress Overview */ +.progress-overview { + margin: 24px 0; + background: var(--color-background-hover); + border-radius: 12px; + padding: 20px; + border: 1px solid var(--color-border); +} + +.progress-overview h4 { + margin: 0 0 16px 0; + color: var(--color-main-text); + font-size: 16px; + font-weight: 600; +} + +.progress-summary { + display: flex; + flex-direction: column; + gap: 12px; +} + +.progress-bar-container { + display: flex; + flex-direction: column; + gap: 8px; +} + +.progress-bar { + width: 100%; + height: 8px; + background: var(--color-background-dark); + border-radius: 4px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + transition: width 0.3s ease; + border-radius: 4px; +} + +.progress-fill.success { + background: linear-gradient(90deg, var(--color-success-light) 0%, var(--color-success) 100%); +} + +.progress-fill.error { + background: linear-gradient(90deg, var(--color-error-light) 0%, var(--color-error) 100%); +} + +.progress-text { + font-size: 14px; + color: var(--color-text-maxcontrast); + text-align: center; +} + +.failure-info { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background: rgba(var(--color-error), 0.1); + border: 1px solid var(--color-error); + border-radius: 8px; +} + +.failure-badge { + background: var(--color-error); + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +.failure-step { + color: var(--color-error-text); + font-weight: 500; +} + +/* Error Details Section */ +.error-details-section { + margin: 24px 0; +} + +.error-details-section h4 { + margin: 0 0 16px 0; + color: var(--color-main-text); + font-size: 16px; + font-weight: 600; +} + +.error-card { + background: rgba(var(--color-error), 0.05); + border: 1px solid var(--color-error); + border-radius: 12px; + padding: 20px; +} + +.error-primary h5 { + margin: 0 0 12px 0; + color: var(--color-error-text); + font-size: 16px; + font-weight: 600; +} + +.error-meta { + display: flex; + gap: 12px; + margin-bottom: 16px; +} + +.error-type, +.error-operation { + background: var(--color-background-dark); + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + color: var(--color-text-maxcontrast); + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.configuration-used h6 { + margin: 0 0 12px 0; + color: var(--color-main-text); + font-size: 14px; + font-weight: 600; +} + +.config-grid { + display: grid; + gap: 8px; +} + +.config-item { + display: flex; + justify-content: space-between; + padding: 8px 12px; + background: var(--color-background-dark); + border-radius: 6px; + font-size: 13px; +} + +.config-key { + font-weight: 500; + color: var(--color-main-text); +} + +.config-value { + color: var(--color-text-maxcontrast); + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +/* Troubleshooting Section */ +.troubleshooting-section { + margin: 24px 0; + background: var(--color-background-hover); + border-radius: 12px; + padding: 20px; + border: 1px solid var(--color-border); +} + +.troubleshooting-section h4 { + margin: 0 0 16px 0; + color: var(--color-main-text); + font-size: 16px; + font-weight: 600; +} + +.troubleshooting-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.troubleshooting-item { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px; + background: var(--color-background-dark); + border-radius: 8px; + border-left: 4px solid var(--color-warning); +} + +.troubleshooting-number { + width: 24px; + height: 24px; + background: var(--color-warning); + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 12px; + flex-shrink: 0; +} + +.troubleshooting-text { + flex: 1; + color: var(--color-text-light); + font-size: 14px; + line-height: 1.4; +} + +/* Enhanced Step Items */ +.step-item.failed { + border-left-color: var(--color-error); + background: rgba(var(--color-error), 0.05); +} + +.step-item.completed { + border-left-color: var(--color-success); +} + +.step-timestamp { + font-size: 12px; + color: var(--color-text-maxcontrast); + margin-top: 8px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + .timestamp { display: flex; align-items: center;