/** * Copyright IBM Corp. 2021, 2026 * SPDX-License-Identifier: BUSL-1.1 */ import Controller from '@ember/controller'; import { service } from '@ember/service'; import { getOwner } from '@ember/application'; import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import { defaultValidator } from 'ember-a11y-refocus'; import { paramValueFinder } from 'core/utils/param-value-finder'; export default class ApplicationController extends Controller { // =services @service clusterUrl; @service flashMessages; @service session; @service('browser/window') window; @service router; @service scope; @service intl; @service terminal; // =attributes @tracked isLoggingOut = false; @tracked isAppQuitting = false; removeOnAppQuitListener; constructor() { super(...arguments); // Listen for when user attempts to quit app // Setup removeOnAppQuitListener to destroy the listener afterwards this.removeOnAppQuitListener = this.window.desktop?.app.onAppQuit(() => { this.isAppQuitting = true; this.hideTerminalViewForModal(); }); } /** * Returns display name and icon for current scope. * @type {object} */ get currentScope() { if (this.scope.org?.isOrg) { return { name: this.scope.org.displayName, icon: 'org' }; } else { return { name: this.intl.t('titles.global'), icon: 'globe' }; } } /** * Shows side navigation only for routes nested under a scope * and if user has been authenticated. * @type {boolean} */ get showSideNav() { return ( this.router.currentRouteName.startsWith('scopes.scope') && this.session.isAuthenticated ); } // =actions /** * Stop all active/pending target sessions * Logout or close app */ @action async confirmCloseSessions() { await window.desktop.session.stopAllSessions(); if (this.isAppQuitting) { this.isAppQuitting = false; this.close(); } else { // this.session.invalidate() comes from Ember Simple Auth BaseSessionService this.session.invalidate(); } this.isLoggingOut = false; } /** * Only renders the signout modal if target sessions are running */ @action async showModalOrLogout() { const hasRunningSessions = await window.desktop.session.hasRunningSessions(); if (hasRunningSessions) { this.isLoggingOut = true; this.hideTerminalViewForModal(); } else { this.session.invalidate(); } } /** * Invalidates the session and navigates to the cluster-url route. * The cluster URL is preserved so that upon reload the user is directed * back to the authentication screen for the same cluster. */ @action disconnect() { this.session.invalidate(); this.router.replaceWith('cluster-url'); } @action minimize() { window.desktop.windowAction.minimizeWindow(); } @action toggleFullScreen() { window.desktop.windowAction.toggleFullscreenWindow(); } @action close() { window.desktop.windowAction.closeWindow(); } /** * Applies the specified color theme to the root ember element. * @param {string} theme - "light", "dark", or nullish (system default) */ @action toggleTheme(theme) { const rootElementSelector = getOwner(this).rootElement; const rootEl = getOwner(this) .lookup('service:-document') .querySelector(rootElementSelector); this.session.set('data.theme', theme); switch (theme) { case 'light': rootEl.classList.add('rose-theme-light'); rootEl.classList.remove('rose-theme-dark'); break; case 'dark': rootEl.classList.add('rose-theme-dark'); rootEl.classList.remove('rose-theme-light'); break; default: rootEl.classList.remove('rose-theme-dark'); rootEl.classList.remove('rose-theme-light'); } } @action cancel() { this.isLoggingOut = false; this.isAppQuitting = false; if (this.terminal.isTerminalTabActive) { this.terminal.displayTerminalView(); } } willDestroy() { super.willDestroy(...arguments); this.removeOnAppQuitListener?.(); } /** * Add custom route change validation to prevent refocus when * user is attempting to search, filter, or sort. * @param {object} transition * @returns {boolean} */ customRouteChangeValidator(transition) { if (!transition.to || !transition.from) { return true; } if (transition.to.name === transition.from.name) { const toParams = paramValueFinder( transition.to.localName, transition.to.parent, ); const fromParams = paramValueFinder( transition.from.localName, transition.from.parent, ); // Return false to prevent refocus when routes have equivalent dynamic segments (params). return JSON.stringify(toParams) !== JSON.stringify(fromParams); } return defaultValidator(transition); } /** * Hides terminal if open and on terminal tab of Session details page for logout/app quit modal */ hideTerminalViewForModal() { if (this.terminal.isTerminalTabActive) { this.terminal.hideTerminalView(); } } /** * Handles side nav toggle to hide/show terminal based on side nav state * @param {boolean} isMinimized - Whether the side nav is minimized i.e. collapsed */ @action handleSideNavToggle(isMinimized) { this.terminal.setSideNavMinimized(isMinimized); // update terminal view if we're on the terminal tab if (this.terminal.isTerminalTabActive) { if (isMinimized) { // Side nav is collapsed, display terminal view this.terminal.displayTerminalView(); } else { // Side nav is expanded, hide terminal view this.terminal.hideTerminalView(); } } } }