mirror of
https://github.com/Ryder-Sun/Meldwork.git
synced 2026-11-07 06:56:03 -10:00
Expose Connector package trust details and history
Show inspected transport, permissions, credential slots, outbound destinations and durable trust events in Agent settings. Checks: frontend tests (393 passed); web and desktop builds; desktop pack; git diff --check.
This commit is contained in:
1 parent
025034de7f
commit
f4cb6d1372
4 files changed
+72
-1
No files matched your search
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -10,6 +10,7 @@
|
||||
<article v-for="item in packages" :key="item.packageId" class="connector-row">
|
||||
<div><strong>{{ item.label }}</strong> <span>{{ item.connectorVersion }}</span><p>{{ item.publisher.name }} · {{ t(`connectorPackages.state.${item.state}`) }}</p></div>
|
||||
<div class="connector-actions">
|
||||
<button type="button" :disabled="busy" @click="inspect(item)">{{ t('connectorPackages.inspect') }}</button>
|
||||
<button v-if="item.state === 'imported'" type="button" :disabled="busy" @click="perform('approve', item)">{{ t('connectorPackages.approve') }}</button>
|
||||
<button v-if="item.state === 'approved' || item.state === 'disabled'" type="button" :disabled="busy" @click="perform('install', item)">{{ t('connectorPackages.install') }}</button>
|
||||
<button v-if="item.state === 'approved' && packages.some(other => other.connectorId === item.connectorId && other.state === 'installed')" type="button" :disabled="busy" @click="perform('upgrade', item)">{{ t('connectorPackages.upgrade') }}</button>
|
||||
@@ -18,6 +19,15 @@
|
||||
<button v-if="!['revoked', 'removed'].includes(item.state)" type="button" :disabled="busy" @click="perform('revoke', item)">{{ t('connectorPackages.revoke') }}</button>
|
||||
<button v-if="['imported', 'approved', 'disabled', 'revoked'].includes(item.state)" type="button" :disabled="busy" @click="perform('remove', item)">{{ t('connectorPackages.remove') }}</button>
|
||||
</div>
|
||||
<div v-if="selected?.packageId === item.packageId" class="connector-detail">
|
||||
<p>{{ t('connectorPackages.origin') }}: {{ selected.origin.filename }} · {{ selected.origin.sha256 }}</p>
|
||||
<p>{{ t('connectorPackages.transport') }}: {{ selected.manifest.transport.type }} / {{ selected.manifest.transport.protocol }}</p>
|
||||
<p>{{ t('connectorPackages.permissions') }}: {{ selected.manifest.permissionModes.join(', ') || t('connectorPackages.none') }}</p>
|
||||
<p>{{ t('connectorPackages.credentials') }}: {{ selected.manifest.credentials.slots.map(slot => slot.slotId).join(', ') || t('connectorPackages.none') }}</p>
|
||||
<p>{{ t('connectorPackages.destinations') }}: {{ selected.manifest.outboundDestinations.join(', ') || t('connectorPackages.none') }}</p>
|
||||
<h4>{{ t('connectorPackages.audit') }}</h4>
|
||||
<ol><li v-for="event in audit" :key="event.eventId">{{ t(`connectorPackages.event.${event.action}`) }} · {{ event.timestamp }}</li></ol>
|
||||
</div>
|
||||
</article>
|
||||
<h3 v-if="instances.length">{{ t('connectorPackages.instances') }}</h3>
|
||||
<article v-for="instance in instances" :key="instance.instanceId" class="connector-row">
|
||||
@@ -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; }
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': '已安装',
|
||||
|
||||
Reference in new issue
Block a user