This commit is contained in:
Timothy Jaeryang Baek 2026-10-12 22:14:04 +04:00
parent 3a54306c50
commit 4ab60527e7
4 changed files with 55 additions and 7 deletions

View file

@ -102,6 +102,8 @@ class AppInteractionForm(StrictModel):
class AppManifestForm(StrictModel):
url: str = Field(max_length=2048)
api_key: SecretStr | None = Field(default=None, min_length=1, max_length=4096)
app_id: str | None = None
class AppForm(StrictModel):

View file

@ -76,13 +76,30 @@ async def get_apps(user=Depends(get_verified_user), db: AsyncSession = Depends(g
@router.post('/manifest/preview', response_model=Manifest)
async def preview_app_manifest(form_data: AppManifestForm, user=Depends(get_admin_user)):
async def preview_app_manifest(
form_data: AppManifestForm, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)
):
if not await Config.get('apps.enable'):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Apps are disabled')
api_key = form_data.api_key.get_secret_value() if form_data.api_key is not None else None
if form_data.app_id:
app = await get_app_by_id(form_data.app_id, db=db)
if form_data.url != app.data['url']:
raise HTTPException(400, 'App URL does not match the installed app')
if api_key is None and app.api_key:
api_key = get_app_encryption_cipher().decrypt(app.api_key.encode()).decode()
if not api_key:
raise HTTPException(400, 'An app API key is required')
try:
status_code, raw = await send_app_request(form_data.url)
status_code, raw = await send_app_request(
form_data.url, headers={'Authorization': f'Bearer {api_key}'}
)
if status_code in (401, 403):
raise HTTPException(400, 'App rejected the API key. Check the API key configured on the app server.')
if status_code != 200:
raise HTTPException(502, f'App manifest endpoint returned HTTP {status_code}. Check the URL or enter details manually.')
raise HTTPException(
502, f'App manifest endpoint returned HTTP {status_code}. Check the URL or enter details manually.'
)
manifest = Manifest.model_validate(raw)
return manifest.model_dump()
except HTTPException:

View file

@ -157,7 +157,11 @@ export const deleteAppById = async (token: string = '', app_id: string) => {
return res;
};
export const previewAppManifest = async (token: string = '', url: string) => {
export const previewAppManifest = async (
token: string = '',
url: string,
data: { api_key?: string; app_id?: string }
) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/apps/manifest/preview`, {
@ -167,7 +171,7 @@ export const previewAppManifest = async (token: string = '', url: string) => {
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({ url })
body: JSON.stringify({ url, ...data })
})
.then(async (res) => {
if (!res.ok) throw await res.json();

View file

@ -77,7 +77,10 @@
async function submit() {
await run(async () => {
if (!manifest) {
manifest = await previewAppManifest(localStorage.token, url);
manifest = await previewAppManifest(localStorage.token, url, {
...(apiKey ? { api_key: apiKey } : {}),
...(app ? { app_id: app.id } : {})
});
manual = false;
name = manifest.name;
slug =
@ -288,6 +291,24 @@
placeholder="https://app.example.com/events"
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
forId="app-api-key"
description={app?.configured
? $i18n.t('Leave empty to keep the current API key.')
: $i18n.t('Enter the API key provided by the app.')}
>
<input
id="app-api-key"
type="password"
autocomplete="new-password"
class={inputClass}
bind:value={apiKey}
required={!app?.configured}
maxlength="4096"
placeholder={app?.configured ? '••••••••' : ''}
/>
</AdminSettingField>
{:else}
<div class="flex gap-3">
<AdminSettingField label={$i18n.t('Name')} forId="app-name" className="min-w-0 flex-1"
@ -342,7 +363,10 @@
class="mt-2 text-xs text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white hover:underline"
on:click={() =>
run(async () => {
manifest = await previewAppManifest(localStorage.token, url);
manifest = await previewAppManifest(localStorage.token, url, {
...(apiKey ? { api_key: apiKey } : {}),
...(app ? { app_id: app.id } : {})
});
scopes = scopes.filter((scope) => manifest.requested_scopes.includes(scope));
manifestUpdated = true;
showScopes = true;
@ -360,6 +384,7 @@
{$i18n.t('Saving approves the updated app identity and selected scopes.')}
</p>
{/if}
<AdminSettingField
label={$i18n.t('API Key')}
forId="app-api-key"