diff --git a/docs/EXTENSIONS.md b/docs/EXTENSIONS.md index 24b69c46..51e5b5e7 100644 --- a/docs/EXTENSIONS.md +++ b/docs/EXTENSIONS.md @@ -58,6 +58,13 @@ applies — installed extension code runs with full session authority — so onl install extensions from the vetted gallery or sources you trust as much as the WebUI source itself. +Some gallery entries need more than WebUI assets. If an extension declares +post-install guidance or lifecycle requirements such as a loopback sidecar or a +native host, Settings -> Extensions shows a **Next step** note on the card after +install. For example, Desktop Companion can install the WebUI bridge from the +gallery, but the desktop pet is only visible after the local Desktop Companion +app is started. + ### Manual / advanced configuration (optional) `HERMES_WEBUI_EXTENSION_DIR` is **optional** and overrides the managed default. diff --git a/static/i18n.js b/static/i18n.js index 08864c9f..e47b779b 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -680,6 +680,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Next step', + ext_gallery_after_install: 'After install', + ext_gallery_local_component_required: 'This extension has a local component. Start the required local app or sidecar after installing.', + ext_gallery_local_app_label: 'Local app', + ext_gallery_required_suffix: '{0} required', + ext_gallery_sidecar_required: 'Sidecar required', + ext_gallery_native_host_required: 'Native host required', + ext_gallery_open_setup_guide: 'Open setup guide', + ext_gallery_install_restart_required: 'Extension installed. Restart required to apply.', + ext_gallery_install_followup: 'Extension installed. See the card for next steps.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugins', @@ -2301,6 +2311,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Prossimo passo', + ext_gallery_after_install: "Dopo l'installazione", + ext_gallery_local_component_required: "Questa estensione ha un componente locale. Avvia l'app locale o il sidecar richiesto dopo l'installazione.", + ext_gallery_local_app_label: 'App locale', + ext_gallery_required_suffix: '{0} richiesto', + ext_gallery_sidecar_required: 'Sidecar richiesto', + ext_gallery_native_host_required: 'Host nativo richiesto', + ext_gallery_open_setup_guide: 'Apri guida di configurazione', + ext_gallery_install_restart_required: 'Estensione installata. Riavvio richiesto per applicare le modifiche.', + ext_gallery_install_followup: 'Estensione installata. Consulta la scheda per i prossimi passi.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugins', // TODO: translate @@ -3913,6 +3933,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: '次の手順', + ext_gallery_after_install: 'インストール後', + ext_gallery_local_component_required: 'この拡張機能にはローカルコンポーネントが必要です。インストール後に必要なローカルアプリまたはサイドカーを起動してください。', + ext_gallery_local_app_label: 'ローカルアプリ', + ext_gallery_required_suffix: '{0} が必要', + ext_gallery_sidecar_required: 'サイドカーが必要', + ext_gallery_native_host_required: 'ネイティブホストが必要', + ext_gallery_open_setup_guide: 'セットアップガイドを開く', + ext_gallery_install_restart_required: '拡張機能をインストールしました。適用するには再起動が必要です。', + ext_gallery_install_followup: '拡張機能をインストールしました。次の手順はカードで確認してください。', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'プラグイン', @@ -6233,6 +6263,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Следующий шаг', + ext_gallery_after_install: 'После установки', + ext_gallery_local_component_required: 'У этого расширения есть локальный компонент. После установки запустите требуемое локальное приложение или sidecar.', + ext_gallery_local_app_label: 'Локальное приложение', + ext_gallery_required_suffix: 'Требуется {0}', + ext_gallery_sidecar_required: 'Требуется sidecar', + ext_gallery_native_host_required: 'Требуется нативный хост', + ext_gallery_open_setup_guide: 'Открыть руководство по настройке', + ext_gallery_install_restart_required: 'Расширение установлено. Для применения требуется перезапуск.', + ext_gallery_install_followup: 'Расширение установлено. Следующие шаги смотрите на карточке.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugins', // TODO: translate @@ -7770,6 +7810,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Siguiente paso', + ext_gallery_after_install: 'Después de instalar', + ext_gallery_local_component_required: 'Esta extensión tiene un componente local. Después de instalarla, inicia la app local o el sidecar requerido.', + ext_gallery_local_app_label: 'App local', + ext_gallery_required_suffix: 'Se requiere {0}', + ext_gallery_sidecar_required: 'Se requiere sidecar', + ext_gallery_native_host_required: 'Se requiere host nativo', + ext_gallery_open_setup_guide: 'Abrir guía de configuración', + ext_gallery_install_restart_required: 'Extensión instalada. Reinicio requerido para aplicar.', + ext_gallery_install_followup: 'Extensión instalada. Consulta la tarjeta para ver los próximos pasos.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugins', // TODO: translate @@ -8991,6 +9041,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Nächster Schritt', + ext_gallery_after_install: 'Nach der Installation', + ext_gallery_local_component_required: 'Diese Erweiterung hat eine lokale Komponente. Starte nach der Installation die erforderliche lokale App oder den Sidecar.', + ext_gallery_local_app_label: 'Lokale App', + ext_gallery_required_suffix: '{0} erforderlich', + ext_gallery_sidecar_required: 'Sidecar erforderlich', + ext_gallery_native_host_required: 'Nativer Host erforderlich', + ext_gallery_open_setup_guide: 'Einrichtungsanleitung öffnen', + ext_gallery_install_restart_required: 'Erweiterung installiert. Neustart zum Anwenden erforderlich.', + ext_gallery_install_followup: 'Erweiterung installiert. Die nächsten Schritte stehen auf der Karte.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugins', // TODO: translate @@ -10879,6 +10939,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: '下一步', + ext_gallery_after_install: '安装后', + ext_gallery_local_component_required: '此扩展包含本地组件。安装后请启动所需的本地应用或 sidecar。', + ext_gallery_local_app_label: '本地应用', + ext_gallery_required_suffix: '需要 {0}', + ext_gallery_sidecar_required: '需要 sidecar', + ext_gallery_native_host_required: '需要原生主机', + ext_gallery_open_setup_guide: '打开设置指南', + ext_gallery_install_restart_required: '扩展已安装。需要重启后生效。', + ext_gallery_install_followup: '扩展已安装。请查看卡片中的后续步骤。', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_tab_system: '系统', @@ -11722,6 +11792,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: '下一步', + ext_gallery_after_install: '安裝後', + ext_gallery_local_component_required: '此擴充功能包含本機元件。安裝後請啟動所需的本機應用程式或 sidecar。', + ext_gallery_local_app_label: '本機應用程式', + ext_gallery_required_suffix: '需要 {0}', + ext_gallery_sidecar_required: '需要 sidecar', + ext_gallery_native_host_required: '需要原生主機', + ext_gallery_open_setup_guide: '開啟設定指南', + ext_gallery_install_restart_required: '擴充功能已安裝。需要重新啟動後套用。', + ext_gallery_install_followup: '擴充功能已安裝。請查看卡片中的後續步驟。', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: '外掛', @@ -13230,6 +13310,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Próximo passo', + ext_gallery_after_install: 'Após instalar', + ext_gallery_local_component_required: 'Esta extensão tem um componente local. Depois de instalar, inicie o app local ou sidecar necessário.', + ext_gallery_local_app_label: 'App local', + ext_gallery_required_suffix: '{0} necessário', + ext_gallery_sidecar_required: 'Sidecar necessário', + ext_gallery_native_host_required: 'Host nativo necessário', + ext_gallery_open_setup_guide: 'Abrir guia de configuração', + ext_gallery_install_restart_required: 'Extensão instalada. Reinício necessário para aplicar.', + ext_gallery_install_followup: 'Extensão instalada. Veja o cartão para os próximos passos.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugins', // TODO: translate @@ -14744,6 +14834,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: '다음 단계', + ext_gallery_after_install: '설치 후', + ext_gallery_local_component_required: '이 확장에는 로컬 구성 요소가 있습니다. 설치 후 필요한 로컬 앱이나 사이드카를 시작하세요.', + ext_gallery_local_app_label: '로컬 앱', + ext_gallery_required_suffix: '{0} 필요', + ext_gallery_sidecar_required: '사이드카 필요', + ext_gallery_native_host_required: '네이티브 호스트 필요', + ext_gallery_open_setup_guide: '설정 가이드 열기', + ext_gallery_install_restart_required: '확장이 설치되었습니다. 적용하려면 다시 시작해야 합니다.', + ext_gallery_install_followup: '확장이 설치되었습니다. 카드에서 다음 단계를 확인하세요.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugins', // TODO: translate @@ -16368,6 +16468,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Étape suivante', + ext_gallery_after_install: 'Après l’installation', + ext_gallery_local_component_required: 'Cette extension comporte un composant local. Après l’installation, démarrez l’application locale ou le sidecar requis.', + ext_gallery_local_app_label: 'Application locale', + ext_gallery_required_suffix: '{0} requis', + ext_gallery_sidecar_required: 'Sidecar requis', + ext_gallery_native_host_required: 'Hôte natif requis', + ext_gallery_open_setup_guide: 'Ouvrir le guide de configuration', + ext_gallery_install_restart_required: 'Extension installée. Redémarrage requis pour appliquer.', + ext_gallery_install_followup: 'Extension installée. Consultez la carte pour les prochaines étapes.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugins', @@ -17948,6 +18058,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Sonraki adım', + ext_gallery_after_install: 'Kurulumdan sonra', + ext_gallery_local_component_required: "Bu uzantının yerel bir bileşeni var. Kurulumdan sonra gerekli yerel uygulamayı veya sidecar'ı başlatın.", + ext_gallery_local_app_label: 'Yerel uygulama', + ext_gallery_required_suffix: '{0} gerekli', + ext_gallery_sidecar_required: 'Sidecar gerekli', + ext_gallery_native_host_required: 'Native host gerekli', + ext_gallery_open_setup_guide: 'Kurulum kılavuzunu aç', + ext_gallery_install_restart_required: 'Uzantı kuruldu. Uygulamak için yeniden başlatma gerekli.', + ext_gallery_install_followup: 'Uzantı kuruldu. Sonraki adımlar için kartı kontrol edin.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Eklentiler', @@ -19574,6 +19694,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Następny krok', + ext_gallery_after_install: 'Po instalacji', + ext_gallery_local_component_required: 'To rozszerzenie ma komponent lokalny. Po instalacji uruchom wymaganą aplikację lokalną lub sidecar.', + ext_gallery_local_app_label: 'Aplikacja lokalna', + ext_gallery_required_suffix: 'Wymagane: {0}', + ext_gallery_sidecar_required: 'Wymagany sidecar', + ext_gallery_native_host_required: 'Wymagany host natywny', + ext_gallery_open_setup_guide: 'Otwórz przewodnik konfiguracji', + ext_gallery_install_restart_required: 'Rozszerzenie zainstalowane. Do zastosowania wymagany jest restart.', + ext_gallery_install_followup: 'Rozszerzenie zainstalowane. Sprawdź kartę, aby zobaczyć kolejne kroki.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Wtyczki', @@ -21088,6 +21218,16 @@ const LOCALES = { ext_gallery_version: 'Version', ext_gallery_author: 'Author', ext_gallery_capabilities: 'Capabilities', + ext_gallery_next_step: 'Bước tiếp theo', + ext_gallery_after_install: 'Sau khi cài đặt', + ext_gallery_local_component_required: 'Tiện ích mở rộng này có thành phần cục bộ. Sau khi cài đặt, hãy khởi động ứng dụng cục bộ hoặc sidecar bắt buộc.', + ext_gallery_local_app_label: 'Ứng dụng cục bộ', + ext_gallery_required_suffix: 'Cần {0}', + ext_gallery_sidecar_required: 'Cần sidecar', + ext_gallery_native_host_required: 'Cần native host', + ext_gallery_open_setup_guide: 'Mở hướng dẫn thiết lập', + ext_gallery_install_restart_required: 'Đã cài đặt tiện ích mở rộng. Cần khởi động lại để áp dụng.', + ext_gallery_install_followup: 'Đã cài đặt tiện ích mở rộng. Xem thẻ để biết các bước tiếp theo.', ext_gallery_install_ok: 'Extension installed successfully.', ext_gallery_uninstall_ok: 'Extension uninstalled.', settings_plugins_title: 'Plugin', diff --git a/static/panels.js b/static/panels.js index ab9d50ff..14e8bf9b 100644 --- a/static/panels.js +++ b/static/panels.js @@ -8421,6 +8421,50 @@ function switchExtensionsTab(tab){ if(tab==='gallery'&&!_extensionsGalleryLoaded) loadExtensionsGallery(); } +function _extensionSafeHttpUrl(value){ + if(!value) return ''; + const raw=String(value).trim(); + if(!/^https?:\/\//i.test(raw)) return ''; + try{ + const url=new URL(raw); + if(url.username||url.password) return ''; + return (url.protocol==='http:'||url.protocol==='https:')?url.href:''; + }catch(_){ + return ''; + } +} + +function _extensionPostInstallNote(entry,isInstalled){ + const lifecycle=(entry&&entry.lifecycle&&typeof entry.lifecycle==='object')?entry.lifecycle:{}; + const post=(entry&&entry.post_install&&typeof entry.post_install==='object')?entry.post_install:null; + const needsSidecar=!!lifecycle.sidecar_start_required; + const needsNative=!!lifecycle.native_host_start_required; + const summary=post&&post.summary?String(post.summary):( + (needsSidecar||needsNative) + ? t('ext_gallery_local_component_required') + : '' + ); + if(!summary) return ''; + const docsUrl=_extensionSafeHttpUrl(post&&post.docs_url); + const localAppLabel=post&&post.local_app_label?String(post.local_app_label):t('ext_gallery_local_app_label'); + const chips=[]; + if(post&&post.requires_local_app===true) chips.push(t('ext_gallery_required_suffix',localAppLabel)); + if(needsSidecar) chips.push(t('ext_gallery_sidecar_required')); + if(needsNative) chips.push(t('ext_gallery_native_host_required')); + const chipHtml=chips.length + ? '
' + : ''; + const docsHtml=docsUrl + ? `${esc(t('ext_gallery_open_setup_guide'))}` + : ''; + return ``; +} + async function loadExtensionsGallery(){ _extensionsGalleryLoaded=true; const galleryEl=$('extensionsGallery'); @@ -8468,9 +8512,10 @@ function _renderExtensionsGallery(entries,statusData){ const caps=Array.isArray(entry.capabilities)?entry.capabilities:[]; const perms=entry.permissions||null; const isInstalled=installedIds.has(String(entry.id||'')); - const restartRequired=!!(entry.lifecycle&&entry.lifecycle.restart_required); + const restartRequired=!!(entry.lifecycle&&(entry.lifecycle.restart_required||entry.lifecycle.webui_restart_required)); const badgesHtml=caps.map(c=>`${esc(String(c))}`).join(''); const permsHtml=perms?`${esc(JSON.stringify(perms,null,2))}