From 1efb0e041fa589129e7f763d049ef3130397134f Mon Sep 17 00:00:00 2001 From: Andrew Rublyov Date: Fri, 9 Dec 2022 00:05:34 +0100 Subject: [PATCH] #131, #111, #53, #128, #54: Add missing dialogs data save feature, add workaround for broken SDK in 2022.3 --- CHANGELOG.md | 10 ++- build.gradle | 6 +- docs/values-saved-between-dialogs.md | 63 ++++++++++++++++ gradle.properties | 6 +- src/dotnet/Plugin.props | 2 +- .../PreferredCommandExecutorProvider.kt | 3 +- .../update/UpdateDatabaseDataContext.kt | 30 ++++++++ .../scaffold/ScaffoldDbContextDataContext.kt | 73 ++++++++++++++++++- .../migrations/add/AddMigrationDataContext.kt | 19 +++++ .../script/GenerateScriptDataContext.kt | 31 ++++++++ .../shared/dialog/CommonDataContext.kt | 46 +++++++----- .../shared/dialog/CommonDialogWrapper.kt | 2 +- .../efcore/settings/EfCoreUiConfigurable.kt | 19 ++++- .../efcore/settings/EfCoreUiSettingsState.kt | 1 + .../settings/EfCoreUiSettingsStateService.kt | 13 +++- .../plugins/efcore/state/DialogsState.kt | 5 +- .../efcore/state/DialogsStateService.kt | 49 ++++++++++--- src/rider/main/resources/META-INF/plugin.xml | 4 +- 18 files changed, 332 insertions(+), 50 deletions(-) create mode 100644 docs/values-saved-between-dialogs.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f2fa25..c129a797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [223.1.0] - 2022-12-07 +### Added +- Most common values in dialogs are saved between instances +- Ability to run commands in terminal-like tool window +- Support for a stable Rider 2022.3 + +### Fixed +- DbContext class not showing up (#105) ## [223.0.0] - 2022-09-30 ### Added @@ -114,6 +121,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Deleting used database [Unreleased]: https://github.com/seclerp/rider-efcore/compare/v223.0.0...HEAD +[223.1.0]: https://github.com/seclerp/rider-efcore/compare/v223.0.0...HEAD [223.0.0]: https://github.com/seclerp/rider-efcore/compare/v222.2.0...v223.0.0 [222.2.0]: https://github.com/seclerp/rider-efcore/compare/v222.1.1...v222.2.0 [222.1.1]: https://github.com/seclerp/rider-efcore/compare/v222.1.0...v222.1.1 diff --git a/build.gradle b/build.gradle index cb56648b..4432aafb 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,9 @@ import java.text.SimpleDateFormat plugins { id 'java' id 'org.jetbrains.kotlin.jvm' version '1.6.21' - id 'org.jetbrains.intellij' version '1.9.0' // https://github.com/JetBrains/gradle-intellij-plugin/releases - id 'com.jetbrains.rdgen' version '2022.3.2' // https://www.myget.org/feed/rd-snapshots/package/maven/com.jetbrains.rd/rd-gen - id 'org.jetbrains.changelog' version '1.3.1' + id 'org.jetbrains.intellij' version '1.10.0' // https://github.com/JetBrains/gradle-intellij-plugin/releases + id 'com.jetbrains.rdgen' version '2022.3.4' // https://www.myget.org/feed/rd-snapshots/package/maven/com.jetbrains.rd/rd-gen + id 'org.jetbrains.changelog' version '2.0.0' } ext { diff --git a/docs/values-saved-between-dialogs.md b/docs/values-saved-between-dialogs.md new file mode 100644 index 00000000..823740fe --- /dev/null +++ b/docs/values-saved-between-dialogs.md @@ -0,0 +1,63 @@ +## Which values are saved between dialogs? + +> **Note**: To save selected values between dialogs the +> `Settings` | `Tools` | `EF Core UI` | `Use previously selected options in dialogs` option should be **enabled**. + + +### Common +- **Startup project**: Yes _(determined by previously selected pair with Migrations project)_ +- **Migrations project**: Yes _(determined by previously selected pair with Startup project)_ +- **DbContext value**: Yes _(per Migrations project)_ +- **Build configuration**: No _(reason: default value equals to current solution configuration)_ +- **SKip project build process**: Yes +- **Target framework**: Yes _(per Startup project)_ +- **Additional arguments**: Yes _(when "Store sensitive data in a secure store" option is enabled)_ + + +### Add Migration +- **Migration name**: No +- **Migrations folder**: Yes + + +### Remove Last Migration +Nothing to save. + + +### Generate SQL Script +- **From migration**: No +- **To migration**: No +- **Output file**: Yes +- **Make script idempotent**: Yes +- **No transactions**: Yes + + +### Update Database +- **Target migration**: No +- **Use default connection of startup project**: Yes +- **Connection**: Yes _(when "Store sensitive data in a secure store" option is enabled)_ + + +### Drop Database +Nothing to save. + + +### Scaffold DbContext + +#### Main +- **Connection**: Yes _(when "Store sensitive data in a secure store" option is enabled)_ +- **Provider**: Yes +- **Output folder**: Yes +- **Use attributes to generate the model**: Yes +- **Use database names**: Yes +- **Generate OnConfiguring method**: Yes +- **Use the pluralizer**: Yes + +#### DbContext +- **Generated DbContext name**: Yes +- **Generated DbContext folder**: Yes + +#### Tables +- **Scaffold all schemas**: No + +#### Schemas +- **Scaffold all schemas**: No \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 21db9868..e25e13b4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ javaVersion=17 DotnetPluginId=Rider.Plugins.EfCore DotnetSolution=Rider.Plugins.EfCore.sln RiderPluginId=me.seclerp.rider.plugins.efcore -PluginVersion=223.0.0 +PluginVersion=223.1.0 BuildConfiguration=Debug @@ -15,12 +15,12 @@ PublishChannel=default # Possible values: # Release: 2021.2.0 # EAP: 2022.2.0-eap04 -RiderSdkVersion=2022.3.0-eap01 +RiderSdkVersion=2022.3.0 # Possible values (minor is omitted): # Release: 2020.2 # Nightly: 2020.3-SNAPSHOT # EAP: 2020.3-EAP2-SNAPSHOT -ProductVersion=2022.3-EAP1-SNAPSHOT +ProductVersion=2022.3-SNAPSHOT # Kotlin 1.4 will bundle the stdlib dependency by default, causing problems with the version bundled with the IDE # https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-rc-released/#stdlib-default diff --git a/src/dotnet/Plugin.props b/src/dotnet/Plugin.props index a50a7ef7..8d8cd2ac 100644 --- a/src/dotnet/Plugin.props +++ b/src/dotnet/Plugin.props @@ -1,7 +1,7 @@  - 2022.3.0-eap01 + 2022.3.0 Entity Framework Core JetBrains Rider plugin for Entity Framework Core diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/cli/execution/PreferredCommandExecutorProvider.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/cli/execution/PreferredCommandExecutorProvider.kt index 81534e72..be74975c 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/cli/execution/PreferredCommandExecutorProvider.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/cli/execution/PreferredCommandExecutorProvider.kt @@ -1,13 +1,12 @@ package me.seclerp.rider.plugins.efcore.cli.execution import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import me.seclerp.rider.plugins.efcore.settings.EfCoreUiSettingsStateService @Service class PreferredCommandExecutorProvider(private val intellijProject: Project) { - private val settingsStateService = service() + private val settingsStateService = EfCoreUiSettingsStateService.getInstance() fun getExecutor(): CliCommandExecutor = when (settingsStateService.useTerminalExecution) { diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/database/update/UpdateDatabaseDataContext.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/database/update/UpdateDatabaseDataContext.kt index 82df71cd..12be441f 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/database/update/UpdateDatabaseDataContext.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/database/update/UpdateDatabaseDataContext.kt @@ -5,6 +5,7 @@ import me.seclerp.observables.bind import me.seclerp.observables.observable import me.seclerp.rider.plugins.efcore.features.shared.ObservableMigrations import me.seclerp.rider.plugins.efcore.features.shared.dialog.CommonDataContext +import me.seclerp.rider.plugins.efcore.state.DialogsStateService class UpdateDatabaseDataContext(intellijProject: Project): CommonDataContext(intellijProject, true) { val availableMigrations = ObservableMigrations(intellijProject, migrationsProject, dbContext) @@ -25,4 +26,33 @@ class UpdateDatabaseDataContext(intellijProject: Project): CommonDataContext(int "" } } + + override fun loadState(commonDialogState: DialogsStateService.SpecificDialogState) { + super.loadState(commonDialogState) + + commonDialogState.getBool(KnownStateKeys.USE_DEFAULT_CONNECTION)?.apply { + useDefaultConnection.value = this + } + + if (pluginSettings.storeSensitiveData && !useDefaultConnection.value) { + commonDialogState.getSensitive(KnownStateKeys.CONNECTION)?.apply { + connection.value = this + } + } + } + + override fun saveState(commonDialogState: DialogsStateService.SpecificDialogState) { + super.saveState(commonDialogState) + + commonDialogState.set(KnownStateKeys.USE_DEFAULT_CONNECTION, useDefaultConnection.value) + + if (pluginSettings.storeSensitiveData && !useDefaultConnection.value) { + commonDialogState.setSensitive(KnownStateKeys.CONNECTION, connection.value) + } + } + + object KnownStateKeys { + const val USE_DEFAULT_CONNECTION = "useDefaultConnection" + const val CONNECTION = "connection" + } } \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/dbcontext/scaffold/ScaffoldDbContextDataContext.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/dbcontext/scaffold/ScaffoldDbContextDataContext.kt index de05a9ff..d2f59564 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/dbcontext/scaffold/ScaffoldDbContextDataContext.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/dbcontext/scaffold/ScaffoldDbContextDataContext.kt @@ -1,10 +1,10 @@ package me.seclerp.rider.plugins.efcore.features.dbcontext.scaffold import com.intellij.openapi.project.Project -import me.seclerp.observables.ObservableCollection import me.seclerp.observables.observable import me.seclerp.observables.observableList import me.seclerp.rider.plugins.efcore.features.shared.dialog.CommonDataContext +import me.seclerp.rider.plugins.efcore.state.DialogsStateService import me.seclerp.rider.plugins.efcore.ui.items.SimpleItem class ScaffoldDbContextDataContext(intellijProject: Project) : CommonDataContext(intellijProject, false) { @@ -25,4 +25,75 @@ class ScaffoldDbContextDataContext(intellijProject: Project) : CommonDataContext val scaffoldAllTables = observable(true) val scaffoldAllSchemas = observable(true) + + override fun loadState(commonDialogState: DialogsStateService.SpecificDialogState) { + super.loadState(commonDialogState) + + if (pluginSettings.storeSensitiveData) { + commonDialogState.getSensitive(KnownStateKeys.CONNECTION)?.apply { + connection.value = this + } + } + + commonDialogState.get(KnownStateKeys.PROVIDER)?.apply { + provider.value = this + } + + commonDialogState.get(KnownStateKeys.OUTPUT_FOLDER)?.apply { + outputFolder.value = this + } + + commonDialogState.getBool(KnownStateKeys.USE_ATTRIBUTES)?.apply { + useAttributes.value = this + } + + commonDialogState.getBool(KnownStateKeys.USE_DATABASE_NAMES)?.apply { + useDatabaseNames.value = this + } + + commonDialogState.getBool(KnownStateKeys.GENERATE_ON_CONFIGURING)?.apply { + generateOnConfiguring.value = this + } + + commonDialogState.getBool(KnownStateKeys.USE_PLURALIZER)?.apply { + usePluralizer.value = this + } + + commonDialogState.get(KnownStateKeys.DB_CONTEXT_NAME)?.apply { + dbContextName.value = this + } + + commonDialogState.get(KnownStateKeys.DB_CONTEXT_FOLDER)?.apply { + dbContextFolder.value = this + } + } + + override fun saveState(commonDialogState: DialogsStateService.SpecificDialogState) { + super.saveState(commonDialogState) + + if (pluginSettings.storeSensitiveData) { + commonDialogState.setSensitive(KnownStateKeys.CONNECTION, connection.value) + } + + commonDialogState.set(KnownStateKeys.PROVIDER, provider.value) + commonDialogState.set(KnownStateKeys.OUTPUT_FOLDER, outputFolder.value) + commonDialogState.set(KnownStateKeys.USE_ATTRIBUTES, useAttributes.value) + commonDialogState.set(KnownStateKeys.USE_DATABASE_NAMES, useDatabaseNames.value) + commonDialogState.set(KnownStateKeys.GENERATE_ON_CONFIGURING, generateOnConfiguring.value) + commonDialogState.set(KnownStateKeys.USE_PLURALIZER, usePluralizer.value) + commonDialogState.set(KnownStateKeys.DB_CONTEXT_NAME, dbContextName.value) + commonDialogState.set(KnownStateKeys.DB_CONTEXT_FOLDER, dbContextFolder.value) + } + + object KnownStateKeys { + const val CONNECTION = "connection" + const val PROVIDER = "provider" + const val OUTPUT_FOLDER = "outputFolder" + const val USE_ATTRIBUTES = "useAttributes" + const val USE_DATABASE_NAMES = "useDatabaseNames" + const val GENERATE_ON_CONFIGURING = "generateOnConfiguring" + const val USE_PLURALIZER = "usePluralizer" + const val DB_CONTEXT_NAME = "dbContextName" + const val DB_CONTEXT_FOLDER = "dbContextFolder" + } } \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/migrations/add/AddMigrationDataContext.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/migrations/add/AddMigrationDataContext.kt index 454bfc4f..10c17e0c 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/migrations/add/AddMigrationDataContext.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/migrations/add/AddMigrationDataContext.kt @@ -4,6 +4,7 @@ import com.intellij.openapi.project.Project import me.seclerp.observables.* import me.seclerp.rider.plugins.efcore.features.shared.ObservableMigrations import me.seclerp.rider.plugins.efcore.features.shared.dialog.CommonDataContext +import me.seclerp.rider.plugins.efcore.state.DialogsStateService class AddMigrationDataContext( intellijProject: Project @@ -24,4 +25,22 @@ class AddMigrationDataContext( migrationName.value } } + + override fun loadState(commonDialogState: DialogsStateService.SpecificDialogState) { + super.loadState(commonDialogState) + + commonDialogState.get(KnownStateKeys.OUTPUT_FOLDER)?.apply { + migrationsOutputFolder.value = this + } + } + + override fun saveState(commonDialogState: DialogsStateService.SpecificDialogState) { + super.saveState(commonDialogState) + + commonDialogState.set(KnownStateKeys.OUTPUT_FOLDER, migrationsOutputFolder.value) + } + + object KnownStateKeys { + const val OUTPUT_FOLDER = "outputFolder" + } } diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/migrations/script/GenerateScriptDataContext.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/migrations/script/GenerateScriptDataContext.kt index 0de68fc6..c66050a6 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/migrations/script/GenerateScriptDataContext.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/migrations/script/GenerateScriptDataContext.kt @@ -4,6 +4,7 @@ import com.intellij.openapi.project.Project import me.seclerp.observables.* import me.seclerp.rider.plugins.efcore.features.shared.ObservableMigrations import me.seclerp.rider.plugins.efcore.features.shared.dialog.CommonDataContext +import me.seclerp.rider.plugins.efcore.state.DialogsStateService class GenerateScriptDataContext( intellijProject: Project @@ -44,4 +45,34 @@ class GenerateScriptDataContext( } } } + + override fun loadState(commonDialogState: DialogsStateService.SpecificDialogState) { + super.loadState(commonDialogState) + + commonDialogState.get(KnownStateKeys.OUTPUT_FILE)?.apply { + outputFilePath.value = this + } + + commonDialogState.getBool(KnownStateKeys.IDEMPOTENT)?.apply { + idempotent.value = this + } + + commonDialogState.getBool(KnownStateKeys.NO_TRANSACTIONS)?.apply { + noTransactions.value = this + } + } + + override fun saveState(commonDialogState: DialogsStateService.SpecificDialogState) { + super.saveState(commonDialogState) + + commonDialogState.set(KnownStateKeys.OUTPUT_FILE, outputFilePath.value) + commonDialogState.set(KnownStateKeys.IDEMPOTENT, idempotent.value) + commonDialogState.set(KnownStateKeys.NO_TRANSACTIONS, noTransactions.value) + } + + object KnownStateKeys { + const val OUTPUT_FILE = "outputFilePath" + const val IDEMPOTENT = "idempotent" + const val NO_TRANSACTIONS = "noTransactions" + } } \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/shared/dialog/CommonDataContext.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/shared/dialog/CommonDataContext.kt index fc90b429..dbe95717 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/shared/dialog/CommonDataContext.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/shared/dialog/CommonDataContext.kt @@ -6,6 +6,7 @@ import com.jetbrains.rider.projectView.solution import com.jetbrains.rider.util.idea.runUnderProgress import me.seclerp.observables.* import me.seclerp.rider.plugins.efcore.rd.* +import me.seclerp.rider.plugins.efcore.settings.EfCoreUiSettingsStateService import me.seclerp.rider.plugins.efcore.state.DialogsStateService open class CommonDataContext( @@ -13,6 +14,7 @@ open class CommonDataContext( val requireDbContext: Boolean ) : DataContext() { protected val beModel = intellijProject.solution.riderEfCoreModel + protected val pluginSettings by lazy { EfCoreUiSettingsStateService.getInstance() } val availableStartupProjects = observableList().withLogger("availableStartupProjects") val availableMigrationsProjects = observableList().withLogger("availableMigrationsProjects") @@ -102,29 +104,30 @@ open class CommonDataContext( if (requireDbContext) { val dbContextName = commonDialogState.get("${migrationsProjectId}:${KnownStateKeys.DB_CONTEXT}") - val dbContext = availableDbContexts.value.firstOrNull { it.fullName == dbContextName } - if (dbContext != null) { - this.dbContext.value = dbContext + availableDbContexts.value.firstOrNull { it.fullName == dbContextName }?.apply { + dbContext.value = this } } val buildConfigurationName = commonDialogState.get(KnownStateKeys.BUILD_CONFIGURATION) - val buildConfiguration = availableBuildConfigurations.firstOrNull { it == buildConfigurationName } - if (buildConfiguration != null) { - this.buildConfiguration.value = buildConfiguration + availableBuildConfigurations.firstOrNull { it == buildConfigurationName }?.apply { + buildConfiguration.value = this } val targetFrameworkName = commonDialogState.get("${startupProjectId}:${KnownStateKeys.TARGET_FRAMEWORK}") - val targetFramework = availableTargetFrameworks.value.firstOrNull { it == targetFrameworkName } - if (targetFramework != null) { - this.targetFramework.value = targetFramework + availableTargetFrameworks.value.firstOrNull { it == targetFrameworkName }?.apply { + targetFramework.value = this } - val noBuild = commonDialogState.getBool(KnownStateKeys.NO_BUILD) ?: false - this.noBuild.value = noBuild + commonDialogState.getBool(KnownStateKeys.NO_BUILD)?.apply { + noBuild.value = this + } - val additionalArguments = commonDialogState.get(KnownStateKeys.ADDITIONAL_ARGUMENTS) ?: "" - this.additionalArguments.value = additionalArguments + if (pluginSettings.storeSensitiveData) { + commonDialogState.getSensitive(KnownStateKeys.ADDITIONAL_ARGUMENTS)?.apply { + additionalArguments.value = this + } + } } open fun saveState(commonDialogState: DialogsStateService.SpecificDialogState) { @@ -140,15 +143,18 @@ open class CommonDataContext( commonDialogState.set(KnownStateKeys.TARGET_FRAMEWORK, targetFramework.value!!) } - commonDialogState.set(KnownStateKeys.NO_BUILD, noBuild.value.toString()) - commonDialogState.set(KnownStateKeys.ADDITIONAL_ARGUMENTS, additionalArguments.value) + commonDialogState.set(KnownStateKeys.NO_BUILD, noBuild.value) + + if (pluginSettings.storeSensitiveData) { + commonDialogState.setSensitive(KnownStateKeys.ADDITIONAL_ARGUMENTS, additionalArguments.value) + } } private object KnownStateKeys { - val DB_CONTEXT = "dbContext" - val BUILD_CONFIGURATION = "buildConfiguration" - val TARGET_FRAMEWORK = "targetFramework" - val NO_BUILD = "noBuild" - val ADDITIONAL_ARGUMENTS = "additionalArguments" + const val DB_CONTEXT = "dbContext" + const val BUILD_CONFIGURATION = "buildConfiguration" + const val TARGET_FRAMEWORK = "targetFramework" + const val NO_BUILD = "noBuild" + const val ADDITIONAL_ARGUMENTS = "additionalArguments" } } diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/shared/dialog/CommonDialogWrapper.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/shared/dialog/CommonDialogWrapper.kt index ced0fb42..dbccfe7c 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/shared/dialog/CommonDialogWrapper.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/features/shared/dialog/CommonDialogWrapper.kt @@ -68,7 +68,7 @@ abstract class CommonDialogWrapper( // // Preferences private val preferredProjectsManager = intellijProject.service() - private val settingsStateService = service() + private val settingsStateService = EfCoreUiSettingsStateService.getInstance() private val dialogsStateService = intellijProject.service() // diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiConfigurable.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiConfigurable.kt index beafb1ec..69805681 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiConfigurable.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiConfigurable.kt @@ -1,25 +1,40 @@ package me.seclerp.rider.plugins.efcore.settings -import com.intellij.openapi.components.service import com.intellij.openapi.options.BoundConfigurable import com.intellij.ui.dsl.builder.bindSelected import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.gridLayout.HorizontalAlign +import me.seclerp.rider.plugins.efcore.state.DialogsStateService class EfCoreUiConfigurable : BoundConfigurable("EF Core UI") { - private val settingsStateService = service() + private val settingsStateService = EfCoreUiSettingsStateService.getInstance() override fun createPanel() = panel { group("Dialogs Data") { row { checkBox("Use previously selected options in dialogs") .bindSelected(settingsStateService::usePreviouslySelectedOptionsInDialogs) + .horizontalAlign(HorizontalAlign.FILL) .comment("Experimental: If enabled, next opened dialog instance will reuse data from a previous one") } + row { + checkBox("Store sensitive data in a secure store") + .bindSelected(settingsStateService::storeSensitiveData) + .horizontalAlign(HorizontalAlign.FILL) + .comment("If enabled, plugin will securely store data that may contain credentials, such as \"Additional arguments\" or \"Connection\" fields
" + + "Please use this option carefully, as it could lead to accidents on non-local environments") + } + row { + button("Clear Stored Data") { + DialogsStateService.getInstance().clearState() + } + } } group("Execution") { row { checkBox("Execute commands in terminal") .bindSelected(settingsStateService::useTerminalExecution) + .horizontalAlign(HorizontalAlign.FILL) .comment("If enabled, command will be executed in the terminal with full output available") } } diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiSettingsState.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiSettingsState.kt index 9ffd7030..5405772b 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiSettingsState.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiSettingsState.kt @@ -2,5 +2,6 @@ package me.seclerp.rider.plugins.efcore.settings class EfCoreUiSettingsState { var usePreviouslySelectedOptionsInDialogs: Boolean = true + var storeSensitiveData: Boolean = false var useTerminalExecution: Boolean = true } \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiSettingsStateService.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiSettingsStateService.kt index 8d8b31ef..a1ef882e 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiSettingsStateService.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/settings/EfCoreUiSettingsStateService.kt @@ -1,19 +1,24 @@ package me.seclerp.rider.plugins.efcore.settings -import com.intellij.openapi.components.PersistentStateComponent -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.State -import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.* @Service @State(name = "EfCoreUiSettings", storages = [Storage("efCoreUiSettings.xml")]) class EfCoreUiSettingsStateService : PersistentStateComponent { + companion object { + fun getInstance(): EfCoreUiSettingsStateService = service() + } + private var myState = EfCoreUiSettingsState() var usePreviouslySelectedOptionsInDialogs : Boolean get() = myState.usePreviouslySelectedOptionsInDialogs set(value) { myState.usePreviouslySelectedOptionsInDialogs = value } + var storeSensitiveData : Boolean + get() = myState.storeSensitiveData + set(value) { myState.storeSensitiveData = value } + var useTerminalExecution : Boolean get() = myState.useTerminalExecution set(value) { myState.useTerminalExecution = value } diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/state/DialogsState.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/state/DialogsState.kt index c2b6dc1d..6a31c12d 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/state/DialogsState.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/state/DialogsState.kt @@ -1,5 +1,8 @@ package me.seclerp.rider.plugins.efcore.state +import com.intellij.credentialStore.CredentialAttributes + class DialogsState { - var keyValueStorage : MutableMap = mutableMapOf() + var keyValueStorage = mutableMapOf() + val storedSecureAttributes = mutableSetOf() } \ No newline at end of file diff --git a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/state/DialogsStateService.kt b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/state/DialogsStateService.kt index 7d7276ff..d1c121f0 100644 --- a/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/state/DialogsStateService.kt +++ b/src/rider/main/kotlin/me/seclerp/rider/plugins/efcore/state/DialogsStateService.kt @@ -1,13 +1,18 @@ package me.seclerp.rider.plugins.efcore.state -import com.intellij.openapi.components.PersistentStateComponent -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.State -import com.intellij.openapi.components.Storage +import com.intellij.credentialStore.CredentialAttributes +import com.intellij.credentialStore.Credentials +import com.intellij.credentialStore.generateServiceName +import com.intellij.ide.passwordSafe.PasswordSafe +import com.intellij.openapi.components.* @Service @State(name = "EfCoreDialogsState", storages = [Storage("efCoreDialogsState.xml")]) class DialogsStateService : PersistentStateComponent { + companion object { + fun getInstance(): DialogsStateService = service() + } + private var myState = DialogsState() override fun getState(): DialogsState = myState @@ -16,24 +21,50 @@ class DialogsStateService : PersistentStateComponent { myState = state } + fun clearState() { + myState.keyValueStorage.clear() + myState.storedSecureAttributes.forEach { + PasswordSafe.instance.set(it, null) + } + myState.storedSecureAttributes.clear() + } + fun forDialog(dialogId: String) = - SpecificDialogState(dialogId, myState.keyValueStorage) + SpecificDialogState(dialogId, myState.keyValueStorage, myState.storedSecureAttributes) class SpecificDialogState( private val dialogId: String, - private val storage: MutableMap + private val storage: MutableMap, + private val storedSecureAttributes: MutableSet ) { + fun get(key: String) = storage["${dialogId}:${key}"] fun getBool(key: String) = get(key)?.toBoolean() - fun getInt(key: String) = - get(key)?.toInt() - fun set(key: String, value: String) { storage["${dialogId}:${key}"] = value } + + fun set(key: String, value: Boolean) { + storage["${dialogId}:${key}"] = value.toString() + } + + fun getSensitive(key: String): String? { + val attributes = createCredentialAttributes(key) + return PasswordSafe.instance.get(attributes)?.password?.toString() + } + + fun setSensitive(key: String, value: String) { + val attributes = createCredentialAttributes(key) + storedSecureAttributes.add(attributes) + PasswordSafe.instance.set(attributes, Credentials("EfCoreUiPlugin", value)) + } + + private fun createCredentialAttributes(key: String) = CredentialAttributes( + generateServiceName("EfCoreDialogsData", "$dialogId:$key") + ) } } \ No newline at end of file diff --git a/src/rider/main/resources/META-INF/plugin.xml b/src/rider/main/resources/META-INF/plugin.xml index 788208e4..57ba01d3 100644 --- a/src/rider/main/resources/META-INF/plugin.xml +++ b/src/rider/main/resources/META-INF/plugin.xml @@ -2,8 +2,8 @@ me.seclerp.rider.plugins.efcore Entity Framework Core UI _PLACEHOLDER_ - Andrew Rublyov - + Andrii Rublov + com.intellij.modules.rider