diff --git a/src/App.svelte b/src/App.svelte index edec988..0153d0c 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -187,7 +187,7 @@ let secretDescription = ''; let secretValue = ''; let secretError = ''; - let plugins: Array<{ id: string; plugin_id: string; name: string; version: string; manifest: { permissions: string[]; api_version: string; entrypoint: string }; enabled: boolean; installed_at: string; updated_at: string }> = []; + let plugins: Array<{ id: string; plugin_id: string; name: string; version: string; manifest: { permissions: string[]; required_secrets?: string[]; api_version: string; entrypoint: string }; enabled: boolean; installed_at: string; updated_at: string }> = []; let pluginError = ''; let pluginBusy = false; let pluginDropActive = false; @@ -1122,6 +1122,22 @@ } } + async function bindPluginSecret(plugin: (typeof plugins)[number]) { + const secretName = window.prompt('Enter the existing secret name to approve for this plugin:'); + if (!secretName) return; + pluginBusy = true; + pluginError = ''; + try { + const response = await fetch(`${serverAddress}/api/v1/admin/plugins/${encodeURIComponent(plugin.plugin_id)}/secrets`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${sessionToken}` }, body: JSON.stringify({ secret_name: secretName, approved: true }) }); + if (!response.ok) throw new Error('The plugin secret binding could not be approved.'); + pluginError = `Approved ${secretName} for ${plugin.name}. Enable the plugin when all requirements are satisfied.`; + } catch (error) { + pluginError = error instanceof Error ? error.message : 'The plugin secret binding could not be approved.'; + } finally { + pluginBusy = false; + } + } + async function removePlugin(plugin: (typeof plugins)[number]) { if (!window.confirm(`Remove plugin ${plugin.name}?`)) return; pluginBusy = true; @@ -1456,6 +1472,7 @@ {:else if activeView === 'Plugins'}
WEBASSEMBLY PLUGINS

Packages are validated and started in a resource-limited WebAssembly runtime. Only explicitly declared Trixie permissions are accepted; installation never grants filesystem or process access.

{#if pluginError}
{pluginError}
{/if}{#if plugins.length}
{#each plugins as plugin}
{plugin.name}{plugin.plugin_id} · v{plugin.version}
API {plugin.manifest.api_version} · {plugin.manifest.permissions.length ? plugin.manifest.permissions.join(' · ') : 'NO PERMISSIONS'}
{plugin.enabled ? 'ENABLED' : 'DISABLED'}
{/each}
{:else}
No plugins are installed.
{/if}
+
PLUGIN SECRET APPROVAL

Plugins cannot read secrets until an administrator explicitly approves a binding. The secret value is never shown here.

{#if plugins.some((plugin) => plugin.manifest.required_secrets?.length)}
{#each plugins.filter((plugin) => plugin.manifest.required_secrets?.length) as plugin}
{plugin.name}Required: {plugin.manifest.required_secrets?.join(' · ')}
{/each}
{:else}
Installed plugins have no secret requirements.
{/if}
{:else if activeView === 'Bot'}
{commandFormId ? 'EDIT COMMAND' : 'NEW COMMAND'}
{#if commandVariableSuggestions.length}
{#each commandVariableSuggestions as variable}{/each}
{/if}
SIMULATION

Preview with literal sample data. Real requests are opt-in and use the same server-side safety checks.

{#if commandSimulationError}
{commandSimulationError}
{/if}{#if commandSimulationResult}
{commandSimulationResult.rendered ?? commandSimulationResult.error ?? 'Simulation completed.'}{#if commandSimulationResult.extracted_variables && Object.keys(commandSimulationResult.extracted_variables).length}Extracted: {formatExtractedVariables(commandSimulationResult.extracted_variables)}{/if}
{/if}
BOT COMMANDS
PUBLIC COMMAND CATALOG
{#if botCommandError}
{botCommandError}
{:else if botCommands.length}
{#each botCommands as command}
!{command.name}{command.description}{#if command.tags.length}
{command.tags.join(' · ')}
{/if}
{command.permission_level.toUpperCase()}{#if command.cooldown_seconds}{command.cooldown_seconds}s / {command.cooldown_scope}{/if}
{/each}
{:else}
No enabled commandsCreate commands on the server to make them available here.
{/if}
SECRETS MANAGEMENT
Values are encrypted and cannot be read after saving.
{#if botSecrets.length}
{#each botSecrets as secret}
{secret.name}{secret.description || 'No description'}{#if secret.used_by.length}
USED BY: {secret.used_by.join(', ')}
{/if}
{/each}
{:else}
There are no secrets yet.
{/if}{#if secretError}
{secretError}
{/if}