diff --git a/ProcessMaker/Http/Controllers/Api/DevLinkController.php b/ProcessMaker/Http/Controllers/Api/DevLinkController.php index 3eb474fee0..eeb19cd24d 100644 --- a/ProcessMaker/Http/Controllers/Api/DevLinkController.php +++ b/ProcessMaker/Http/Controllers/Api/DevLinkController.php @@ -2,7 +2,9 @@ namespace ProcessMaker\Http\Controllers\Api; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Notification; use Illuminate\Validation\Rule; use ProcessMaker\Exception\ValidationException; use ProcessMaker\Http\Controllers\Controller; @@ -10,9 +12,13 @@ use ProcessMaker\Jobs\DevLinkInstall; use ProcessMaker\Models\Bundle; use ProcessMaker\Models\BundleAsset; +use ProcessMaker\Models\BundleInstance; use ProcessMaker\Models\BundleSetting; use ProcessMaker\Models\DevLink; use ProcessMaker\Models\Setting; +use ProcessMaker\Models\SettingsMenus; +use ProcessMaker\Models\User; +use ProcessMaker\Notifications\BundleUpdatedNotification; class DevLinkController extends Controller { @@ -154,12 +160,34 @@ public function updateBundle(Request $request, Bundle $bundle) public function increaseBundleVersion(Bundle $bundle) { + $bundle->notifyBundleUpdated(); + $bundle->version = $bundle->version + 1; $bundle->saveOrFail(); return $bundle; } + public function bundleUpdated($bundleId, $token) + { + try { + $bundle = Bundle::where('remote_id', $bundleId)->firstOrFail(); + } catch (ModelNotFoundException $e) { + return response()->json(['error' => 'Bundle not found'], 403); + } + + $storedToken = $bundle->webhook_token; + + if ($token !== $storedToken) { + return response()->json(['error' => 'Invalid token'], 403); + } + $adminUsers = User::where('is_administrator', true)->get(); + + Notification::send($adminUsers, new BundleUpdatedNotification($bundle)); + + return $bundle; + } + public function deleteBundle(Bundle $bundle) { $bundle->delete(); @@ -198,6 +226,14 @@ public function reinstallBundle(Request $request, Bundle $bundle) ]; } + public function addBundleInstance(Request $request, Bundle $bundle) + { + BundleInstance::create([ + 'bundle_id' => $bundle->id, + 'instance_url' => $request->input('instance_url'), + ]); + } + public function exportLocalBundle(Bundle $bundle) { return ['payloads' => $bundle->export()]; @@ -330,4 +366,16 @@ public function deleteBundleSetting(BundleSetting $bundleSetting) return response()->json(['message' => 'Bundle setting deleted.'], 200); } + + public function getBundleSetting(Bundle $bundle, $settingKey) + { + $setting = $bundle->settings()->where('setting', $settingKey)->first(); + + return $setting; + } + + public function getBundleAllSettings($settingKey) + { + return Setting::where([['group_id', SettingsMenus::getId($settingKey)], ['hidden', 0]])->get(); + } } diff --git a/ProcessMaker/Jobs/DevLinkInstall.php b/ProcessMaker/Jobs/DevLinkInstall.php index 96b993b8fe..25c347fbf3 100644 --- a/ProcessMaker/Jobs/DevLinkInstall.php +++ b/ProcessMaker/Jobs/DevLinkInstall.php @@ -48,6 +48,8 @@ public function __construct( */ public function handle(): void { + //log + \Log::info('DevLinkInstall job started: ' . $this->devLinkId); $devLink = DevLink::findOrFail($this->devLinkId); $logger = new Logger($this->userId); diff --git a/ProcessMaker/Models/Bundle.php b/ProcessMaker/Models/Bundle.php index 2a6134f9b6..1cdd68fc76 100644 --- a/ProcessMaker/Models/Bundle.php +++ b/ProcessMaker/Models/Bundle.php @@ -3,6 +3,7 @@ namespace ProcessMaker\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Support\Facades\Http; use ProcessMaker\Exception\ExporterNotSupported; use ProcessMaker\Exception\ValidationException; use ProcessMaker\ImportExport\Importer; @@ -173,6 +174,16 @@ public function addSettings($setting, $newId, $type = null) if (json_last_error() === JSON_ERROR_NONE) { if (is_array($decodedNewId)) { $newId = $decodedNewId; + if ($existingSetting) { + if ($decodedNewId['id'] === []) { + $existingSetting->delete(); + + return; + } + $existingSetting->update(['config' => json_encode($newId)]); + + return; + } } elseif (isset($decodedNewId['id'])) { $newId = [$decodedNewId['id']]; } else { @@ -400,4 +411,22 @@ public function reinstall(string $mode, Logger $logger = null) $logger?->setStatus('done'); } + + public function notifyBundleUpdated() + { + $bundleInstances = BundleInstance::where('bundle_id', $this->id)->get(); + foreach ($bundleInstances as $bundleInstance) { + $url = $bundleInstance->instance_url; + + try { + $response = Http::post($url); + + if ($response->status() === 403) { + \Log::error("Failed to notify bundle update for URL: $url " . $response); + } + } catch (\Exception $e) { + \Log::error('Error notifying bundle update: ' . $e->getMessage()); + } + } + } } diff --git a/ProcessMaker/Models/BundleInstance.php b/ProcessMaker/Models/BundleInstance.php new file mode 100644 index 0000000000..b5b438bc47 --- /dev/null +++ b/ProcessMaker/Models/BundleInstance.php @@ -0,0 +1,21 @@ +belongsTo(Bundle::class); + } +} diff --git a/ProcessMaker/Models/DevLink.php b/ProcessMaker/Models/DevLink.php index 9fd0724379..a7d83f6cde 100644 --- a/ProcessMaker/Models/DevLink.php +++ b/ProcessMaker/Models/DevLink.php @@ -138,6 +138,14 @@ public function installRemoteBundle($remoteBundleId, $updateType) $this->logger->status(__('Downloading bundle from remote instance')); $bundleInfo = $this->remoteBundle($remoteBundleId)->json(); + $token = Str::random(60); + + $addBundleInstance = $this->client()->post( + route('api.devlink.add-bundle-instance', ['bundle' => $remoteBundleId], false), + [ + 'instance_url' => env('APP_URL') . '/devlink/bundle-updated/' . $remoteBundleId . '/' . $token, + ] + ); $bundleExport = $this->client()->get( route('api.devlink.export-local-bundle', ['bundle' => $remoteBundleId], false) @@ -160,6 +168,7 @@ public function installRemoteBundle($remoteBundleId, $updateType) 'name' => $bundleInfo['name'], 'published' => $bundleInfo['published'], 'version' => $bundleInfo['version'], + 'webhook_token' => $token, ] ); diff --git a/ProcessMaker/Notifications/BundleUpdatedNotification.php b/ProcessMaker/Notifications/BundleUpdatedNotification.php new file mode 100644 index 0000000000..482399cf81 --- /dev/null +++ b/ProcessMaker/Notifications/BundleUpdatedNotification.php @@ -0,0 +1,81 @@ +bundleName = $bundle->name; + $this->bundleUid = $bundle->id; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + return ['broadcast', NotificationChannel::class]; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->line('The introduction to the notification.') + ->action('Notification Action', url('/')) + ->line('Thank you for using our application!'); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray($notifiable) + { + $bundle = Bundle::find($this->bundleUid); + + return [ + 'type' => 'BUNDLE_UPDATED', + 'message' => sprintf('Bundle updated: %s', $this->bundleName), + 'dateTime' => $bundle->updated_at->toIso8601String(), + 'name' => $this->bundleName, + 'bundleName' => $this->bundleName, + 'url' => sprintf( + '/admin/devlink/local-bundles/%s/', + $this->bundleUid + ), + ]; + } + + public function toDatabase($notifiable) + { + return $this->toArray($notifiable); + } + + public function toBroadcast($notifiable) + { + return new BroadcastMessage($this->toArray($notifiable)); + } +} diff --git a/database/migrations/2025_01_16_202146_create_bundle_instances_table.php b/database/migrations/2025_01_16_202146_create_bundle_instances_table.php new file mode 100644 index 0000000000..9c0bbe0f57 --- /dev/null +++ b/database/migrations/2025_01_16_202146_create_bundle_instances_table.php @@ -0,0 +1,30 @@ +id(); + $table->unsignedBigInteger('bundle_id'); + $table->string('instance_url'); + $table->timestamps(); + + $table->index(['bundle_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bundle_instances'); + } +}; diff --git a/database/migrations/2025_01_20_163338_add_webhook_token_to_bundles_table.php b/database/migrations/2025_01_20_163338_add_webhook_token_to_bundles_table.php new file mode 100644 index 0000000000..c6f00f5cf3 --- /dev/null +++ b/database/migrations/2025_01_20_163338_add_webhook_token_to_bundles_table.php @@ -0,0 +1,27 @@ +string('webhook_token')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('bundles', function (Blueprint $table) { + $table->dropColumn('webhook_token'); + }); + } +}; diff --git a/resources/js/admin/devlink/components/BundleConfigurations.vue b/resources/js/admin/devlink/components/BundleConfigurations.vue index 1e488b23c2..690f5633d4 100644 --- a/resources/js/admin/devlink/components/BundleConfigurations.vue +++ b/resources/js/admin/devlink/components/BundleConfigurations.vue @@ -10,14 +10,22 @@
{{ status(config) }}
- { return (props.values || []).find(value => value.setting === type.type); diff --git a/resources/js/admin/devlink/components/BundleDetail.vue b/resources/js/admin/devlink/components/BundleDetail.vue index 218a10debc..717c36cdf8 100644 --- a/resources/js/admin/devlink/components/BundleDetail.vue +++ b/resources/js/admin/devlink/components/BundleDetail.vue @@ -92,6 +92,7 @@ :values="bundle.settings" :disabled="bundle.dev_link_id !== null" @config-change="handleConfigChange" + @open-settings-modal="openSettingsModal" title="Settings" type="settings" /> @@ -105,6 +106,11 @@ ref="reinstallBundle" /> + + { } }; +const openSettingsModal = (event) => { + bundleSettingsModal.value.show(event); +}; + const updateBundle = () => { if (bundleForEdit.value.id === null) { return; diff --git a/resources/js/admin/devlink/components/BundleSettingsModal.vue b/resources/js/admin/devlink/components/BundleSettingsModal.vue new file mode 100644 index 0000000000..f4ac2d3feb --- /dev/null +++ b/resources/js/admin/devlink/components/BundleSettingsModal.vue @@ -0,0 +1,147 @@ + + + diff --git a/resources/js/admin/devlink/components/platformConfigurations.js b/resources/js/admin/devlink/components/platformConfigurations.js index 61d477b21c..408a671057 100644 --- a/resources/js/admin/devlink/components/platformConfigurations.js +++ b/resources/js/admin/devlink/components/platformConfigurations.js @@ -31,8 +31,4 @@ export default [ type: "public_files", name: "Public Files", }, - { - type: "translations", - name: "Translations", - }, ]; \ No newline at end of file diff --git a/resources/js/notifications/components/notification-message.vue b/resources/js/notifications/components/notification-message.vue index b5d3e1230d..70f862f2f9 100644 --- a/resources/js/notifications/components/notification-message.vue +++ b/resources/js/notifications/components/notification-message.vue @@ -36,6 +36,7 @@ const messages = { TASK_OVERDUE: "Task {{- subject }} is overdue. Originally due on {{- due }}", PROCESS_CREATED: "{{- user}} started the process {{- subject }}", PROCESS_COMPLETED: "{{- subject }} completed", + BUNDLE_UPDATED: "The bundle {{- subject }} has a new version. Click to check it", ERROR_EXECUTION: "{{- subject }} caused an error", COMMENT: "{{- user}} commented on {{- subject}}", "ProcessMaker\\Notifications\\ImportReady": "Imported {{- subject }}", diff --git a/routes/api.php b/routes/api.php index 855435f6d4..f2ff80d38e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -394,9 +394,13 @@ Route::get('devlink/local-bundles', [DevLinkController::class, 'localBundles'])->name('devlink.local-bundles'); Route::get('devlink/local-bundles/{bundle}', [DevLinkController::class, 'showBundle'])->name('devlink.local-bundle'); + Route::get('devlink/local-bundles/{bundle}/setting/{settingKey}', [DevLinkController::class, 'getBundleSetting'])->name('devlink.local-bundle-setting'); + Route::get('devlink/local-bundles/all-settings/{settingKey}', [DevLinkController::class, 'getBundleAllSettings'])->name('devlink.local-bundle-all-settings'); Route::post('devlink/local-bundles', [DevLinkController::class, 'createBundle'])->name('devlink.create-bundle'); Route::put('devlink/local-bundles/{bundle}', [DevLinkController::class, 'updateBundle'])->name('devlink.update-bundle'); Route::post('devlink/local-bundles/{bundle}/increase-version', [DevLinkController::class, 'increaseBundleVersion'])->name('devlink.increase-bundle-version'); + Route::post('devlink/local-bundles/{bundle}/add-bundle-instance', [DevLinkController::class, 'addBundleInstance'])->name('devlink.add-bundle-instance'); + Route::post('devlink/bundle-updated/{bundle}', [DevLinkController::class, 'bundleUpdated'])->name('devlink.bundle-updated'); Route::post('devlink/local-bundles/{bundle}/add-assets', [DevLinkController::class, 'addAsset'])->name('devlink.add-asset'); Route::post('devlink/local-bundles/{bundle}/add-settings', [DevLinkController::class, 'addSettings'])->name('devlink.add-settings'); Route::post('devlink/local-bundles/add-asset-to-bundles', [DevLinkController::class, 'addAssetToBundles'])->name('devlink.add-asset-to-bundles'); @@ -417,3 +421,4 @@ Route::get('devlink/{devLink}', [DevLinkController::class, 'show'])->name('devlink.show'); }); }); +Route::post('devlink/bundle-updated/{bundle}/{token}', [DevLinkController::class, 'bundleUpdated'])->name('devlink.bundle-updated'); diff --git a/tests/Model/DevLinkTest.php b/tests/Model/DevLinkTest.php index dcec8f0c75..04c559d280 100644 --- a/tests/Model/DevLinkTest.php +++ b/tests/Model/DevLinkTest.php @@ -93,6 +93,7 @@ public function testInstallRemoteBundle() 'http://remote-instance.test/api/1.0/devlink/export-local-bundle/123/settings-payloads' => Http::response([ 'payloads' => $exportsSettingsPayloads, ]), + 'http://remote-instance.test/api/1.0/devlink/local-bundles/123/add-bundle-instance' => Http::response([], 200), ]); $devLink = DevLink::factory()->create([ @@ -237,6 +238,7 @@ public function testUpdateBundle() 'http://remote-instance.test/api/1.0/devlink/export-local-bundle/123/settings-payloads' => Http::response([ 'payloads' => $exportsSettingsPayloads, ]), + 'http://remote-instance.test/api/1.0/devlink/local-bundles/123/add-bundle-instance' => Http::response([], 200), ]); $devLink->installRemoteBundle(123, 'update');