diff --git a/backend/crates/gateway-admin/src/model/plugins/instances.rs b/backend/crates/gateway-admin/src/model/plugins/instances.rs index bd497062b..16d0d0c0e 100644 --- a/backend/crates/gateway-admin/src/model/plugins/instances.rs +++ b/backend/crates/gateway-admin/src/model/plugins/instances.rs @@ -165,8 +165,9 @@ pub struct ConfigurePluginInstance { pub artifact_sha256: String, pub enabled: bool, pub configuration: serde_json::Value, - /// 缺省保留旧值;显式空对象清除全部 secret - pub secrets: Option>, + /// 按字段更新,省略的字段保留,null 清除对应字段 + #[serde(default)] + pub secrets: BTreeMap>, pub bindings: Vec, } diff --git a/backend/crates/gateway-admin/src/use_case/plugins/artifacts.rs b/backend/crates/gateway-admin/src/use_case/plugins/artifacts.rs index b861085d9..d42b50c95 100644 --- a/backend/crates/gateway-admin/src/use_case/plugins/artifacts.rs +++ b/backend/crates/gateway-admin/src/use_case/plugins/artifacts.rs @@ -217,7 +217,7 @@ impl PluginsService { artifact_sha256: metadata.sha256.clone(), enabled: !configuration_required && compatible, configuration: configuration.clone(), - secrets: None, + secrets: Default::default(), bindings: bindings.clone(), }; match self.configure_instance(None, input, context).await { diff --git a/backend/crates/gateway-admin/src/use_case/plugins/instances/configuration.rs b/backend/crates/gateway-admin/src/use_case/plugins/instances/configuration.rs index d7908a9c6..78809c22c 100644 --- a/backend/crates/gateway-admin/src/use_case/plugins/instances/configuration.rs +++ b/backend/crates/gateway-admin/src/use_case/plugins/instances/configuration.rs @@ -28,17 +28,16 @@ pub(super) fn same_creation_request( existing: &PluginInstance, input: &ConfigurePluginInstance, ) -> bool { - let same_secrets = input + let mut requested_secrets = input .secrets - .as_ref() - .map_or(existing.secrets.is_empty(), |secrets| { - secrets.len() == existing.secrets.len() - && secrets.iter().all(|(key, value)| { - existing - .secrets - .get(key) - .is_some_and(|current| current.expose_secret() == value.expose_secret()) - }) + .iter() + .filter_map(|(key, value)| value.as_ref().map(|value| (key, value))); + let same_secrets = requested_secrets.clone().count() == existing.secrets.len() + && requested_secrets.all(|(key, value)| { + existing + .secrets + .get(key) + .is_some_and(|current| current.expose_secret() == value.expose_secret()) }); existing.name == input.name.trim() && existing.artifact_sha256 == input.artifact_sha256 diff --git a/backend/crates/gateway-admin/src/use_case/plugins/instances/mutations.rs b/backend/crates/gateway-admin/src/use_case/plugins/instances/mutations.rs index a2f5d8c90..e7b983e77 100644 --- a/backend/crates/gateway-admin/src/use_case/plugins/instances/mutations.rs +++ b/backend/crates/gateway-admin/src/use_case/plugins/instances/mutations.rs @@ -158,7 +158,6 @@ impl PluginsService { } let saved = if let Some(previous) = &existing && previous.artifact_sha256 != input.artifact_sha256 - && input.secrets.is_none() { self.store .load_version_configuration(&previous.id, &input.artifact_sha256) @@ -167,6 +166,21 @@ impl PluginsService { } else { None }; + let mut secrets = saved.map(|saved| saved.secrets).unwrap_or_else(|| { + existing + .as_ref() + .map_or_else(Default::default, |instance| instance.secrets.clone()) + }); + for (name, value) in input.secrets { + match value { + Some(value) => { + secrets.insert(name, value); + } + None => { + secrets.remove(&name); + } + } + } let instance = PluginInstance { id: existing.as_ref().map_or_else( || { @@ -182,14 +196,7 @@ impl PluginsService { enabled: input.enabled, trusted_process: true, configuration: input.configuration, - secrets: input - .secrets - .or_else(|| saved.map(|saved| saved.secrets)) - .unwrap_or_else(|| { - existing - .as_ref() - .map_or_else(Default::default, |instance| instance.secrets.clone()) - }), + secrets, bindings: input.bindings, revision: candidate_revision, }; diff --git a/backend/crates/gateway-admin/src/use_case/plugins/versions.rs b/backend/crates/gateway-admin/src/use_case/plugins/versions.rs index 759d9b667..a0bb70673 100644 --- a/backend/crates/gateway-admin/src/use_case/plugins/versions.rs +++ b/backend/crates/gateway-admin/src/use_case/plugins/versions.rs @@ -69,7 +69,7 @@ impl PluginsService { artifact_sha256: input.artifact_sha256, enabled: existing.enabled, configuration: saved.configuration, - secrets: Some(saved.secrets), + secrets: Default::default(), bindings: saved.bindings, }; // 沿用同一份快照的 CAS 和状态迁移流程,不能在检查目标后重新读取并覆盖并发配置 @@ -202,7 +202,7 @@ impl PluginsService { artifact_sha256: input.artifact_sha256, enabled: existing.enabled, configuration: configuration.configuration, - secrets: Some(configuration.secrets), + secrets: Default::default(), bindings: configuration.bindings, }; // 同一次读取的 CAS 覆盖草稿计算、准备与提交,不能覆盖并发设置 diff --git a/backend/crates/gateway-admin/tests/use_case/plugins/instances/configuration.rs b/backend/crates/gateway-admin/tests/use_case/plugins/instances/configuration.rs new file mode 100644 index 000000000..9c5e9a0ba --- /dev/null +++ b/backend/crates/gateway-admin/tests/use_case/plugins/instances/configuration.rs @@ -0,0 +1,115 @@ +//! 实例敏感配置的逐字段更新、版本基准与提交隔离测试 + +use super::*; +use secrecy::ExposeSecret; + +fn secret_values(instance: &PluginInstance) -> serde_json::Value { + serde_json::Value::Object( + instance + .secrets + .iter() + .map(|(name, value)| (name.clone(), json!(value.expose_secret()))) + .collect(), + ) +} + +#[tokio::test] +async fn secret_changes_apply_to_current_or_target_version_configuration() { + for (target, saved_version) in [ + (OLD_ARTIFACT, false), + (NEW_ARTIFACT, false), + (NEW_ARTIFACT, true), + ] { + let token = if saved_version { + "saved-token" + } else { + "current-token" + }; + let untouched = if saved_version { + "saved-value" + } else { + "current-value" + }; + for (changes, expected) in [ + (json!({}), json!({"token":token,"untouched":untouched})), + ( + json!({"adminKey":"new-key"}), + json!({"token":token,"untouched":untouched,"adminKey":"new-key"}), + ), + ( + json!({"token":"new-token"}), + json!({"token":"new-token","untouched":untouched}), + ), + (json!({"token":null}), json!({"untouched":untouched})), + ( + json!({"token":null,"adminKey":"new-key","absent":null}), + json!({"untouched":untouched,"adminKey":"new-key"}), + ), + ] { + let fixture = LifecycleFixture::new(MigrationBehavior::Succeed); + { + let mut data = fixture.data.lock().unwrap(); + data.snapshot.instances[0].secrets = BTreeMap::from([ + ("token".into(), "current-token".into()), + ("untouched".into(), "current-value".into()), + ]); + if saved_version { + data.history.get_mut(NEW_ARTIFACT).unwrap().secrets = BTreeMap::from([ + ("token".into(), "saved-token".into()), + ("untouched".into(), "saved-value".into()), + ]); + } else { + data.history.clear(); + } + } + let before = fixture.snapshot(); + let mut request = input(); + request.artifact_sha256 = target.into(); + request.expected_revision = Some(10); + request.secrets = serde_json::from_value(changes).unwrap(); + let result = service(fixture.clone(), Arc::new(Published::default())) + .configure_instance(Some(&before.instances[0].id), request, &context()) + .await + .unwrap(); + assert_eq!(secret_values(&result.instance), expected); + assert_eq!(secret_values(&fixture.snapshot().instances[0]), expected); + } + } +} + +#[tokio::test] +async fn failed_secret_changes_leave_saved_values_unchanged() { + for failure in ["stale", "preparation", "size"] { + let fixture = LifecycleFixture::new(MigrationBehavior::Succeed); + fixture.data.lock().unwrap().snapshot.instances[0].secrets = + BTreeMap::from([("token".into(), "saved-token".into())]); + let before = fixture.snapshot(); + let mut request = input(); + request.artifact_sha256 = OLD_ARTIFACT.into(); + request.expected_revision = Some(if failure == "stale" { 9 } else { 10 }); + request.secrets = BTreeMap::from([ + ("token".into(), None), + ( + "adminKey".into(), + Some( + if failure == "size" { + "x".repeat(8193) + } else { + "new-key".into() + } + .into(), + ), + ), + ]); + fixture.data.lock().unwrap().fail_preparation = failure == "preparation"; + let result = service(fixture.clone(), Arc::new(Published::default())) + .configure_instance(Some(&before.instances[0].id), request, &context()) + .await; + assert!(result.is_err(), "{failure} must reject the update"); + assert_eq!( + secret_values(&fixture.snapshot().instances[0]), + secret_values(&before.instances[0]) + ); + assert!(fixture.data.lock().unwrap().saves.is_empty()); + } +} diff --git a/backend/crates/gateway-admin/tests/use_case/plugins/instances/mod.rs b/backend/crates/gateway-admin/tests/use_case/plugins/instances/mod.rs index d6867af45..8b7e1eae1 100644 --- a/backend/crates/gateway-admin/tests/use_case/plugins/instances/mod.rs +++ b/backend/crates/gateway-admin/tests/use_case/plugins/instances/mod.rs @@ -49,6 +49,7 @@ use serde_json::json; use super::TestPluginPorts; +mod configuration; mod queries; const OLD_ARTIFACT: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; @@ -759,7 +760,7 @@ fn input() -> ConfigurePluginInstance { artifact_sha256: NEW_ARTIFACT.into(), enabled: true, configuration: json!({}), - secrets: None, + secrets: Default::default(), bindings: vec![], } } @@ -942,6 +943,8 @@ async fn configuration_retry_reuses_creation_id_and_rejects_changed_draft() { // 此用例验证创建重试,使用不需要私有状态迁移的固定版本 value.artifact_sha256 = OLD_ARTIFACT.into(); value.enabled = false; + value.secrets = + serde_json::from_value(json!({"token": "saved-token", "absent": null})).unwrap(); value }; let created = service @@ -955,6 +958,12 @@ async fn configuration_retry_reuses_creation_id_and_rejects_changed_draft() { assert_eq!(created.instance.id, creation_id); assert_eq!(retried.instance.id, creation_id); assert_eq!(fixture.snapshot().instances.len(), 2); + use secrecy::ExposeSecret as _; + assert_eq!( + retried.instance.secrets["token"].expose_secret(), + "saved-token" + ); + assert!(!retried.instance.secrets.contains_key("absent")); let before = fixture.snapshot().config_revision; let mut changed = request(); changed.name = "changed draft".into(); @@ -965,6 +974,17 @@ async fn configuration_retry_reuses_creation_id_and_rejects_changed_draft() { .unwrap(); assert_eq!(error.kind(), AdminErrorKind::Conflict); assert_eq!(fixture.snapshot().config_revision, before); + let mut changed = request(); + changed + .secrets + .insert("token".into(), Some("different-token".into())); + let error = service + .configure_instance(None, changed, &context()) + .await + .err() + .unwrap(); + assert_eq!(error.kind(), AdminErrorKind::Conflict); + assert_eq!(fixture.snapshot().config_revision, before); } #[tokio::test] @@ -1435,23 +1455,6 @@ async fn version_switch_preparation_failure_preserves_current_and_saved_settings ); } -#[tokio::test] -async fn version_edit_preserves_the_target_versions_secrets_when_omitted() { - use secrecy::ExposeSecret as _; - let fixture = LifecycleFixture::new(MigrationBehavior::Succeed); - let before = fixture.snapshot(); - let mut request = input(); - request.secrets = None; - let result = service(fixture, Arc::new(Published::default())) - .configure_instance(Some(&before.instances[0].id), request, &context()) - .await - .unwrap(); - assert_eq!( - result.instance.secrets["token"].expose_secret(), - "old-version-secret" - ); -} - #[tokio::test] async fn rollback_requires_a_saved_configuration_instead_of_reusing_incompatible_current_settings() { diff --git a/docs/api.md b/docs/api.md index 3043456d9..f3636a89e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2929,7 +2929,8 @@ GitHub 的 `location` 使用 `kind: "github"`、`repository: "owner/repo"`、`ta 创建与更新的配置字段为 `name`、`artifactSha256`、`enabled`、`configuration` 和必填的 `bindings`, `secrets` 可选。输入严格拒绝未知字段。`artifactSha256` 必须指向 已接受的制品。`configuration` 是匹配插件 schema 的 JSON 对象;敏感字段必须 -放入 `secrets`,空对象清除全部值。省略时同版本编辑保留当前值;跨版本编辑优先保留目标版本快照的密钥,没有快照则保留当前值 +放入 `secrets`,按字段更新:字符串设置对应值,`null` 清除对应字段,未出现的字段保留。省略 `secrets` 或传空对象均不修改密钥 +同版本编辑以当前值为基础;跨版本编辑优先以目标版本快照的密钥为基础,没有快照则使用当前值。创建以空集合为基础,密钥值不会返回给管理端 创建可传 `creationId`(标准小写 UUID),同一草稿重试复用该 ID,已保存且内容不同则返回 409,不创建副本或覆盖旧配置。 相同内容的重试仍会重新准备并发布,不保证配置 revision 不变。更新可传 `expectedRevision`,与实例列表的 `revision` diff --git a/docs/plugins.md b/docs/plugins.md index 852bc241e..29e76fced 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -48,7 +48,8 @@ flowchart LR 在插件详情中直接启用或停用,设置会保留,无需重新填写。插件没有可设置的参数或认证映射时不显示设置入口 -“设置”按当前包的 schema 显示参数,敏感字段单独保存且不回显。首次补齐必填项后“保存并启用”;其他设置修改保持 +“设置”按当前包的 schema 显示参数,敏感字段单独保存且不回显。每项显示保存状态,留空保留现有值,填写新值替换该项; +“清除”只删除对应字段,保存前可撤销。首次补齐必填项后“保存并启用”;其他设置修改保持 原有启停状态。请求处理能力随包自动注册,无需在安装时逐项勾选;可在“高级设置”中调整各挂载阶段的启停、顺序和失败处理。 模型请求阶段支持 Key、分组、模型等作用范围;HTTP、WebSocket 消息和服务调用全局生效,具体匹配由插件处理。 客户端 Key、账号分组、Provider 和模型等范围同时满足才生效,同一范围内任一值匹配即可 diff --git a/frontend/src/api/modules/plugins/index.ts b/frontend/src/api/modules/plugins/index.ts index e8753545f..ad47b5d28 100644 --- a/frontend/src/api/modules/plugins/index.ts +++ b/frontend/src/api/modules/plugins/index.ts @@ -266,7 +266,7 @@ export interface ConfigurePluginInstanceRequest { artifactSha256: string enabled: boolean configuration: Record - secrets?: Record + secrets?: Record bindings: PluginCapabilityBinding[] } diff --git a/frontend/src/components/schema-form/index.vue b/frontend/src/components/schema-form/index.vue index 7943d2e9a..e80cfa673 100644 --- a/frontend/src/components/schema-form/index.vue +++ b/frontend/src/components/schema-form/index.vue @@ -128,8 +128,8 @@ defineExpose({ focusField }) @@ -156,8 +156,8 @@ defineExpose({ focusField }) import type { JsonSchema } from '../utils/model' -import { ZEmpty, ZFormItem, ZInput, ZSegmented, ZTag } from '@codex-proxy/ui' +import { ZEmpty, ZFormItem, ZIconButton, ZInput } from '@codex-proxy/ui' +import { RotateCcw, X } from '@lucide/vue' import { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue' import { useI18n } from 'vue-i18n' @@ -28,8 +29,7 @@ const emit = defineEmits<{ const { t } = useI18n({ useScope: 'global' }) const configuration = defineModel>('configuration', { required: true }) -const secrets = defineModel>('secrets', { required: true }) -const secretMode = defineModel<'preserve' | 'replace' | 'clear'>('secretMode', { required: true }) +const secrets = defineModel>('secrets', { required: true }) const configurationText = shallowRef('{}') const schemaForm = useTemplateRef<{ focusField: (key?: string) => Promise }>('schemaForm') const secretFieldsRoot = useTemplateRef('secretFieldsRoot') @@ -49,30 +49,19 @@ const configurationSchema = computed(() => { const secretSchemas = computed(() => { const properties = (props.schema as JsonSchema).properties ?? {} const required = (props.schema as JsonSchema).required ?? [] - return props.secretFields.map(name => ({ + return [ + ...new Set([ + ...props.secretFields, + ...props.existingSecretFields, + ]), + ].map(name => ({ name, schema: properties[name] ?? { type: 'string' }, - required: required.includes(name), + required: props.secretFields.includes(name) && required.includes(name), + declared: props.secretFields.includes(name), + saved: props.existingSecretFields.includes(name), })) }) -const secretModes = computed(() => ([ - { - label: t('plugins.configuration.keepExistingValues'), - value: 'preserve', - }, - { - label: t('plugins.configuration.replaceAllValues'), - value: 'replace', - }, - { - label: t('plugins.configuration.clearAllValues'), - value: 'clear', - }, -])) -const existingSecretLabels = computed(() => props.existingSecretFields.map(name => ({ - name, - label: (props.schema as JsonSchema).properties?.[name]?.title || name, -}))) const requiredConfigurationFields = computed(() => (configurationSchema.value as JsonSchema).required ?? []) const missingConfigurationFields = computed(() => requiredConfigurationFields.value.filter(name => !Object.hasOwn(configuration.value, name), @@ -84,23 +73,46 @@ const configurationFieldErrors = computed(() => Object.fromEntries( ]), )) const requiredSecretFields = computed(() => secretSchemas.value.filter(field => field.required)) -const missingSecretFields = computed(() => requiredSecretFields.value.filter((field) => { - if (secretMode.value === 'clear') - return true - if (secretMode.value === 'preserve') - return !props.existingSecretFields.includes(field.name) - return !valuePresent(secrets.value[field.name]) -})) -const secretModeError = computed(() => { - if (secretMode.value === 'preserve' && props.existingSecretFields.some(name => !props.secretFields.includes(name))) - return t('plugins.configuration.incompatibleSecrets') - if (!missingSecretFields.value.length || secretMode.value === 'replace') - return '' - const labels = missingSecretFields.value.map(field => field.schema.title || field.name).join('、') - return secretMode.value === 'clear' - ? t('plugins.configuration.requiredSecretsCannotClear', { labels }) - : t('plugins.configuration.requiredSecretsMissing', { labels }) -}) +const missingSecretFields = computed(() => requiredSecretFields.value.filter(field => !secretPresent(field.name))) +const incompatibleSecretFields = computed(() => secretSchemas.value.filter(field => !field.declared && secretPresent(field.name))) +const secretError = computed(() => incompatibleSecretFields.value.length + ? t('plugins.configuration.incompatibleSecrets') + : '') + +function secretChange(name: string) { + return Object.hasOwn(secrets.value, name) + ? secrets.value[name] + : undefined +} + +function secretPresent(name: string) { + const change = secretChange(name) + return change === undefined + ? props.existingSecretFields.includes(name) + : valuePresent(change) +} + +function secretPlaceholder(name: string) { + if (secretChange(name) === null) + return t('plugins.configuration.secretPendingClear') + if (!props.secretFields.includes(name)) + return t('plugins.configuration.secretNotDeclared') + return props.existingSecretFields.includes(name) + ? t('plugins.configuration.emptySecretPlaceholder') + : t('plugins.configuration.enterNewValue') +} + +function toggleSecretClear(name: string) { + if (secretChange(name) === null) { + const next = { ...secrets.value } + delete next[name] + secrets.value = next + } + else { + secrets.value = { ...secrets.value, [name]: null } + } +} + const hasConfiguration = computed(() => { const properties = (configurationSchema.value as JsonSchema).properties return Boolean(properties && Object.keys(properties).length > 0) @@ -126,10 +138,10 @@ function valuePresent(value: unknown) { } const valid = computed(() => !configurationError.value - && !secretModeError.value + && !secretError.value && !missingConfigurationFields.value.length && !missingSecretFields.value.length) -const validationMessage = computed(() => secretModeError.value +const validationMessage = computed(() => secretError.value || configurationError.value || (missingSecretFields.value.length ? t('plugins.configuration.secretsRequired') @@ -153,8 +165,6 @@ watch(parsedConfiguration, ({ value }) => { }) async function focusInvalid() { - if (secretModeError.value) - return 'secretMode' as const if (configurationError.value) { await schemaForm.value?.focusField() return 'configuration' as const @@ -163,18 +173,15 @@ async function focusInvalid() { await schemaForm.value?.focusField(missingConfigurationFields.value[0]) return 'configuration' as const } - if (!missingSecretFields.value.length) + const missingName = missingSecretFields.value[0]?.name ?? incompatibleSecretFields.value[0]?.name + if (!missingName) return null - if (secretMode.value !== 'replace') - return 'secretMode' as const - await nextTick() - const missingName = missingSecretFields.value[0]?.name const container = [ ...(secretFieldsRoot.value?.querySelectorAll('[data-secret-field]') ?? []), ] .find(element => element.dataset.secretField === missingName) - container?.querySelector('input:not([type="hidden"]), textarea, button, [tabindex]:not([tabindex="-1"])')?.focus() + container?.querySelector('input')?.focus() return 'secret' as const } @@ -200,35 +207,14 @@ defineExpose({ focusInvalid, validationMessage }) :field-errors="configurationFieldErrors" /> -
-
- {{ t('plugins.configuration.sensitiveConfiguration') }} - - {{ t('plugins.configuration.settingsSaved', { count: existingSecretFields.length }) }} - -
-

- {{ t('plugins.configuration.secretsHelp') }} +

+

+ {{ t('plugins.configuration.sensitiveConfiguration') }} +

+ - - -
    -
  • - - {{ field.label }} - -
  • -
-
+
+ > + + +
diff --git a/frontend/src/views/plugins/components/PluginConfigurationModal.vue b/frontend/src/views/plugins/components/PluginConfigurationModal.vue index f067530ed..d2a15a36d 100644 --- a/frontend/src/views/plugins/components/PluginConfigurationModal.vue +++ b/frontend/src/views/plugins/components/PluginConfigurationModal.vue @@ -2,7 +2,7 @@ import type { ConfigurePluginInstanceRequest, PluginArtifact, PluginInstance, PluginVersionPlan } from '@/api' import { ZButton, ZDialog, ZSegmented } from '@codex-proxy/ui' -import { Blocks, Save, Settings2, ShieldCheck } from '@lucide/vue' +import { Blocks, Settings2, ShieldCheck } from '@lucide/vue' import { cloneDeep } from 'es-toolkit' import { computed, nextTick, ref, shallowRef, useTemplateRef, watch } from 'vue' import { useI18n } from 'vue-i18n' @@ -29,8 +29,7 @@ const section = shallowRef('general') const configurationValid = shallowRef(true) const authenticationValid = shallowRef(true) const bindingsValid = shallowRef(true) -const secretMode = shallowRef<'preserve' | 'replace' | 'clear'>('preserve') -const secretValues = ref>({}) +const secretValues = ref>({}) const configuration = ref>({}) const bindings = ref([]) const configurationFields = useTemplateRef<{ focusInvalid: () => Promise, validationMessage: string }>('configurationFields') @@ -117,11 +116,8 @@ async function submit() { configuration: cloneDeep(configuration.value), bindings: cloneDeep(bindings.value), } - if (secretMode.value !== 'preserve') { - input.secrets = secretMode.value === 'clear' - ? {} - : cloneDeep(secretValues.value) - } + if (Object.keys(secretValues.value).length) + input.secrets = cloneDeep(secretValues.value) emit('save', input) } @@ -131,7 +127,6 @@ watch(open, async (value) => { secretValues.value = {} configuration.value = cloneDeep(props.draft?.configuration ?? props.instance?.configuration ?? {}) bindings.value = cloneDeep(props.draft?.bindings ?? props.instance?.bindings ?? []) - secretMode.value = existingSecretFields.value.length ? 'preserve' : 'replace' configurationValid.value = true authenticationValid.value = true bindingsValid.value = true @@ -158,7 +153,6 @@ function clearSecrets() { ref="configurationFields" v-model:configuration="configuration" v-model:secrets="secretValues" - v-model:secret-mode="secretMode" :schema="artifact.metadata.configurationSchema" :secret-fields="artifact.metadata.secretFields" :existing-secret-fields="existingSecretFields" @@ -192,9 +186,6 @@ function clearSecrets() { {{ t('common.actions.cancel') }} - {{ primaryLabel }}