diff --git a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/DevSpacesPlugin.kt b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/DevSpacesPlugin.kt index 0425578..6a82bad 100644 --- a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/DevSpacesPlugin.kt +++ b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/DevSpacesPlugin.kt @@ -57,7 +57,7 @@ class DevSpacesRemoteDevExtension : RemoteDevExtension { repository.startPolling() logger.info("DevSpacesRemoteProvider initialized with ${dataSource::class.simpleName}") - return DevSpacesRemoteProvider(repository, localizableStringFactory, logger) + return DevSpacesRemoteProvider(repository, localizableStringFactory, logger, coroutineScope) } private fun createDataSource(logger: Logger, clientFactory: OpenShiftClientFactory): EnvironmentDataSource { diff --git a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/DevSpacesRemoteProvider.kt b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/DevSpacesRemoteProvider.kt index 2ff1b9b..42abbfa 100644 --- a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/DevSpacesRemoteProvider.kt +++ b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/DevSpacesRemoteProvider.kt @@ -17,11 +17,17 @@ import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.localization.LocalizableStringFactory import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState import com.jetbrains.toolbox.api.remoteDev.RemoteProvider +import com.jetbrains.toolbox.api.ui.actions.ActionDescription +import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription import com.jetbrains.toolbox.api.ui.components.UiPage import com.jetbrains.toolbox.platform.image.ImageResource import com.jetbrains.toolbox.platform.image.image import com.jetbrains.toolbox.platform.resource.jvm.jvmResourceReader -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import com.redhat.devtools.toolbox.environment.DevSpacesRemoteEnvironment import java.net.URI @@ -31,7 +37,8 @@ import java.net.URI class DevSpacesRemoteProvider( val repository: EnvironmentRepository, val localizableStringFactory: LocalizableStringFactory, - val logger: Logger + val logger: Logger, + val coroutineScope: CoroutineScope ) : RemoteProvider("Dev Spaces") { override val iconResource: ImageResource = jvmResourceReader().image("/icon.svg") @@ -42,9 +49,21 @@ class DevSpacesRemoteProvider( "make sure you are logged in to the correct cluster\r\n" + "by running the 'oc login ...' command in the terminal.") - override val environments: MutableStateFlow>> = + override val environments: StateFlow>> = repository.environments + override val additionalPluginActions: StateFlow> = + repository.currentUserOnly.map { onlyMine -> + listOf(object : RunnableActionDescription { + override val label = localizableStringFactory.pnotr( + if (onlyMine) "Show all workspaces" else "Show only my workspaces" + ) + override fun run() { + repository.currentUserOnly.value = !repository.currentUserOnly.value + } + }) + }.stateIn(coroutineScope, SharingStarted.Eagerly, emptyList()) + override val canCreateNewEnvironments: Boolean = false override val isSingleEnvironment: Boolean = false diff --git a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/EnvironmentRepository.kt b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/EnvironmentRepository.kt index ec9fdbc..8dcb99d 100644 --- a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/EnvironmentRepository.kt +++ b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/EnvironmentRepository.kt @@ -19,11 +19,16 @@ import com.redhat.devtools.toolbox.datasource.DataSourceException import com.redhat.devtools.toolbox.datasource.EnvironmentDataSource import com.redhat.devtools.toolbox.environment.* import com.redhat.devtools.toolbox.openshift.OpenShiftClientFactory +import io.fabric8.openshift.client.OpenShiftClient +import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn import kotlin.coroutines.cancellation.CancellationException import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds /** @@ -44,16 +49,31 @@ class EnvironmentRepository( private val localizableStringFactory: LocalizableStringFactory, private val clientFactory: OpenShiftClientFactory ) { - // Internal mutable state - private val _environments = MutableStateFlow>>( + // Internal mutable state - holds the full unfiltered list + private val _allEnvironments = MutableStateFlow>>( LoadableState.Loading ) // Cache of created environments by ID - allows updating existing instances - private val environmentCache = mutableMapOf() - - // Observable environment list for the provider - val environments: MutableStateFlow>> = _environments + private val environmentCache = ConcurrentHashMap() + + // Username of the currently logged-in OpenShift user (resolved on first fetch) + private var currentUsername: String? = null + + // When true, the environments list is filtered to show only the current user's workspaces + val currentUserOnly = MutableStateFlow(true) + + // Filtered view exposed to the provider — reacts to both list updates and toggle changes + val environments: StateFlow>> = + combine(_allEnvironments, currentUserOnly) { allEnvs, onlyMine -> + if (!onlyMine || currentUsername == null) { + allEnvs + } else { + allEnvs.map { list -> + list.filter { it.getConfig().tags["owner"] == currentUsername } + } + } + }.stateIn(coroutineScope, SharingStarted.Eagerly, LoadableState.Loading) fun startPolling() { coroutineScope.launch(CoroutineName("EnvironmentRepository-Polling")) { @@ -75,17 +95,21 @@ class EnvironmentRepository( logger.debug("Refreshing environments from ${dataSource::class.simpleName}") try { + if (currentUsername == null) { + currentUsername = resolveCurrentUsername() + } + val configs = dataSource.fetchEnvironments() - val environments = configs.map { config -> - getOrCreateEnvironment(config) - } + val environments = configs + .sortedWith(compareBy(nullsLast()) { it.tags["owner"] }) + .map { config -> getOrCreateEnvironment(config) } // Remove environments that no longer exist val currentIds = configs.map { it.id }.toSet() environmentCache.keys.removeAll { it !in currentIds } - _environments.value = LoadableState.Value(environments) + _allEnvironments.value = LoadableState.Value(environments) logger.info("PLUGIN: Setting environments to ${environments.size} items: ${environments.map { it.id }}") } catch (e: CancellationException) { @@ -139,4 +163,15 @@ class EnvironmentRepository( * Get a specific environment by ID. */ fun getEnvironment(id: String): DevSpacesRemoteEnvironment? = environmentCache[id] -} \ No newline at end of file + + private fun resolveCurrentUsername(): String? { + return try { + clientFactory.create().use { client -> + (client as? OpenShiftClient)?.currentUser()?.metadata?.name + } + } catch (e: Exception) { + logger.warn("Could not resolve current username: ${e.message}") + null + } + } +} diff --git a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/datasource/DevWorkspacesDataSource.kt b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/datasource/DevWorkspacesDataSource.kt index af7f455..e134d74 100644 --- a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/datasource/DevWorkspacesDataSource.kt +++ b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/datasource/DevWorkspacesDataSource.kt @@ -36,11 +36,16 @@ class DevWorkspacesDataSource( val projects = Projects(client).list() projects - .mapNotNull { it.metadata?.name } - .flatMap { namespace -> - DevWorkspaces(client, logger).list(namespace) + .mapNotNull { project -> + val namespace = project.metadata?.name ?: return@mapNotNull null + val namespaceOwner = project.metadata?.annotations?.get("che.eclipse.org/username") + namespace to namespaceOwner } - .map { workspace -> + .flatMap { (namespace, namespaceOwner) -> + DevWorkspaces(client, logger).list(namespace).map { it to namespaceOwner } + } + .map { (workspace, namespaceOwner) -> + val owner = workspace.owner ?: namespaceOwner EnvironmentConfig( id = workspace.id, name = MutableStateFlow(workspace.name), @@ -49,7 +54,10 @@ class DevWorkspacesDataSource( // availableIdeProductCodes = listOf("IU"), // TODO: implement fetching the PROJECT_SOURCES env. var. value projectPaths = listOf("/projects"), - tags = mapOf("namespace" to workspace.namespace) + tags = buildMap { + put("namespace", workspace.namespace) + if (owner != null) put("owner", owner) + } ) } } diff --git a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/environment/DevSpacesRemoteEnvironment.kt b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/environment/DevSpacesRemoteEnvironment.kt index 51f4ef2..35cd962 100644 --- a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/environment/DevSpacesRemoteEnvironment.kt +++ b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/environment/DevSpacesRemoteEnvironment.kt @@ -57,6 +57,8 @@ class DevSpacesRemoteEnvironment( private var activePortForward: LocalPortForward? = null // Public reactive properties (observed by Toolbox UI) + override val secondaryInformation: String? = initialConfig.tags["owner"] + override var nameFlow: MutableStateFlow = _currentConfig.name override val state: MutableStateFlow = _state @@ -103,9 +105,7 @@ class DevSpacesRemoteEnvironment( require(newConfig.id == initialConfig.id) { logger.info("Cannot change environment ID for ${initialConfig.id}") } _currentConfig = newConfig _description.update { - EnvironmentDescription.General(newConfig.description?.let { text -> - localizableStringFactory.ptrl(text) - }) + EnvironmentDescription.General(newConfig.description?.let { localizableStringFactory.ptrl(it) }) } } diff --git a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/openshift/DevWorkspace.kt b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/openshift/DevWorkspace.kt index 1d7f5b1..6d17d7e 100644 --- a/plugin/src/main/kotlin/com/redhat/devtools/toolbox/openshift/DevWorkspace.kt +++ b/plugin/src/main/kotlin/com/redhat/devtools/toolbox/openshift/DevWorkspace.kt @@ -20,7 +20,7 @@ data class DevWorkspace( val uid: String, val started: Boolean, val phase: String, - val cheEditor: String? + val owner: String? ) { val running: Boolean get() = phase == PHASE_RUNNING @@ -36,7 +36,7 @@ data class DevWorkspace( val metadata = resource.metadata val spec = resource.additionalProperties["spec"] as? Map<*, *> ?: emptyMap() val status = resource.additionalProperties["status"] as? Map<*, *> ?: emptyMap() - val cheEditor = metadata.annotations?.get("che.eclipse.org/che-editor") + val owner = metadata.annotations?.get("che.eclipse.org/username") return DevWorkspace( namespace = metadata.namespace ?: "", @@ -45,7 +45,7 @@ data class DevWorkspace( uid = metadata.uid ?: "", started = spec["started"] as? Boolean ?: false, phase = status["phase"] as? String ?: "", - cheEditor = cheEditor + owner = owner ) } }