1import { Controller } from "@hotwired/stimulus" 2 3// Connects to data-controller="admin-theme" 4export default class extends Controller { 5 static targets = ["sidebar", "overlay", "sunIcon", "moonIcon", "apiModal", "modalOverlay", "workspaceModal", "workspaceModalOverlay", "inviteModal", "inviteModalOverlay", "editWorkspaceModal", "editWorkspaceModalOverlay", "deleteWorkspaceModal", "deleteWorkspaceModalOverlay", "credentialModal", "credentialModalOverlay", "editCredentialModal", "editCredentialModalOverlay", "deleteCredentialModal", "deleteCredentialModalOverlay", "viewTokenModal", "viewTokenModalOverlay", "associateCredentialModal", "associateCredentialModalOverlay", "oauthAppDetailsModal", "oauthAppDetailsModalOverlay", "revokeAllTokensModal", "revokeAllTokensModalOverlay", "deleteOAuthAppModal", "deleteOAuthAppModalOverlay", "docModal", "docModalOverlay"] 6 7 connect() { 8 // Inicializar dark mode desde localStorage 9 const initialDarkMode = localStorage.theme === 'dark' 10 this.applyDarkMode(initialDarkMode) 11 12 // Inicializar estado del sidebar desde localStorage 13 const initialSidebarState = localStorage.getItem('sidebarCollapsed') === 'true' 14 this.applySidebarState(initialSidebarState) 15 } 16 17 // Dark Mode Toggle 18 toggleDarkMode() { 19 const isDark = document.documentElement.classList.contains('dark') 20 this.applyDarkMode(!isDark) 21 } 22 23 applyDarkMode(isDark) { 24 if (isDark) { 25 document.documentElement.classList.add('dark') 26 document.documentElement.classList.remove('light') 27 if (this.hasSunIconTarget) this.sunIconTarget.classList.add('hidden') 28 if (this.hasMoonIconTarget) this.moonIconTarget.classList.remove('hidden') 29 localStorage.theme = 'dark' 30 } else { 31 document.documentElement.classList.add('light') 32 document.documentElement.classList.remove('dark') 33 if (this.hasSunIconTarget) this.sunIconTarget.classList.remove('hidden') 34 if (this.hasMoonIconTarget) this.moonIconTarget.classList.add('hidden') 35 localStorage.theme = 'light' 36 } 37 } 38 39 // Sidebar Toggle (Desktop - Collapse/Expand) 40 toggleSidebar() { 41 const sidebar = document.getElementById('sidebar') 42 const mainContent = document.getElementById('main-content') 43 const sidebarToggleDesktop = document.getElementById('sidebar-toggle-desktop') 44 const sidebarTextContents = document.querySelectorAll('.sidebar-text-content') 45 const sidebarNavLinks = document.getElementById('sidebar-nav-links') 46 47 const isCollapsed = sidebar.classList.contains('w-20') 48 this.applySidebarState(!isCollapsed, sidebar, mainContent, sidebarToggleDesktop, sidebarTextContents, sidebarNavLinks) 49 } 50 51 applySidebarState(isCollapsed, sidebar = null, mainContent = null, toggleBtn = null, textContents = null, navLinks = null) { 52 sidebar = sidebar || document.getElementById('sidebar') 53 mainContent = mainContent || document.getElementById('main-content') 54 toggleBtn = toggleBtn || document.getElementById('sidebar-toggle-desktop') 55 textContents = textContents || document.querySelectorAll('.sidebar-text-content') 56 navLinks = navLinks || document.getElementById('sidebar-nav-links') 57 58 if (isCollapsed) { 59 // Colapsar 60 sidebar.classList.remove('w-64') 61 sidebar.classList.add('w-20') 62 mainContent.classList.remove('md:ml-64') 63 mainContent.classList.add('md:ml-20') 64 textContents.forEach(span => span.classList.add('hidden')) 65 navLinks.querySelectorAll('a').forEach(a => { 66 a.classList.add('justify-center') 67 const svg = a.querySelector('svg') 68 if (svg) svg.classList.remove('mr-3') 69 }) 70 if (toggleBtn) toggleBtn.classList.add('rotate-180') 71 localStorage.setItem('sidebarCollapsed', 'true') 72 } else { 73 // Expandir 74 sidebar.classList.add('w-64') 75 sidebar.classList.remove('w-20') 76 mainContent.classList.add('md:ml-64') 77 mainContent.classList.remove('md:ml-20') 78 textContents.forEach(span => span.classList.remove('hidden')) 79 navLinks.querySelectorAll('a').forEach(a => { 80 a.classList.remove('justify-center') 81 const svg = a.querySelector('svg') 82 if (svg) svg.classList.add('mr-3') 83 }) 84 if (toggleBtn) toggleBtn.classList.remove('rotate-180') 85 localStorage.setItem('sidebarCollapsed', 'false') 86 } 87 } 88 89 // Sidebar Mobile - Open 90 openSidebar() { 91 const sidebar = document.getElementById('sidebar')
92 const overlay = document.getElementById('sidebar-overlay') 93 94 sidebar.classList.remove('-translate-x-full') 95 overlay.classList.remove('hidden') 96 setTimeout(() => overlay.classList.add('opacity-100'), 10) 97 } 98 99 // Sidebar Mobile - Close 100 closeSidebar() { 101 const sidebar = document.getElementById('sidebar') 102 const overlay = document.getElementById('sidebar-overlay') 103 104 sidebar.classList.add('-translate-x-full') 105 overlay.classList.remove('opacity-100') 106 setTimeout(() => overlay.classList.add('hidden'), 300) 107 } 108 109 // API Modal - Open 110 openApiModal() { 111 if (this.hasApiModalTarget && this.hasModalOverlayTarget) { 112 this.apiModalTarget.classList.remove('hidden') 113 this.modalOverlayTarget.classList.remove('hidden') 114 setTimeout(() => { 115 this.apiModalTarget.classList.add('opacity-100', 'scale-100') 116 this.apiModalTarget.classList.remove('scale-95') 117 this.modalOverlayTarget.classList.add('opacity-100') 118 }, 10) 119 } 120 } 121 122 // API Modal - Close 123 closeApiModal() { 124 if (this.hasApiModalTarget && this.hasModalOverlayTarget) { 125 this.apiModalTarget.classList.remove('opacity-100', 'scale-100') 126 this.apiModalTarget.classList.add('scale-95') 127 this.modalOverlayTarget.classList.remove('opacity-100') 128 setTimeout(() => { 129 this.apiModalTarget.classList.add('hidden') 130 this.modalOverlayTarget.classList.add('hidden') 131 }, 300) 132 } 133 } 134 135 // Workspace Modal - Open 136 openWorkspaceModal() { 137 if (this.hasWorkspaceModalTarget && this.hasWorkspaceModalOverlayTarget) { 138 this.workspaceModalTarget.classList.remove('hidden') 139 this.workspaceModalOverlayTarget.classList.remove('hidden') 140 setTimeout(() => { 141 this.workspaceModalTarget.classList.add('opacity-100', 'scale-100') 142 this.workspaceModalTarget.classList.remove('scale-95') 143 this.workspaceModalOverlayTarget.classList.add('opacity-100') 144 }, 10) 145 } 146 } 147 148 // Workspace Modal - Close 149 closeWorkspaceModal() { 150 if (this.hasWorkspaceModalTarget && this.hasWorkspaceModalOverlayTarget) { 151 this.workspaceModalTarget.classList.remove('opacity-100', 'scale-100') 152 this.workspaceModalTarget.classList.add('scale-95') 153 this.workspaceModalOverlayTarget.classList.remove('opacity-100') 154 setTimeout(() => { 155 this.workspaceModalTarget.classList.add('hidden') 156 this.workspaceModalOverlayTarget.classList.add('hidden') 157 }, 300) 158 } 159 } 160 161 // Invite Modal - Open 162 openInviteModal() { 163 if (this.hasInviteModalTarget && this.hasInviteModalOverlayTarget) { 164 this.inviteModalTarget.classList.remove('hidden') 165 this.inviteModalOverlayTarget.classList.remove('hidden') 166 setTimeout(() => { 167 this.inviteModalTarget.classList.add('opacity-100', 'scale-100') 168 this.inviteModalTarget.classList.remove('scale-95') 169 this.inviteModalOverlayTarget.classList.add('opacity-100') 170 }, 10) 171 } 172 } 173 174 // Invite Modal - Close 175 closeInviteModal() { 176 if (this.hasInviteModalTarget && this.hasInviteModalOverlayTarget) { 177 this.inviteModalTarget.classList.remove('opacity-100', 'scale-100') 178 this.inviteModalTarget.classList.add('scale-95') 179 this.inviteModalOverlayTarget.classList.remove('opacity-100') 180 setTimeout(() => { 181 this.inviteModalTarget.classList.add('hidden') 182 this.inviteModalOverlayTarget.classList.add('hidden') 183 }, 300) 184 } 185 } 186 187 // Edit Workspace Modal - Open 188 openEditWorkspaceModal(event) { 189 event.preventDefault() 190 const button = event.currentTarget 191 const workspaceId = button.dataset.workspaceId 192 const workspaceName = button.dataset.workspaceName 193 194 if (this.hasEditWorkspaceModalTarget && this.hasEditWorkspaceModalOverlayTarget) { 195 // Actualizar la acción del formulario y el valor del campo nombre 196 const form = document.getElementById('edit-workspace-form') 197 const nameInput = document.getElementById('edit_workspace_name') 198 199 form.action = `/admin/workspaces/${workspaceId}` 200 nameInput.value = workspaceName 201
202 this.editWorkspaceModalTarget.classList.remove('hidden') 203 this.editWorkspaceModalOverlayTarget.classList.remove('hidden') 204 setTimeout(() => { 205 this.editWorkspaceModalTarget.classList.add('opacity-100', 'scale-100') 206 this.editWorkspaceModalTarget.classList.remove('scale-95') 207 this.editWorkspaceModalOverlayTarget.classList.add('opacity-100') 208 }, 10) 209 } 210 } 211 212 // Edit Workspace Modal - Close 213 closeEditWorkspaceModal() { 214 if (this.hasEditWorkspaceModalTarget && this.hasEditWorkspaceModalOverlayTarget) { 215 this.editWorkspaceModalTarget.classList.remove('opacity-100', 'scale-100') 216 this.editWorkspaceModalTarget.classList.add('scale-95') 217 this.editWorkspaceModalOverlayTarget.classList.remove('opacity-100') 218 setTimeout(() => { 219 this.editWorkspaceModalTarget.classList.add('hidden') 220 this.editWorkspaceModalOverlayTarget.classList.add('hidden') 221 }, 300) 222 } 223 } 224 225 // Delete Workspace Modal - Open 226 openDeleteWorkspaceModal(event) { 227 event.preventDefault() 228 const button = event.currentTarget 229 const workspaceId = button.dataset.workspaceId 230 const workspaceName = button.dataset.workspaceName 231 232 if (this.hasDeleteWorkspaceModalTarget && this.hasDeleteWorkspaceModalOverlayTarget) { 233 // Actualizar la acción del formulario y el nombre del workspace en el mensaje 234 const form = document.getElementById('delete-workspace-form') 235 const nameDisplay = document.getElementById('delete-workspace-name') 236 237 form.action = `/admin/workspaces/${workspaceId}` 238 nameDisplay.textContent = workspaceName 239 240 this.deleteWorkspaceModalTarget.classList.remove('hidden') 241 this.deleteWorkspaceModalOverlayTarget.classList.remove('hidden') 242 setTimeout(() => { 243 this.deleteWorkspaceModalTarget.classList.add('opacity-100', 'scale-100') 244 this.deleteWorkspaceModalTarget.classList.remove('scale-95') 245 this.deleteWorkspaceModalOverlayTarget.classList.add('opacity-100') 246 }, 10) 247 } 248 } 249 250 // Delete Workspace Modal - Close 251 closeDeleteWorkspaceModal() { 252 if (this.hasDeleteWorkspaceModalTarget && this.hasDeleteWorkspaceModalOverlayTarget) { 253 this.deleteWorkspaceModalTarget.classList.remove('opacity-100', 'scale-100') 254 this.deleteWorkspaceModalTarget.classList.add('scale-95') 255 this.deleteWorkspaceModalOverlayTarget.classList.remove('opacity-100') 256 setTimeout(() => { 257 this.deleteWorkspaceModalTarget.classList.add('hidden') 258 this.deleteWorkspaceModalOverlayTarget.classList.add('hidden') 259 }, 300) 260 } 261 } 262 263 // Credential Modal - Open 264 openCredentialModal() { 265 if (this.hasCredentialModalTarget && this.hasCredentialModalOverlayTarget) { 266 this.credentialModalTarget.classList.remove('hidden') 267 this.credentialModalOverlayTarget.classList.remove('hidden') 268 setTimeout(() => { 269 this.credentialModalTarget.classList.add('opacity-100', 'scale-100') 270 this.credentialModalTarget.classList.remove('scale-95') 271 this.credentialModalOverlayTarget.classList.add('opacity-100') 272 }, 10) 273 } 274 } 275 276 // Credential Modal - Close 277 closeCredentialModal() { 278 if (this.hasCredentialModalTarget && this.hasCredentialModalOverlayTarget) { 279 this.credentialModalTarget.classList.remove('opacity-100', 'scale-100') 280 this.credentialModalTarget.classList.add('scale-95') 281 this.credentialModalOverlayTarget.classList.remove('opacity-100') 282 setTimeout(() => { 283 this.credentialModalTarget.classList.add('hidden') 284 this.credentialModalOverlayTarget.classList.add('hidden') 285 }, 300) 286 } 287 } 288 289 // Edit Credential Modal - Open 290 openEditCredentialModal(event) { 291 event.preventDefault() 292 const button = event.currentTarget 293 const credentialId = button.dataset.credentialId 294 const credentialName = button.dataset.credentialName 295 const credentialEnabled = button.dataset.credentialEnabled === 'true' 296 297 if (this.hasEditCredentialModalTarget && this.hasEditCredentialModalOverlayTarget) { 298 const form = document.getElementById('edit-credential-form') 299 const nameInput = document.getElementById('edit_credential_name') 300 const enabledInput = document.getElementById('edit_credential_enabled') 301 302 form.action = `/admin/credentials/${credentialId}` 303 nameInput.value = credentialName 304 enabledInput.checked = credentialEnabled 305 306 this.editCredentialModalTarget.classList.remove('hidden') 307 this.editCredentialModalOverlayTarget.classList.remove('hidden') 308 setTimeout(() => { 309 this.editCredentialModalTarget.classList.add('opacity-100', 'scale-100') 310 this.editCredentialModalTarget.classList.remove('scale-95') 311 this.editCredentialModalOverlayTarget.classList.add('opacity-100') 312 }, 10) 313 } 314 } 315 316 // Edit Credential Modal - Close 317 closeEditCredentialModal() { 318 if (this.hasEditCredentialModalTarget && this.hasEditCredentialModalOverlayTarget) { 319 this.editCredentialModalTarget.classList.remove('opacity-100', 'scale-100') 320 this.editCredentialModalTarget.classList.add('scale-95') 321 this.editCredentialModalOverlayTarget.classList.remove('opacity-100') 322 setTimeout(() => { 323 this.editCredentialModalTarget.classList.add('hidden') 324 this.editCredentialModalOverlayTarget.classList.add('hidden') 325 }, 300) 326 } 327 } 328 329 // Delete Credential Modal - Open 330 openDeleteCredentialModal(event) { 331 event.preventDefault() 332 const button = event.currentTarget 333 const credentialId = button.dataset.credentialId 334 const credentialName = button.dataset.credentialName 335 const workspaceCount = parseInt(button.dataset.credentialWorkspaceCount || 0) 336 337 if (this.hasDeleteCredentialModalTarget && this.hasDeleteCredentialModalOverlayTarget) { 338 const form = document.getElementById('delete-credential-form') 339 const nameDisplay = document.getElementById('delete-credential-name') 340 const warning = document.getElementById('delete-credential-warning') 341 const warningText = document.getElementById('delete-credential-warning-text') 342 343 form.action = `/admin/credentials/${credentialId}` 344 nameDisplay.textContent = credentialName 345 346 if (workspaceCount > 0) { 347 warning.classList.remove('hidden') 348 warningText.textContent = `This credential is associated with ${workspaceCount} workspace(s). Deleting it will remove authentication from these workspaces.` 349 } else { 350 warning.classList.add('hidden') 351 } 352 353 this.deleteCredentialModalTarget.classList.remove('hidden') 354 this.deleteCredentialModalOverlayTarget.classList.remove('hidden') 355 setTimeout(() => { 356 this.deleteCredentialModalTarget.classList.add('opacity-100', 'scale-100') 357 this.deleteCredentialModalTarget.classList.remove('scale-95') 358 this.deleteCredentialModalOverlayTarget.classList.add('opacity-100') 359 }, 10) 360 } 361 } 362 363 // Delete Credential Modal - Close 364 closeDeleteCredentialModal() { 365 if (this.hasDeleteCredentialModalTarget && this.hasDeleteCredentialModalOverlayTarget) { 366 this.deleteCredentialModalTarget.classList.remove('opacity-100', 'scale-100') 367 this.deleteCredentialModalTarget.classList.add('scale-95') 368 this.deleteCredentialModalOverlayTarget.classList.remove('opacity-100') 369 setTimeout(() => { 370 this.deleteCredentialModalTarget.classList.add('hidden') 371 this.deleteCredentialModalOverlayTarget.classList.add('hidden') 372 }, 300) 373 } 374 } 375 376 // View Token Modal - Open 377 openViewTokenModal(event) { 378 event.preventDefault() 379 const button = event.currentTarget 380 const credentialId = button.dataset.credentialId 381 const credentialName = button.dataset.credentialName 382 383 if (this.hasViewTokenModalTarget && this.hasViewTokenModalOverlayTarget) { 384 const tokenInput = document.getElementById('view-token-input') 385 const nameDisplay = document.getElementById('view-token-credential-name') 386 387 nameDisplay.textContent = credentialName 388 389 // TODO: El token solo se muestra una vez al crear la credencial 390 // No es posible recuperarlo posteriormente por seguridad 391 tokenInput.value = 'Token no disponible (solo se muestra al crear)' 392 393 this.viewTokenModalTarget.classList.remove('hidden') 394 this.viewTokenModalOverlayTarget.classList.remove('hidden') 395 setTimeout(() => { 396 this.viewTokenModalTarget.classList.add('opacity-100', 'scale-100') 397 this.viewTokenModalTarget.classList.remove('scale-95') 398 this.viewTokenModalOverlayTarget.classList.add('opacity-100') 399 }, 10) 400 } 401 } 402 403 // View Token Modal - Close 404 closeViewTokenModal() { 405 if (this.hasViewTokenModalTarget && this.hasViewTokenModalOverlayTarget) { 406 this.viewTokenModalTarget.classList.remove('opacity-100', 'scale-100') 407 this.viewTokenModalTarget.classList.add('scale-95') 408 this.viewTokenModalOverlayTarget.classList.remove('opacity-100') 409 setTimeout(() => { 410 this.viewTokenModalTarget.classList.add('hidden') 411 this.viewTokenModalOverlayTarget.classList.add('hidden') 412 }, 300) 413 } 414 } 415 416 // Associate Credential Modal - Open 417 openAssociateCredentialModal(event) { 418 event.preventDefault() 419 const button = event.currentTarget 420 const credentialId = button.dataset.credentialId 421 const credentialName = button.dataset.credentialName 422 423 if (this.hasAssociateCredentialModalTarget && this.hasAssociateCredentialModalOverlayTarget) { 424 const form = document.getElementById('associate-credential-form') 425 const nameDisplay = document.getElementById('associate-credential-name') 426 const workspacesList = document.getElementById('workspaces-list') 427 const loadingDiv = document.getElementById('workspaces-loading') 428 const emptyDiv = document.getElementById('workspaces-empty') 429 const submitBtn = document.getElementById('associate-submit-btn') 430 431 form.action = `/admin/credentials/${credentialId}/associate` 432 nameDisplay.textContent = credentialName 433 434 // Reset state 435 form.classList.add('hidden') 436 loadingDiv.classList.remove('hidden') 437 emptyDiv.classList.add('hidden') 438 workspacesList.innerHTML = '' 439 submitBtn.disabled = true 440 441 // Load workspaces from Rails endpoint (which fetches from Go backend) 442 fetch(`/admin/credentials/workspaces_for_association?credential_id=${credentialId}`) 443 .then(response => response.text()) 444 .then(html => { 445 loadingDiv.classList.add('hidden') 446 workspacesList.innerHTML = html 447 448 // Check if workspaces were loaded 449 const hasWorkspaces = workspacesList.querySelector('.workspace-c
449heckbox') 450 if (hasWorkspaces) { 451 form.classList.remove('hidden') 452 453 // Store initial state of checkboxes 454 const checkboxes = workspacesList.querySelectorAll('.workspace-checkbox') 455 const initialState = new Map() 456 checkboxes.forEach(checkbox => { 457 initialState.set(checkbox.value, checkbox.checked) 458 }) 459 460 // Function to check if there are changes 461 const hasChanges = () => { 462 return Array.from(checkboxes).some(checkbox => { 463 return checkbox.checked !== initialState.get(checkbox.value) 464 }) 465 } 466 467 // Initially disable button (no changes yet) 468 submitBtn.disabled = !hasChanges() 469 470 // Add event listener to checkboxes to enable/disable submit button based on changes 471 checkboxes.forEach(checkbox => { 472 checkbox.addEventListener('change', () => { 473 submitBtn.disabled = !hasChanges() 474 }) 475 }) 476 } 477 // If no workspaces, the partial already shows the empty state message 478 }) 479 .catch(error => { 480 console.error('Error loading workspaces:', error) 481 loadingDiv.classList.add('hidden') 482 workspacesList.innerHTML = '<p class="text-sm text-red-600 dark:text-red-400 text-center py-4">Error al cargar workspaces</p>' 483 }) 484 485 this.associateCredentialModalTarget.classList.remove('hidden') 486 this.associateCredentialModalOverlayTarget.classList.remove('hidden') 487 setTimeout(() => { 488 this.associateCredentialModalTarget.classList.add('opacity-100', 'scale-100') 489 this.associateCredentialModalTarget.classList.remove('scale-95') 490 this.associateCredentialModalOverlayTarget.classList.add('opacity-100') 491 }, 10) 492 } 493 } 494 495 // Associate Credential Modal - Close 496 closeAssociateCredentialModal() { 497 if (this.hasAssociateCredentialModalTarget && this.hasAssociateCredentialModalOverlayTarget) { 498 this.associateCredentialModalTarget.classList.remove('opacity-100', 'scale-100') 499 this.associateCredentialModalTarget.classList.add('scale-95') 500 this.associateCredentialModalOverlayTarget.classList.remove('opacity-100') 501 setTimeout(() => { 502 this.associateCredentialModalTarget.classList.add('hidden') 503 this.associateCredentialModalOverlayTarget.classList.add('hidden') 504 }, 300) 505 } 506 } 507 508 // OAuth App Details Modal - Open 509 openOAuthAppDetailsModal(event) { 510 if (this.hasOauthAppDetailsModalTarget && this.hasOauthAppDetailsModalOverlayTarget) { 511 const button = event.currentTarget 512 const appId = button.dataset.appId 513 const appName = button.dataset.appName 514 const clientId = button.dataset.appUid 515 const clientSecret = button.dataset.appSecret 516 const redirectUris = button.dataset.appRedirectUri 517 const scopes = button.dataset.appScopes 518 519 // Llenar el modal con los datos 520 document.getElementById('oauth-app-details-name').textContent = appName 521 document.getElementById('oauth-app-details-app-name').textContent = appName 522 document.getElementById('oauth-app-details-client-id').value = clientId 523 document.getElementById('oauth-app-details-secret').value = clientSecret 524 document.getElementById('oauth-app-details-redirect-uris').textContent = redirectUris 525 document.getElementById('oauth-app-details-scopes').textContent = scopes || 'mcp:*' 526 527 // Mostrar modal con animación 528 this.oauthAppDetailsModalTarget.classList.remove('hidden') 529 this.oauthAppDetailsModalOverlayTarget.classList.remove('hidden') 530 setTimeout(() => { 531 this.oauthAppDetailsModalTarget.classList.remove('opacity-0', 'scale-95') 532 this.oauthAppDetailsModalTarget.classList.add('opacity-100', 'scale-100') 533 this.oauthAppDetailsModalOverlayTarget.classList.add('opacity-100') 534 }, 10) 535 } 536 } 537 538 // OAuth App Details Modal - Close 539 closeOAuthAppDetailsModal() { 540 if (this.hasOauthAppDetailsModalTarget && this.hasOauthAppDetailsModalOverlayTarget) { 541 this.oauthAppDetailsModalTarget.classList.remove('opacity-100', 'scale-100') 542 this.oauthAppDetailsModalTarget.classList.add('scale-95') 543 this.oauthAppDetailsModalOverlayTarget.classList.remove('opacity-100') 544 setTimeout(() => { 545 this.oauthAppDetailsModalTarget.classList.add('hidden') 546 this.oauthAppDetailsModalOverlayTarget.classList.add('hidden') 547 }, 300) 548 } 549 } 550 551 // Revoke All Tokens Modal - Open 552 openRevokeAllTokensModal(event) { 553 if (this.hasRevokeAllTokensModalTarget && this.hasRevokeAllTokensModalOverlayTarget) { 554 const button = event.currentTarget 555 const appId = button.dataset.appId 556 const appName = button.dataset.appName 557 558 // Llenar el modal con los datos 559 document.getElementById('revoke-all-tokens-app-name').textContent = appName 560 document.getElementById('revoke-all-tokens-app-id').value = appId 561 562 // Configurar el action del formulario 563 const form = document.getElementById('revoke-all-tokens-form') 564 form.action = button.dataset.revokeUrl || '/admin/oauth/applications/' + appId + '/revoke_all' 565 566 // Mostrar modal con animación 567 this.revokeAllTokensModalTarget.classList.remove('hidden') 568 this.revokeAllTokensModalOverlayTarget.classList.remove('hidden') 569 setTimeout(() => { 570 this.revokeAllTokensModalTarget.classList.remove('opacity-0', 'scale-95') 571 this.revokeAllTokensModalTarget.classList.add('opacity-100', 'scale-100') 572 this.revokeAllTokensModalOverlayTarget.classList.add('opacity-100') 573 }, 10) 574 } 575 } 576 577 // Revoke All Tokens Modal - Close 578 closeRevokeAllTokensModal() { 579 if (this.hasRevokeAllTokensModalTarget && this.hasRevokeAllTokensModalOverlayTarget) { 580 this.revokeAllTokensModalTarget.classList.remove('opacity-100', 'scale-100') 581 this.revokeAllTokensModalTarget.classList.add('scale-95') 582 this.revokeAllTokensModalOverlayTarget.classList.remove('opacity-100') 583 setTimeout(() => { 584 this.revokeAllTokensModalTarget.classList.add('hidden') 585 this.revokeAllTokensModalOverlayTarget.classList.add('hidden') 586 }, 300) 587 } 588 } 589 590 // Delete OAuth App Modal - Open 591 openDeleteOAuthAppModal(event) { 592 if (this.hasDeleteOAuthAppModalTarget && this.hasDeleteOAuthAppModalOverlayTarget) { 593 const button = event.currentTarget 594 const appId = button.dataset.appId 595 const appName = button.dataset.appName 596 597 // Llenar el modal con los datos 598 document.getElementById('delete-oauth-app-name').textContent = appName 599 600 // Configurar el action del formulario 601 const form = document.getElementById('delete-oauth-app-form') 602 form.action = button.dataset.deleteUrl || '/oauth/applications/' + appId 603 604 // Mostrar modal con animación 605 this.deleteOAuthAppModalTarget.classList.remove('hidden') 606 this.deleteOAuthAppModalOverlayTarget.classList.remove('hidden') 607 setTimeout(() => { 608 this.deleteOAuthAppModalTarget.classList.remove('opacity-0', 'scale-95') 609 this.deleteOAuthAppModalTarget.classList.add('opacity-100', 'scale-100') 610 this.deleteOAuthAppModalOverlayTarget.classList.add('opacity-100') 611 }, 10) 612 } 613 } 614 615 // Delete OAuth App Modal - Close 616 closeDeleteOAuthAppModal() { 617 if (this.hasDeleteOAuthAppModalTarget && this.hasDeleteOAuthAppModalOverlayTarget) { 618 this.deleteOAuthAppModalTarget.classList.remove('opacity-100', 'scale-100') 619 this.deleteOAuthAppModalTarget.classList.add('scale-95') 620 this.deleteOAuthAppModalOverlayTarget.classList.remove('opacity-100') 621 setTimeout(() => { 622 this.deleteOAuthAppModalTarget.classList.add('hidden') 623 this.deleteOAuthAppModalOverlayTarget.classList.add('hidden') 624 }, 300) 625 } 626 } 627 628 // Doc Modal - Open 629 openDocModal() { 630 if (this.hasDocModalTarget && this.hasDocModalOverlayTarget) { 631 this.docModalTarget.classList.remove('hidden') 632 this.docModalOverlayTarget.classList.remove('hidden') 633 setTimeout(() => { 634 this.docModalTarget.classList.add('opacity-100', 'scale-100') 635 this.docModalTarget.classList.remove('scale-95') 636 this.docModalOverlayTarget.classList.add('opacity-100') 637 }, 10) 638 } 639 } 640 641 // Doc Modal - Close 642 closeDocModal() { 643 if (this.hasDocModalTarget && this.hasDocModalOverlayTarget) { 644 this.docModalTarget.classList.remove('opacity-100', 'scale-100') 645 this.docModalTarget.classList.add('scale-95') 646 this.docModalOverlayTarget.classList.remove('opacity-100') 647 setTimeout(() => { 648 this.docModalTarget.classList.add('hidden') 649 this.docModalOverlayTarget.classList.add('hidden') 650 }, 300) 651 } 652 } 653 654 // Copy to Clipboard 655 copyToClipboard(event) { 656 const button = event.currentTarget 657 const textToCopy = button.dataset.clipboardText 658 const originalText = button.innerHTML 659 const feedbackText = button.dataset.feedbackText || 'Copied!' 660 661 navigator.clipboard.writeText(textToCopy).then(() => { 662 button.innerHTML = feedbackText 663 button.disabled = true 664 setTimeout(() => { 665 button.innerHTML = originalText 666 button.disabled = false 667 }, 2000) 668 }) 669 } 670};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.