diff --git a/core/http/react-ui/public/locales/de/addons.json b/core/http/react-ui/public/locales/de/addons.json index c5b21a0365..9035d05391 100644 --- a/core/http/react-ui/public/locales/de/addons.json +++ b/core/http/react-ui/public/locales/de/addons.json @@ -858,6 +858,7 @@ "loading": "Lese, was dieses Add-on anfordert...", "loadingPage": "Host-Zugriff wird geladen", "locked": "Das Deployment hat Änderungen am Host-Zugriff abgeschaltet. Du kannst diese Seite lesen, aber nicht ändern.", + "lockedNoAuth": "Die Anmeldung ist ausgeschaltet, daher könnte jeder, der diesen Server erreicht, dies ändern; der Administrator legt es in der Richtliniendatei fest.", "newOptional": "Die neue Version fordert auch {{ops}} an. Diese sind optional und nicht erlaubt.", "noLongerRequested": "Diese Version fordert es nicht mehr an. Du kannst es ausschalten.", "notOfficial": "Dieses Add-on stammt aus {{library}}, nicht aus der offiziellen Bibliothek.", diff --git a/core/http/react-ui/public/locales/en/addons.json b/core/http/react-ui/public/locales/en/addons.json index d97a386898..e2500fd0cc 100644 --- a/core/http/react-ui/public/locales/en/addons.json +++ b/core/http/react-ui/public/locales/en/addons.json @@ -858,6 +858,7 @@ "loading": "Reading what this add-on asks for...", "loadingPage": "Loading host access", "locked": "The deployment turned off changes to host access. You can read this page but not change it.", + "lockedNoAuth": "Sign-in is off, so anyone who can reach this server could change these; they are set by the administrator in the policy file.", "newOptional": "The new version also asks for {{ops}}. These are optional and not allowed.", "noLongerRequested": "This version no longer asks for it. You can turn it off.", "notOfficial": "This add-on comes from {{library}}, not the official library.", diff --git a/core/http/react-ui/public/locales/es/addons.json b/core/http/react-ui/public/locales/es/addons.json index 79dd47e4f7..526b98bcc6 100644 --- a/core/http/react-ui/public/locales/es/addons.json +++ b/core/http/react-ui/public/locales/es/addons.json @@ -858,6 +858,7 @@ "loading": "Leyendo lo que pide este complemento...", "loadingPage": "Cargando el acceso al host", "locked": "El despliegue desactivó los cambios en el acceso al host. Puedes leer esta página, pero no cambiarla.", + "lockedNoAuth": "El inicio de sesión está desactivado, así que cualquiera que llegue a este servidor podría cambiar esto; el administrador lo define en el archivo de políticas.", "newOptional": "La nueva versión también pide {{ops}}. Son opcionales y no están permitidos.", "noLongerRequested": "Esta versión ya no lo pide. Puedes desactivarlo.", "notOfficial": "Este complemento viene de {{library}}, no de la biblioteca oficial.", diff --git a/core/http/react-ui/public/locales/fr/addons.json b/core/http/react-ui/public/locales/fr/addons.json index f3b277eac0..5cc8f6f46f 100644 --- a/core/http/react-ui/public/locales/fr/addons.json +++ b/core/http/react-ui/public/locales/fr/addons.json @@ -862,6 +862,7 @@ "loading": "Lecture de ce que demande ce module...", "loadingPage": "Chargement de l'accès à l'hôte", "locked": "Le déploiement a désactivé les changements d'accès à l'hôte. Vous pouvez lire cette page, pas la modifier.", + "lockedNoAuth": "La connexion est désactivée, donc toute personne qui atteint ce serveur pourrait modifier ceci ; l'administrateur le définit dans le fichier de politique.", "newOptional": "La nouvelle version demande aussi {{ops}}. Elles sont facultatives et non autorisées.", "noLongerRequested": "Cette version ne le demande plus. Vous pouvez le désactiver.", "notOfficial": "Ce module vient de {{library}}, pas de la bibliothèque officielle.", diff --git a/core/http/react-ui/public/locales/id/addons.json b/core/http/react-ui/public/locales/id/addons.json index 0a80a1aaef..8ec90046ea 100644 --- a/core/http/react-ui/public/locales/id/addons.json +++ b/core/http/react-ui/public/locales/id/addons.json @@ -858,6 +858,7 @@ "loading": "Membaca apa yang diminta add-on ini...", "loadingPage": "Memuat akses host", "locked": "Deployment mematikan perubahan akses host. Anda dapat membaca halaman ini tetapi tidak mengubahnya.", + "lockedNoAuth": "Login dimatikan, sehingga siapa pun yang dapat menjangkau server ini dapat mengubahnya; administrator mengaturnya di berkas kebijakan.", "newOptional": "Versi baru juga meminta {{ops}}. Ini opsional dan tidak diizinkan.", "noLongerRequested": "Versi ini tidak lagi memintanya. Anda dapat mematikannya.", "notOfficial": "Add-on ini berasal dari {{library}}, bukan pustaka resmi.", diff --git a/core/http/react-ui/public/locales/it/addons.json b/core/http/react-ui/public/locales/it/addons.json index bee3e932b6..2cefd05999 100644 --- a/core/http/react-ui/public/locales/it/addons.json +++ b/core/http/react-ui/public/locales/it/addons.json @@ -858,6 +858,7 @@ "loading": "Lettura di ciò che questo add-on richiede...", "loadingPage": "Caricamento dell'accesso all'host", "locked": "Il deployment ha disattivato le modifiche all'accesso all'host. Puoi leggere questa pagina ma non modificarla.", + "lockedNoAuth": "L'accesso è disattivato, quindi chiunque raggiunga questo server potrebbe modificarlo; l'amministratore lo imposta nel file di policy.", "newOptional": "La nuova versione chiede anche {{ops}}. Sono facoltative e non consentite.", "noLongerRequested": "Questa versione non lo chiede più. Puoi spegnerlo.", "notOfficial": "Questo add-on viene da {{library}}, non dalla libreria ufficiale.", diff --git a/core/http/react-ui/public/locales/ko/addons.json b/core/http/react-ui/public/locales/ko/addons.json index 05b1d9c3c2..60b2797831 100644 --- a/core/http/react-ui/public/locales/ko/addons.json +++ b/core/http/react-ui/public/locales/ko/addons.json @@ -858,6 +858,7 @@ "loading": "이 애드온이 요청하는 내용을 읽는 중...", "loadingPage": "호스트 접근을 불러오는 중", "locked": "배포 설정이 호스트 접근 변경을 꺼 두었습니다. 이 페이지는 읽을 수만 있습니다.", + "lockedNoAuth": "로그인이 꺼져 있어 이 서버에 접근할 수 있는 누구나 이를 바꿀 수 있습니다. 관리자가 정책 파일에서 설정합니다.", "newOptional": "새 버전은 {{ops}}도 요청합니다. 선택 항목이며 허용되지 않았습니다.", "noLongerRequested": "이 버전은 더 이상 요청하지 않습니다. 끌 수 있습니다.", "notOfficial": "이 애드온은 공식 라이브러리가 아니라 {{library}}에서 왔습니다.", diff --git a/core/http/react-ui/public/locales/pt-BR/addons.json b/core/http/react-ui/public/locales/pt-BR/addons.json index f14dffea26..eb62309fce 100644 --- a/core/http/react-ui/public/locales/pt-BR/addons.json +++ b/core/http/react-ui/public/locales/pt-BR/addons.json @@ -858,6 +858,7 @@ "loading": "Lendo o que este add-on pede...", "loadingPage": "Carregando o acesso ao host", "locked": "A implantação desativou mudanças no acesso ao host. Você pode ler esta página, mas não alterá-la.", + "lockedNoAuth": "O login está desativado, então qualquer pessoa que alcance este servidor poderia alterar isto; o administrador define no arquivo de política.", "newOptional": "A nova versão também pede {{ops}}. São opcionais e não permitidas.", "noLongerRequested": "Esta versão não pede mais. Você pode desligar.", "notOfficial": "Este add-on vem de {{library}}, não da biblioteca oficial.", diff --git a/core/http/react-ui/public/locales/zh-CN/addons.json b/core/http/react-ui/public/locales/zh-CN/addons.json index b5b091662f..ff163c4c09 100644 --- a/core/http/react-ui/public/locales/zh-CN/addons.json +++ b/core/http/react-ui/public/locales/zh-CN/addons.json @@ -858,6 +858,7 @@ "loading": "正在读取此插件的请求...", "loadingPage": "正在加载主机访问", "locked": "部署已关闭对主机访问的修改。你可以查看此页面,但不能修改。", + "lockedNoAuth": "登录已关闭,任何能访问此服务器的人都可能修改这些设置;它们由管理员在策略文件中设定。", "newOptional": "新版本还请求 {{ops}}。这些是可选的,且未被允许。", "noLongerRequested": "此版本不再请求它。你可以将其关闭。", "notOfficial": "此插件来自 {{library}},不是官方库。", diff --git a/core/http/react-ui/src/components/addons/ApproveUpgradeDialog.jsx b/core/http/react-ui/src/components/addons/ApproveUpgradeDialog.jsx index 08ede691f7..c76acec774 100644 --- a/core/http/react-ui/src/components/addons/ApproveUpgradeDialog.jsx +++ b/core/http/react-ui/src/components/addons/ApproveUpgradeDialog.jsx @@ -64,7 +64,8 @@ export default function ApproveUpgradeDialog({ addon, sourceId, api, actions, on {describeError(host.error, t)} )} - {ready && } + {ready && } + {ready && host.preview.locked && {t(host.preview.lockedReason === 'no_auth' ? 'hostAccess.lockedNoAuth' : 'hostAccess.locked')}} {block && {t(block.kind === 'blocked' ? 'hostAccess.cannotInstallBlocked' : 'hostAccess.cannotInstallUnsupported', { ops: block.ops.join(', ') })}} {failure && {failure}} diff --git a/core/http/react-ui/src/components/addons/HostAccessList.jsx b/core/http/react-ui/src/components/addons/HostAccessList.jsx index 7205010cf9..00abf2f2a9 100644 --- a/core/http/react-ui/src/components/addons/HostAccessList.jsx +++ b/core/http/react-ui/src/components/addons/HostAccessList.jsx @@ -16,7 +16,7 @@ export function opNote(op, t) { // are on and cannot be turned off; optional ones start off. A blocked one is // greyed with the reason. `added` highlights what a new version asks for on // top of what the old one held. -export default function HostAccessList({ ops, ticked, onTick, added = [], testId = 'hostaccess' }) { +export default function HostAccessList({ ops, ticked, onTick, added = [], testId = 'hostaccess', locked = false }) { const { t } = useTranslation('addons') return (
@@ -35,7 +35,7 @@ export default function HostAccessList({ ops, ticked, onTick, added = [], testId reason={note.reason} meta={[op.risk ? t(`hostAccess.risk.${op.risk}`) : '', isNew ? t('hostAccess.op.new') : ''].filter(Boolean).join(' · ') || undefined} checked={on} - disabled={!tickable(op) || op.required} + disabled={locked || !tickable(op) || op.required} onChange={(next) => onTick(op.name, next)} stacked testId={`${testId}-op`} diff --git a/core/http/react-ui/src/components/addons/HostAccessView.jsx b/core/http/react-ui/src/components/addons/HostAccessView.jsx index d37e21e7c7..1c1a9e82fb 100644 --- a/core/http/react-ui/src/components/addons/HostAccessView.jsx +++ b/core/http/react-ui/src/components/addons/HostAccessView.jsx @@ -69,7 +69,7 @@ export default function HostAccessView({ grants, api = hostGrantsApi }) {

{t('hostAccess.intro')}

{grants.stale && {t('apps.stale')}} - {locked && {t('hostAccess.locked')}} + {locked && {t(data.lockedReason === 'no_auth' ? 'hostAccess.lockedNoAuth' : 'hostAccess.locked')}} {notice && {notice.text}} {pending.map(a => setApproving(a)} />)} diff --git a/core/http/react-ui/src/components/addons/InstallDialog.jsx b/core/http/react-ui/src/components/addons/InstallDialog.jsx index d8e574a42e..ce2a80c61b 100644 --- a/core/http/react-ui/src/components/addons/InstallDialog.jsx +++ b/core/http/react-ui/src/components/addons/InstallDialog.jsx @@ -122,7 +122,8 @@ export default function InstallDialog({ entry, flow, actions, onClose, installed {host.preview.official === false && (

{t('hostAccess.notOfficial', { library: entry.sourceId })}

)} - + + {host.preview.locked && {t(host.preview.lockedReason === 'no_auth' ? 'hostAccess.lockedNoAuth' : 'hostAccess.locked')}} {block && ( {t(block.kind === 'blocked' ? 'hostAccess.cannotInstallBlocked' : 'hostAccess.cannotInstallUnsupported', { ops: block.ops.join(', ') })} diff --git a/core/http/react-ui/src/components/addons/hostGrantsState.js b/core/http/react-ui/src/components/addons/hostGrantsState.js index 6a64572565..7b9fee530c 100644 --- a/core/http/react-ui/src/components/addons/hostGrantsState.js +++ b/core/http/react-ui/src/components/addons/hostGrantsState.js @@ -79,6 +79,9 @@ export function normalizePreview(payload) { revision: Number(p.revision) || 0, nonce: typeof p.nonce === 'string' ? p.nonce : '', official: p.official === true ? true : (p.official === false ? false : null), + // The owner keeps UI grants locked: the dialog shows what the add-on asks for and grants nothing. + locked: p.locked === true, + lockedReason: p.lockedReason === 'no_auth' ? 'no_auth' : 'deployment', // Operations that are new in this version. Empty on an install. added: list(p.added), requested, @@ -103,6 +106,7 @@ export function tickable(op) { // before an uninstall (the owner sends it as a hint). export function initialTicks(preview) { const out = [] + if (preview.locked) return out for (const op of preview.ops) { if (!tickable(op)) continue if (op.required || op.hint) out.push(op.name) @@ -114,6 +118,7 @@ export function initialTicks(preview) { // can still be granted, so a required operation can never go missing from the // request because of a stale tick list. export function approvedOps(preview, ticked) { + if (preview.locked) return [] const set = new Set(ticked) for (const op of preview.ops) if (tickable(op) && op.required) set.add(op.name) return preview.ops.filter(op => tickable(op) && set.has(op.name)).map(op => op.name) @@ -201,6 +206,7 @@ export function normalizeOverview(payload) { return { revision: Number(p.revision) || 0, locked: p.locked === true, + lockedReason: p.lockedReason === 'no_auth' ? 'no_auth' : 'deployment', blanket: { mode, includeOptional: b.includeOptional === true, diff --git a/core/http/react-ui/tests/unit/hostgrants.test.mjs b/core/http/react-ui/tests/unit/hostgrants.test.mjs index 9ac920b153..208256ea0d 100644 --- a/core/http/react-ui/tests/unit/hostgrants.test.mjs +++ b/core/http/react-ui/tests/unit/hostgrants.test.mjs @@ -199,3 +199,13 @@ test('hostgrants: install with an approval goes through the host-grants route, a assert.ok(calls.includes('plain:install')) assert.ok(!calls.some(c => c.startsWith('grants:'))) }) + +test('hostgrants: a locked preview ticks nothing and approves no operation', () => { + const p = normalizePreview({ ...PREVIEW, locked: true, lockedReason: 'no_auth' }) + assert.equal(p.locked, true) + assert.equal(p.lockedReason, 'no_auth') + assert.deepEqual(initialTicks(p), []) + assert.deepEqual(approvedOps(p, ['egress.check']), []) + assert.equal(normalizeOverview({ locked: true, lockedReason: 'no_auth' }).lockedReason, 'no_auth') + assert.equal(normalizePreview(PREVIEW).locked, false) +})