diff --git a/frontend/src/__tests__/meldwork/ConnectorPackagesPanel.spec.js b/frontend/src/__tests__/meldwork/ConnectorPackagesPanel.spec.js index ae3c7be..414e760 100644 --- a/frontend/src/__tests__/meldwork/ConnectorPackagesPanel.spec.js +++ b/frontend/src/__tests__/meldwork/ConnectorPackagesPanel.spec.js @@ -60,4 +60,22 @@ describe('Connector package settings', () => { expect(wrapper.text()).toContain(t('connectorPackages.instanceInUse')) wrapper.unmount() }) + + it('displays inspected permissions and durable trust history', async () => { + const item = { packageId: 'package', manifestId: 'manifest', state: 'imported', label: 'Example', connectorVersion: '1.0.0', publisher: { name: 'Publisher' } } + const api = { packages: vi.fn().mockResolvedValue([item]), list: vi.fn().mockResolvedValue([]), + inspect: vi.fn().mockResolvedValue({ packageId: 'package', origin: { filename: 'example.json', sha256: 'abc' }, manifest: { + transport: { type: 'http', protocol: 'json' }, permissionModes: ['read-only'], + credentials: { slots: [{ slotId: 'key' }] }, outboundDestinations: ['https://example.com'], + } }), audit: vi.fn().mockResolvedValue([{ eventId: 'event', action: 'imported', timestamp: '2026-11-06T00:00:00Z' }]) } + window.meldworkDesktop = { localAgentConnector: api } + const wrapper = mount(ConnectorPackagesPanel) + await flushPromises() + await wrapper.findAll('button').find(button => button.text() === t('connectorPackages.inspect')).trigger('click') + await flushPromises() + expect(wrapper.text()).toContain('read-only') + expect(wrapper.text()).toContain('https://example.com') + expect(wrapper.text()).toContain(t('connectorPackages.event.imported')) + wrapper.unmount() + }) }) diff --git a/frontend/src/components/ConnectorPackagesPanel.vue b/frontend/src/components/ConnectorPackagesPanel.vue index 3a60323..0bb2f81 100644 --- a/frontend/src/components/ConnectorPackagesPanel.vue +++ b/frontend/src/components/ConnectorPackagesPanel.vue @@ -10,6 +10,7 @@
{{ item.label }} {{ item.connectorVersion }}

{{ item.publisher.name }} · {{ t(`connectorPackages.state.${item.state}`) }}

+ @@ -18,6 +19,15 @@
+
+

{{ t('connectorPackages.origin') }}: {{ selected.origin.filename }} · {{ selected.origin.sha256 }}

+

{{ t('connectorPackages.transport') }}: {{ selected.manifest.transport.type }} / {{ selected.manifest.transport.protocol }}

+

{{ t('connectorPackages.permissions') }}: {{ selected.manifest.permissionModes.join(', ') || t('connectorPackages.none') }}

+

{{ t('connectorPackages.credentials') }}: {{ selected.manifest.credentials.slots.map(slot => slot.slotId).join(', ') || t('connectorPackages.none') }}

+

{{ t('connectorPackages.destinations') }}: {{ selected.manifest.outboundDestinations.join(', ') || t('connectorPackages.none') }}

+

{{ t('connectorPackages.audit') }}

+
  1. {{ t(`connectorPackages.event.${event.action}`) }} · {{ event.timestamp }}
+

{{ t('connectorPackages.instances') }}

@@ -49,6 +59,8 @@ const busy = ref(false) const error = ref('') const notice = ref('') const confirmingInstance = ref('') +const selected = ref(null) +const audit = ref([]) async function run(action) { busy.value = true error.value = '' @@ -58,7 +70,16 @@ async function run(action) { finally { busy.value = false } } function importPackage() { return run(() => api.import()) } -function perform(action, item) { return run(() => api[action](item.packageId)) } +function perform(action, item) { + selected.value = null + return run(() => api[action](item.packageId)) +} +function inspect(item) { + if (selected.value?.packageId === item.packageId) { selected.value = null; return } + return run(async () => { + ;[selected.value, audit.value] = await Promise.all([api.inspect(item.packageId), api.audit(item.packageId)]) + }) +} function testInstance(instance) { return run(async () => { const result = await api.test(instance.instanceId) @@ -88,6 +109,8 @@ onMounted(() => run(async () => {})) .connector-heading, .connector-row { display: flex; justify-content: space-between; align-items: center; gap: 16px; } .connector-heading h2 { font-size: 18px; } .connector-row { padding: 14px 0; border-bottom: 1px solid var(--border); } +.connector-row:has(.connector-detail) { flex-wrap: wrap; } +.connector-detail { flex-basis: 100%; overflow-wrap: anywhere; } .connector-row p { color: var(--muted); margin: 4px 0 0; } .connector-actions { display: flex; flex-wrap: wrap; gap: 8px; } .connector-actions button { border: 0; background: transparent; color: var(--accent-text); cursor: pointer; padding: 8px; } diff --git a/frontend/src/locales/en.js b/frontend/src/locales/en.js index 56bfaaf..2197710 100644 --- a/frontend/src/locales/en.js +++ b/frontend/src/locales/en.js @@ -20,6 +20,21 @@ export default { 'connectorPackages.delete': 'Delete instance', 'connectorPackages.deletePrompt': 'Delete this instance?', 'connectorPackages.instanceInUse': 'This instance is still used by a conversation. Remove it from the conversation first.', + 'connectorPackages.inspect': 'Details and history', + 'connectorPackages.origin': 'Origin and hash', + 'connectorPackages.transport': 'Transport', + 'connectorPackages.permissions': 'Permissions', + 'connectorPackages.credentials': 'Credential slots', + 'connectorPackages.destinations': 'Outbound destinations', + 'connectorPackages.none': 'None', + 'connectorPackages.audit': 'Trust history', + 'connectorPackages.event.imported': 'Imported', + 'connectorPackages.event.approved': 'Approved', + 'connectorPackages.event.installed': 'Installed', + 'connectorPackages.event.disabled': 'Disabled', + 'connectorPackages.event.revoked': 'Trust revoked', + 'connectorPackages.event.upgraded': 'Upgraded', + 'connectorPackages.event.removed': 'Removed', 'connectorPackages.state.imported': 'Awaiting approval', 'connectorPackages.state.approved': 'Approved', 'connectorPackages.state.installed': 'Installed', diff --git a/frontend/src/locales/zh.js b/frontend/src/locales/zh.js index d32ce20..f83a8d5 100644 --- a/frontend/src/locales/zh.js +++ b/frontend/src/locales/zh.js @@ -20,6 +20,21 @@ export default { 'connectorPackages.delete': '删除实例', 'connectorPackages.deletePrompt': '确定删除此实例?', 'connectorPackages.instanceInUse': '此实例仍被会话使用,请先从会话中移除。', + 'connectorPackages.inspect': '查看详情和记录', + 'connectorPackages.origin': '来源与哈希', + 'connectorPackages.transport': '传输方式', + 'connectorPackages.permissions': '权限', + 'connectorPackages.credentials': '凭据槽', + 'connectorPackages.destinations': '外联目标', + 'connectorPackages.none': '无', + 'connectorPackages.audit': '信任记录', + 'connectorPackages.event.imported': '已导入', + 'connectorPackages.event.approved': '已批准', + 'connectorPackages.event.installed': '已安装', + 'connectorPackages.event.disabled': '已停用', + 'connectorPackages.event.revoked': '已撤销信任', + 'connectorPackages.event.upgraded': '已升级', + 'connectorPackages.event.removed': '已移除', 'connectorPackages.state.imported': '待批准', 'connectorPackages.state.approved': '已批准', 'connectorPackages.state.installed': '已安装',